From 89ebb11520bec4cd49fd5dd79830b1d136986572 Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Fri, 17 May 2019 03:09:23 -0700 Subject: [PATCH] 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 --- headless/index.js | 42 ++++++++--- src/utils/__tests__/serialization.node.js | 6 ++ src/utils/exportMetrics.js | 88 ++++++++++++++++++----- src/utils/serialization.js | 5 +- 4 files changed, 115 insertions(+), 26 deletions(-) diff --git a/headless/index.js b/headless/index.js index 21a6d6204..ea35e8028 100644 --- a/headless/index.js +++ b/headless/index.js @@ -9,12 +9,14 @@ import path from 'path'; import {createStore} from 'redux'; import {applyMiddleware} from 'redux'; import yargs from 'yargs'; - import dispatcher from '../src/dispatcher/index.js'; import {init as initLogger} from '../src/fb-stubs/Logger.js'; import reducers from '../src/reducers/index.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! import setup from '../static/setup.js'; @@ -54,9 +56,9 @@ yargs }); yargs.option('metrics', { alias: 'metrics', - default: false, + default: undefined, describe: 'Will export metrics instead of data when flipper terminates', - type: 'boolean', + type: 'string', }); }, startFlipper, @@ -64,7 +66,14 @@ yargs .version(global.__VERSION__) .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, verbose, metrics, @@ -109,8 +118,9 @@ function startFlipper({ // TODO(T42325892): Investigate why the export stalls without exiting the // current eventloop task here. setTimeout(() => { - if (metrics) { - exportMetrics(store) + if (shouldExportMetric(metrics) && !metrics) { + const state = store.getState(); + exportMetricsWithoutTrace(state, state.pluginStates) .then(payload => { originalConsole.log(payload); process.exit(); @@ -137,11 +147,25 @@ function startFlipper({ const logger = initLogger(store, {isHeadless: true}); 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') { process.on('SIGINT', async () => { try { - if (metrics) { - const payload = await exportMetrics(store); + if (shouldExportMetric(metrics) && !metrics) { + const state = store.getState(); + const payload = await exportMetricsWithoutTrace( + state, + state.pluginStates, + ); originalConsole.log(payload); } else { const {serializedString, errorArray} = await exportStore(store); diff --git a/src/utils/__tests__/serialization.node.js b/src/utils/__tests__/serialization.node.js index 5ce27b370..00f38b7c2 100644 --- a/src/utils/__tests__/serialization.node.js +++ b/src/utils/__tests__/serialization.node.js @@ -29,6 +29,12 @@ test('test makeObjectSerializable function for unnested object with no Set and M 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', () => { let obj = {key1: 'value1', key2: 'value2'}; const output = deserializeObject(obj); diff --git a/src/utils/exportMetrics.js b/src/utils/exportMetrics.js index db65741f8..a517651b1 100644 --- a/src/utils/exportMetrics.js +++ b/src/utils/exportMetrics.js @@ -6,32 +6,26 @@ */ import type {FlipperPlugin, FlipperDevicePlugin} from 'flipper'; 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}; type MetricPluginType = {[pluginID: string]: MetricType}; export type ExportMetricType = {[clientID: string]: MetricPluginType}; -export default async function exportMetrics( - store: MiddlewareAPI, +export async function exportMetrics( + pluginStates: PluginStatesState, + pluginsMap: Map | FlipperPlugin<>>>, ): Promise { - const state = store.getState(); const metrics: ExportMetricType = {}; - for (let key in state.pluginStates) { - const pluginStateData = state.pluginStates[key]; + for (let key in pluginStates) { + const pluginStateData = pluginStates[key]; const arr = key.split('#'); const pluginName = arr.pop(); const clientID = arr.join('#'); - const pluginsMap: Map< - string, - Class | 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: ?( persistedState: any, ) => Promise = pluginsMap.get(pluginName)?.metricsReducer; @@ -49,3 +43,65 @@ export default async function exportMetrics( } return Promise.resolve(serialize(metrics)); } + +export async function exportMetricsWithoutTrace( + state: State, + pluginStates: PluginStatesState, +): Promise { + const pluginsMap: Map< + string, + Class | 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 { + 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; +} diff --git a/src/utils/serialization.js b/src/utils/serialization.js index 8445b4e89..97601ef3a 100644 --- a/src/utils/serialization.js +++ b/src/utils/serialization.js @@ -145,7 +145,10 @@ export function makeObjectSerializable(obj: any): any { } const serializedKey = dict.get(key); const serializedValue = dict.get(value); - if (serializedValue && serializedKey) { + if ( + typeof serializedKey !== 'undefined' && + typeof serializedValue !== 'undefined' + ) { obj = {...obj, [serializedKey]: serializedValue}; } }