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:
Pritesh Nandgaonkar
2019-05-17 03:09:23 -07:00
committed by Facebook Github Bot
parent b8f3729752
commit 89ebb11520
4 changed files with 115 additions and 26 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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};
} }
} }