Kill metrics reducer

Reviewed By: nikoant

Differential Revision: D24332440

fbshipit-source-id: 0a48b25f98d93b181b622e8477a74c7ef0094816
This commit is contained in:
Michel Weststrate
2020-12-15 01:43:06 -08:00
committed by Facebook GitHub Bot
parent 707b8a922e
commit 41a1af33cb
6 changed files with 22 additions and 457 deletions

View File

@@ -33,7 +33,6 @@ export {
} from './plugin'; } from './plugin';
export {PluginClient, Props} from './plugin'; export {PluginClient, Props} from './plugin';
export {default as Client} from './Client'; export {default as Client} from './Client';
export {MetricType} from './utils/exportMetrics';
export {reportUsage} from './utils/metrics'; export {reportUsage} from './utils/metrics';
export {default as promiseTimeout} from './utils/promiseTimeout'; export {default as promiseTimeout} from './utils/promiseTimeout';
export {clipboard, remote, OpenDialogOptions} from 'electron'; export {clipboard, remote, OpenDialogOptions} from 'electron';

View File

@@ -11,7 +11,6 @@ import {KeyboardActions} from './MenuBar';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import Client from './Client'; import Client from './Client';
import {Store} from './reducers/index'; import {Store} from './reducers/index';
import {MetricType} from './utils/exportMetrics';
import {ReactNode, Component} from 'react'; import {ReactNode, Component} from 'react';
import BaseDevice from './devices/BaseDevice'; import BaseDevice from './devices/BaseDevice';
import {serialize, deserialize} from './utils/serialization'; import {serialize, deserialize} from './utils/serialization';
@@ -131,9 +130,6 @@ export abstract class FlipperBasePlugin<
static defaultPersistedState: any; static defaultPersistedState: any;
static persistedStateReducer: PersistedStateReducer | null; static persistedStateReducer: PersistedStateReducer | null;
static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE; static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE;
static metricsReducer:
| ((persistedState: StaticPersistedState) => Promise<MetricType>)
| undefined;
static exportPersistedState: static exportPersistedState:
| (( | ((
callClient: (method: string, params?: any) => Promise<any>, callClient: (method: string, params?: any) => Promise<any>,

View File

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

View File

@@ -17,10 +17,6 @@ import dispatcher from '../app/src/dispatcher/index';
import reducers, {Actions, State} from '../app/src/reducers/index'; import reducers, {Actions, State} from '../app/src/reducers/index';
import {init as initLogger} from '../app/src/fb-stubs/Logger'; import {init as initLogger} from '../app/src/fb-stubs/Logger';
import {exportStore} from '../app/src/utils/exportData'; import {exportStore} from '../app/src/utils/exportData';
import {
exportMetricsWithoutTrace,
exportMetricsFromTrace,
} from '../app/src/utils/exportMetrics';
import {listDevices} from '../app/src/utils/listDevices'; import {listDevices} from '../app/src/utils/listDevices';
import setup from '../static/setup'; import setup from '../static/setup';
import { import {
@@ -42,7 +38,7 @@ type UserArguments = {
dev: boolean; dev: boolean;
exit: 'sigint' | 'disconnect'; exit: 'sigint' | 'disconnect';
verbose: boolean; verbose: boolean;
metrics: string; metrics: boolean;
listDevices: boolean; listDevices: boolean;
device: string; device: string;
listPlugins: boolean; listPlugins: boolean;
@@ -116,13 +112,6 @@ type UserArguments = {
.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 shouldExportMetric(metrics: string): boolean {
if (!metrics) {
return process.argv.includes('--metrics');
}
return true;
}
function outputAndExit(output: string | null | undefined): void { function outputAndExit(output: string | null | undefined): void {
output = output || ''; output = output || '';
console.log(`Finished. Outputting ${output.length} characters.`); console.log(`Finished. Outputting ${output.length} characters.`);
@@ -161,7 +150,7 @@ async function exitActions(
userArguments: UserArguments, userArguments: UserArguments,
store: Store, store: Store,
): Promise<void> { ): Promise<void> {
const {metrics, exit} = userArguments; const {exit} = userArguments;
for (const exitAction of exitClosures) { for (const exitAction of exitClosures) {
try { try {
const action = await exitAction(userArguments, store); const action = await exitAction(userArguments, store);
@@ -176,20 +165,11 @@ async function exitActions(
if (exit == 'sigint') { if (exit == 'sigint') {
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
try { try {
if (shouldExportMetric(metrics) && !metrics) { const {serializedString, fetchMetaDataErrors} = await exportStore(
const state = store.getState(); store,
const payload = await exportMetricsWithoutTrace( );
store, console.error('Error while fetching metadata', fetchMetaDataErrors);
state.pluginStates, outputAndExit(serializedString);
);
outputAndExit(payload);
} else {
const {serializedString, fetchMetaDataErrors} = await exportStore(
store,
);
console.error('Error while fetching metadata', fetchMetaDataErrors);
outputAndExit(serializedString);
}
} catch (e) { } catch (e) {
errorAndExit(e); errorAndExit(e);
} }
@@ -217,7 +197,7 @@ async function storeModifyingActions(
} }
async function startFlipper(userArguments: UserArguments) { async function startFlipper(userArguments: UserArguments) {
const {verbose, metrics, exit, insecurePort, securePort} = userArguments; const {verbose, exit, insecurePort, securePort, metrics} = userArguments;
console.error(` console.error(`
_____ _ _ _____ _ _
| __| |_|___ ___ ___ ___ | __| |_|___ ___ ___ ___
@@ -225,6 +205,11 @@ async function startFlipper(userArguments: UserArguments) {
|__| |_|_| _| _|___|_| v${global.__VERSION__} |__| |_|_| _| _|___|_| v${global.__VERSION__}
|_| |_| |_| |_|
`); `);
if (metrics) {
throw new Error(
'--metrics is no longer supported, see D24332440 for details.',
);
}
// redirect all logging to stderr // redirect all logging to stderr
const overriddenMethods = ['debug', 'info', 'log', 'warn', 'error']; const overriddenMethods = ['debug', 'info', 'log', 'warn', 'error'];
@@ -253,24 +238,13 @@ async function startFlipper(userArguments: UserArguments) {
// 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 (shouldExportMetric(metrics) && !metrics) { exportStore(store)
const state = store.getState(); .then(({serializedString}) => {
exportMetricsWithoutTrace(store as Store, state.pluginStates) outputAndExit(serializedString);
.then((payload: string | null) => { })
outputAndExit(payload || ''); .catch((e: Error) => {
}) errorAndExit(e);
.catch((e: Error) => { });
errorAndExit(e);
});
} else {
exportStore(store)
.then(({serializedString}) => {
outputAndExit(serializedString);
})
.catch((e: Error) => {
errorAndExit(e);
});
}
}, 10); }, 10);
} }
return next(action); return next(action);
@@ -378,19 +352,6 @@ async function startFlipper(userArguments: UserArguments) {
}); });
}, },
async (userArguments: UserArguments, store: Store) => { 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}); return Promise.resolve({exit: false});
}, },
]; ];

View File

@@ -10,7 +10,7 @@
import FrescoPlugin from '../index'; import FrescoPlugin from '../index';
import {PersistedState, ImageEventWithId} from '../index'; import {PersistedState, ImageEventWithId} from '../index';
import {AndroidCloseableReferenceLeakEvent} from '../api'; import {AndroidCloseableReferenceLeakEvent} from '../api';
import {MetricType, Notification} from 'flipper'; import {Notification} from 'flipper';
import {ImagesMap} from '../ImagePool'; import {ImagesMap} from '../ImagePool';
type ScanDisplayTime = {[scan_number: number]: number}; 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', () => { test('notifications for leaks', () => {
const notificationReducer: ( const notificationReducer: (
persistedState: PersistedState, persistedState: PersistedState,

View File

@@ -19,7 +19,7 @@ import {
} from './api'; } from './api';
import {Fragment} from 'react'; import {Fragment} from 'react';
import {ImagesMap} from './ImagePool'; import {ImagesMap} from './ImagePool';
import {MetricType, ReduxState} from 'flipper'; import {ReduxState} from 'flipper';
import React from 'react'; import React from 'react';
import ImagesCacheOverview from './ImagesCacheOverview'; import ImagesCacheOverview from './ImagesCacheOverview';
import { import {
@@ -207,37 +207,6 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
return persistedState; 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 = ({ static getActiveNotifications = ({
closeableReferenceLeaks = [], closeableReferenceLeaks = [],
isLeakTrackingEnabled = false, isLeakTrackingEnabled = false,