From 1973432f78f88a732e835c6cb024452128a19092 Mon Sep 17 00:00:00 2001 From: Pascal Hartig Date: Wed, 5 Jun 2019 11:31:40 -0700 Subject: [PATCH] Add active leak tracking in Fresco Summary: Adds a "Track Leaks" option that will show notifications (even retroactively) for `CloseableReferences` that were tracked. Reviewed By: danielbuechele Differential Revision: D15622596 fbshipit-source-id: ef610379aa96f9a5e541f741af608db30bee74e1 --- src/plugins/fresco/ImagesCacheOverview.js | 7 +++++ src/plugins/fresco/__tests__/index.node.js | 35 ++++++++++++++++++++++ src/plugins/fresco/index.js | 28 +++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/src/plugins/fresco/ImagesCacheOverview.js b/src/plugins/fresco/ImagesCacheOverview.js index e849ae4dd..0d5042f89 100644 --- a/src/plugins/fresco/ImagesCacheOverview.js +++ b/src/plugins/fresco/ImagesCacheOverview.js @@ -81,6 +81,8 @@ type ImagesCacheOverviewProps = { onImageSelected: (selectedImage: ImageId) => void, imagesMap: ImagesMap, events: Array, + onTrackLeaks: (enabled: boolean) => void, + isLeakTrackingEnabled: boolean, }; type ImagesCacheOverviewState = {| @@ -184,6 +186,11 @@ export default class ImagesCacheOverview extends PureComponent< onClick={this.props.onColdStartChange} label="Show Cold Start Images" /> + { const metrics = metricsReducer(persistedState); expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 2}); }); + +test('notifications for leaks', () => { + const notificationReducer: ( + persistedState: PersistedState, + ) => Array = (FrescoPlugin.getActiveNotifications: any); + const closeableReferenceLeaks: Array = [ + { + identityHashCode: 'deadbeef', + className: 'com.facebook.imagepipeline.memory.NativeMemoryChunk', + }, + { + identityHashCode: 'f4c3b00c', + className: 'com.facebook.flipper.SomeMemoryAbstraction', + }, + ]; + const persistedStateWithoutTracking = { + ...mockPersistedState(), + closeableReferenceLeaks, + isLeakTrackingEnabled: false, + }; + const emptyNotifs = notificationReducer(persistedStateWithoutTracking); + expect(emptyNotifs).toHaveLength(0); + + const persistedStateWithTracking = { + ...mockPersistedState(), + closeableReferenceLeaks, + isLeakTrackingEnabled: true, + }; + const notifs = notificationReducer(persistedStateWithTracking); + expect(notifs).toHaveLength(2); + expect(notifs[0].message).toContain('deadbeef'); + expect(notifs[1].title).toContain('SomeMemoryAbstraction'); +}); diff --git a/src/plugins/fresco/index.js b/src/plugins/fresco/index.js index 92198c82b..fce354805 100644 --- a/src/plugins/fresco/index.js +++ b/src/plugins/fresco/index.js @@ -30,6 +30,7 @@ import { } from 'flipper'; import ImagesSidebar from './ImagesSidebar.js'; import ImagePool from './ImagePool.js'; +import type {Notification} from '../../plugin'; export type ImageEventWithId = ImageEvent & {eventId: number}; @@ -39,6 +40,7 @@ export type PersistedState = { events: Array, imagesMap: ImagesMap, closeableReferenceLeaks: Array, + isLeakTrackingEnabled: boolean, }; type PluginState = { @@ -80,6 +82,7 @@ export default class extends FlipperPlugin { imagesMap: {}, surfaceList: new Set(), closeableReferenceLeaks: [], + isLeakTrackingEnabled: false, }; static exportPersistedState = ( @@ -184,6 +187,21 @@ export default class extends FlipperPlugin { }); }; + static getActiveNotifications = ({ + closeableReferenceLeaks, + isLeakTrackingEnabled, + }: PersistedState): Array => + closeableReferenceLeaks + .filter(_ => isLeakTrackingEnabled) + .map((event: AndroidCloseableReferenceLeakEvent, index) => ({ + id: event.identityHashCode, + title: `Leaked CloseableReference: ${event.className}`, + message: `CloseableReference leaked for ${event.className} + (identity hashcode: ${event.identityHashCode})`, + severity: 'error', + category: 'closeablereference_leak', + })); + state: PluginState; imagePool: ImagePool; nextEventId: number = 1; @@ -381,6 +399,12 @@ export default class extends FlipperPlugin { ); }; + onTrackLeaks = (checked: boolean) => { + this.props.setPersistedState({ + isLeakTrackingEnabled: checked, + }); + }; + render() { const options = [...this.props.persistedState.surfaceList].reduce( (acc, item) => { @@ -407,6 +431,10 @@ export default class extends FlipperPlugin { onImageSelected={this.onImageSelected} imagesMap={this.props.persistedState.imagesMap} events={this.props.persistedState.events} + isLeakTrackingEnabled={ + this.props.persistedState.isLeakTrackingEnabled + } + onTrackLeaks={this.onTrackLeaks} /> {this.renderSidebar()}