Summary: This diff introduces the logic for queueing incoming messages rather then directly processing them you are behind the `flipper_event_queue` GK. The reason the queue processing is a bit complicated is to make the queue can be processed non-blocking, can be cancelled, and is safe to concurrency issues. The idea here is that the queue is processed when we switch to a plugin, report it's progress, and abort the process when switching to another plugin without loosing any work. This diff does not include [x] updates to the UI (**SO DON"T LAND IN ISOLATION**) [x] metrics to see the effect The effect of the changes can be seen when profiling the application, before this change there are very regular CPU spikes (see the small yellow bar on the top): https://pxl.cl/TQtl These go away when the events are no longer processed https://pxl.cl/TQtp Reviewed By: nikoant Differential Revision: D19095564 fbshipit-source-id: 0b8c3421acc4a4f240bf2aab5c1743132f69aa6e
180 lines
3.9 KiB
TypeScript
180 lines
3.9 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import stream from 'stream';
|
|
import {FlipperDevicePlugin} from 'flipper';
|
|
import {sortPluginsByName} from '../plugin';
|
|
|
|
export type LogLevel =
|
|
| 'unknown'
|
|
| 'verbose'
|
|
| 'debug'
|
|
| 'info'
|
|
| 'warn'
|
|
| 'error'
|
|
| 'fatal';
|
|
|
|
export type DeviceLogEntry = {
|
|
date: Date;
|
|
pid: number;
|
|
tid: number;
|
|
app?: string;
|
|
type: LogLevel;
|
|
tag: string;
|
|
message: string;
|
|
};
|
|
|
|
export type DeviceShell = {
|
|
stdout: stream.Readable;
|
|
stderr: stream.Readable;
|
|
stdin: stream.Writable;
|
|
};
|
|
|
|
export type DeviceLogListener = (entry: DeviceLogEntry) => void;
|
|
|
|
export type DeviceType =
|
|
| 'emulator'
|
|
| 'physical'
|
|
| 'archivedEmulator'
|
|
| 'archivedPhysical';
|
|
|
|
export type DeviceExport = {
|
|
os: OS;
|
|
title: string;
|
|
deviceType: DeviceType;
|
|
serial: string;
|
|
logs: Array<DeviceLogEntry>;
|
|
};
|
|
|
|
export type OS = 'iOS' | 'Android' | 'Windows' | 'MacOS' | 'JSWebApp';
|
|
|
|
export default class BaseDevice {
|
|
constructor(serial: string, deviceType: DeviceType, title: string, os: OS) {
|
|
this.serial = serial;
|
|
this.title = title;
|
|
this.deviceType = deviceType;
|
|
this.os = os;
|
|
}
|
|
|
|
// operating system of this device
|
|
os: OS;
|
|
|
|
// human readable name for this device
|
|
title: string;
|
|
|
|
// type of this device
|
|
deviceType: DeviceType;
|
|
|
|
// serial number for this device
|
|
serial: string;
|
|
|
|
// possible src of icon to display next to the device title
|
|
icon: string | null | undefined;
|
|
|
|
logListeners: Map<Symbol, DeviceLogListener> = new Map();
|
|
logEntries: Array<DeviceLogEntry> = [];
|
|
isArchived: boolean = false;
|
|
// if imported, stores the original source location
|
|
source = '';
|
|
|
|
// sorted list of supported device plugins
|
|
devicePlugins!: string[];
|
|
|
|
supportsOS(os: OS) {
|
|
return os.toLowerCase() === this.os.toLowerCase();
|
|
}
|
|
|
|
toJSON(): DeviceExport {
|
|
return {
|
|
os: this.os,
|
|
title: this.title,
|
|
deviceType: this.deviceType,
|
|
serial: this.serial,
|
|
logs: this.getLogs(),
|
|
};
|
|
}
|
|
|
|
teardown() {}
|
|
|
|
supportedColumns(): Array<string> {
|
|
return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time'];
|
|
}
|
|
|
|
addLogListener(callback: DeviceLogListener): Symbol {
|
|
const id = Symbol();
|
|
this.logListeners.set(id, callback);
|
|
return id;
|
|
}
|
|
|
|
_notifyLogListeners(entry: DeviceLogEntry) {
|
|
if (this.logListeners.size > 0) {
|
|
this.logListeners.forEach(listener => {
|
|
// prevent breaking other listeners, if one listener doesn't work.
|
|
try {
|
|
listener(entry);
|
|
} catch (e) {
|
|
console.error(`Log listener exception:`, e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
addLogEntry(entry: DeviceLogEntry) {
|
|
this.logEntries.push(entry);
|
|
this._notifyLogListeners(entry);
|
|
}
|
|
|
|
getLogs() {
|
|
return this.logEntries;
|
|
}
|
|
|
|
clearLogs(): Promise<void> {
|
|
// Only for device types that allow clearing.
|
|
this.logEntries = [];
|
|
return Promise.resolve();
|
|
}
|
|
|
|
removeLogListener(id: Symbol) {
|
|
this.logListeners.delete(id);
|
|
}
|
|
|
|
navigateToLocation(_location: string) {
|
|
throw new Error('unimplemented');
|
|
}
|
|
|
|
archive(): any | null | undefined {
|
|
return null;
|
|
}
|
|
|
|
screenshot(): Promise<Buffer> {
|
|
return Promise.reject(
|
|
new Error('No screenshot support for current device'),
|
|
);
|
|
}
|
|
|
|
async screenCaptureAvailable(): Promise<boolean> {
|
|
return false;
|
|
}
|
|
|
|
async startScreenCapture(_destination: string) {
|
|
throw new Error('startScreenCapture not implemented on BaseDevice ');
|
|
}
|
|
|
|
async stopScreenCapture(): Promise<string | null> {
|
|
return null;
|
|
}
|
|
|
|
loadDevicePlugins(devicePlugins?: Map<string, typeof FlipperDevicePlugin>) {
|
|
this.devicePlugins = Array.from(devicePlugins ? devicePlugins.values() : [])
|
|
.filter(plugin => plugin.supportsDevice(this))
|
|
.sort(sortPluginsByName)
|
|
.map(plugin => plugin.id);
|
|
}
|
|
}
|