diff --git a/src/plugins/fresco/__tests__/index.node.js b/src/plugins/fresco/__tests__/index.node.js new file mode 100644 index 000000000..15cfaa534 --- /dev/null +++ b/src/plugins/fresco/__tests__/index.node.js @@ -0,0 +1,259 @@ +/** + * 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 FrescoPlugin from '../index.js'; +import type {PersistedState, ImageEventWithId} from '../index.js'; + +function mockPersistedState( + imageSizes: Array<{ + width: number, + height: number, + }>, + viewport: { + width: number, + height: number, + }, +): PersistedState { + const scanDisplayTime = {}; + scanDisplayTime[1] = 3; + const events: Array = [ + { + imageIds: [...Array(imageSizes.length).keys()].map(String), + eventId: 0, + attribution: [], + startTime: 1, + endTime: 2, + source: 'source', + coldStart: true, + viewport: {...viewport, scanDisplayTime}, + }, + ]; + + const imagesMap = imageSizes.reduce((acc, val, index) => { + acc[index] = { + imageId: String(index), + width: val.width, + height: val.height, + sizeBytes: 10, + data: undefined, + }; + return acc; + }, {}); + + return { + surfaceList: new Set(), + images: [], + events, + imagesMap, + closeableReferenceLeaks: [], + }; +} + +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(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = FrescoPlugin.metricsReducer(persistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 37500, + CLOSEABLE_REFERENCE_LEAKS: 0, + }); +}); + +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(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = metricsReducer(persistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 0, + CLOSEABLE_REFERENCE_LEAKS: 0, + }); +}); + +test('the metric reducer for the default persisted state', () => { + const metricsReducer = FrescoPlugin.metricsReducer; + expect(metricsReducer).toBeDefined(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = metricsReducer(FrescoPlugin.defaultPersistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 0, + CLOSEABLE_REFERENCE_LEAKS: 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(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = metricsReducer(persistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 0, + CLOSEABLE_REFERENCE_LEAKS: 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(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = metricsReducer(persistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 0, + CLOSEABLE_REFERENCE_LEAKS: 0, + }); +}); + +test('the metric reducer with the multiple events', () => { + const scanDisplayTime = {}; + scanDisplayTime[1] = 3; + const events: Array = [ + { + 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; + }, {}); + const persistedState = { + surfaceList: new Set(), + images: [], + events, + imagesMap, + }; + const metricsReducer = FrescoPlugin.metricsReducer; + expect(metricsReducer).toBeDefined(); + //$FlowFixMe: Added a check if the metricsReducer exists in FrescoPlugin + const metrics = metricsReducer(persistedState); + expect(metrics).resolves.toMatchObject({ + WASTED_BYTES: 160000, + CLOSEABLE_REFERENCE_LEAKS: 0, + }); +}); diff --git a/src/plugins/fresco/index.js b/src/plugins/fresco/index.js index 47a4962a1..92198c82b 100644 --- a/src/plugins/fresco/index.js +++ b/src/plugins/fresco/index.js @@ -33,7 +33,7 @@ import ImagePool from './ImagePool.js'; export type ImageEventWithId = ImageEvent & {eventId: number}; -type PersistedState = { +export type PersistedState = { surfaceList: Set, images: ImagesList, events: Array, @@ -157,28 +157,27 @@ export default class extends FlipperPlugin { persistedState: PersistedState, ): Promise => { const {events, imagesMap, closeableReferenceLeaks} = persistedState; + const wastedBytes = (events || []).reduce((acc, event) => { const {viewport, imageIds} = event; if (!viewport) { return acc; } - return ( - acc + - 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) - ); + 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,