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
This commit is contained in:
committed by
Facebook Github Bot
parent
bef1ff26bc
commit
1973432f78
@@ -81,6 +81,8 @@ type ImagesCacheOverviewProps = {
|
|||||||
onImageSelected: (selectedImage: ImageId) => void,
|
onImageSelected: (selectedImage: ImageId) => void,
|
||||||
imagesMap: ImagesMap,
|
imagesMap: ImagesMap,
|
||||||
events: Array<ImageEventWithId>,
|
events: Array<ImageEventWithId>,
|
||||||
|
onTrackLeaks: (enabled: boolean) => void,
|
||||||
|
isLeakTrackingEnabled: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ImagesCacheOverviewState = {|
|
type ImagesCacheOverviewState = {|
|
||||||
@@ -184,6 +186,11 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
onClick={this.props.onColdStartChange}
|
onClick={this.props.onColdStartChange}
|
||||||
label="Show Cold Start Images"
|
label="Show Cold Start Images"
|
||||||
/>
|
/>
|
||||||
|
<Toggle
|
||||||
|
toggled={this.props.isLeakTrackingEnabled}
|
||||||
|
onClick={this.props.onTrackLeaks}
|
||||||
|
label="Track Leaks"
|
||||||
|
/>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import FrescoPlugin from '../index.js';
|
|||||||
import type {PersistedState, ImageEventWithId} from '../index.js';
|
import type {PersistedState, ImageEventWithId} from '../index.js';
|
||||||
import type {AndroidCloseableReferenceLeakEvent} from '../api.js';
|
import type {AndroidCloseableReferenceLeakEvent} from '../api.js';
|
||||||
import type {MetricType} from 'flipper';
|
import type {MetricType} from 'flipper';
|
||||||
|
import type {Notification} from '../../../plugin';
|
||||||
|
|
||||||
function mockPersistedState(
|
function mockPersistedState(
|
||||||
imageSizes: Array<{
|
imageSizes: Array<{
|
||||||
@@ -52,6 +53,7 @@ function mockPersistedState(
|
|||||||
events,
|
events,
|
||||||
imagesMap,
|
imagesMap,
|
||||||
closeableReferenceLeaks: [],
|
closeableReferenceLeaks: [],
|
||||||
|
isLeakTrackingEnabled: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,3 +278,36 @@ test('closeable reference metrics on input', () => {
|
|||||||
const metrics = metricsReducer(persistedState);
|
const metrics = metricsReducer(persistedState);
|
||||||
expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 2});
|
expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 2});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('notifications for leaks', () => {
|
||||||
|
const notificationReducer: (
|
||||||
|
persistedState: PersistedState,
|
||||||
|
) => Array<Notification> = (FrescoPlugin.getActiveNotifications: any);
|
||||||
|
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
|
||||||
|
{
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import ImagesSidebar from './ImagesSidebar.js';
|
import ImagesSidebar from './ImagesSidebar.js';
|
||||||
import ImagePool from './ImagePool.js';
|
import ImagePool from './ImagePool.js';
|
||||||
|
import type {Notification} from '../../plugin';
|
||||||
|
|
||||||
export type ImageEventWithId = ImageEvent & {eventId: number};
|
export type ImageEventWithId = ImageEvent & {eventId: number};
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ export type PersistedState = {
|
|||||||
events: Array<ImageEventWithId>,
|
events: Array<ImageEventWithId>,
|
||||||
imagesMap: ImagesMap,
|
imagesMap: ImagesMap,
|
||||||
closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>,
|
closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>,
|
||||||
|
isLeakTrackingEnabled: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type PluginState = {
|
type PluginState = {
|
||||||
@@ -80,6 +82,7 @@ export default class extends FlipperPlugin<PluginState, *, PersistedState> {
|
|||||||
imagesMap: {},
|
imagesMap: {},
|
||||||
surfaceList: new Set(),
|
surfaceList: new Set(),
|
||||||
closeableReferenceLeaks: [],
|
closeableReferenceLeaks: [],
|
||||||
|
isLeakTrackingEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
static exportPersistedState = (
|
static exportPersistedState = (
|
||||||
@@ -184,6 +187,21 @@ export default class extends FlipperPlugin<PluginState, *, PersistedState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static getActiveNotifications = ({
|
||||||
|
closeableReferenceLeaks,
|
||||||
|
isLeakTrackingEnabled,
|
||||||
|
}: PersistedState): Array<Notification> =>
|
||||||
|
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;
|
state: PluginState;
|
||||||
imagePool: ImagePool;
|
imagePool: ImagePool;
|
||||||
nextEventId: number = 1;
|
nextEventId: number = 1;
|
||||||
@@ -381,6 +399,12 @@ export default class extends FlipperPlugin<PluginState, *, PersistedState> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onTrackLeaks = (checked: boolean) => {
|
||||||
|
this.props.setPersistedState({
|
||||||
|
isLeakTrackingEnabled: checked,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const options = [...this.props.persistedState.surfaceList].reduce(
|
const options = [...this.props.persistedState.surfaceList].reduce(
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
@@ -407,6 +431,10 @@ export default class extends FlipperPlugin<PluginState, *, PersistedState> {
|
|||||||
onImageSelected={this.onImageSelected}
|
onImageSelected={this.onImageSelected}
|
||||||
imagesMap={this.props.persistedState.imagesMap}
|
imagesMap={this.props.persistedState.imagesMap}
|
||||||
events={this.props.persistedState.events}
|
events={this.props.persistedState.events}
|
||||||
|
isLeakTrackingEnabled={
|
||||||
|
this.props.persistedState.isLeakTrackingEnabled
|
||||||
|
}
|
||||||
|
onTrackLeaks={this.onTrackLeaks}
|
||||||
/>
|
/>
|
||||||
<DetailSidebar>{this.renderSidebar()}</DetailSidebar>
|
<DetailSidebar>{this.renderSidebar()}</DetailSidebar>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
Reference in New Issue
Block a user