Skip to content

Commit 00b93d1

Browse files
committed
feat: heap annotations
1 parent f26ab17 commit 00b93d1

File tree

7 files changed

+243
-131
lines changed

7 files changed

+243
-131
lines changed

packages/vscode-js-profile-core/src/common/display.ts

-5
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,3 @@ export const getNodeText = (node: INode) => {
2727

2828
return `${node.src.source.path}:${node.src.lineNumber}`;
2929
};
30-
31-
export const decimalFormat = new Intl.NumberFormat(undefined, {
32-
maximumFractionDigits: 2,
33-
minimumFractionDigits: 2,
34-
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { CodeLens, Position, Range } from 'vscode';
6+
import { ILocation } from '../cpu/model';
7+
import { lowerCaseInsensitivePath } from '../path';
8+
import { ProfileAnnotations } from '../profileAnnotations';
9+
import { decimalFormat } from './display';
10+
11+
export interface IProfileInformation {
12+
selfTime: number;
13+
aggregateTime: number;
14+
ticks: number;
15+
}
16+
17+
/**
18+
* A collection of profile data. Paths are expanded lazily, as doing so
19+
* up-front for very large profiles turned out to be costly (mainly in path)
20+
* manipulation.
21+
*/
22+
export class CpuProfileAnnotations extends ProfileAnnotations<IProfileInformation, ILocation> {
23+
/**
24+
* Adds a new code lens at the given location in the file.
25+
*/
26+
protected set(file: string, position: Position, data: ILocation) {
27+
let list = this.data.get(lowerCaseInsensitivePath(file));
28+
if (!list) {
29+
list = [];
30+
this.data.set(lowerCaseInsensitivePath(file), list);
31+
}
32+
33+
let index = 0;
34+
while (index < list.length && list[index].position.line < position.line) {
35+
index++;
36+
}
37+
38+
if (list[index]?.position.line === position.line) {
39+
const existing = list[index];
40+
if (position.character < existing.position.character) {
41+
existing.position = new Position(position.line, position.character);
42+
}
43+
existing.data.aggregateTime += data.aggregateTime;
44+
existing.data.selfTime += data.selfTime;
45+
existing.data.ticks += data.ticks;
46+
} else {
47+
list.splice(index, 0, {
48+
position: new Position(position.line, position.character),
49+
data: {
50+
aggregateTime: data.aggregateTime,
51+
selfTime: data.selfTime,
52+
ticks: data.ticks,
53+
},
54+
});
55+
}
56+
}
57+
58+
/**
59+
* Get all lenses for a file. Ordered by line number.
60+
*/
61+
public getLensesForFile(file: string): CodeLens[] {
62+
this.expandForFile(file);
63+
64+
return (
65+
this.data
66+
.get(lowerCaseInsensitivePath(file))
67+
?.map(({ position, data }) => {
68+
if (data.aggregateTime === 0 && data.selfTime === 0) {
69+
return [];
70+
}
71+
72+
const range = new Range(position, position);
73+
return [
74+
new CodeLens(range, {
75+
title:
76+
`${decimalFormat.format(data.selfTime / 1000)}ms Self Time, ` +
77+
`${decimalFormat.format(data.aggregateTime / 1000)}ms Total`,
78+
command: '',
79+
}),
80+
new CodeLens(range, {
81+
title: 'Clear',
82+
command: 'extension.jsProfileVisualizer.table.clearCodeLenses',
83+
}),
84+
];
85+
})
86+
.reduce((acc, lenses) => [...acc, ...lenses], []) ?? []
87+
);
88+
}
89+
}

packages/vscode-js-profile-core/src/cpu/editorProvider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import * as vscode from 'vscode';
66
import { bundlePage } from '../bundlePage';
77
import { Message } from '../common/types';
88
import { openLocation } from '../open-location';
9-
import { ProfileAnnotations } from '../profileAnnotations';
109
import { ProfileCodeLensProvider } from '../profileCodeLensProvider';
1110
import { ReadonlyCustomDocument } from '../readonly-custom-document';
1211
import { reopenWithEditor } from '../reopenWithEditor';
12+
import { CpuProfileAnnotations } from './cpuProfileAnnotations';
1313
import { buildModel, IProfileModel } from './model';
1414
import { ICpuProfileRaw } from './types';
1515

@@ -32,7 +32,7 @@ export class CpuProfileEditorProvider
3232
const raw: ICpuProfileRaw = JSON.parse(new TextDecoder().decode(content));
3333
const document = new ReadonlyCustomDocument(uri, buildModel(raw));
3434

35-
const annotations = new ProfileAnnotations();
35+
const annotations = new CpuProfileAnnotations();
3636
const rootPath = document.userData.rootPath;
3737
for (const location of document.userData.locations) {
3838
annotations.add(rootPath, location);

packages/vscode-js-profile-core/src/heap/editorProvider.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { openLocation } from '../open-location';
99
import { ProfileCodeLensProvider } from '../profileCodeLensProvider';
1010
import { ReadonlyCustomDocument } from '../readonly-custom-document';
1111
import { reopenWithEditor } from '../reopenWithEditor';
12-
import { buildModel, IHeapProfileRaw, IProfileModel } from './model';
12+
import { HeapProfileAnnotations } from './heapProfileAnnotations';
13+
import { buildModel, IHeapProfileRaw, IProfileModel, ITreeNode } from './model';
14+
import { createTree } from './tree';
1315

1416
export class HeapProfileEditorProvider
1517
implements vscode.CustomEditorProvider<ReadonlyCustomDocument<IProfileModel>>
@@ -30,14 +32,27 @@ export class HeapProfileEditorProvider
3032
const raw: IHeapProfileRaw = JSON.parse(new TextDecoder().decode(content));
3133
const document = new ReadonlyCustomDocument(uri, buildModel(raw));
3234

33-
// TODO: annotations
34-
// const annotations = new ProfileAnnotations();
35-
// const rootPath = document.userData.rootPath;
36-
// for (const treeNode of document.userData.treeNodes) {
37-
// annotations.add(rootPath, treeNode);
38-
// }
35+
const tree = createTree(document.userData);
36+
const treeNodes: ITreeNode[] = [tree];
37+
let nodes: ITreeNode[] = [tree];
3938

40-
// this.lens.registerLenses(annotations);
39+
while (nodes.length) {
40+
const node = nodes.pop();
41+
if (node) {
42+
treeNodes.push(node);
43+
if (node.children) {
44+
nodes = nodes.concat(Object.values(node.children));
45+
}
46+
}
47+
}
48+
49+
const annotations = new HeapProfileAnnotations();
50+
const rootPath = document.userData.rootPath;
51+
for (const treeNode of treeNodes) {
52+
annotations.add(rootPath, treeNode);
53+
}
54+
55+
this.lens.registerLenses(annotations);
4156
return document;
4257
}
4358

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { CodeLens, Position, Range } from 'vscode';
6+
import { lowerCaseInsensitivePath } from '../path';
7+
import { ProfileAnnotations } from '../profileAnnotations';
8+
import { decimalFormat } from './display';
9+
import { ITreeNode } from './model';
10+
11+
export interface IProfileInformation {
12+
selfSize: number;
13+
totalSize: number;
14+
}
15+
16+
/**
17+
* A collection of profile data. Paths are expanded lazily, as doing so
18+
* up-front for very large profiles turned out to be costly (mainly in path)
19+
* manipulation.
20+
*/
21+
export class HeapProfileAnnotations extends ProfileAnnotations<IProfileInformation, ITreeNode> {
22+
/**
23+
* Adds a new code lens at the given location in the file.
24+
*/
25+
protected set(file: string, position: Position, data: ITreeNode) {
26+
let list = this.data.get(lowerCaseInsensitivePath(file));
27+
if (!list) {
28+
list = [];
29+
this.data.set(lowerCaseInsensitivePath(file), list);
30+
}
31+
32+
let index = 0;
33+
while (index < list.length && list[index].position.line < position.line) {
34+
index++;
35+
}
36+
37+
if (list[index]?.position.line === position.line) {
38+
const existing = list[index];
39+
if (position.character < existing.position.character) {
40+
existing.position = new Position(position.line, position.character);
41+
}
42+
existing.data.totalSize += data.totalSize;
43+
existing.data.selfSize += data.selfSize;
44+
} else {
45+
list.splice(index, 0, {
46+
position: new Position(position.line, position.character),
47+
data: {
48+
totalSize: data.totalSize,
49+
selfSize: data.selfSize,
50+
},
51+
});
52+
}
53+
}
54+
55+
/**
56+
* Get all lenses for a file. Ordered by line number.
57+
*/
58+
public getLensesForFile(file: string): CodeLens[] {
59+
this.expandForFile(file);
60+
61+
return (
62+
this.data
63+
.get(lowerCaseInsensitivePath(file))
64+
?.map(({ position, data }) => {
65+
if (data.totalSize === 0 && data.selfSize === 0) {
66+
return [];
67+
}
68+
69+
const range = new Range(position, position);
70+
return [
71+
new CodeLens(range, {
72+
title:
73+
`${decimalFormat.format(data.totalSize / 1000)}kB Total Size, ` +
74+
`${decimalFormat.format(data.selfSize / 1000)}kB Self Size`,
75+
command: '',
76+
}),
77+
new CodeLens(range, {
78+
title: 'Clear',
79+
command: 'extension.jsProfileVisualizer.table.clearCodeLenses',
80+
}),
81+
];
82+
})
83+
.reduce((acc, lenses) => [...acc, ...lenses], []) ?? []
84+
);
85+
}
86+
}

0 commit comments

Comments
 (0)