meminsight-test-demo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +36 -0
- package/docs/Build.md +68 -0
- package/docs/Interface.md +35 -0
- package/docs/UserGuide.md +332 -0
- package/docs/resources/analyzer.png +0 -0
- package/docs/resources/cfg_filter_filetype_json.png +0 -0
- package/docs/resources/filetype_txt.png +0 -0
- package/docs/resources/framework.png +0 -0
- package/docs/resources/output.png +0 -0
- package/docs/resources/process.png +0 -0
- package/package.json +54 -0
- package/packages/cfg/ArkStatCfg.json +33 -0
- package/packages/core/jest.config.js +8 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/Index.ts +53 -0
- package/packages/core/src/analyzer/AnalysisInfo.ts +298 -0
- package/packages/core/src/analyzer/ArkAnalyzer.ts +42 -0
- package/packages/core/src/analyzer/ArkCmpCfg.ts +22 -0
- package/packages/core/src/analyzer/ArkCompareAnalyzer.ts +173 -0
- package/packages/core/src/analyzer/ArkLeakAnalyzer.ts +196 -0
- package/packages/core/src/analyzer/ArkSerializer.ts +163 -0
- package/packages/core/src/analyzer/ArkStatAnalyzer.ts +191 -0
- package/packages/core/src/analyzer/ArkStatCfg.ts +77 -0
- package/packages/core/src/analyzer/ArkTracePath.ts +269 -0
- package/packages/core/src/analyzer/ArkTracer.ts +42 -0
- package/packages/core/src/analyzer/ArkXAnalyzer.ts +631 -0
- package/packages/core/src/analyzer/IAnalyzer.ts +27 -0
- package/packages/core/src/file/FileReader.ts +82 -0
- package/packages/core/src/file/FileService.ts +50 -0
- package/packages/core/src/file/FileWriter.ts +148 -0
- package/packages/core/src/report/Reporter.ts +81 -0
- package/packages/core/src/report/templates/template.ejs +101 -0
- package/packages/core/src/report/templates/template.ts +103 -0
- package/packages/core/src/shell/DeviceShell.ts +179 -0
- package/packages/core/src/shell/Shell.ts +99 -0
- package/packages/core/src/types/Constants.ts +16 -0
- package/packages/core/src/types/LeakTypes.ts +21 -0
- package/packages/core/src/types/OhosTypes.ts +115 -0
- package/packages/core/src/utils/Common.ts +37 -0
- package/packages/core/src/utils/Finder.ts +390 -0
- package/packages/core/src/utils/Loader.ts +53 -0
- package/packages/core/src/utils/Log.ts +252 -0
- package/packages/core/src/utils/Output.ts +271 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/exampletools/package.json +52 -0
- package/packages/exampletools/src/MemTest.ts +64 -0
- package/packages/exampletools/tsconfig.json +15 -0
- package/packages/meminsight/jest.config.js +8 -0
- package/packages/meminsight/package.json +52 -0
- package/packages/meminsight/src/Index.ts +4 -0
- package/packages/meminsight/src/Version.ts +7 -0
- package/packages/meminsight/src/process/ArkCompareProc.ts +160 -0
- package/packages/meminsight/src/process/ArkGCProc.ts +61 -0
- package/packages/meminsight/src/process/ArkLeakProc.ts +47 -0
- package/packages/meminsight/src/process/ArkStatProc.ts +320 -0
- package/packages/meminsight/src/process/ArkXProc.ts +73 -0
- package/packages/meminsight/src/process/HMemXProc.ts +50 -0
- package/packages/meminsight/src/process/IProcess.ts +12 -0
- package/packages/meminsight/tsconfig.json +15 -0
- package/packages/stack/README.md +31 -0
- package/packages/stack/libs/hstack_lib-1.0.0.tgz +0 -0
- package/packages/stack/libs/hstack_lib-1.0.1.tgz +0 -0
- package/packages/stack/libs/hstack_lib-1.0.4.tgz +0 -0
- package/packages/stack/libs/lib_list.json +34 -0
- package/packages/stack/package.json +27 -0
- package/packages/stack/src/Index.js +29 -0
- package/packages/stack/src/StackTracer.js +53 -0
- package/packages/templates/ArkLeaks.template +9 -0
- package/packages/templates/ArkNodes.template +9 -0
- package/packages/templates/ArkPaths.template +9 -0
- package/test/scripts/merge.py +145 -0
- package/test/scripts/stat.py +175 -0
- package/test/test_ark_stat_proc.sh +14 -0
- package/tsconfig.base.json +38 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { IHeapSnapshot, IHeapNode, LeakTracePathItem } from "@memlab/core";
|
|
3
|
+
import { ArkAnalyzer } from "./ArkAnalyzer";
|
|
4
|
+
import { Loader } from "../utils/Loader";
|
|
5
|
+
import { Finder } from '../utils/Finder';
|
|
6
|
+
import { Log, ILoggable } from "../utils/Log";
|
|
7
|
+
import { ArkStatCfg } from './ArkStatCfg';
|
|
8
|
+
import { ArkNodesMap, ArkTracePathItem } from './ArkTracePath';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ark Heapsnapshot 统计分析器
|
|
12
|
+
*/
|
|
13
|
+
export class ArkStatAnalyzer extends ArkAnalyzer implements ILoggable {
|
|
14
|
+
public readonly DOMAIN = 'meminsight';
|
|
15
|
+
public readonly TAG = ArkStatAnalyzer.name;
|
|
16
|
+
public static readonly NAME = 'ark-stat-analyzer';
|
|
17
|
+
public static readonly DESC = "Analyze single heapsnapshot file and show statistics.";
|
|
18
|
+
|
|
19
|
+
protected snapshot: IHeapSnapshot;
|
|
20
|
+
protected snapshotFilePath: string;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.type = 'ark-stat-analyzer';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public override async analyze(args: any[]) : Promise<any> {
|
|
28
|
+
if (args.length < 1) {
|
|
29
|
+
Log.errorX(this, 'Argument count is not enough.');
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
this.snapshotFilePath = args[0] as string;
|
|
33
|
+
if (!fs.existsSync(this.snapshotFilePath)) {
|
|
34
|
+
Log.errorX(this, `File not found, ${this.snapshotFilePath}`);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
this.snapshot = await Loader.loadFromFile(this.snapshotFilePath);
|
|
38
|
+
return this.snapshot;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* get snapshot file path
|
|
43
|
+
* @returns snapshot file path
|
|
44
|
+
*/
|
|
45
|
+
public getSnapshotFilePath() : string {
|
|
46
|
+
return this.snapshotFilePath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async getGCRoots() : Promise<Array<IHeapNode>> {
|
|
50
|
+
if (!this.snapshot) {
|
|
51
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return Finder.findGCRoots(this.snapshot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async getDetachedNodes() : Promise<Array<IHeapNode>> {
|
|
58
|
+
if (!this.snapshot) {
|
|
59
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
return Finder.findDetachedNodes(this.snapshot);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async getNodesByFilterCondition(
|
|
66
|
+
filter: (condition: any) => boolean,
|
|
67
|
+
condition: any) : Promise<Array<IHeapNode>> {
|
|
68
|
+
if (!this.snapshot) {
|
|
69
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const result = new Array<IHeapNode>();
|
|
73
|
+
this.snapshot.nodes.forEach((node, ) => {
|
|
74
|
+
if (filter(condition)) {
|
|
75
|
+
result.push(node);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public async getNodesByConfig() : Promise<Array<IHeapNode>> {
|
|
82
|
+
if (!this.snapshot) {
|
|
83
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const result = new Array<IHeapNode>();
|
|
87
|
+
let cfg = this.configuration as ArkStatCfg;
|
|
88
|
+
if (cfg) {
|
|
89
|
+
this.snapshot.nodes.forEach((node, ) => {
|
|
90
|
+
if (!((cfg.Node.enableTypeWhiteList && !cfg.Node.typeWhiteList.includes(node.type))
|
|
91
|
+
|| (cfg.Node.enableTypeBlackList && cfg.Node.typeBlackList.includes(node.type))
|
|
92
|
+
|| (cfg.Node.enableNameWhiteList && !cfg.Node.nameWhiteList.includes(node.name))
|
|
93
|
+
|| (cfg.Node.enableNameBlackList && cfg.Node.nameBlackList.includes(node.name)))) {
|
|
94
|
+
result.push(node);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
Log.errorX(this, 'No analyzer configuration found.');
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 获取指定节点的最短引用路径
|
|
105
|
+
* @param nodeId node id
|
|
106
|
+
* @returns 最短引用路径
|
|
107
|
+
*/
|
|
108
|
+
public async getShortestPathToNode(nodeId: number) : Promise<ArkTracePathItem | undefined> {
|
|
109
|
+
if (!this.snapshot) {
|
|
110
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const node = this.snapshot.nodes.get(nodeId);
|
|
114
|
+
if (!node) {
|
|
115
|
+
Log.errorX(this, `Node(${nodeId}) does not exist.`);
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return Finder.findShortestPathToGCRoot(this.snapshot, node);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 根据配置参数获取符合条件的节点信息
|
|
123
|
+
* @param nameWhiteList class name 白名单
|
|
124
|
+
* @param typeWhiteList node type 白名单
|
|
125
|
+
* @param enableShortestPath 是否生成最短路径
|
|
126
|
+
* @param enableAggrationPath 是否生成聚合路径,enableShortestPath 为 true 时有效
|
|
127
|
+
* @param maxAggrationLength 最大聚合路径的个数,enableShortestPath 为 true 时有效
|
|
128
|
+
* @param enableMinRetainedSize 是否按最小retained size过滤,enableShortestPath 为 true 时有效
|
|
129
|
+
* @param minRetainedSize 最小retained size,enableShortestPath 为 true 时有效
|
|
130
|
+
* @param descending 聚合路径是否自上而下排序,enableShortestPath 为 true 时有效
|
|
131
|
+
* @returns 节点路径信息,可以按节点名、节点类型、节点到GC Roots的最短路径等信息过滤
|
|
132
|
+
*/
|
|
133
|
+
public async getArkNodesMap(
|
|
134
|
+
nameWhiteList: string[],
|
|
135
|
+
typeWhiteList: string[] = ['object'],
|
|
136
|
+
enableShortestPath: boolean = true,
|
|
137
|
+
enableAggrationPath: boolean = true,
|
|
138
|
+
maxAggrationLength = 3,
|
|
139
|
+
enableMinRetainedSize = false,
|
|
140
|
+
minRetainedSize = 1024 * 1024,
|
|
141
|
+
descending: boolean = true): Promise<ArkNodesMap | undefined> {
|
|
142
|
+
if (!this.snapshot) {
|
|
143
|
+
Log.errorX(this, 'Heapsnapshot has not been loaded.');
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const result = new ArkNodesMap();
|
|
147
|
+
this.snapshot.nodes.forEach((node, ) => {
|
|
148
|
+
result.addNode(this.snapshot, node, nameWhiteList, typeWhiteList, enableShortestPath);
|
|
149
|
+
});
|
|
150
|
+
if (enableShortestPath && enableAggrationPath) {
|
|
151
|
+
for (const [key, value] of result) {
|
|
152
|
+
value.getAggrationPathIndexes(
|
|
153
|
+
descending,
|
|
154
|
+
maxAggrationLength,
|
|
155
|
+
enableMinRetainedSize ? minRetainedSize : -1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 根据配置获取符合条件的节点信息
|
|
163
|
+
* 配置通过 setConfig(cfg: any) 设置,默认配置参见 ArkStatCfg.defaultArkStatCfg()
|
|
164
|
+
*/
|
|
165
|
+
public async getArkNodesMapByConfig() : Promise<ArkNodesMap | undefined> {
|
|
166
|
+
let cfg = this.configuration as ArkStatCfg;
|
|
167
|
+
if (!cfg) {
|
|
168
|
+
Log.errorX(this, 'No analyzer configuration found.');
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
let nameWhiteList = cfg.Node.nameWhiteList;
|
|
172
|
+
let typeWhiteList = cfg.Node.typeWhiteList;
|
|
173
|
+
let enableShortestPath = cfg.Path.enableShortest;
|
|
174
|
+
let enableAggrationPath = cfg.Path.enableAggration;
|
|
175
|
+
let maxAggrationLength = cfg.Path.aggrationLength;
|
|
176
|
+
let enableMinRetainedSize = cfg.Path.enableMinRetainedSize;
|
|
177
|
+
let minRetainedSize = cfg.Path.minRetainedSize;
|
|
178
|
+
let descending = cfg.Path.aggrationDescending;
|
|
179
|
+
return this.getArkNodesMap(nameWhiteList, typeWhiteList,
|
|
180
|
+
enableShortestPath, enableAggrationPath, maxAggrationLength,
|
|
181
|
+
enableMinRetainedSize, minRetainedSize, descending);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 获取所有共享对象,Map 以对象tag作为 key,相同tag的对象作为 value
|
|
186
|
+
* @returns Map<string, Array<IHeapNode>>
|
|
187
|
+
*/
|
|
188
|
+
public async getSharedHeapNodesMap() : Promise<Map<string, Array<IHeapNode>>> {
|
|
189
|
+
return Finder.getSharedHeapNodesMap(this.snapshot);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ark Statistics Configuration
|
|
3
|
+
*/
|
|
4
|
+
export type ArkStatCfg = {
|
|
5
|
+
Node: {
|
|
6
|
+
enableTypeWhiteList: boolean,
|
|
7
|
+
typeWhiteList: string[],
|
|
8
|
+
enableTypeBlackList: boolean,
|
|
9
|
+
typeBlackList: string[],
|
|
10
|
+
enableNameWhiteList: boolean,
|
|
11
|
+
nameWhiteList: string[],
|
|
12
|
+
enableNameBlackList: boolean,
|
|
13
|
+
nameBlackList: string[],
|
|
14
|
+
enableId: boolean,
|
|
15
|
+
enableType: boolean,
|
|
16
|
+
enableName: boolean,
|
|
17
|
+
enableSelfSize: boolean,
|
|
18
|
+
enableRetainedSize: boolean,
|
|
19
|
+
},
|
|
20
|
+
Path: {
|
|
21
|
+
enableDistance: boolean,
|
|
22
|
+
enableSelfSize: boolean,
|
|
23
|
+
enableRetainedSize: boolean,
|
|
24
|
+
enableNativeSize: boolean,
|
|
25
|
+
enableShortest: boolean,
|
|
26
|
+
enableShortestLength: boolean,
|
|
27
|
+
shortestLength: number,
|
|
28
|
+
enableAggration: boolean,
|
|
29
|
+
aggrationLength: number,
|
|
30
|
+
aggrationDescending: boolean,
|
|
31
|
+
enableMinRetainedSize: boolean,
|
|
32
|
+
minRetainedSize: number,
|
|
33
|
+
enableMaxRetainedSize: boolean,
|
|
34
|
+
maxRetainedSize: number,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ark Statistics default configuration
|
|
40
|
+
* @returns default configuration
|
|
41
|
+
*/
|
|
42
|
+
export function defaultArkStatCfg() : ArkStatCfg {
|
|
43
|
+
return {
|
|
44
|
+
Node: {
|
|
45
|
+
enableTypeWhiteList: true,
|
|
46
|
+
typeWhiteList: ["object", "closure", "string", "array"],
|
|
47
|
+
enableTypeBlackList: false,
|
|
48
|
+
typeBlackList: [],
|
|
49
|
+
enableNameWhiteList: false,
|
|
50
|
+
nameWhiteList: [],
|
|
51
|
+
enableNameBlackList: false,
|
|
52
|
+
nameBlackList: [],
|
|
53
|
+
enableId: true,
|
|
54
|
+
enableType: true,
|
|
55
|
+
enableName: true,
|
|
56
|
+
enableSelfSize: false,
|
|
57
|
+
enableRetainedSize: false,
|
|
58
|
+
},
|
|
59
|
+
Path: {
|
|
60
|
+
enableDistance: true,
|
|
61
|
+
enableSelfSize: true,
|
|
62
|
+
enableRetainedSize: true,
|
|
63
|
+
enableNativeSize: false,
|
|
64
|
+
enableShortest: true,
|
|
65
|
+
enableShortestLength: false,
|
|
66
|
+
shortestLength: 0,
|
|
67
|
+
enableAggration: true,
|
|
68
|
+
aggrationLength: 3,
|
|
69
|
+
aggrationDescending: true,
|
|
70
|
+
enableMinRetainedSize: true,
|
|
71
|
+
minRetainedSize: 1024 * 1024, // 1MB
|
|
72
|
+
enableMaxRetainedSize: false,
|
|
73
|
+
maxRetainedSize: 0,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { IHeapSnapshot, IHeapNode, LeakTracePathItem } from '@memlab/core'
|
|
2
|
+
import { Finder } from '../utils/Finder';
|
|
3
|
+
import { ILoggable, Log } from '../utils/Log';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ArkTracePathItem
|
|
7
|
+
*/
|
|
8
|
+
export type ArkTracePathItem = LeakTracePathItem;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ArkNodeTracePath
|
|
12
|
+
*/
|
|
13
|
+
export class ArkNodeTracePath {
|
|
14
|
+
public readonly tracePathItem: ArkTracePathItem;
|
|
15
|
+
public readonly uid: string;
|
|
16
|
+
public readonly nodeIds: number[];
|
|
17
|
+
public readonly reverse: boolean;
|
|
18
|
+
public readonly retainedSize: number;
|
|
19
|
+
|
|
20
|
+
constructor(item: ArkTracePathItem, reverse: boolean = false) {
|
|
21
|
+
this.tracePathItem = item;
|
|
22
|
+
this.nodeIds = ArkNodeTracePath.getNodeIdTrace(item);
|
|
23
|
+
this.retainedSize = ArkNodeTracePath.getRetainedSize(item);
|
|
24
|
+
this.reverse = reverse;
|
|
25
|
+
this.uid = reverse ? this.nodeIds.reverse().join('<-') : this.nodeIds.join('->');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public getTracePathInfo(joinStr: string = '===>') : string {
|
|
29
|
+
if (this.nodeIds.length === 0) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
const snapshot = this.tracePathItem.node?.snapshot;
|
|
33
|
+
if (!snapshot) {
|
|
34
|
+
Log.error('meminsight', ArkNodeTracePath.name, 'Failed to get snapshot.');
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
const result: string[] = [];
|
|
38
|
+
this.nodeIds.forEach(id => {
|
|
39
|
+
let node = snapshot.getNodeById(id);
|
|
40
|
+
if (node) {
|
|
41
|
+
let sizeStr = node.retainedSize >= 1024 * 1024 ?
|
|
42
|
+
(node.retainedSize / (1024 * 1024)).toFixed(2) + 'MB' :
|
|
43
|
+
(node.retainedSize / 1024).toFixed(2) + 'KB';
|
|
44
|
+
result.push(`@${id} (${node.type}) [${sizeStr}] ${node.name}`)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
return result.join(`\n${joinStr}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static getNodeIdTrace(item: ArkTracePathItem) : number[] {
|
|
51
|
+
const nodeIds: number[] = [];
|
|
52
|
+
let curItem : ArkTracePathItem | undefined = item;
|
|
53
|
+
while (curItem) {
|
|
54
|
+
let node: IHeapNode | undefined = curItem.node;
|
|
55
|
+
if (!node) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
nodeIds.push(node.id);
|
|
59
|
+
curItem = curItem.next;
|
|
60
|
+
}
|
|
61
|
+
return nodeIds;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 获取本路径上的节点retained size
|
|
66
|
+
* @param item
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
public static getRetainedSize(item: ArkTracePathItem) : number {
|
|
70
|
+
let retained: number = 0;
|
|
71
|
+
let curItem : ArkTracePathItem | undefined = item;
|
|
72
|
+
while (curItem) {
|
|
73
|
+
let node: IHeapNode | undefined = curItem.node;
|
|
74
|
+
if (!node) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
retained += node.self_size;
|
|
78
|
+
curItem = curItem.next;
|
|
79
|
+
}
|
|
80
|
+
return retained;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* ArkNode最短路径uid和最短路径索引关系对象
|
|
86
|
+
*/
|
|
87
|
+
export class ArkNodePathIndexes {
|
|
88
|
+
public readonly uid: string;
|
|
89
|
+
public indexes: number[];
|
|
90
|
+
|
|
91
|
+
constructor(uid: string) {
|
|
92
|
+
this.uid = uid;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public addIndex(index: number) {
|
|
96
|
+
this.indexes.push(index);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public get(filter: (condition: any) => boolean, condition: any) {
|
|
100
|
+
return this.indexes.filter((index) => {
|
|
101
|
+
return filter(condition);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* ArkNodes
|
|
108
|
+
*/
|
|
109
|
+
export class ArkNodes extends Array<number> implements ILoggable {
|
|
110
|
+
public readonly DOMAIN: string = 'meminsight';
|
|
111
|
+
public readonly TAG: string = ArkNodes.name;
|
|
112
|
+
public className: string = ''; // node class name
|
|
113
|
+
public selfSize: number = 0; // node total self size
|
|
114
|
+
public retainedSize: number = 0; // node total retained size
|
|
115
|
+
public shortestPaths: ArkNodeTracePath[]; // shortest path array of all node with same calss name
|
|
116
|
+
public aggrationPathIndexes: Map<string, ArkNodePathIndexes>; // <path uid, path indexes>
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ArkNodes constructor
|
|
120
|
+
* @param className node name
|
|
121
|
+
* @param selfSize node self size
|
|
122
|
+
* @param retainedSize node retained size
|
|
123
|
+
*/
|
|
124
|
+
constructor(className: string, selfSize: number, retainedSize: number) {
|
|
125
|
+
super();
|
|
126
|
+
this.className = className;
|
|
127
|
+
this.selfSize = selfSize;
|
|
128
|
+
this.retainedSize = retainedSize;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* add shortest node trace path
|
|
133
|
+
* @param path shortest node trace path
|
|
134
|
+
*/
|
|
135
|
+
public addShortestPath(path: ArkNodeTracePath) : void {
|
|
136
|
+
this.shortestPaths.push(path);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* add node id
|
|
141
|
+
* @param nodeId node id
|
|
142
|
+
*/
|
|
143
|
+
public addNodeId(nodeId: number) : void {
|
|
144
|
+
this.push(nodeId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 获取最短路径uid和最短路径索引和引用次数的映射数据
|
|
149
|
+
* uid = [...node.id].join('->')
|
|
150
|
+
* @param descending 按引用次数降序排序,否则升序排序
|
|
151
|
+
* @param maxIndexLength 最大引用路径个数
|
|
152
|
+
*/
|
|
153
|
+
public getAggrationPathIndexes(descending: boolean = true,
|
|
154
|
+
maxIndexLength: number = -1, minRetainedSize = -1) : void {
|
|
155
|
+
if (this.shortestPaths.length === 0) {
|
|
156
|
+
Log.warnX(this, 'No shortest path found');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const indexes = new Map<string, ArkNodePathIndexes>();
|
|
160
|
+
this.shortestPaths.forEach((path, index) => {
|
|
161
|
+
const indexesItem = indexes.get(path.uid);
|
|
162
|
+
if (!indexesItem) {
|
|
163
|
+
const pathIndexes = new ArkNodePathIndexes(path.uid);
|
|
164
|
+
pathIndexes.addIndex(index);
|
|
165
|
+
indexes.set(path.uid, pathIndexes);
|
|
166
|
+
} else {
|
|
167
|
+
indexesItem.addIndex(index);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const sortedIndexes = descending
|
|
171
|
+
? new Map([...indexes.entries()].sort((a, b) => a[1].indexes.length - b[1].indexes.length))
|
|
172
|
+
: new Map([...indexes.entries()].sort((a, b) => b[1].indexes.length - a[1].indexes.length));
|
|
173
|
+
if ((maxIndexLength <= 0) || (sortedIndexes.size <= maxIndexLength)) {
|
|
174
|
+
this.aggrationPathIndexes = sortedIndexes;
|
|
175
|
+
} else {
|
|
176
|
+
this.aggrationPathIndexes = new Map([...sortedIndexes.entries()].slice(0, maxIndexLength));
|
|
177
|
+
}
|
|
178
|
+
if (minRetainedSize > 0) {
|
|
179
|
+
this.aggrationPathIndexes.forEach((pathIndexes, uid) => {
|
|
180
|
+
const tmpIndexes: number[] = [];
|
|
181
|
+
let idx = pathIndexes.indexes.pop();
|
|
182
|
+
while (idx) {
|
|
183
|
+
let size = this.shortestPaths.at(idx)?.retainedSize || 0;
|
|
184
|
+
if (size >= minRetainedSize) {
|
|
185
|
+
tmpIndexes.push(idx);
|
|
186
|
+
}
|
|
187
|
+
idx = pathIndexes.indexes.pop();
|
|
188
|
+
}
|
|
189
|
+
pathIndexes.indexes = tmpIndexes;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public getPathIndexesRetainedSize(uid: string) : number {
|
|
195
|
+
let pathIndexes = this.aggrationPathIndexes.get(uid);
|
|
196
|
+
if (!pathIndexes) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
let preSize = 0;
|
|
200
|
+
let curSize: number | undefined;
|
|
201
|
+
return pathIndexes.indexes.reduce((pre, cur) => {
|
|
202
|
+
curSize = this.shortestPaths.at(cur)?.retainedSize;
|
|
203
|
+
if (!curSize) {
|
|
204
|
+
return preSize;
|
|
205
|
+
}
|
|
206
|
+
preSize = curSize;
|
|
207
|
+
return preSize + curSize;
|
|
208
|
+
}, 0);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* ArkNodes Map<nodeName, nodeId array>
|
|
214
|
+
*/
|
|
215
|
+
export class ArkNodesMap extends Map<string, ArkNodes> implements ILoggable {
|
|
216
|
+
DOMAIN: string = 'meminsight'
|
|
217
|
+
TAG: string = ArkNodesMap.name;
|
|
218
|
+
|
|
219
|
+
constructor() {
|
|
220
|
+
super();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* add node to map
|
|
225
|
+
* @param snapshot heap snapshot
|
|
226
|
+
* @param node heap node
|
|
227
|
+
* @param nameWhiteList node names
|
|
228
|
+
* @param typeWhiteList node types
|
|
229
|
+
* @param enableShortestPath enable shortest path
|
|
230
|
+
*/
|
|
231
|
+
public addNode(
|
|
232
|
+
snapshot: IHeapSnapshot,
|
|
233
|
+
node: IHeapNode,
|
|
234
|
+
nameWhiteList: string[],
|
|
235
|
+
typeWhiteList: string[] = ['object'],
|
|
236
|
+
enableShortestPath: boolean = true) : void {
|
|
237
|
+
// true if names is empty or types include node.type
|
|
238
|
+
if ((nameWhiteList.length == 0
|
|
239
|
+
|| nameWhiteList.includes(node.name))
|
|
240
|
+
&& typeWhiteList.includes(node.type)) {
|
|
241
|
+
const arkNodes: ArkNodes | undefined = this.get(node.name);
|
|
242
|
+
if (!arkNodes) {
|
|
243
|
+
let arkNodes: ArkNodes = new ArkNodes(node.name, node.self_size, node.retainedSize);
|
|
244
|
+
if (enableShortestPath) {
|
|
245
|
+
const arkTracePathItem: ArkTracePathItem | undefined = Finder.findShortestPathToGCRoot(snapshot, node);
|
|
246
|
+
if (arkTracePathItem) {
|
|
247
|
+
arkNodes.addShortestPath(new ArkNodeTracePath(arkTracePathItem));
|
|
248
|
+
} else {
|
|
249
|
+
Log.errorX(this, 'ArkNodesMap1: Finder.findShortestPathToGCRoot return undefined.');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
arkNodes.addNodeId(node.id);
|
|
253
|
+
this.set(node.name, arkNodes);
|
|
254
|
+
} else {
|
|
255
|
+
arkNodes.selfSize += node.self_size;
|
|
256
|
+
arkNodes.retainedSize += node.retainedSize;
|
|
257
|
+
if (enableShortestPath) {
|
|
258
|
+
const arkTracePathItem: ArkTracePathItem | undefined = Finder.findShortestPathToGCRoot(snapshot, node);
|
|
259
|
+
if (arkTracePathItem) {
|
|
260
|
+
arkNodes.addShortestPath(new ArkNodeTracePath(arkTracePathItem));
|
|
261
|
+
} else {
|
|
262
|
+
Log.errorX(this, 'ArkNodesMap2: Finder.findShortestPathToGCRoot return undefined.');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
arkNodes.addNodeId(node.id);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { serializer } from "@memlab/core"
|
|
2
|
+
import { ArkTracePathItem } from "./ArkTracePath";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ArkTracePath Filter function
|
|
6
|
+
*/
|
|
7
|
+
export type ArkTracePathFilter = (condition?: any) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* ArkTracePath Output function
|
|
10
|
+
*/
|
|
11
|
+
export type ArkTracePathOutput = (path: ArkTracePathItem, filter?: ArkTracePathFilter, condition?: any) => string;
|
|
12
|
+
|
|
13
|
+
const defaultOutput: ArkTracePathOutput = (path: ArkTracePathItem) => {
|
|
14
|
+
let snapshot = path?.node?.snapshot;
|
|
15
|
+
return snapshot ? serializer.summarizePath(path, new Set<number>(), snapshot, {color: true}) : '';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ArkTracer
|
|
20
|
+
*/
|
|
21
|
+
export class ArkTracer {
|
|
22
|
+
public static outputTracePath(
|
|
23
|
+
tracePath: ArkTracePathItem,
|
|
24
|
+
output?: ArkTracePathOutput,
|
|
25
|
+
filter?: ArkTracePathFilter,
|
|
26
|
+
condition?: any) : void {
|
|
27
|
+
let result: string = output ? output(tracePath, filter, condition) : defaultOutput(tracePath);
|
|
28
|
+
if (result !== '') {
|
|
29
|
+
console.log(result);
|
|
30
|
+
} else {
|
|
31
|
+
console.log('No leak trace path.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static getTracePathString(
|
|
36
|
+
tracePath: ArkTracePathItem,
|
|
37
|
+
output?: ArkTracePathOutput,
|
|
38
|
+
filter?: ArkTracePathFilter,
|
|
39
|
+
condition?: any) : string {
|
|
40
|
+
return output ? output(tracePath, filter, condition) : defaultOutput(tracePath);
|
|
41
|
+
}
|
|
42
|
+
}
|