diff --git a/android/sample/src/main/java/com/facebook/flipper/sample/ExampleActions.java b/android/sample/src/main/java/com/facebook/flipper/sample/ExampleActions.java index f74adcc5f..78732e579 100644 --- a/android/sample/src/main/java/com/facebook/flipper/sample/ExampleActions.java +++ b/android/sample/src/main/java/com/facebook/flipper/sample/ExampleActions.java @@ -46,8 +46,7 @@ public final class ExampleActions { } public static void sendGetRequest() { - final Request request = - new Request.Builder().url("https://api.github.com/repos/facebook/yoga").get().build(); + final Request request = new Request.Builder().url("https://api.github.com/repos/facebook/yoga").get().build(); FlipperSampleApplication.sOkHttpClient .newCall(request) .enqueue( diff --git a/headless/index.js b/headless/index.js index 7f6de350a..21a6d6204 100644 --- a/headless/index.js +++ b/headless/index.js @@ -14,6 +14,8 @@ 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'; + // $FlowFixMe this file exist, trust me, flow! import setup from '../static/setup.js'; @@ -50,6 +52,12 @@ yargs describe: 'Enable verbose logging', type: 'boolean', }); + yargs.option('metrics', { + alias: 'metrics', + default: false, + describe: 'Will export metrics instead of data when flipper terminates', + type: 'boolean', + }); }, startFlipper, ) @@ -59,6 +67,7 @@ yargs function startFlipper({ dev, verbose, + metrics, exit, 'insecure-port': insecurePort, 'secure-port': securePort, @@ -100,12 +109,21 @@ function startFlipper({ // TODO(T42325892): Investigate why the export stalls without exiting the // current eventloop task here. setTimeout(() => { - exportStore(store) - .then(({serializedString}) => { - originalConsole.log(serializedString); - process.exit(); - }) - .catch(console.error); + if (metrics) { + exportMetrics(store) + .then(payload => { + originalConsole.log(payload); + process.exit(); + }) + .catch(console.error); + } else { + exportStore(store) + .then(({serializedString}) => { + originalConsole.log(serializedString); + process.exit(); + }) + .catch(console.error); + } }, 10); } return next(action); @@ -122,9 +140,14 @@ function startFlipper({ if (exit == 'sigint') { process.on('SIGINT', async () => { try { - const {serializedString, errorArray} = await exportStore(store); - errorArray.forEach(console.error); - originalConsole.log(serializedString); + if (metrics) { + const payload = await exportMetrics(store); + originalConsole.log(payload); + } else { + const {serializedString, errorArray} = await exportStore(store); + errorArray.forEach(console.error); + originalConsole.log(serializedString); + } } catch (e) { console.error(e); } diff --git a/src/plugin.js b/src/plugin.js index 0c958bc93..ddca78837 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -83,6 +83,9 @@ export class FlipperBasePlugin< method: string, data: Object, ) => $Shape; + static exportMetrics: ?( + persistedState: PersistedState, + ) => Promise>; static exportPersistedState: ?( callClient: (string, ?Object) => Promise, persistedState: ?PersistedState, diff --git a/src/plugins/network/index.js b/src/plugins/network/index.js index 6ee1b893e..5ebce2925 100644 --- a/src/plugins/network/index.js +++ b/src/plugins/network/index.js @@ -107,6 +107,19 @@ export default class extends FlipperPlugin { responses: {}, }; + static exportMetrics = ( + persistedState: PersistedState, + ): Promise> => { + const failures = Object.keys(persistedState.responses).reduce(function( + previous, + key, + ) { + return previous + (persistedState.responses[key].status >= 400); + }, + 0); + return Promise.resolve(new Map([['NUMBER_NETWORK_FAILURES', failures]])); + }; + static persistedStateReducer = ( persistedState: PersistedState, method: string, diff --git a/src/utils/exportMetrics.js b/src/utils/exportMetrics.js new file mode 100644 index 000000000..0997676c5 --- /dev/null +++ b/src/utils/exportMetrics.js @@ -0,0 +1,52 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import type {MiddlewareAPI} from '../reducers'; +import {serialize} from './serialization.js'; +import type {FlipperPlugin, FlipperDevicePlugin} from 'flipper'; + +type MetricType = Map>; + +export type ExportMetricType = Map; + +export default async function exportMetrics( + store: MiddlewareAPI, +): Promise { + const state = store.getState(); + let metrics: ExportMetricType = new Map(); + for (let key in state.pluginStates) { + const pluginStateData = state.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 exportMetrics1: ?( + persistedState: any, + ) => Promise> = pluginsMap.get(pluginName) + ?.exportMetrics; + if (pluginsMap.has(pluginName) && exportMetrics1) { + const metricMap = await exportMetrics1(pluginStateData); + const pluginMap = new Map([[pluginName, metricMap]]); + if (!metrics.get(clientID)) { + metrics.set(clientID, pluginMap); + continue; + } + const prevMetricMap = metrics.get(clientID); + // $FlowFixMe: prevMetricMap cannot be null, because clientID is added only when the pluingMetricMap is available + metrics.set(clientID, new Map([...prevMetricMap, ...pluginMap])); + } + } + return Promise.resolve(serialize(metrics)); +}