Skip to content

Commit b4f393e

Browse files
committed
feat: support source locations of heap
1 parent 06ef9cb commit b4f393e

File tree

5 files changed

+129
-68
lines changed

5 files changed

+129
-68
lines changed

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

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@
55
import { Protocol as Cdp } from 'devtools-protocol';
66
import { ISourceLocation } from '../location-mapping';
77

8+
export interface IAnnotationLocation {
9+
callFrame: Cdp.Runtime.CallFrame;
10+
locations: ISourceLocation[];
11+
}
12+
13+
/**
14+
* Extra annotations added by js-debug.
15+
*/
16+
export interface IJsDebugAnnotations {
17+
/**
18+
* Workspace root path, if set.
19+
*/
20+
rootPath?: string;
21+
22+
/**
23+
* For each node in the profile, the list of locations in corresponds to
24+
* in the workspace (if any).
25+
*/
26+
locations: ReadonlyArray<IAnnotationLocation>;
27+
28+
/**
29+
* Optional cell data saved from previously opening the profile as a notebook.
30+
*/
31+
cellData?: {
32+
version: number;
33+
};
34+
}
35+
836
/**
937
* Request from the webview to open a document
1038
*/

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

+4-20
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import { Protocol as Cdp } from 'devtools-protocol';
66
import { categorize, INode } from '../common/model';
7-
import { addRelativeDiskPath, ISourceLocation } from '../location-mapping';
7+
import { IAnnotationLocation } from '../common/types';
8+
import { getBestLocation } from '../getBestLocation';
9+
import { ISourceLocation } from '../location-mapping';
810
import { maybeFileUrlToPath } from '../path';
9-
import { IAnnotationLocation, ICpuProfileRaw } from './types';
11+
import { ICpuProfileRaw } from './types';
1012

1113
/**
1214
* One measured node in the call stack. Contains the time it spent in itself,
@@ -71,24 +73,6 @@ const computeAggregateTime = (index: number, nodes: IComputedNode[]): number =>
7173
return (row.aggregateTime = total);
7274
};
7375

74-
const getBestLocation = (
75-
profile: ICpuProfileRaw,
76-
candidates: ReadonlyArray<ISourceLocation> = [],
77-
) => {
78-
if (!profile.$vscode?.rootPath) {
79-
return candidates[0];
80-
}
81-
82-
for (const candidate of candidates) {
83-
const mapped = addRelativeDiskPath(profile.$vscode.rootPath, candidate);
84-
if (mapped.relativePath) {
85-
return mapped;
86-
}
87-
}
88-
89-
return candidates[0];
90-
};
91-
9276
/**
9377
* Ensures that all profile nodes have a location ID, setting them if they
9478
* aren't provided by default.

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

+1-29
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,12 @@
33
*--------------------------------------------------------*/
44

55
import { Protocol as Cdp } from 'devtools-protocol';
6-
import { ISourceLocation } from '../location-mapping';
6+
import { IJsDebugAnnotations } from '../common/types';
77

88
export const enum Constants {
99
CurrentDataVersion = 1,
1010
}
1111

12-
export interface IAnnotationLocation {
13-
callFrame: Cdp.Runtime.CallFrame;
14-
locations: ISourceLocation[];
15-
}
16-
17-
/**
18-
* Extra annotations added by js-debug.
19-
*/
20-
export interface IJsDebugAnnotations {
21-
/**
22-
* Workspace root path, if set.
23-
*/
24-
rootPath?: string;
25-
26-
/**
27-
* For each node in the profile, the list of locations in corresponds to
28-
* in the workspace (if any).
29-
*/
30-
locations: ReadonlyArray<IAnnotationLocation>;
31-
32-
/**
33-
* Optional cell data saved from previously opening the profile as a notebook.
34-
*/
35-
cellData?: {
36-
version: number;
37-
};
38-
}
39-
4012
export interface IProfileNode extends Cdp.Profiler.ProfileNode {
4113
locationId?: number;
4214
positionTicks?: (Cdp.Profiler.PositionTickInfo & {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { IJsDebugAnnotations } from './common/types';
6+
import { addRelativeDiskPath, ISourceLocation } from './location-mapping';
7+
8+
export const getBestLocation = (
9+
profile: { $vscode?: IJsDebugAnnotations },
10+
candidates: ReadonlyArray<ISourceLocation> = [],
11+
) => {
12+
if (!profile.$vscode?.rootPath) {
13+
return candidates[0];
14+
}
15+
16+
for (const candidate of candidates) {
17+
const mapped = addRelativeDiskPath(profile.$vscode.rootPath, candidate);
18+
if (mapped.relativePath) {
19+
return mapped;
20+
}
21+
}
22+
23+
return candidates[0];
24+
};

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

+72-19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import { Protocol as Cdp } from 'devtools-protocol';
66
import { INode } from '../common/model';
7+
import { IAnnotationLocation, IJsDebugAnnotations } from '../common/types';
8+
import { getBestLocation } from '../getBestLocation';
79
import { ISourceLocation } from '../location-mapping';
810
import { maybeFileUrlToPath } from '../path';
911

@@ -18,24 +20,16 @@ export interface ITreeNode extends IHeapProfileNode {
1820
parent?: ITreeNode;
1921
}
2022

21-
/**
22-
* Extra annotations added by js-debug.
23-
*/
24-
export interface IJsDebugAnnotations {
25-
/**
26-
* Workspace root path, if set.
27-
*/
28-
rootPath?: string;
29-
}
30-
3123
export interface IHeapProfileRaw extends Cdp.HeapProfiler.SamplingHeapProfile {
3224
$vscode?: IJsDebugAnnotations;
25+
head: IProfileModelNode;
3326
}
3427

3528
export interface IProfileModelNode
3629
extends Omit<Cdp.HeapProfiler.SamplingHeapProfileNode, 'children'> {
3730
src?: ISourceLocation;
3831
children: IProfileModelNode[];
32+
locationId?: number;
3933
}
4034

4135
/**
@@ -48,25 +42,84 @@ export type IProfileModel = {
4842
};
4943

5044
/**
51-
* Computes the model for the given profile.
45+
* Ensures that all profile nodes have a location ID, setting them if they
46+
* aren't provided by default.
5247
*/
53-
export const buildModel = (profile: IHeapProfileRaw): IProfileModel => {
54-
let nodes = [profile.head];
48+
const ensureSourceLocations = (profile: IHeapProfileRaw): ReadonlyArray<IAnnotationLocation> => {
49+
if (profile.$vscode) {
50+
return profile.$vscode.locations; // profiles we generate are already good
51+
}
5552

56-
while (nodes.length) {
57-
const node = nodes.pop();
53+
let locationIdCounter = 0;
54+
const locationsByRef = new Map<
55+
string,
56+
{ id: number; callFrame: Cdp.Runtime.CallFrame; location: ISourceLocation }
57+
>();
5858

59-
if (node) {
60-
const { callFrame } = node;
61-
(node as unknown as IHeapProfileNode).src = {
59+
const getLocationIdFor = (callFrame: Cdp.Runtime.CallFrame) => {
60+
const ref = [
61+
callFrame.functionName,
62+
callFrame.url,
63+
callFrame.scriptId,
64+
callFrame.lineNumber,
65+
callFrame.columnNumber,
66+
].join(':');
67+
68+
const existing = locationsByRef.get(ref);
69+
if (existing) {
70+
return existing.id;
71+
}
72+
const id = locationIdCounter++;
73+
locationsByRef.set(ref, {
74+
id,
75+
callFrame,
76+
location: {
6277
lineNumber: callFrame.lineNumber,
6378
columnNumber: callFrame.columnNumber,
6479
source: {
6580
name: maybeFileUrlToPath(callFrame.url),
6681
path: maybeFileUrlToPath(callFrame.url),
6782
sourceReference: 0,
6883
},
69-
};
84+
},
85+
});
86+
87+
return id;
88+
};
89+
90+
let nodes = [profile.head];
91+
92+
while (nodes.length) {
93+
const node = nodes.pop();
94+
95+
if (node) {
96+
const { callFrame } = node;
97+
node.locationId = getLocationIdFor(callFrame);
98+
99+
nodes = nodes.concat(node.children);
100+
}
101+
}
102+
103+
return [...locationsByRef.values()]
104+
.sort((a, b) => a.id - b.id)
105+
.map(l => ({ locations: [l.location], callFrame: l.callFrame }));
106+
};
107+
108+
/**
109+
* Computes the model for the given profile.
110+
*/
111+
export const buildModel = (profile: IHeapProfileRaw): IProfileModel => {
112+
let nodes = [profile.head];
113+
114+
const sourceLocations = ensureSourceLocations(profile);
115+
116+
while (nodes.length) {
117+
const node = nodes.pop();
118+
119+
if (node) {
120+
if (node.locationId) {
121+
node.src = getBestLocation(profile, sourceLocations[node.locationId].locations);
122+
}
70123
nodes = nodes.concat(node.children);
71124
}
72125
}

0 commit comments

Comments
 (0)