perfetto-ui: Integrate merging callsites with expanding
Clicking on callsite will expand the flamegraph and do merging afterward,
so that previously small callsites can be visible in zoomed in view.
Test trace: ?s=c3653414e9a8ae91eae74d2671f277034473ae822634f23a1e421746f5938
Change-Id: Id95eb79415d7e65f05de3c5f694beaf6f08007eb
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 56afebd..552a0b6 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -436,7 +436,7 @@
kind: 'HEAP_PROFILE_FLAMEGRAPH',
id: args.id,
upid: args.upid,
- ts: args.ts
+ ts: args.ts,
};
},
diff --git a/ui/src/frontend/flamegraph.ts b/ui/src/frontend/flamegraph.ts
index 125d9f8..5343a25 100644
--- a/ui/src/frontend/flamegraph.ts
+++ b/ui/src/frontend/flamegraph.ts
@@ -59,12 +59,6 @@
private hoveredCallsite?: CallsiteInfo;
private clickedCallsite?: CallsiteInfo;
- // For each node, we use this map to get information about it's parent
- // (total size of it, width and where it starts in graph) so we can
- // calculate it's own position in graph.
- // This one is store for base flamegraph when no clicking has happend.
- private baseMap = new Map<number, Node>();
-
constructor(flamegraphData: CallsiteInfo[]) {
this.flamegraphData = flamegraphData;
this.findMaxDepth();
@@ -101,12 +95,13 @@
* Caller will have to call draw method ater updating data to have updated
* graph.
*/
- updateDataIfChanged(flamegraphData: CallsiteInfo[]) {
+ updateDataIfChanged(
+ flamegraphData: CallsiteInfo[], clickedCallsite?: CallsiteInfo) {
if (this.flamegraphData === flamegraphData) {
return;
}
this.flamegraphData = flamegraphData;
- this.clickedCallsite = undefined;
+ this.clickedCallsite = clickedCallsite;
this.findMaxDepth();
// Finding total size of roots.
this.totalSize = findRootSize(flamegraphData);
@@ -139,43 +134,24 @@
ctx.fillRect(x, currentY, width, nodeHeight);
currentY += nodeHeight;
- const clickedNode = this.clickedCallsite !== undefined ?
- this.baseMap.get(this.clickedCallsite.hash) :
- undefined;
for (let i = 0; i < this.flamegraphData.length; i++) {
if (currentY > height) {
break;
}
const value = this.flamegraphData[i];
- const parentNode = nodesMap.get(value.parentHash);
+ const parentNode = nodesMap.get(value.parentId);
if (parentNode === undefined) {
continue;
}
- // If node is clicked, determine if we should draw current node.
- let shouldDraw = true;
- let isFullWidth = false;
- let isGreyedOut = false;
- const oldNode = this.baseMap.get(value.hash);
- // We want to display full shape if it's thumbnail.
- if (!this.isThumbnail && clickedNode !== undefined &&
- this.clickedCallsite !== undefined && oldNode !== undefined) {
- isFullWidth = value.depth <= this.clickedCallsite.depth;
- isGreyedOut = value.depth < this.clickedCallsite.depth;
- shouldDraw = isFullWidth ? (oldNode.x <= clickedNode.x) &&
- ((oldNode.x + oldNode.width >=
- clickedNode.x + clickedNode.width)) :
- (oldNode.x >= clickedNode.x) &&
- ((oldNode.x + oldNode.width <=
- clickedNode.x + clickedNode.width));
- }
+ const isClicked = !this.isThumbnail && this.clickedCallsite !== undefined;
+ const isFullWidth =
+ isClicked && value.depth <= this.clickedCallsite!.depth;
+ const isGreyedOut =
+ isClicked && value.depth < this.clickedCallsite!.depth;
- if (!shouldDraw) {
- continue;
- }
-
- const parent = value.parentHash;
+ const parent = value.parentId;
const parentSize = parent === -1 ? this.totalSize : parentNode.size;
// Calculate node's width based on its proportion in parent.
const width =
@@ -189,14 +165,14 @@
ctx.fillRect(currentX, currentY, width, nodeHeight);
// Set current node's data in map for children to use.
- nodesMap.set(value.hash, {
+ nodesMap.set(value.id, {
width,
nextXForChildren: currentX,
size: value.totalSize,
x: currentX
});
// Update next x coordinate in parent.
- nodesMap.set(value.parentHash, {
+ nodesMap.set(value.parentId, {
width: parentNode.width,
nextXForChildren: currentX + width,
size: parentNode.size,
@@ -241,10 +217,6 @@
}
}
- if (clickedNode === undefined) {
- this.baseMap = nodesMap;
- }
-
if (this.hoveredX > -1 && this.hoveredY > -1 && this.hoveredCallsite) {
// Draw the tooltip.
const line1 = this.getCallsiteName(this.hoveredCallsite);
@@ -288,12 +260,13 @@
this.hoveredCallsite = undefined;
}
- onMouseClick({x, y}: {x: number, y: number}) {
+ // Returns id of clicked callsite if any.
+ onMouseClick({x, y}: {x: number, y: number}): number {
if (this.isThumbnail) {
- return true;
+ return -1;
}
- this.clickedCallsite = this.findSelectedCallsite(x, y);
- return this.clickedCallsite !== undefined;
+ const clickedCallsite = this.findSelectedCallsite(x, y);
+ return clickedCallsite === undefined ? -1 : clickedCallsite.id;
}
private findSelectedCallsite(x: number, y: number): CallsiteInfo|undefined {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 9ed553f..c78fc11 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -46,8 +46,8 @@
}
export interface CallsiteInfo {
- hash: number;
- parentHash: number;
+ id: number;
+ parentId: number;
depth: number;
name?: string;
totalSize: number;
diff --git a/ui/src/tracks/heap_profile_flamegraph/common.ts b/ui/src/tracks/heap_profile_flamegraph/common.ts
index 8e0a820..1786681 100644
--- a/ui/src/tracks/heap_profile_flamegraph/common.ts
+++ b/ui/src/tracks/heap_profile_flamegraph/common.ts
@@ -20,9 +20,13 @@
export interface Data extends TrackData {
flamegraph: CallsiteInfo[];
+ clickedCallsite?: CallsiteInfo;
+ key: string;
}
export interface Config {
upid: number;
+ ts: number;
isMinimized: boolean;
+ expandedId: number;
}
diff --git a/ui/src/tracks/heap_profile_flamegraph/controller.ts b/ui/src/tracks/heap_profile_flamegraph/controller.ts
index d679553..dc618cb 100644
--- a/ui/src/tracks/heap_profile_flamegraph/controller.ts
+++ b/ui/src/tracks/heap_profile_flamegraph/controller.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {Actions} from '../../common/actions';
import {globals} from '../../controller/globals';
import {
TrackController,
@@ -29,6 +30,34 @@
const MIN_PIXEL_DISPLAYED = 1;
+export function expandCallsites(
+ data: CallsiteInfo[], clickedCallsiteIndex: number): CallsiteInfo[] {
+ if (clickedCallsiteIndex === -1) return data;
+ const expandedCallsites: CallsiteInfo[] = [];
+ if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) {
+ return expandedCallsites;
+ }
+ const clickedCallsite = data[clickedCallsiteIndex];
+ expandedCallsites.unshift(clickedCallsite);
+ // Adding parents
+ let parentId = clickedCallsite.parentId;
+ while (parentId > -1) {
+ expandedCallsites.unshift(data[parentId]);
+ parentId = data[parentId].parentId;
+ }
+ // Adding children
+ const parents: number[] = [];
+ parents.push(clickedCallsiteIndex);
+ for (let i = clickedCallsiteIndex + 1; i < data.length; i++) {
+ const element = data[i];
+ if (parents.includes(element.parentId)) {
+ expandedCallsites.push(element);
+ parents.push(element.id);
+ }
+ }
+ return expandedCallsites;
+}
+
// Merge callsites that have approximately width less than
// MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same
// parent will be merged to one callsite with size of the biggest merged
@@ -40,11 +69,11 @@
// When a small callsite is found, it will be merged with other small
// callsites of the same depth. So if the current callsite has already been
// merged we can skip it.
- if (mergedCallsites.has(data[i].hash)) {
+ if (mergedCallsites.has(data[i].id)) {
continue;
}
const copiedCallsite = copyCallsite(data[i]);
- copiedCallsite.parentHash =
+ copiedCallsite.parentId =
getCallsitesParentHash(copiedCallsite, mergedCallsites);
// If current callsite is small, find other small callsites with same depth
@@ -53,11 +82,11 @@
let j = i + 1;
let nextCallsite = data[j];
while (j < data.length && copiedCallsite.depth === nextCallsite.depth) {
- if (copiedCallsite.parentHash ===
+ if (copiedCallsite.parentId ===
getCallsitesParentHash(nextCallsite, mergedCallsites) &&
nextCallsite.totalSize <= minSizeDisplayed) {
copiedCallsite.totalSize += nextCallsite.totalSize;
- mergedCallsites.set(nextCallsite.hash, copiedCallsite.hash);
+ mergedCallsites.set(nextCallsite.id, copiedCallsite.id);
}
j++;
nextCallsite = data[j];
@@ -70,8 +99,8 @@
function copyCallsite(callsite: CallsiteInfo): CallsiteInfo {
return {
- hash: callsite.hash,
- parentHash: callsite.parentHash,
+ id: callsite.id,
+ parentId: callsite.parentId,
depth: callsite.depth,
name: callsite.name,
totalSize: callsite.totalSize
@@ -80,17 +109,21 @@
function getCallsitesParentHash(
callsite: CallsiteInfo, map: Map<number, number>): number {
- return map.has(callsite.parentHash) ? map.get(callsite.parentHash)! :
- callsite.parentHash;
+ return map.has(callsite.parentId) ? map.get(callsite.parentId)! :
+ callsite.parentId;
}
-function getMinSizeDisplayed(flamegraphData: CallsiteInfo[]): number {
+function getMinSizeDisplayed(
+ flamegraphData: CallsiteInfo[], rootSize?: number): number {
const timeState = globals.state.frontendLocalState.visibleState;
const width = (timeState.endSec - timeState.startSec) / timeState.resolution;
- const rootSize = findRootSize(flamegraphData);
+ if (rootSize === undefined) {
+ rootSize = findRootSize(flamegraphData);
+ }
return MIN_PIXEL_DISPLAYED * rootSize / width;
}
+const EMPTY_KEY = 'empty';
class HeapProfileFlameraphTrackController extends
TrackController<Config, Data> {
static readonly kind = HEAP_PROFILE_FLAMEGRAPH_TRACK_KIND;
@@ -100,6 +133,7 @@
private length = 0;
private lastSelectedTs?: number;
private lastSelectedId?: number;
+ private lastExpandedId?: number;
private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map();
@@ -118,65 +152,106 @@
end: -1,
resolution: this.resolution,
length: 0,
- flamegraph: []
+ flamegraph: [],
+ key: EMPTY_KEY
};
return data;
}
run() {
- super.run();
const selection = globals.state.currentHeapProfileFlamegraph;
+
if (selection && selection.kind === 'HEAP_PROFILE_FLAMEGRAPH') {
- if (this.lastSelectedId === selection.id &&
- this.lastSelectedTs === selection.ts) {
- return;
+ if (this.lastSelectedId !== selection.id ||
+ this.lastSelectedTs !== selection.ts) {
+ const selectedId = selection.id;
+ const selectedUpid = selection.upid;
+ const selectedKind = selection.kind;
+ const selectedTs = selection.ts;
+ // If we opened new heap profile, we don't want to show it expanded, but
+ // if we are opening trace for the first time with existing state (ie.
+ // via link), we want to show it expanded.
+ this.lastExpandedId = !this.lastSelectedTs && this.config.expandedId ?
+ this.config.expandedId :
+ -1;
+ this.lastSelectedId = selection.id;
+ this.lastSelectedTs = selection.ts;
+
+ this.config.ts = selectedTs;
+ this.config.upid = selectedUpid;
+ this.config.expandedId = this.lastExpandedId;
+
+ const key = `${selectedUpid};${selectedTs}`;
+
+ // TODO(tneda): Prevent lots of flamegraph queries being queued if a
+ // user clicks lots of the markers quickly.
+ this.getFlamegraphData(key, selection.ts, selectedUpid)
+ .then(flamegraphData => {
+ if (flamegraphData !== undefined && selection &&
+ selection.kind === selectedKind &&
+ selection.id === selectedId && selection.ts === selectedTs) {
+ this.prepareAndMergeCallsites(flamegraphData, key);
+ globals.dispatch(Actions.updateTrackConfig(
+ {id: this.trackState.id, config: this.config}));
+ }
+ });
+ } else if (this.config.expandedId !== this.lastExpandedId) {
+ const key = `${this.config.upid};${this.lastSelectedTs}`;
+ this.lastExpandedId = this.config.expandedId;
+ this.getFlamegraphData(key, this.lastSelectedTs, this.config.upid)
+ .then(flamegraphData => {
+ this.prepareAndMergeCallsites(flamegraphData, key);
+ });
}
- const selectedId = selection.id;
- const selectedUpid = selection.upid;
- const selectedKind = selection.kind;
- const selectedTs = selection.ts;
- this.lastSelectedId = selection.id;
- this.lastSelectedTs = selection.ts;
-
- // Sending empty data to show tha Loading state before we get an actual
- // data.
+ } else {
globals.publish(
- 'TrackData',
- {id: HeapProfileFlamegraphKey, data: this.generateEmptyData()});
-
- const key = `${selectedUpid};${selectedTs}`;
-
- // TODO(tneda): Prevent lots of flamegraph queries being queued if a user
- // clicks lots of the markers quickly.
- this.getFlamegraphData(key, selection.ts, selectedUpid)
- .then(flamegraphData => {
- if (flamegraphData !== undefined && selection &&
- selection.kind === selectedKind &&
- selection.id === selectedId && selection.ts === selectedTs) {
- // TODO(tneda): Remove before submitting.
- const mergedFlamegraphData = mergeCallsites(
- flamegraphData, getMinSizeDisplayed(flamegraphData));
-
- globals.publish('TrackData', {
- id: HeapProfileFlamegraphKey,
- data: {
- start: this.start,
- end: this.end,
- resolution: this.resolution,
- length: this.length,
- flamegraph: mergedFlamegraphData
- }
- });
- }
- });
+ 'TrackData', {id: HeapProfileFlamegraphKey, data: undefined});
}
}
- async getFlamegraphData(key: string, ts: number, upid: number) {
+ private publishEmptyData() {
+ globals.publish(
+ 'TrackData',
+ {id: HeapProfileFlamegraphKey, data: this.generateEmptyData()});
+ }
+
+ private prepareAndMergeCallsites(
+ flamegraphData: CallsiteInfo[], key: string) {
+ const expandedFlamegraphData =
+ expandCallsites(flamegraphData, this.config.expandedId);
+ const expandedCallsite = this.config.expandedId === -1 ?
+ undefined :
+ flamegraphData[this.config.expandedId];
+
+ const rootSize =
+ expandedCallsite === undefined ? undefined : expandedCallsite.totalSize;
+ const mergedFlamegraphData = mergeCallsites(
+ expandedFlamegraphData,
+ getMinSizeDisplayed(expandedFlamegraphData, rootSize));
+
+ globals.publish('TrackData', {
+ id: HeapProfileFlamegraphKey,
+ data: {
+ start: this.start,
+ end: this.end,
+ resolution: this.resolution,
+ length: this.length,
+ flamegraph: mergedFlamegraphData,
+ key,
+ clickedCallsite: expandedCallsite
+ }
+ });
+ }
+
+ async getFlamegraphData(key: string, ts: number, upid: number):
+ Promise<CallsiteInfo[]> {
let currentData;
if (this.flamegraphDatasets.has(key)) {
- currentData = this.flamegraphDatasets.get(key);
+ currentData = this.flamegraphDatasets.get(key)!;
} else {
+ // Sending empty data to show Loading state before we get an actual
+ // data.
+ this.publishEmptyData();
currentData = await this.getFlamegraphDataFromTables(ts, upid);
this.flamegraphDatasets.set(key, currentData);
}
@@ -258,19 +333,21 @@
group by hash
order by depth, parent_hash, size desc, name`);
const flamegraphData: CallsiteInfo[] = [];
+ const hashToindex: Map<number, number> = new Map();
for (let i = 0; i < callsites.numRecords; i++) {
const hash = callsites.columns[0].longValues![i];
const name = callsites.columns[1].stringValues![i];
const parentHash = callsites.columns[2].longValues![i];
const depth = callsites.columns[3].longValues![i];
const totalSize = callsites.columns[4].longValues![i];
- flamegraphData.push({
- hash: +hash,
- totalSize: +totalSize,
- depth: +depth,
- parentHash: +parentHash,
- name
- });
+ const parentId =
+ hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1;
+ hashToindex.set(+hash, i);
+ // Instead of hash, we will store index of callsite in this original array
+ // as an id of callsite. That way, we have quicker access to parent and it
+ // will stay unique.
+ flamegraphData.push(
+ {id: i, totalSize: +totalSize, depth: +depth, parentId, name});
}
return flamegraphData;
}
diff --git a/ui/src/tracks/heap_profile_flamegraph/controller_unittest.ts b/ui/src/tracks/heap_profile_flamegraph/controller_unittest.ts
index 63853f1..22db892 100644
--- a/ui/src/tracks/heap_profile_flamegraph/controller_unittest.ts
+++ b/ui/src/tracks/heap_profile_flamegraph/controller_unittest.ts
@@ -3,10 +3,10 @@
test('zeroCallsitesMerged', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 8},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 4},
- {hash: 4, parentHash: 2, name: 'B4', depth: 1, totalSize: 4},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 8},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 4},
+ {id: 4, parentId: 2, name: 'B4', depth: 1, totalSize: 4},
];
const mergedCallsites = mergeCallsites(callsites, 5);
@@ -17,11 +17,11 @@
test('zeroCallsitesMerged2', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 8},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 6},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 4},
- {hash: 5, parentHash: 2, name: 'B5', depth: 1, totalSize: 8},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 8},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 6},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 4},
+ {id: 5, parentId: 2, name: 'B5', depth: 1, totalSize: 8},
];
const mergedCallsites = mergeCallsites(callsites, 5);
@@ -32,36 +32,36 @@
test('twoCallsitesMerged', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 5},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 5},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 5},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 5},
];
const mergedCallsites = mergeCallsites(callsites, 6);
expect(mergedCallsites).toEqual([
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 10},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 10},
]);
});
test('manyCallsitesMerged', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 5},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 3},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 1},
- {hash: 5, parentHash: 1, name: 'A5', depth: 1, totalSize: 1},
- {hash: 6, parentHash: 3, name: 'A36', depth: 2, totalSize: 1},
- {hash: 7, parentHash: 4, name: 'A47', depth: 2, totalSize: 1},
- {hash: 8, parentHash: 5, name: 'A58', depth: 2, totalSize: 1},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 5},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 3},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 1},
+ {id: 5, parentId: 1, name: 'A5', depth: 1, totalSize: 1},
+ {id: 6, parentId: 3, name: 'A36', depth: 2, totalSize: 1},
+ {id: 7, parentId: 4, name: 'A47', depth: 2, totalSize: 1},
+ {id: 8, parentId: 5, name: 'A58', depth: 2, totalSize: 1},
];
const expectedMergedCallsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 5},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 5},
- {hash: 6, parentHash: 3, name: 'A36', depth: 2, totalSize: 3},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 5},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 5},
+ {id: 6, parentId: 3, name: 'A36', depth: 2, totalSize: 3},
];
const mergedCallsites = mergeCallsites(callsites, 4);
@@ -74,23 +74,23 @@
test('manyCallsitesMergedWithoutChildren', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 5},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 5},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 3},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 1},
- {hash: 5, parentHash: 1, name: 'A5', depth: 1, totalSize: 1},
- {hash: 6, parentHash: 2, name: 'B6', depth: 1, totalSize: 5},
- {hash: 7, parentHash: 4, name: 'A47', depth: 2, totalSize: 1},
- {hash: 8, parentHash: 6, name: 'B68', depth: 2, totalSize: 1},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 5},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 5},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 3},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 1},
+ {id: 5, parentId: 1, name: 'A5', depth: 1, totalSize: 1},
+ {id: 6, parentId: 2, name: 'B6', depth: 1, totalSize: 5},
+ {id: 7, parentId: 4, name: 'A47', depth: 2, totalSize: 1},
+ {id: 8, parentId: 6, name: 'B68', depth: 2, totalSize: 1},
];
const expectedMergedCallsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 5},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 5},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 5},
- {hash: 6, parentHash: 2, name: 'B6', depth: 1, totalSize: 5},
- {hash: 7, parentHash: 3, name: 'A47', depth: 2, totalSize: 1},
- {hash: 8, parentHash: 6, name: 'B68', depth: 2, totalSize: 1},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 5},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 5},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 5},
+ {id: 6, parentId: 2, name: 'B6', depth: 1, totalSize: 5},
+ {id: 7, parentId: 3, name: 'A47', depth: 2, totalSize: 1},
+ {id: 8, parentId: 6, name: 'B68', depth: 2, totalSize: 1},
];
const mergedCallsites = mergeCallsites(callsites, 4);
@@ -104,18 +104,18 @@
test('smallCallsitesNotNextToEachOtherInArray', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 20},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 8},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 1},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 8},
- {hash: 5, parentHash: 1, name: 'A5', depth: 1, totalSize: 3},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 20},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 8},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 1},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 8},
+ {id: 5, parentId: 1, name: 'A5', depth: 1, totalSize: 3},
];
const expectedMergedCallsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 20},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 8},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 4},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 8},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 20},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 8},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 4},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 8},
];
const mergedCallsites = mergeCallsites(callsites, 4);
@@ -129,9 +129,9 @@
test('smallCallsitesNotMerged', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: 1, name: 'A2', depth: 1, totalSize: 2},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 2},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: 1, name: 'A2', depth: 1, totalSize: 2},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 2},
];
const mergedCallsites = mergeCallsites(callsites, 1);
@@ -141,53 +141,53 @@
test('mergingRootCallsites', () => {
const callsites: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 10},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 2},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 10},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 2},
];
const mergedCallsites = mergeCallsites(callsites, 20);
expect(mergedCallsites).toEqual([
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 12},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 12},
]);
});
test('largerFlamegraph', () => {
const data: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 60},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 40},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 25},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 15},
- {hash: 5, parentHash: 1, name: 'A5', depth: 1, totalSize: 10},
- {hash: 6, parentHash: 1, name: 'A6', depth: 1, totalSize: 10},
- {hash: 7, parentHash: 2, name: 'B7', depth: 1, totalSize: 30},
- {hash: 8, parentHash: 2, name: 'B8', depth: 1, totalSize: 10},
- {hash: 9, parentHash: 3, name: 'A39', depth: 2, totalSize: 20},
- {hash: 10, parentHash: 4, name: 'A410', depth: 2, totalSize: 10},
- {hash: 11, parentHash: 4, name: 'A411', depth: 2, totalSize: 3},
- {hash: 12, parentHash: 4, name: 'A412', depth: 2, totalSize: 2},
- {hash: 13, parentHash: 5, name: 'A513', depth: 2, totalSize: 5},
- {hash: 14, parentHash: 5, name: 'A514', depth: 2, totalSize: 5},
- {hash: 15, parentHash: 7, name: 'A715', depth: 2, totalSize: 10},
- {hash: 16, parentHash: 7, name: 'A716', depth: 2, totalSize: 5},
- {hash: 17, parentHash: 7, name: 'A717', depth: 2, totalSize: 5},
- {hash: 18, parentHash: 7, name: 'A718', depth: 2, totalSize: 5},
- {hash: 19, parentHash: 9, name: 'A919', depth: 3, totalSize: 10},
- {hash: 20, parentHash: 17, name: 'A1720', depth: 3, totalSize: 2},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 60},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 40},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 25},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 15},
+ {id: 5, parentId: 1, name: 'A5', depth: 1, totalSize: 10},
+ {id: 6, parentId: 1, name: 'A6', depth: 1, totalSize: 10},
+ {id: 7, parentId: 2, name: 'B7', depth: 1, totalSize: 30},
+ {id: 8, parentId: 2, name: 'B8', depth: 1, totalSize: 10},
+ {id: 9, parentId: 3, name: 'A39', depth: 2, totalSize: 20},
+ {id: 10, parentId: 4, name: 'A410', depth: 2, totalSize: 10},
+ {id: 11, parentId: 4, name: 'A411', depth: 2, totalSize: 3},
+ {id: 12, parentId: 4, name: 'A412', depth: 2, totalSize: 2},
+ {id: 13, parentId: 5, name: 'A513', depth: 2, totalSize: 5},
+ {id: 14, parentId: 5, name: 'A514', depth: 2, totalSize: 5},
+ {id: 15, parentId: 7, name: 'A715', depth: 2, totalSize: 10},
+ {id: 16, parentId: 7, name: 'A716', depth: 2, totalSize: 5},
+ {id: 17, parentId: 7, name: 'A717', depth: 2, totalSize: 5},
+ {id: 18, parentId: 7, name: 'A718', depth: 2, totalSize: 5},
+ {id: 19, parentId: 9, name: 'A919', depth: 3, totalSize: 10},
+ {id: 20, parentId: 17, name: 'A1720', depth: 3, totalSize: 2},
];
const expectedData: CallsiteInfo[] = [
- {hash: 1, parentHash: -1, name: 'A', depth: 0, totalSize: 60},
- {hash: 2, parentHash: -1, name: 'B', depth: 0, totalSize: 40},
- {hash: 3, parentHash: 1, name: 'A3', depth: 1, totalSize: 25},
- {hash: 4, parentHash: 1, name: 'A4', depth: 1, totalSize: 35},
- {hash: 7, parentHash: 2, name: 'B7', depth: 1, totalSize: 30},
- {hash: 8, parentHash: 2, name: 'B8', depth: 1, totalSize: 10},
- {hash: 9, parentHash: 3, name: 'A39', depth: 2, totalSize: 20},
- {hash: 10, parentHash: 4, name: 'A410', depth: 2, totalSize: 25},
- {hash: 15, parentHash: 7, name: 'A715', depth: 2, totalSize: 25},
- {hash: 19, parentHash: 9, name: 'A919', depth: 3, totalSize: 10},
- {hash: 20, parentHash: 15, name: 'A1720', depth: 3, totalSize: 2},
+ {id: 1, parentId: -1, name: 'A', depth: 0, totalSize: 60},
+ {id: 2, parentId: -1, name: 'B', depth: 0, totalSize: 40},
+ {id: 3, parentId: 1, name: 'A3', depth: 1, totalSize: 25},
+ {id: 4, parentId: 1, name: 'A4', depth: 1, totalSize: 35},
+ {id: 7, parentId: 2, name: 'B7', depth: 1, totalSize: 30},
+ {id: 8, parentId: 2, name: 'B8', depth: 1, totalSize: 10},
+ {id: 9, parentId: 3, name: 'A39', depth: 2, totalSize: 20},
+ {id: 10, parentId: 4, name: 'A410', depth: 2, totalSize: 25},
+ {id: 15, parentId: 7, name: 'A715', depth: 2, totalSize: 25},
+ {id: 19, parentId: 9, name: 'A919', depth: 3, totalSize: 10},
+ {id: 20, parentId: 15, name: 'A1720', depth: 3, totalSize: 2},
];
// In this case, on depth 1, callsites A4, A5 and A6 should be merged and
diff --git a/ui/src/tracks/heap_profile_flamegraph/frontend.ts b/ui/src/tracks/heap_profile_flamegraph/frontend.ts
index a3d39a6..e1147db 100644
--- a/ui/src/tracks/heap_profile_flamegraph/frontend.ts
+++ b/ui/src/tracks/heap_profile_flamegraph/frontend.ts
@@ -55,7 +55,8 @@
if (data === undefined) {
this.flamegraph.updateDataIfChanged([]);
} else {
- this.flamegraph.updateDataIfChanged(data.flamegraph);
+ this.flamegraph.updateDataIfChanged(
+ data.flamegraph, data.clickedCallsite);
}
}
@@ -97,7 +98,9 @@
}
onMouseClick({x, y}: {x: number, y: number}): boolean {
- this.flamegraph.onMouseClick({x, y});
+ this.config.expandedId = this.flamegraph.onMouseClick({x, y});
+ globals.dispatch(Actions.updateTrackConfig(
+ {id: this.trackState.id, config: this.config}));
return true;
}