perfetto-ui: Prevent lots of flamegraph queries being queued
If a user clicks on a markers quickly we should prevent queueing up
queries and re-run queries for the last one.
Change-Id: I4e2a822dcd882d40080df9edc45da589cdbeba10
diff --git a/ui/src/controller/heap_profile_controller.ts b/ui/src/controller/heap_profile_controller.ts
index f3f23b5..a556b92 100644
--- a/ui/src/controller/heap_profile_controller.ts
+++ b/ui/src/controller/heap_profile_controller.ts
@@ -24,6 +24,8 @@
SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
} from '../common/flamegraph_util';
import {CallsiteInfo, HeapProfileFlamegraph} from '../common/state';
+import {fromNs} from '../common/time';
+import {HeapProfileDetails} from '../frontend/globals';
import {Controller} from './controller';
import {globals} from './globals';
@@ -36,6 +38,9 @@
export class HeapProfileController extends Controller<'main'> {
private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map();
private lastSelectedHeapProfile?: HeapProfileFlamegraph;
+ private requestingData = false;
+ private queuedRequest = false;
+ private heapProfileDetails: HeapProfileDetails = {};
constructor(private args: HeapProfileControllerArgs) {
super('main');
@@ -46,70 +51,102 @@
if (!selection) return;
- if (selection.kind === 'HEAP_PROFILE_FLAMEGRAPH') {
- if (this.lastSelectedHeapProfile === undefined ||
- (this.lastSelectedHeapProfile !== undefined &&
- (this.lastSelectedHeapProfile.id !== selection.id ||
- this.lastSelectedHeapProfile.ts !== selection.ts ||
- this.lastSelectedHeapProfile.upid !== selection.upid ||
- this.lastSelectedHeapProfile.viewingOption !==
- selection.viewingOption ||
- this.lastSelectedHeapProfile.expandedCallsite !==
- selection.expandedCallsite))) {
- const selectedId = selection.id;
- const selectedUpid = selection.upid;
- const selectedKind = selection.kind;
- const selectedTs = selection.ts;
- const selectedExpandedCallsite = selection.expandedCallsite;
- const lastSelectedViewingOption = selection.viewingOption ?
- selection.viewingOption :
- DEFAULT_VIEWING_OPTION;
+ if (this.shouldRequestData(selection)) {
+ if (this.requestingData) {
+ this.queuedRequest = true;
+ } else {
+ this.requestingData = true;
+ const selectedHeapProfile: HeapProfileFlamegraph =
+ this.copyHeapProfile(selection);
- this.lastSelectedHeapProfile = {
- kind: selectedKind,
- id: selectedId,
- upid: selectedUpid,
- ts: selectedTs,
- expandedCallsite: selectedExpandedCallsite,
- viewingOption: lastSelectedViewingOption
- };
-
- const expandedId =
- selectedExpandedCallsite ? selectedExpandedCallsite.id : -1;
- const rootSize = selectedExpandedCallsite === undefined ?
- undefined :
- selectedExpandedCallsite.totalSize;
-
- const key = `${selectedUpid};${selectedTs}`;
-
- this.getFlamegraphData(
- key, lastSelectedViewingOption, selection.ts, selectedUpid)
- .then(flamegraphData => {
- if (flamegraphData !== undefined && selection &&
- selection.kind === selectedKind &&
- selection.id === selectedId && selection.ts === selectedTs) {
- const expandedFlamegraphData =
- expandCallsites(flamegraphData, expandedId);
- this.prepareAndMergeCallsites(
- expandedFlamegraphData,
- this.lastSelectedHeapProfile!.viewingOption,
- rootSize,
- this.lastSelectedHeapProfile!.expandedCallsite);
+ this.getHeapProfileMetadata(
+ selectedHeapProfile.ts, selectedHeapProfile.upid)
+ .then(result => {
+ if (result !== undefined) {
+ Object.assign(this.heapProfileDetails, result);
}
+
+ this.lastSelectedHeapProfile = this.copyHeapProfile(selection);
+
+ const expandedId = selectedHeapProfile.expandedCallsite ?
+ selectedHeapProfile.expandedCallsite.id :
+ -1;
+ const rootSize =
+ selectedHeapProfile.expandedCallsite === undefined ?
+ undefined :
+ selectedHeapProfile.expandedCallsite.totalSize;
+
+ const key =
+ `${selectedHeapProfile.upid};${selectedHeapProfile.ts}`;
+
+ this.getFlamegraphData(
+ key,
+ selectedHeapProfile.viewingOption ?
+ selectedHeapProfile.viewingOption :
+ DEFAULT_VIEWING_OPTION,
+ selection.ts,
+ selectedHeapProfile.upid)
+ .then(flamegraphData => {
+ if (flamegraphData !== undefined && selection &&
+ selection.kind === selectedHeapProfile.kind &&
+ selection.id === selectedHeapProfile.id &&
+ selection.ts === selectedHeapProfile.ts) {
+ const expandedFlamegraphData =
+ expandCallsites(flamegraphData, expandedId);
+ this.prepareAndMergeCallsites(
+ expandedFlamegraphData,
+ this.lastSelectedHeapProfile!.viewingOption,
+ rootSize,
+ this.lastSelectedHeapProfile!.expandedCallsite);
+ }
+ })
+ .finally(() => {
+ this.requestingData = false;
+ if (this.queuedRequest) {
+ this.queuedRequest = false;
+ this.run();
+ }
+ });
});
}
}
}
+ private copyHeapProfile(heapProfile: HeapProfileFlamegraph):
+ HeapProfileFlamegraph {
+ return {
+ kind: heapProfile.kind,
+ id: heapProfile.id,
+ upid: heapProfile.upid,
+ ts: heapProfile.ts,
+ expandedCallsite: heapProfile.expandedCallsite,
+ viewingOption: heapProfile.viewingOption
+ };
+ }
+
+ private shouldRequestData(selection: HeapProfileFlamegraph) {
+ return selection.kind === 'HEAP_PROFILE_FLAMEGRAPH' &&
+ (this.lastSelectedHeapProfile === undefined ||
+ (this.lastSelectedHeapProfile !== undefined &&
+ (this.lastSelectedHeapProfile.id !== selection.id ||
+ this.lastSelectedHeapProfile.ts !== selection.ts ||
+ this.lastSelectedHeapProfile.upid !== selection.upid ||
+ this.lastSelectedHeapProfile.viewingOption !==
+ selection.viewingOption ||
+ this.lastSelectedHeapProfile.expandedCallsite !==
+ selection.expandedCallsite)));
+ }
+
private prepareAndMergeCallsites(
flamegraphData: CallsiteInfo[],
viewingOption: string|undefined = DEFAULT_VIEWING_OPTION,
rootSize?: number, expandedCallsite?: CallsiteInfo) {
const mergedFlamegraphData = mergeCallsites(
flamegraphData, this.getMinSizeDisplayed(flamegraphData, rootSize));
- globals.publish(
- 'HeapProfileFlamegraph',
- {flamegraph: mergedFlamegraphData, expandedCallsite, viewingOption});
+ this.heapProfileDetails.flamegraph = mergedFlamegraphData;
+ this.heapProfileDetails.expandedCallsite = expandedCallsite;
+ this.heapProfileDetails.viewingOption = viewingOption;
+ globals.publish('HeapProfileDetails', this.heapProfileDetails);
}
@@ -308,4 +345,29 @@
}
return MIN_PIXEL_DISPLAYED * rootSize / width;
}
+
+ async getHeapProfileMetadata(ts: number, upid: number) {
+ // Don't do anything if selection of the marker stayed the same.
+ if ((this.lastSelectedHeapProfile !== undefined &&
+ ((this.lastSelectedHeapProfile.ts === ts &&
+ this.lastSelectedHeapProfile.upid === upid)))) {
+ return undefined;
+ }
+
+ // Collecting data for more information about heap profile, such as:
+ // total memory allocated, memory that is allocated and not freed.
+ const pidValue = await this.args.engine.query(
+ `select pid from process where upid = ${upid}`);
+ const pid = pidValue.columns[0].longValues![0];
+ const allocatedMemory = await this.args.engine.query(
+ `select sum(size) from heap_profile_allocation where ts <= ${
+ ts} and size > 0 and upid = ${upid}`);
+ const allocated = allocatedMemory.columns[0].longValues![0];
+ const allocatedNotFreedMemory = await this.args.engine.query(
+ `select sum(size) from heap_profile_allocation where ts <= ${
+ ts} and upid = ${upid}`);
+ const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
+ const startTime = fromNs(ts) - globals.state.traceTime.startSec;
+ return {ts: startTime, allocated, allocatedNotFreed, tsNs: ts, pid, upid};
+ }
}