Accept Trace file and export a metric
Summary: This diff adds support to pass a path to the trace file to the headless Flipper. The headless Flipper then exports the metrics out of it. Reviewed By: passy Differential Revision: D15337067 fbshipit-source-id: 61aca1ffd58e879dafe6aa176f058f7b11460952
This commit is contained in:
committed by
Facebook Github Bot
parent
b8f3729752
commit
89ebb11520
@@ -9,12 +9,14 @@ import path from 'path';
|
|||||||
import {createStore} from 'redux';
|
import {createStore} from 'redux';
|
||||||
import {applyMiddleware} from 'redux';
|
import {applyMiddleware} from 'redux';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
|
|
||||||
import dispatcher from '../src/dispatcher/index.js';
|
import dispatcher from '../src/dispatcher/index.js';
|
||||||
import {init as initLogger} from '../src/fb-stubs/Logger.js';
|
import {init as initLogger} from '../src/fb-stubs/Logger.js';
|
||||||
import reducers from '../src/reducers/index.js';
|
import reducers from '../src/reducers/index.js';
|
||||||
import {exportStore} from '../src/utils/exportData.js';
|
import {exportStore} from '../src/utils/exportData.js';
|
||||||
import exportMetrics from '../src/utils/exportMetrics.js';
|
import {
|
||||||
|
exportMetricsWithoutTrace,
|
||||||
|
exportMetricsFromTrace,
|
||||||
|
} from '../src/utils/exportMetrics.js';
|
||||||
|
|
||||||
// $FlowFixMe this file exist, trust me, flow!
|
// $FlowFixMe this file exist, trust me, flow!
|
||||||
import setup from '../static/setup.js';
|
import setup from '../static/setup.js';
|
||||||
@@ -54,9 +56,9 @@ yargs
|
|||||||
});
|
});
|
||||||
yargs.option('metrics', {
|
yargs.option('metrics', {
|
||||||
alias: 'metrics',
|
alias: 'metrics',
|
||||||
default: false,
|
default: undefined,
|
||||||
describe: 'Will export metrics instead of data when flipper terminates',
|
describe: 'Will export metrics instead of data when flipper terminates',
|
||||||
type: 'boolean',
|
type: 'string',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
startFlipper,
|
startFlipper,
|
||||||
@@ -64,7 +66,14 @@ yargs
|
|||||||
.version(global.__VERSION__)
|
.version(global.__VERSION__)
|
||||||
.help().argv; // http://yargs.js.org/docs/#api-argv
|
.help().argv; // http://yargs.js.org/docs/#api-argv
|
||||||
|
|
||||||
function startFlipper({
|
function shouldExportMetric(metrics): boolean {
|
||||||
|
if (!metrics) {
|
||||||
|
return process.argv.includes('--metrics');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startFlipper({
|
||||||
dev,
|
dev,
|
||||||
verbose,
|
verbose,
|
||||||
metrics,
|
metrics,
|
||||||
@@ -109,8 +118,9 @@ function startFlipper({
|
|||||||
// TODO(T42325892): Investigate why the export stalls without exiting the
|
// TODO(T42325892): Investigate why the export stalls without exiting the
|
||||||
// current eventloop task here.
|
// current eventloop task here.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (metrics) {
|
if (shouldExportMetric(metrics) && !metrics) {
|
||||||
exportMetrics(store)
|
const state = store.getState();
|
||||||
|
exportMetricsWithoutTrace(state, state.pluginStates)
|
||||||
.then(payload => {
|
.then(payload => {
|
||||||
originalConsole.log(payload);
|
originalConsole.log(payload);
|
||||||
process.exit();
|
process.exit();
|
||||||
@@ -137,11 +147,25 @@ function startFlipper({
|
|||||||
const logger = initLogger(store, {isHeadless: true});
|
const logger = initLogger(store, {isHeadless: true});
|
||||||
dispatcher(store, logger);
|
dispatcher(store, logger);
|
||||||
|
|
||||||
|
if (shouldExportMetric(metrics) && metrics && metrics.length > 0) {
|
||||||
|
try {
|
||||||
|
const payload = await exportMetricsFromTrace(metrics, store.getState());
|
||||||
|
originalConsole.log(payload);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
if (exit == 'sigint') {
|
if (exit == 'sigint') {
|
||||||
process.on('SIGINT', async () => {
|
process.on('SIGINT', async () => {
|
||||||
try {
|
try {
|
||||||
if (metrics) {
|
if (shouldExportMetric(metrics) && !metrics) {
|
||||||
const payload = await exportMetrics(store);
|
const state = store.getState();
|
||||||
|
const payload = await exportMetricsWithoutTrace(
|
||||||
|
state,
|
||||||
|
state.pluginStates,
|
||||||
|
);
|
||||||
originalConsole.log(payload);
|
originalConsole.log(payload);
|
||||||
} else {
|
} else {
|
||||||
const {serializedString, errorArray} = await exportStore(store);
|
const {serializedString, errorArray} = await exportStore(store);
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ test('test makeObjectSerializable function for unnested object with no Set and M
|
|||||||
expect(output2).toEqual(obj2);
|
expect(output2).toEqual(obj2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('makeObjectSerializable function for unnested object with values which returns false when put in an if condition', () => {
|
||||||
|
const obj2 = {key1: 0, key2: ''};
|
||||||
|
const output2 = makeObjectSerializable(obj2);
|
||||||
|
expect(output2).toEqual(obj2);
|
||||||
|
});
|
||||||
|
|
||||||
test('test deserializeObject function for unnested object with no Set and Map', () => {
|
test('test deserializeObject function for unnested object with no Set and Map', () => {
|
||||||
let obj = {key1: 'value1', key2: 'value2'};
|
let obj = {key1: 'value1', key2: 'value2'};
|
||||||
const output = deserializeObject(obj);
|
const output = deserializeObject(obj);
|
||||||
|
|||||||
@@ -6,32 +6,26 @@
|
|||||||
*/
|
*/
|
||||||
import type {FlipperPlugin, FlipperDevicePlugin} from 'flipper';
|
import type {FlipperPlugin, FlipperDevicePlugin} from 'flipper';
|
||||||
import {serialize} from './serialization';
|
import {serialize} from './serialization';
|
||||||
import type {MiddlewareAPI} from '../reducers';
|
import type {State as PluginStatesState} from '../reducers/pluginStates';
|
||||||
|
import type {State} from '../reducers/index.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import type {ExportType} from './exportData';
|
||||||
|
import {deserializeObject} from './serialization';
|
||||||
|
|
||||||
export type MetricType = {[metricName: string]: number};
|
export type MetricType = {[metricName: string]: number};
|
||||||
type MetricPluginType = {[pluginID: string]: MetricType};
|
type MetricPluginType = {[pluginID: string]: MetricType};
|
||||||
export type ExportMetricType = {[clientID: string]: MetricPluginType};
|
export type ExportMetricType = {[clientID: string]: MetricPluginType};
|
||||||
|
|
||||||
export default async function exportMetrics(
|
export async function exportMetrics(
|
||||||
store: MiddlewareAPI,
|
pluginStates: PluginStatesState,
|
||||||
|
pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const state = store.getState();
|
|
||||||
const metrics: ExportMetricType = {};
|
const metrics: ExportMetricType = {};
|
||||||
for (let key in state.pluginStates) {
|
for (let key in pluginStates) {
|
||||||
const pluginStateData = state.pluginStates[key];
|
const pluginStateData = pluginStates[key];
|
||||||
const arr = key.split('#');
|
const arr = key.split('#');
|
||||||
const pluginName = arr.pop();
|
const pluginName = arr.pop();
|
||||||
const clientID = arr.join('#');
|
const clientID = arr.join('#');
|
||||||
const pluginsMap: Map<
|
|
||||||
string,
|
|
||||||
Class<FlipperDevicePlugin<> | FlipperPlugin<>>,
|
|
||||||
> = new Map([]);
|
|
||||||
state.plugins.clientPlugins.forEach((val, key) => {
|
|
||||||
pluginsMap.set(key, val);
|
|
||||||
});
|
|
||||||
state.plugins.devicePlugins.forEach((val, key) => {
|
|
||||||
pluginsMap.set(key, val);
|
|
||||||
});
|
|
||||||
const metricsReducer: ?(
|
const metricsReducer: ?(
|
||||||
persistedState: any,
|
persistedState: any,
|
||||||
) => Promise<MetricType> = pluginsMap.get(pluginName)?.metricsReducer;
|
) => Promise<MetricType> = pluginsMap.get(pluginName)?.metricsReducer;
|
||||||
@@ -49,3 +43,65 @@ export default async function exportMetrics(
|
|||||||
}
|
}
|
||||||
return Promise.resolve(serialize(metrics));
|
return Promise.resolve(serialize(metrics));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exportMetricsWithoutTrace(
|
||||||
|
state: State,
|
||||||
|
pluginStates: PluginStatesState,
|
||||||
|
): Promise<?string> {
|
||||||
|
const pluginsMap: Map<
|
||||||
|
string,
|
||||||
|
Class<FlipperDevicePlugin<> | FlipperPlugin<>>,
|
||||||
|
> = new Map([]);
|
||||||
|
state.plugins.clientPlugins.forEach((val, key) => {
|
||||||
|
pluginsMap.set(key, val);
|
||||||
|
});
|
||||||
|
state.plugins.devicePlugins.forEach((val, key) => {
|
||||||
|
pluginsMap.set(key, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
const metrics = await exportMetrics(pluginStates, pluginsMap);
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJSON(str: string): ?any {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportMetricsFromTrace(
|
||||||
|
trace: string,
|
||||||
|
state: State,
|
||||||
|
): Promise<?string> {
|
||||||
|
const data = fs.readFileSync(trace, 'utf8');
|
||||||
|
const parsedJSONData = parseJSON(data);
|
||||||
|
if (!parsedJSONData) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error('Please pass the file which has a valid JSON'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const importedData: ExportType = deserializeObject(parsedJSONData);
|
||||||
|
const importedStore = importedData.store;
|
||||||
|
if (!importedStore) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
'No store in the imported file, thus exiting without exporting metrics.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {pluginStates} = importedStore;
|
||||||
|
|
||||||
|
if (!pluginStates) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
'No pluginStates in the imported file, thus exiting without exporting metrics.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const metrics = await exportMetricsWithoutTrace(state, pluginStates);
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,7 +145,10 @@ export function makeObjectSerializable(obj: any): any {
|
|||||||
}
|
}
|
||||||
const serializedKey = dict.get(key);
|
const serializedKey = dict.get(key);
|
||||||
const serializedValue = dict.get(value);
|
const serializedValue = dict.get(value);
|
||||||
if (serializedValue && serializedKey) {
|
if (
|
||||||
|
typeof serializedKey !== 'undefined' &&
|
||||||
|
typeof serializedValue !== 'undefined'
|
||||||
|
) {
|
||||||
obj = {...obj, [serializedKey]: serializedValue};
|
obj = {...obj, [serializedKey]: serializedValue};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user