Kill metrics reducer
Reviewed By: nikoant Differential Revision: D24332440 fbshipit-source-id: 0a48b25f98d93b181b622e8477a74c7ef0094816
This commit is contained in:
committed by
Facebook GitHub Bot
parent
707b8a922e
commit
41a1af33cb
@@ -33,7 +33,6 @@ export {
|
||||
} from './plugin';
|
||||
export {PluginClient, Props} from './plugin';
|
||||
export {default as Client} from './Client';
|
||||
export {MetricType} from './utils/exportMetrics';
|
||||
export {reportUsage} from './utils/metrics';
|
||||
export {default as promiseTimeout} from './utils/promiseTimeout';
|
||||
export {clipboard, remote, OpenDialogOptions} from 'electron';
|
||||
|
||||
@@ -11,7 +11,6 @@ import {KeyboardActions} from './MenuBar';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import Client from './Client';
|
||||
import {Store} from './reducers/index';
|
||||
import {MetricType} from './utils/exportMetrics';
|
||||
import {ReactNode, Component} from 'react';
|
||||
import BaseDevice from './devices/BaseDevice';
|
||||
import {serialize, deserialize} from './utils/serialization';
|
||||
@@ -131,9 +130,6 @@ export abstract class FlipperBasePlugin<
|
||||
static defaultPersistedState: any;
|
||||
static persistedStateReducer: PersistedStateReducer | null;
|
||||
static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE;
|
||||
static metricsReducer:
|
||||
| ((persistedState: StaticPersistedState) => Promise<MetricType>)
|
||||
| undefined;
|
||||
static exportPersistedState:
|
||||
| ((
|
||||
callClient: (method: string, params?: any) => Promise<any>,
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* 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 {serialize} from './serialization';
|
||||
import {State as PluginStatesState} from '../reducers/pluginStates';
|
||||
import {Store} from '../reducers';
|
||||
import fs from 'fs';
|
||||
import {
|
||||
ExportType,
|
||||
fetchMetadata,
|
||||
determinePluginsToProcess,
|
||||
} from './exportData';
|
||||
import {deserializeObject} from './serialization';
|
||||
import {deconstructPluginKey} from './clientUtils';
|
||||
import {pluginsClassMap} from './pluginUtils';
|
||||
import {PluginDefinition, isSandyPlugin} from '../plugin';
|
||||
|
||||
export type MetricType = {[metricName: string]: number};
|
||||
type MetricPluginType = {[pluginID: string]: MetricType};
|
||||
export type ExportMetricType = {[clientID: string]: MetricPluginType};
|
||||
|
||||
async function exportMetrics(
|
||||
pluginStates: PluginStatesState,
|
||||
pluginsMap: Map<string, PluginDefinition>,
|
||||
selectedPlugins: Array<string>,
|
||||
): Promise<string> {
|
||||
const metrics: ExportMetricType = {};
|
||||
for (const key in pluginStates) {
|
||||
const pluginStateData = pluginStates[key];
|
||||
|
||||
const plugin = deconstructPluginKey(key);
|
||||
const pluginName = plugin.pluginName;
|
||||
if (
|
||||
pluginName === undefined ||
|
||||
(selectedPlugins.length > 0 && !selectedPlugins.includes(pluginName))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const client = plugin.client;
|
||||
const pluginClass = pluginsMap.get(pluginName);
|
||||
const metricsReducer:
|
||||
| (<U>(persistedState: U) => Promise<MetricType>)
|
||||
| undefined =
|
||||
pluginClass && !isSandyPlugin(pluginClass) // This feature doesn't seem to be used at all, so let's add it when needed for Sandy
|
||||
? pluginClass.metricsReducer
|
||||
: undefined;
|
||||
if (pluginsMap.has(pluginName) && metricsReducer) {
|
||||
const metricsObject = await metricsReducer(pluginStateData);
|
||||
const pluginObject: MetricPluginType = {};
|
||||
pluginObject[pluginName] = metricsObject;
|
||||
if (!metrics[client]) {
|
||||
metrics[client] = pluginObject;
|
||||
continue;
|
||||
}
|
||||
const mergedMetrics = {...metrics[client], ...pluginObject};
|
||||
metrics[client] = mergedMetrics;
|
||||
}
|
||||
}
|
||||
return Promise.resolve(await serialize(metrics));
|
||||
}
|
||||
|
||||
export async function exportMetricsWithoutTrace(
|
||||
store: Store,
|
||||
pluginStates: PluginStatesState,
|
||||
): Promise<string | null> {
|
||||
const pluginsMap = pluginsClassMap(store.getState().plugins);
|
||||
const {clients, selectedDevice} = store.getState().connections;
|
||||
const pluginsToProcess = determinePluginsToProcess(
|
||||
clients,
|
||||
selectedDevice,
|
||||
store.getState().plugins,
|
||||
);
|
||||
const metadata = await fetchMetadata(
|
||||
pluginsToProcess,
|
||||
pluginStates,
|
||||
store.getState(),
|
||||
);
|
||||
const newPluginStates = metadata.pluginStates;
|
||||
const {errors} = metadata;
|
||||
if (errors) {
|
||||
console.error(errors);
|
||||
}
|
||||
|
||||
const metrics = await exportMetrics(
|
||||
newPluginStates,
|
||||
pluginsMap,
|
||||
store.getState().plugins.selectedPlugins,
|
||||
);
|
||||
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,
|
||||
pluginsMap: Map<string, PluginDefinition>,
|
||||
selectedPlugins: Array<string>,
|
||||
): 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.',
|
||||
),
|
||||
);
|
||||
}
|
||||
return await exportMetrics(pluginStates, pluginsMap, selectedPlugins);
|
||||
}
|
||||
@@ -17,10 +17,6 @@ import dispatcher from '../app/src/dispatcher/index';
|
||||
import reducers, {Actions, State} from '../app/src/reducers/index';
|
||||
import {init as initLogger} from '../app/src/fb-stubs/Logger';
|
||||
import {exportStore} from '../app/src/utils/exportData';
|
||||
import {
|
||||
exportMetricsWithoutTrace,
|
||||
exportMetricsFromTrace,
|
||||
} from '../app/src/utils/exportMetrics';
|
||||
import {listDevices} from '../app/src/utils/listDevices';
|
||||
import setup from '../static/setup';
|
||||
import {
|
||||
@@ -42,7 +38,7 @@ type UserArguments = {
|
||||
dev: boolean;
|
||||
exit: 'sigint' | 'disconnect';
|
||||
verbose: boolean;
|
||||
metrics: string;
|
||||
metrics: boolean;
|
||||
listDevices: boolean;
|
||||
device: string;
|
||||
listPlugins: boolean;
|
||||
@@ -116,13 +112,6 @@ type UserArguments = {
|
||||
.version(global.__VERSION__)
|
||||
.help().argv; // http://yargs.js.org/docs/#api-argv
|
||||
|
||||
function shouldExportMetric(metrics: string): boolean {
|
||||
if (!metrics) {
|
||||
return process.argv.includes('--metrics');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function outputAndExit(output: string | null | undefined): void {
|
||||
output = output || '';
|
||||
console.log(`Finished. Outputting ${output.length} characters.`);
|
||||
@@ -161,7 +150,7 @@ async function exitActions(
|
||||
userArguments: UserArguments,
|
||||
store: Store,
|
||||
): Promise<void> {
|
||||
const {metrics, exit} = userArguments;
|
||||
const {exit} = userArguments;
|
||||
for (const exitAction of exitClosures) {
|
||||
try {
|
||||
const action = await exitAction(userArguments, store);
|
||||
@@ -176,20 +165,11 @@ async function exitActions(
|
||||
if (exit == 'sigint') {
|
||||
process.on('SIGINT', async () => {
|
||||
try {
|
||||
if (shouldExportMetric(metrics) && !metrics) {
|
||||
const state = store.getState();
|
||||
const payload = await exportMetricsWithoutTrace(
|
||||
store,
|
||||
state.pluginStates,
|
||||
);
|
||||
outputAndExit(payload);
|
||||
} else {
|
||||
const {serializedString, fetchMetaDataErrors} = await exportStore(
|
||||
store,
|
||||
);
|
||||
console.error('Error while fetching metadata', fetchMetaDataErrors);
|
||||
outputAndExit(serializedString);
|
||||
}
|
||||
} catch (e) {
|
||||
errorAndExit(e);
|
||||
}
|
||||
@@ -217,7 +197,7 @@ async function storeModifyingActions(
|
||||
}
|
||||
|
||||
async function startFlipper(userArguments: UserArguments) {
|
||||
const {verbose, metrics, exit, insecurePort, securePort} = userArguments;
|
||||
const {verbose, exit, insecurePort, securePort, metrics} = userArguments;
|
||||
console.error(`
|
||||
_____ _ _
|
||||
| __| |_|___ ___ ___ ___
|
||||
@@ -225,6 +205,11 @@ async function startFlipper(userArguments: UserArguments) {
|
||||
|__| |_|_| _| _|___|_| v${global.__VERSION__}
|
||||
|_| |_|
|
||||
`);
|
||||
if (metrics) {
|
||||
throw new Error(
|
||||
'--metrics is no longer supported, see D24332440 for details.',
|
||||
);
|
||||
}
|
||||
|
||||
// redirect all logging to stderr
|
||||
const overriddenMethods = ['debug', 'info', 'log', 'warn', 'error'];
|
||||
@@ -253,16 +238,6 @@ async function startFlipper(userArguments: UserArguments) {
|
||||
// TODO(T42325892): Investigate why the export stalls without exiting the
|
||||
// current eventloop task here.
|
||||
setTimeout(() => {
|
||||
if (shouldExportMetric(metrics) && !metrics) {
|
||||
const state = store.getState();
|
||||
exportMetricsWithoutTrace(store as Store, state.pluginStates)
|
||||
.then((payload: string | null) => {
|
||||
outputAndExit(payload || '');
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
errorAndExit(e);
|
||||
});
|
||||
} else {
|
||||
exportStore(store)
|
||||
.then(({serializedString}) => {
|
||||
outputAndExit(serializedString);
|
||||
@@ -270,7 +245,6 @@ async function startFlipper(userArguments: UserArguments) {
|
||||
.catch((e: Error) => {
|
||||
errorAndExit(e);
|
||||
});
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
return next(action);
|
||||
@@ -378,19 +352,6 @@ async function startFlipper(userArguments: UserArguments) {
|
||||
});
|
||||
},
|
||||
async (userArguments: UserArguments, store: Store) => {
|
||||
const {metrics} = userArguments;
|
||||
if (shouldExportMetric(metrics) && metrics && metrics.length > 0) {
|
||||
try {
|
||||
const payload = await exportMetricsFromTrace(
|
||||
metrics,
|
||||
pluginsClassMap(store.getState().plugins),
|
||||
store.getState().plugins.selectedPlugins,
|
||||
);
|
||||
return {exit: true, result: payload ? payload.toString() : ''};
|
||||
} catch (error) {
|
||||
return {exit: true, result: error};
|
||||
}
|
||||
}
|
||||
return Promise.resolve({exit: false});
|
||||
},
|
||||
];
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import FrescoPlugin from '../index';
|
||||
import {PersistedState, ImageEventWithId} from '../index';
|
||||
import {AndroidCloseableReferenceLeakEvent} from '../api';
|
||||
import {MetricType, Notification} from 'flipper';
|
||||
import {Notification} from 'flipper';
|
||||
import {ImagesMap} from '../ImagePool';
|
||||
|
||||
type ScanDisplayTime = {[scan_number: number]: number};
|
||||
@@ -63,227 +63,6 @@ function mockPersistedState(
|
||||
};
|
||||
}
|
||||
|
||||
test('the metric reducer for the input having regression', () => {
|
||||
const persistedState = mockPersistedState(
|
||||
[
|
||||
{
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
{
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
{
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
],
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
);
|
||||
expect(FrescoPlugin.metricsReducer).toBeDefined();
|
||||
const metrics = FrescoPlugin.metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({
|
||||
WASTED_BYTES: 37500,
|
||||
});
|
||||
});
|
||||
|
||||
test('the metric reducer for the input having no regression', () => {
|
||||
const persistedState = mockPersistedState(
|
||||
[
|
||||
{
|
||||
width: 50,
|
||||
height: 10,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
],
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
);
|
||||
const metricsReducer = FrescoPlugin.metricsReducer;
|
||||
expect(metricsReducer).toBeDefined();
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({
|
||||
WASTED_BYTES: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('the metric reducer for the default persisted state', () => {
|
||||
const metricsReducer = FrescoPlugin.metricsReducer;
|
||||
expect(metricsReducer).toBeDefined();
|
||||
const metrics = metricsReducer(FrescoPlugin.defaultPersistedState);
|
||||
return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 0});
|
||||
});
|
||||
|
||||
test('the metric reducer with the events data but with no imageData in imagesMap ', () => {
|
||||
const persistedState = mockPersistedState(
|
||||
[
|
||||
{
|
||||
width: 50,
|
||||
height: 10,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
],
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
);
|
||||
persistedState.imagesMap = {};
|
||||
const metricsReducer = FrescoPlugin.metricsReducer;
|
||||
expect(metricsReducer).toBeDefined();
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 0});
|
||||
});
|
||||
|
||||
test('the metric reducer with the no viewPort data in events', () => {
|
||||
const persistedState = mockPersistedState(
|
||||
[
|
||||
{
|
||||
width: 50,
|
||||
height: 10,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
{
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
],
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
);
|
||||
delete persistedState.events[0].viewport;
|
||||
const metricsReducer = FrescoPlugin.metricsReducer;
|
||||
expect(metricsReducer).toBeDefined();
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 0});
|
||||
});
|
||||
test('the metric reducer with the multiple events', () => {
|
||||
const scanDisplayTime: ScanDisplayTime = {};
|
||||
scanDisplayTime[1] = 3;
|
||||
const events: Array<ImageEventWithId> = [
|
||||
{
|
||||
imageIds: ['0', '1'],
|
||||
eventId: 0,
|
||||
attribution: [],
|
||||
startTime: 1,
|
||||
endTime: 2,
|
||||
source: 'source',
|
||||
coldStart: true,
|
||||
viewport: {width: 100, height: 100, scanDisplayTime},
|
||||
},
|
||||
{
|
||||
imageIds: ['2', '3'],
|
||||
eventId: 1,
|
||||
attribution: [],
|
||||
startTime: 1,
|
||||
endTime: 2,
|
||||
source: 'source',
|
||||
coldStart: true,
|
||||
viewport: {width: 50, height: 50, scanDisplayTime},
|
||||
},
|
||||
];
|
||||
const imageSizes = [
|
||||
{
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
{
|
||||
width: 250,
|
||||
height: 250,
|
||||
},
|
||||
{
|
||||
width: 300,
|
||||
height: 300,
|
||||
},
|
||||
];
|
||||
const imagesMap = imageSizes.reduce((acc, val, index) => {
|
||||
acc[index] = {
|
||||
imageId: String(index),
|
||||
width: val.width,
|
||||
height: val.height,
|
||||
sizeBytes: 10,
|
||||
data: 'undefined',
|
||||
};
|
||||
return acc;
|
||||
}, {} as ImagesMap);
|
||||
const persistedState = {
|
||||
surfaceList: new Set<string>(),
|
||||
images: [],
|
||||
nextEventId: 0,
|
||||
events,
|
||||
imagesMap,
|
||||
closeableReferenceLeaks: [],
|
||||
isLeakTrackingEnabled: true,
|
||||
showDiskImages: false,
|
||||
};
|
||||
const metricsReducer = FrescoPlugin.metricsReducer;
|
||||
expect(metricsReducer).toBeDefined();
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 160000});
|
||||
});
|
||||
|
||||
test('closeable reference metrics on empty state', () => {
|
||||
const metricsReducer: (
|
||||
persistedState: PersistedState,
|
||||
) => Promise<MetricType> = FrescoPlugin.metricsReducer;
|
||||
const persistedState = mockPersistedState();
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 0});
|
||||
});
|
||||
|
||||
test('closeable reference metrics on input', () => {
|
||||
const metricsReducer: (
|
||||
persistedState: PersistedState,
|
||||
) => Promise<MetricType> = FrescoPlugin.metricsReducer;
|
||||
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
|
||||
{
|
||||
identityHashCode: 'deadbeef',
|
||||
className: 'com.facebook.imagepipeline.memory.NativeMemoryChunk',
|
||||
stacktrace: null,
|
||||
},
|
||||
{
|
||||
identityHashCode: 'f4c3b00c',
|
||||
className: 'com.facebook.flipper.SomeMemoryAbstraction',
|
||||
stacktrace: null,
|
||||
},
|
||||
];
|
||||
const persistedState = {
|
||||
...mockPersistedState(),
|
||||
closeableReferenceLeaks,
|
||||
};
|
||||
const metrics = metricsReducer(persistedState);
|
||||
return expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 2});
|
||||
});
|
||||
|
||||
test('notifications for leaks', () => {
|
||||
const notificationReducer: (
|
||||
persistedState: PersistedState,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from './api';
|
||||
import {Fragment} from 'react';
|
||||
import {ImagesMap} from './ImagePool';
|
||||
import {MetricType, ReduxState} from 'flipper';
|
||||
import {ReduxState} from 'flipper';
|
||||
import React from 'react';
|
||||
import ImagesCacheOverview from './ImagesCacheOverview';
|
||||
import {
|
||||
@@ -207,37 +207,6 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
|
||||
return persistedState;
|
||||
};
|
||||
|
||||
static metricsReducer = (
|
||||
persistedState: PersistedState,
|
||||
): Promise<MetricType> => {
|
||||
const {events, imagesMap, closeableReferenceLeaks} = persistedState;
|
||||
|
||||
const wastedBytes = (events || []).reduce((acc, event) => {
|
||||
const {viewport, imageIds} = event;
|
||||
if (!viewport) {
|
||||
return acc;
|
||||
}
|
||||
return imageIds.reduce((innerAcc, imageID) => {
|
||||
const imageData: ImageData = imagesMap[imageID];
|
||||
if (!imageData) {
|
||||
return innerAcc;
|
||||
}
|
||||
const imageWidth: number = imageData.width;
|
||||
const imageHeight: number = imageData.height;
|
||||
const viewPortWidth: number = viewport.width;
|
||||
const viewPortHeight: number = viewport.height;
|
||||
const viewPortArea = viewPortWidth * viewPortHeight;
|
||||
const imageArea = imageWidth * imageHeight;
|
||||
return innerAcc + Math.max(0, imageArea - viewPortArea);
|
||||
}, acc);
|
||||
}, 0);
|
||||
|
||||
return Promise.resolve({
|
||||
WASTED_BYTES: wastedBytes,
|
||||
CLOSEABLE_REFERENCE_LEAKS: (closeableReferenceLeaks || []).length,
|
||||
});
|
||||
};
|
||||
|
||||
static getActiveNotifications = ({
|
||||
closeableReferenceLeaks = [],
|
||||
isLeakTrackingEnabled = false,
|
||||
|
||||
Reference in New Issue
Block a user