Move plugins to "sonar/desktop/plugins"
Summary: Plugins moved from "sonar/desktop/src/plugins" to "sonar/desktop/plugins". Fixed all the paths after moving. New "desktop" folder structure: - `src` - Flipper desktop app JS code executing in Electron Renderer (Chrome) process. - `static` - Flipper desktop app JS code executing in Electron Main (Node.js) process. - `plugins` - Flipper desktop JS plugins. - `pkg` - Flipper packaging lib and CLI tool. - `doctor` - Flipper diagnostics lib and CLI tool. - `scripts` - Build scripts for Flipper desktop app. - `headless` - Headless version of Flipper desktop app. - `headless-tests` - Integration tests running agains Flipper headless version. Reviewed By: mweststrate Differential Revision: D20344186 fbshipit-source-id: d020da970b2ea1e001f9061a8782bfeb54e31ba0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
beb5c85e69
commit
10d990c32c
282
desktop/plugins/kaios-ram/index.tsx
Normal file
282
desktop/plugins/kaios-ram/index.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
|
||||
import {FlipperDevicePlugin, Device, KaiOSDevice, sleep} from 'flipper';
|
||||
|
||||
import {FlexColumn, Button, Toolbar, Panel} from 'flipper';
|
||||
|
||||
import {
|
||||
Legend,
|
||||
LineChart,
|
||||
Line,
|
||||
YAxis,
|
||||
XAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
} from 'recharts';
|
||||
|
||||
import adb from 'adbkit';
|
||||
import {exec} from 'promisify-child-process';
|
||||
|
||||
const PALETTE = [
|
||||
'#FFD700',
|
||||
'#FF6347',
|
||||
'#8A2BE2',
|
||||
'#A52A2A',
|
||||
'#40E0D0',
|
||||
'#006400',
|
||||
'#ADFF2F',
|
||||
'#FF00FF',
|
||||
];
|
||||
|
||||
// For now, let's limit the number of points shown
|
||||
// The graph will automatically drop the oldest point if this number is reached
|
||||
const MAX_POINTS = 100;
|
||||
|
||||
// This is to have some consistency in the Y axis scale
|
||||
// Recharts should automatically adjust the axis if any data point gets above this value
|
||||
const Y_AXIS_EXPECTED_MAX_MEM = 200;
|
||||
|
||||
const EXCLUDE_PROCESS_NAME_SUBSTRINGS = [
|
||||
'(Nuwa)',
|
||||
'Launcher',
|
||||
'Built-in',
|
||||
'Jio',
|
||||
'(Preallocated',
|
||||
];
|
||||
|
||||
type DataPoint = {
|
||||
[key: string]: number;
|
||||
};
|
||||
|
||||
type Colors = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
points: Array<DataPoint>;
|
||||
colors: Colors;
|
||||
monitoring: boolean;
|
||||
};
|
||||
|
||||
export default class KaiOSGraphs extends FlipperDevicePlugin<State, any, any> {
|
||||
state = {
|
||||
points: [],
|
||||
colors: {},
|
||||
monitoring: false,
|
||||
};
|
||||
|
||||
static supportsDevice(device: Device) {
|
||||
return device instanceof KaiOSDevice;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await exec('adb root');
|
||||
} catch (e) {
|
||||
console.error('Error obtaining root on the device', e);
|
||||
}
|
||||
}
|
||||
|
||||
teardown() {
|
||||
this.onStopMonitor();
|
||||
}
|
||||
|
||||
onStartMonitor = () => {
|
||||
this.setState(
|
||||
{
|
||||
monitoring: true,
|
||||
},
|
||||
() => {
|
||||
// no await because monitoring runs in the background
|
||||
this.monitorInBackground();
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
onStopMonitor = () => {
|
||||
this.setState({
|
||||
monitoring: false,
|
||||
});
|
||||
};
|
||||
|
||||
monitorInBackground = async () => {
|
||||
while (this.state.monitoring) {
|
||||
await this.updateFreeMem();
|
||||
await sleep(1000);
|
||||
}
|
||||
};
|
||||
|
||||
executeShell = (command: string) => {
|
||||
return (this.device as KaiOSDevice).adb
|
||||
.shell(this.device.serial, command)
|
||||
.then(adb.util.readAll)
|
||||
.then(output => {
|
||||
return output.toString().trim();
|
||||
});
|
||||
};
|
||||
|
||||
getMemory = () => {
|
||||
return this.executeShell('b2g-info').then(output => {
|
||||
const lines = output.split('\n').map(line => line.trim());
|
||||
let freeMem = null;
|
||||
for (const line of lines) {
|
||||
// TODO: regex validation
|
||||
if (line.startsWith('Free + cache')) {
|
||||
const fields = line.split(' ');
|
||||
const mem = fields[fields.length - 2];
|
||||
freeMem = parseFloat(mem);
|
||||
}
|
||||
}
|
||||
|
||||
const appInfoData: {[key: string]: number} = {};
|
||||
let appInfoSectionFieldsCount;
|
||||
const appInfoSectionFieldToIndex: {[key: string]: number} = {};
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('System memory info:')) {
|
||||
// We're outside of the app info section
|
||||
// Reset the counter, since it is used for detecting if we need to parse
|
||||
// app memory usage data
|
||||
appInfoSectionFieldsCount = undefined;
|
||||
break;
|
||||
}
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
if (appInfoSectionFieldsCount) {
|
||||
let fields = line.trim().split(/\s+/);
|
||||
// Assume that only name field can contain spaces
|
||||
const name = fields
|
||||
.slice(0, -appInfoSectionFieldsCount + 1)
|
||||
.join(' ');
|
||||
const restOfTheFields = fields.slice(-appInfoSectionFieldsCount + 1);
|
||||
fields = [name, ...restOfTheFields];
|
||||
if (
|
||||
EXCLUDE_PROCESS_NAME_SUBSTRINGS.some(excludeSubstr =>
|
||||
name.includes(excludeSubstr),
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (fields[1].match(/[^0-9]+/)) {
|
||||
// TODO: probably implement this through something other than b2g
|
||||
throw new Error('Support for names with spaces is not implemented');
|
||||
}
|
||||
|
||||
if (name !== 'b2g') {
|
||||
const ussString = fields[appInfoSectionFieldToIndex['USS']];
|
||||
const uss = ussString ? parseFloat(ussString) : -1;
|
||||
appInfoData[name + ' USS'] = uss;
|
||||
} else {
|
||||
const rssString = fields[appInfoSectionFieldToIndex['RSS']];
|
||||
const rss = rssString ? parseFloat(rssString) : -1;
|
||||
appInfoData[name + ' RSS'] = rss;
|
||||
}
|
||||
}
|
||||
if (line.startsWith('NAME')) {
|
||||
// We're in the app info section now
|
||||
const fields = line.trim().split(/\s+/);
|
||||
appInfoSectionFieldsCount = fields.length;
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
appInfoSectionFieldToIndex[fields[i]] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {'Total free': freeMem != null ? freeMem : -1, ...appInfoData};
|
||||
});
|
||||
};
|
||||
|
||||
getColors = (point: DataPoint) => {
|
||||
const oldColors = this.state.colors;
|
||||
let newColors: Colors | null = null;
|
||||
let newColorsCount = 0;
|
||||
const existingNames = Object.keys(oldColors);
|
||||
for (const name of Object.keys(point)) {
|
||||
if (!(name in oldColors)) {
|
||||
if (!newColors) {
|
||||
newColors = {...oldColors};
|
||||
}
|
||||
newColors[name] = PALETTE[existingNames.length + newColorsCount];
|
||||
newColorsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return newColors;
|
||||
};
|
||||
|
||||
updateFreeMem = () => {
|
||||
// This can be improved by using immutable.js
|
||||
// If more points are necessary
|
||||
return this.getMemory().then(point => {
|
||||
const points = [...this.state.points.slice(-MAX_POINTS + 1), point];
|
||||
const colors = this.getColors(point);
|
||||
let newState = {};
|
||||
if (colors) {
|
||||
newState = {colors, points};
|
||||
} else {
|
||||
newState = {points};
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const pointsToDraw = this.state.points.map((point, idx) => ({
|
||||
...(point as Object),
|
||||
idx,
|
||||
}));
|
||||
const colors: Colors = this.state.colors;
|
||||
|
||||
const names = Object.keys(colors);
|
||||
return (
|
||||
<Panel
|
||||
padded={false}
|
||||
heading="Free memory"
|
||||
floating={false}
|
||||
collapsable={false}
|
||||
grow={true}>
|
||||
<Toolbar position="top">
|
||||
{this.state.monitoring ? (
|
||||
<Button onClick={this.onStopMonitor} icon="pause">
|
||||
Pause
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={this.onStartMonitor} icon="play">
|
||||
Start
|
||||
</Button>
|
||||
)}
|
||||
</Toolbar>
|
||||
<FlexColumn grow={true}>
|
||||
<ResponsiveContainer height={500}>
|
||||
<LineChart data={pointsToDraw}>
|
||||
<XAxis type="number" domain={[0, MAX_POINTS]} dataKey="idx" />
|
||||
<YAxis type="number" domain={[0, Y_AXIS_EXPECTED_MAX_MEM]} />
|
||||
{names.map(name => (
|
||||
<Line
|
||||
key={`line-${name}`}
|
||||
type="linear"
|
||||
dataKey={name}
|
||||
stroke={colors[name]}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
))}
|
||||
<Tooltip />
|
||||
<Legend verticalAlign="bottom" height={36} />
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</FlexColumn>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user