Skip to content

Commit 10e98fb

Browse files
committed
Merge branch 'feat/heap' of https://github.com/zjffun/vscode-js-profile-visualizer into zjffun-feat/heap
2 parents ea4f15c + 156c61b commit 10e98fb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+3407
-1188
lines changed

package-lock.json

+644-120
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { INode } from './model';
6+
7+
/**
8+
* Gets the human-readable label for the given node.
9+
*/
10+
export const getNodeText = (node: INode) => {
11+
if (!node.callFrame.url) {
12+
return; // 'virtual' frames like (program) or (idle)
13+
}
14+
15+
if (!node.src?.source.path) {
16+
let text = `${node.callFrame.url}`;
17+
if (node.callFrame.lineNumber >= 0) {
18+
text += `:${node.callFrame.lineNumber}`;
19+
}
20+
21+
return text;
22+
}
23+
24+
if (node.src.relativePath) {
25+
return `${node.src.relativePath}:${node.src.lineNumber}`;
26+
}
27+
28+
return `${node.src.source.path}:${node.src.lineNumber}`;
29+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { Protocol as Cdp } from 'devtools-protocol';
6+
import { ISourceLocation } from '../location-mapping';
7+
8+
/**
9+
* Category of call frames. Grouped into system, modules, and user code.
10+
*/
11+
export const enum Category {
12+
System,
13+
User,
14+
Module,
15+
Deemphasized,
16+
}
17+
18+
export interface INode {
19+
id: number;
20+
category: Category;
21+
callFrame: Cdp.Runtime.CallFrame;
22+
src?: ISourceLocation;
23+
}
24+
25+
export interface ICommonNode extends INode {
26+
children: { [id: number]: ICommonNode };
27+
childrenSize: number;
28+
parent?: ICommonNode;
29+
}
30+
31+
/**
32+
* Categorizes the given call frame.
33+
*/
34+
export const categorize = (callFrame: Cdp.Runtime.CallFrame, src: ISourceLocation | undefined) => {
35+
callFrame.functionName = callFrame.functionName || '(anonymous)';
36+
if (callFrame.lineNumber < 0) {
37+
return Category.System;
38+
}
39+
40+
if (callFrame.url.includes('node_modules') || !src) {
41+
return Category.Module;
42+
}
43+
44+
return Category.User;
45+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import { Protocol as Cdp } from 'devtools-protocol';
6+
import { ISourceLocation } from '../location-mapping';
7+
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+
36+
/**
37+
* Request from the webview to open a document
38+
*/
39+
export interface IOpenDocumentMessage {
40+
type: 'openDocument';
41+
location?: ISourceLocation;
42+
callFrame?: Cdp.Runtime.CallFrame;
43+
toSide: boolean;
44+
}
45+
46+
/**
47+
* Reopens the current document with the given editor, optionally only if
48+
* the given extension is installed.
49+
*/
50+
export interface IReopenWithEditor {
51+
type: 'reopenWith';
52+
viewType: string;
53+
requireExtension?: string;
54+
}
55+
56+
export type Message = IOpenDocumentMessage | IReopenWithEditor;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5-
import { ILocation, IProfileModel, IComputedNode, IGraphNode, Category } from './model';
5+
import { Category } from '../common/model';
6+
import { IComputedNode, IGraphNode, ILocation, IProfileModel } from './model';
67

78
export class BottomUpNode implements IGraphNode {
89
public static root() {
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/display.ts

-26
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,6 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5-
import { ILocation } from './model';
6-
7-
/**
8-
* Gets the human-readable label for the given location.
9-
*/
10-
export const getLocationText = (location: ILocation) => {
11-
if (!location.callFrame.url) {
12-
return; // 'virtual' frames like (program) or (idle)
13-
}
14-
15-
if (!location.src?.source.path) {
16-
let text = `${location.callFrame.url}`;
17-
if (location.callFrame.lineNumber >= 0) {
18-
text += `:${location.callFrame.lineNumber}`;
19-
}
20-
21-
return text;
22-
}
23-
24-
if (location.src.relativePath) {
25-
return `${location.src.relativePath}:${location.src.lineNumber}`;
26-
}
27-
28-
return `${location.src.source.path}:${location.src.lineNumber}`;
29-
};
30-
315
export const decimalFormat = new Intl.NumberFormat(undefined, {
326
maximumFractionDigits: 2,
337
minimumFractionDigits: 2,

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44

55
import * as vscode from 'vscode';
66
import { bundlePage } from '../bundlePage';
7+
import { Message } from '../common/types';
78
import { openLocation } from '../open-location';
8-
import { ProfileAnnotations } from '../profileAnnotations';
99
import { ProfileCodeLensProvider } from '../profileCodeLensProvider';
1010
import { ReadonlyCustomDocument } from '../readonly-custom-document';
1111
import { reopenWithEditor } from '../reopenWithEditor';
12+
import { CpuProfileAnnotations } from './cpuProfileAnnotations';
1213
import { buildModel, IProfileModel } from './model';
13-
import { ICpuProfileRaw, Message } from './types';
14+
import { ICpuProfileRaw } from './types';
1415

1516
export class CpuProfileEditorProvider
16-
implements vscode.CustomEditorProvider<ReadonlyCustomDocument<IProfileModel>> {
17+
implements vscode.CustomEditorProvider<ReadonlyCustomDocument<IProfileModel>>
18+
{
1719
public readonly onDidChangeCustomDocument = new vscode.EventEmitter<never>().event;
1820

1921
constructor(
@@ -30,7 +32,7 @@ export class CpuProfileEditorProvider
3032
const raw: ICpuProfileRaw = JSON.parse(new TextDecoder().decode(content));
3133
const document = new ReadonlyCustomDocument(uri, buildModel(raw));
3234

33-
const annotations = new ProfileAnnotations();
35+
const annotations = new CpuProfileAnnotations();
3436
const rootPath = document.userData.rootPath;
3537
for (const location of document.userData.locations) {
3638
annotations.add(rootPath, location);

packages/vscode-js-profile-core/src/cpu/layout.tsx

+35-23
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import { ComponentType, Fragment, FunctionComponent, h } from 'preact';
55
import { useMemo, useState } from 'preact/hooks';
66
import { richFilter, RichFilterComponent } from '../client/rich-filter';
7+
import styles from '../common/layout.css';
78
import { IDataSource, IQueryResults } from '../ql';
8-
import styles from './layout.css';
99

1010
export interface IBodyProps<T> {
1111
data: IQueryResults<T>;
@@ -14,32 +14,44 @@ export interface IBodyProps<T> {
1414
type CpuProfileLayoutComponent<T> = FunctionComponent<{
1515
data: IDataSource<T>;
1616
body: ComponentType<IBodyProps<T>>;
17-
filterFooter?: ComponentType<{}>;
17+
filterFooter?: ComponentType<{ viewType: string; requireExtension: string }>;
1818
}>;
1919

2020
/**
2121
* Base layout component to display CPU-profile related info.
2222
*/
23-
export const cpuProfileLayoutFactory = <T extends {}>(): CpuProfileLayoutComponent<T> => ({
24-
data,
25-
body: RowBody,
26-
filterFooter: FilterFooter,
27-
}) => {
28-
const RichFilter = useMemo<RichFilterComponent<T>>(richFilter, []);
29-
const [filteredData, setFilteredData] = useState<IQueryResults<T> | undefined>(undefined);
30-
const footer = useMemo(() => (FilterFooter ? <FilterFooter /> : undefined), [FilterFooter]);
23+
export const cpuProfileLayoutFactory = <T extends {}>(): CpuProfileLayoutComponent<T> => {
24+
const CpuProfileLayout: CpuProfileLayoutComponent<T> = ({
25+
data,
26+
body: RowBody,
27+
filterFooter: FilterFooter,
28+
}) => {
29+
const RichFilter = useMemo<RichFilterComponent<T>>(richFilter, []);
30+
const [filteredData, setFilteredData] = useState<IQueryResults<T> | undefined>(undefined);
31+
const footer = useMemo(
32+
() =>
33+
FilterFooter ? (
34+
<FilterFooter
35+
viewType="jsProfileVisualizer.cpuprofile.flame"
36+
requireExtension="ms-vscode.vscode-js-profile-flame"
37+
/>
38+
) : undefined,
39+
[FilterFooter],
40+
);
3141

32-
return (
33-
<Fragment>
34-
<div className={styles.filter}>
35-
<RichFilter
36-
data={data}
37-
onChange={setFilteredData}
38-
placeholder="Filter functions or files, or start a query()"
39-
foot={footer}
40-
/>
41-
</div>
42-
<div className={styles.rows}>{filteredData && <RowBody data={filteredData} />}</div>
43-
</Fragment>
44-
);
42+
return (
43+
<Fragment>
44+
<div className={styles.filter}>
45+
<RichFilter
46+
data={data}
47+
onChange={setFilteredData}
48+
placeholder="Filter functions or files, or start a query()"
49+
foot={footer}
50+
/>
51+
</div>
52+
<div className={styles.rows}>{filteredData && <RowBody data={filteredData} />}</div>
53+
</Fragment>
54+
);
55+
};
56+
return CpuProfileLayout;
4557
};

0 commit comments

Comments
 (0)