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,173 @@
|
|
|
1
|
+
import { IHeapNode, IHeapSnapshot, utils } from "@memlab/core"
|
|
2
|
+
import { ILoggable } from "../utils/Log";
|
|
3
|
+
import { ArkAnalyzer } from "./ArkAnalyzer";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ark Heapsnapshot 比较分析器
|
|
7
|
+
*/
|
|
8
|
+
export class ArkCompareAnalyzer extends ArkAnalyzer implements ILoggable {
|
|
9
|
+
public readonly DOMAIN: string = 'meminsight';
|
|
10
|
+
public readonly TAG = ArkCompareAnalyzer.name;
|
|
11
|
+
public static readonly NAME = "ark-compare-analyzer";
|
|
12
|
+
public static readonly DESC = "Compare two heapsnapshot files and find the differences.";
|
|
13
|
+
public srcSnapshot: IHeapSnapshot | undefined = undefined;
|
|
14
|
+
public dstSnapshot: IHeapSnapshot | undefined = undefined;
|
|
15
|
+
public srcSnapshotFilePath: string | undefined = undefined;
|
|
16
|
+
public dstSnapshotFilePath: string | undefined = undefined;
|
|
17
|
+
public nodeName: string | undefined = undefined;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.type = 'ark-compare-analyzer';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public override async analyze(args: any[]) : Promise<any> {
|
|
25
|
+
this.srcSnapshotFilePath = args[0] as string;
|
|
26
|
+
this.dstSnapshotFilePath = args[1] as string;
|
|
27
|
+
|
|
28
|
+
this.srcSnapshot = await utils.getSnapshotFromFile(this.srcSnapshotFilePath, { buildNodeIdIndex: true });
|
|
29
|
+
this.dstSnapshot = await utils.getSnapshotFromFile(this.dstSnapshotFilePath, { buildNodeIdIndex: true });
|
|
30
|
+
|
|
31
|
+
if (!this.srcSnapshot || !this.dstSnapshot) {
|
|
32
|
+
throw new Error("Failed to load heap snapshots");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private getStatsOfAllNodes(snapshot: IHeapSnapshot): Record<string, { count: number; selfSize: number; retainedSize: number }> {
|
|
39
|
+
const stats: Record<string, { count: number; selfSize: number; retainedSize: number }> = {};
|
|
40
|
+
snapshot.nodes.forEach((node: IHeapNode) => {
|
|
41
|
+
const typeName = node.name;
|
|
42
|
+
if (!stats[typeName]) {
|
|
43
|
+
stats[typeName] = { count: 0, selfSize: 0, retainedSize: 0 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stats[typeName].count += 1;
|
|
47
|
+
stats[typeName].selfSize += node.self_size;
|
|
48
|
+
stats[typeName].retainedSize += node.retainedSize;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return stats;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private getStatsByNodeName(snapshot: IHeapSnapshot, name: string) : Record<string, { count: number; selfSize: number; retainedSize: number }> {
|
|
55
|
+
const stats: Record<string, { count: number; selfSize: number; retainedSize: number }> = {};
|
|
56
|
+
snapshot.nodes.forEach((node: IHeapNode, _) => {
|
|
57
|
+
if (node.name === name) {
|
|
58
|
+
if (!stats[name]) {
|
|
59
|
+
stats[name] = { count: 0, selfSize: 0, retainedSize: 0 };
|
|
60
|
+
}
|
|
61
|
+
stats[name].count += 1;
|
|
62
|
+
stats[name].selfSize += node.self_size;
|
|
63
|
+
stats[name].retainedSize += node.retainedSize;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return stats;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public getDetailStatsByNodeName(snapshot: IHeapSnapshot, nodeName: string): Array<{ id: number; selfSize: number; retainedSize: number }> {
|
|
70
|
+
const stats = new Array<{ id: number; selfSize: number; retainedSize: number }>();
|
|
71
|
+
snapshot.nodes.forEach((node: IHeapNode, _) => {
|
|
72
|
+
if (node.name === nodeName) {
|
|
73
|
+
stats.push({ id: node.id, selfSize: node.self_size, retainedSize: node.retainedSize })
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return stats;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getDiffs(record1: Record<string, { count: number; selfSize: number; retainedSize: number }>,
|
|
80
|
+
record2: Record<string, { count: number; selfSize: number; retainedSize: number }>): Array<{
|
|
81
|
+
typeName: string;
|
|
82
|
+
srcCount: number;
|
|
83
|
+
dstCount: number;
|
|
84
|
+
countDiff: number;
|
|
85
|
+
srcSelfSize: number;
|
|
86
|
+
dstSelfSize: number;
|
|
87
|
+
selfSizeDiff: number;
|
|
88
|
+
srcRetainedSize: number;
|
|
89
|
+
dstRetainedSize: number;
|
|
90
|
+
retainedSizeDiff: number;
|
|
91
|
+
}> {
|
|
92
|
+
const allTypes = new Set([
|
|
93
|
+
...Object.keys(record1),
|
|
94
|
+
...Object.keys(record2)
|
|
95
|
+
]);
|
|
96
|
+
const diffs = [];
|
|
97
|
+
for (const typeName of allTypes) {
|
|
98
|
+
const srcStats = record1[typeName] || { count: 0, selfSize: 0, retainedSize: 0 };
|
|
99
|
+
const dstStats = record2[typeName] || { count: 0, selfSize: 0, retainedSize: 0 };
|
|
100
|
+
if (srcStats.count !== dstStats.count || srcStats.selfSize !== dstStats.selfSize || srcStats.retainedSize !== dstStats.retainedSize) {
|
|
101
|
+
diffs.push({
|
|
102
|
+
typeName,
|
|
103
|
+
srcCount: srcStats.count,
|
|
104
|
+
dstCount: dstStats.count,
|
|
105
|
+
countDiff: dstStats.count - srcStats.count,
|
|
106
|
+
srcSelfSize: srcStats.selfSize,
|
|
107
|
+
dstSelfSize: dstStats.selfSize,
|
|
108
|
+
selfSizeDiff: dstStats.selfSize - srcStats.selfSize,
|
|
109
|
+
srcRetainedSize: srcStats.retainedSize,
|
|
110
|
+
dstRetainedSize: dstStats.retainedSize,
|
|
111
|
+
retainedSizeDiff: dstStats.retainedSize - srcStats.retainedSize
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return diffs;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public diffSnapshots(node_name?: string): Array<{
|
|
119
|
+
typeName: string;
|
|
120
|
+
srcCount: number;
|
|
121
|
+
dstCount: number;
|
|
122
|
+
countDiff: number;
|
|
123
|
+
srcSelfSize: number;
|
|
124
|
+
dstSelfSize: number;
|
|
125
|
+
selfSizeDiff: number;
|
|
126
|
+
srcRetainedSize: number;
|
|
127
|
+
dstRetainedSize: number;
|
|
128
|
+
retainedSizeDiff: number;
|
|
129
|
+
}> {
|
|
130
|
+
let srcTypeStats = undefined;
|
|
131
|
+
let dstTypeStats = undefined;
|
|
132
|
+
if (node_name) {
|
|
133
|
+
// 按 node_name 统计源快照中的对象类型
|
|
134
|
+
srcTypeStats = this.getStatsByNodeName(this.srcSnapshot!, node_name);
|
|
135
|
+
// 按 node_name 统计目标快照中的对象类型
|
|
136
|
+
dstTypeStats = this.getStatsByNodeName(this.dstSnapshot!, node_name);
|
|
137
|
+
} else {
|
|
138
|
+
// 统计源快照中的对象类型
|
|
139
|
+
srcTypeStats = this.getStatsOfAllNodes(this.srcSnapshot!);
|
|
140
|
+
// 统计目标快照中的对象类型
|
|
141
|
+
dstTypeStats = this.getStatsOfAllNodes(this.dstSnapshot!);
|
|
142
|
+
// console.log(`srcTypeStats: ${JSON.stringify(srcTypeStats)}`);
|
|
143
|
+
// console.log(`dstTypeStats: ${JSON.stringify(dstTypeStats)}`);
|
|
144
|
+
}
|
|
145
|
+
return this.getDiffs(srcTypeStats, dstTypeStats);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public async sortDiffs(type: number,
|
|
149
|
+
diffs: Array<{
|
|
150
|
+
typeName: string;
|
|
151
|
+
srcCount: number;
|
|
152
|
+
dstCount: number;
|
|
153
|
+
countDiff: number;
|
|
154
|
+
srcSelfSize: number;
|
|
155
|
+
dstSelfSize: number;
|
|
156
|
+
selfSizeDiff: number;
|
|
157
|
+
srcRetainedSize: number;
|
|
158
|
+
dstRetainedSize: number;
|
|
159
|
+
retainedSizeDiff: number
|
|
160
|
+
}>): Promise<any> {
|
|
161
|
+
let result: Array<any> | undefined = undefined;
|
|
162
|
+
if (type === 0) {
|
|
163
|
+
result = [...diffs].sort((a, b) => Math.abs(b.countDiff) - Math.abs(a.countDiff));
|
|
164
|
+
} else if (type === 1) {
|
|
165
|
+
result = [...diffs].sort((a, b) => Math.abs(b.selfSizeDiff) - Math.abs(a.selfSizeDiff));
|
|
166
|
+
} else if (type === 2) {
|
|
167
|
+
result = [...diffs].sort((a, b) => Math.abs(b.retainedSizeDiff) - Math.abs(a.retainedSizeDiff));
|
|
168
|
+
} else {
|
|
169
|
+
result = [...diffs].sort((a, b) => Math.abs(b.countDiff) - Math.abs(a.countDiff));
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { IHeapSnapshot } from "@memlab/core";
|
|
2
|
+
import { ArkAnalyzer } from "./ArkAnalyzer";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ark Heapsnapshot 内存泄漏分析器
|
|
6
|
+
*/
|
|
7
|
+
export class ArkLeakAnalyzer extends ArkAnalyzer {
|
|
8
|
+
public readonly DOMAIN = 'meminsight';
|
|
9
|
+
public readonly TAG = ArkLeakAnalyzer.name;
|
|
10
|
+
public static readonly NAME = 'ark-leak-analyzer';
|
|
11
|
+
public static readonly DESCRIPTION = "Analyze heapsnapshot files and find leaks.";
|
|
12
|
+
|
|
13
|
+
protected snapshotList: IHeapSnapshot[];
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.type = 'ark-leak-analyzer';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// public override async analyze(args: any[]) : Promise<any> {
|
|
21
|
+
// if (args.length < 3) {
|
|
22
|
+
// console.log(`Argument count ${args.length} is not enough.`);
|
|
23
|
+
// return undefined;
|
|
24
|
+
// }
|
|
25
|
+
// let filepath = args[0] as string;
|
|
26
|
+
// if (!fs.existsSync(filepath)) {
|
|
27
|
+
// console.log(`${filepath} not found.`);
|
|
28
|
+
// return undefined;
|
|
29
|
+
// }
|
|
30
|
+
// this.snapshot = await Loader.loadFromFileOption(filepath, { buildNodeIdIndex: true, });
|
|
31
|
+
// return this.snapshot;
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// private async analyzeByFilter(snapshot: string, output: string, type: string,
|
|
35
|
+
// filter?: (cfg: any) => boolean): Promise<void> {
|
|
36
|
+
// const result: ILeakResult[] = [];
|
|
37
|
+
// const leakUnits: ILeakUnit[] = [];
|
|
38
|
+
// const filterNodeIds: number[] = [];
|
|
39
|
+
|
|
40
|
+
// const heapSnapshot = await Loader.loadFromFile(snapshot, true);
|
|
41
|
+
// const leakedNodesIds = ArkCompLeaksProc.getLeakedNodeIds(heapSnapshot, true);
|
|
42
|
+
// const leakedTracePathes : Map<number, LeakTracePathItem> =
|
|
43
|
+
// ArkCompLeaksProc.getLeakedTraces(heapSnapshot, leakedNodesIds);
|
|
44
|
+
|
|
45
|
+
// for (const [leakedNodeId, leakItemPath] of leakedTracePathes) {
|
|
46
|
+
// const leakedNode = heapSnapshot.getNodeById(leakedNodeId);
|
|
47
|
+
// if (filter && !filter(leakedNodeId, leakedNode?.type || '')) {
|
|
48
|
+
// filterNodeIds.push(leakedNodeId);
|
|
49
|
+
// continue;
|
|
50
|
+
// }
|
|
51
|
+
// const leakUnit: ILeakUnit = {
|
|
52
|
+
// nodeId: leakedNodeId,
|
|
53
|
+
// startTime: -1,
|
|
54
|
+
// endTime: -1,
|
|
55
|
+
// duration: -1,
|
|
56
|
+
// trace: ArkCompLeaksProc.leakTracePathToStrings(heapSnapshot, leakItemPath)
|
|
57
|
+
// };
|
|
58
|
+
// leakUnits.push(leakUnit);
|
|
59
|
+
// }
|
|
60
|
+
|
|
61
|
+
// if (filterNodeIds.length > 0) {
|
|
62
|
+
// console.log(`[Ignored Leaks: ${filterNodeIds.length}]`);
|
|
63
|
+
// filterNodeIds.forEach((nodeId) => {
|
|
64
|
+
// const node = heapSnapshot.getNodeById(nodeId);
|
|
65
|
+
// console.log(`[${node.name} @${nodeId}]`);
|
|
66
|
+
// });
|
|
67
|
+
// }
|
|
68
|
+
|
|
69
|
+
// const related = new Map<any, any>();
|
|
70
|
+
// related.set("snapshot", snapshot);
|
|
71
|
+
|
|
72
|
+
// result.push(
|
|
73
|
+
// {
|
|
74
|
+
// leaks: leakUnits,
|
|
75
|
+
// }
|
|
76
|
+
// );
|
|
77
|
+
// console.log(`[Leaks Found: ${leakUnits.length}]`);
|
|
78
|
+
// }
|
|
79
|
+
|
|
80
|
+
// /**
|
|
81
|
+
// * 获取组件泄露的节点
|
|
82
|
+
// * @param snapshot heapsnapshot graph
|
|
83
|
+
// * @param isPrint 是否打印泄露的组件对象
|
|
84
|
+
// *
|
|
85
|
+
// * @returns 组件泄露的节点集
|
|
86
|
+
// */
|
|
87
|
+
// private static getLeakedNodeIds(snapshot: IHeapSnapshot, isPrint: boolean = false) : Set<number> {
|
|
88
|
+
// let result: Set<number> = new Set<number>();
|
|
89
|
+
// let arkCompMap: Map<string, Set<number>> = new Map<string, Set<number>>();
|
|
90
|
+
// snapshot.nodes.forEach((node: IHeapNode) => {
|
|
91
|
+
// let edges = node.references.filter((edge, ) => edge.name_or_index == STRINGS.IS_DELETING);
|
|
92
|
+
// if (edges.length > 0 && edges[0].toNode.name === STRINGS.BOOLEAN_TRUE) {
|
|
93
|
+
// result.add(node.id);
|
|
94
|
+
// ArkCompLeaksProc.setLeaks(arkCompMap, node);
|
|
95
|
+
// }
|
|
96
|
+
// });
|
|
97
|
+
// if (isPrint) {
|
|
98
|
+
// ArkTracer.outputTracePath(path, (path) => {
|
|
99
|
+
// this.printTracePath(path);
|
|
100
|
+
// });
|
|
101
|
+
// Log.infoX(this, 'The shortest path to GC roots:');
|
|
102
|
+
// let curPath = path;
|
|
103
|
+
// let curNode = path.node;
|
|
104
|
+
// while (curPath && curNode) {
|
|
105
|
+
// console.log(`(@${curNode.id})${curNode.name})`);
|
|
106
|
+
// curPath = curPath.next;
|
|
107
|
+
// curNode = curPath.next?.node;
|
|
108
|
+
// if (curNode) {
|
|
109
|
+
// console.log(`\t===>`);
|
|
110
|
+
// }
|
|
111
|
+
// }
|
|
112
|
+
// }
|
|
113
|
+
// return result;
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// private static setLeaks(compMap: Map<string, Set<number>>, node: IHeapNode) {
|
|
117
|
+
// let nodeIds: Set<number> | undefined = compMap.get(node.name);
|
|
118
|
+
// if (nodeIds === undefined) {
|
|
119
|
+
// nodeIds = new Set<number>();
|
|
120
|
+
// }
|
|
121
|
+
// nodeIds.add(node.id);
|
|
122
|
+
// compMap.set(node.name, nodeIds);
|
|
123
|
+
// }
|
|
124
|
+
|
|
125
|
+
// private static printLeaks(nodeIds: Set<number>,
|
|
126
|
+
// compMap: Map<string, Set<number>>,
|
|
127
|
+
// scene?: string,
|
|
128
|
+
// region?: string) {
|
|
129
|
+
// let sceneStr = scene || `ArkComponent-Leak`;
|
|
130
|
+
// let regionStr = region || `UI`;
|
|
131
|
+
// console.log(`[${regionStr}-${sceneStr}]: ${nodeIds.size}`);
|
|
132
|
+
// if (compMap.size > 0) {
|
|
133
|
+
// const MAX_LEN = 10;
|
|
134
|
+
// for (const [name, ids] of compMap) {
|
|
135
|
+
// const idArray = Array.from(ids);
|
|
136
|
+
// const truncatedIds = idArray.length > MAX_LEN
|
|
137
|
+
// ? idArray.slice(0, MAX_LEN).concat(["..."])
|
|
138
|
+
// : idArray;
|
|
139
|
+
// console.log(`${name}-${ids.size}: @${JSON.stringify(truncatedIds)}`);
|
|
140
|
+
// }
|
|
141
|
+
// }
|
|
142
|
+
// }
|
|
143
|
+
|
|
144
|
+
// private static getLeakedTraces(snapshot: IHeapSnapshot, nodeIds: Set<number>) : Map<number, LeakTracePathItem> {
|
|
145
|
+
// const leakedTracePathes = new Map<number, LeakTracePathItem>
|
|
146
|
+
// let finder = new TraceFinder();
|
|
147
|
+
// finder.annotateShortestPaths(snapshot); // shortest path for all nodes
|
|
148
|
+
// utils.markAllDetachedFiberNode(snapshot);
|
|
149
|
+
// utils.markAlternateFiberNode(snapshot);
|
|
150
|
+
// utils.applyToNodes(nodeIds, snapshot, node => {
|
|
151
|
+
// let tracePath = finder.getPathToGCRoots(snapshot, node);
|
|
152
|
+
// if (tracePath) leakedTracePathes.set(node?.id, tracePath);
|
|
153
|
+
// });
|
|
154
|
+
// return leakedTracePathes;
|
|
155
|
+
// }
|
|
156
|
+
|
|
157
|
+
// public static leakTracePathToStrings(snapshot: IHeapSnapshot, trace: LeakTracePathItem) : string[] {
|
|
158
|
+
// return serializer.summarizePath(trace, new Set<number>(), snapshot).split('\n').map(str => {
|
|
159
|
+
// const nodeIdStr: string = str.substring(str.indexOf('@') + 1).split(' ')[0];
|
|
160
|
+
// if (Number.isNaN(nodeIdStr)) {
|
|
161
|
+
// return str;
|
|
162
|
+
// }
|
|
163
|
+
// const nodeId: number = Number.parseInt(nodeIdStr);
|
|
164
|
+
// const node: Optional<IHeapNode> = snapshot.getNodeById(nodeId);
|
|
165
|
+
// if (!node) {
|
|
166
|
+
// return str;
|
|
167
|
+
// }
|
|
168
|
+
// if (str.indexOf(STRINGS.OBJECT) >= 0) {
|
|
169
|
+
// return `${str} ${ArkCompLeaksProc.getDetails(node)}`
|
|
170
|
+
// }
|
|
171
|
+
// if (str.indexOf(STRINGS.CLOSURE) >= 0) {
|
|
172
|
+
// return `${str} <${node.name}>`
|
|
173
|
+
// }
|
|
174
|
+
// return str;
|
|
175
|
+
// });
|
|
176
|
+
// }
|
|
177
|
+
|
|
178
|
+
// public static getDetails(node: IHeapNode): string {
|
|
179
|
+
// if (node.type !== 'object' || node?.references?.length === undefined) {
|
|
180
|
+
// return '';
|
|
181
|
+
// }
|
|
182
|
+
// while (node.references.length > 0) {
|
|
183
|
+
// const ctor_edge = node.references.find((edge) => edge.name_or_index === STRINGS.CONSTRUCTOR);
|
|
184
|
+
// if (ctor_edge) {
|
|
185
|
+
// return `<${ctor_edge.toNode.name}>`;
|
|
186
|
+
// }
|
|
187
|
+
// const proto_edge = node.references.find((edge) => edge.name_or_index === STRINGS.PROTO);
|
|
188
|
+
// if (proto_edge) {
|
|
189
|
+
// node = proto_edge.toNode;
|
|
190
|
+
// } else {
|
|
191
|
+
// return '';
|
|
192
|
+
// }
|
|
193
|
+
// }
|
|
194
|
+
// return '';
|
|
195
|
+
// }
|
|
196
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { IHeapNode, serializer } from '@memlab/core'
|
|
2
|
+
import { ArkNodesMap, ArkTracePathItem } from './ArkTracePath';
|
|
3
|
+
import { Log } from '../utils/Log';
|
|
4
|
+
import { ConsoleData, ConsoleTableData, TextData, JsonData, HtmlData, OutputCfg } from '../utils/Output';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ark Serializer
|
|
8
|
+
*/
|
|
9
|
+
export class ArkSerializer {
|
|
10
|
+
private static getNodesTableTitle(cfg?: OutputCfg): string[] {
|
|
11
|
+
return [
|
|
12
|
+
cfg?.node?.enableId ? 'id' : '',
|
|
13
|
+
cfg?.node?.enableType ? 'type' : '',
|
|
14
|
+
cfg?.node?.enableName ? 'name' : '',
|
|
15
|
+
cfg?.node?.enableSelfSize ? 'self size' : '',
|
|
16
|
+
cfg?.node?.enableRetainedSize ? 'retained size' : ''
|
|
17
|
+
].filter(item => !!item);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static getNodesArrayData(nodes: IHeapNode[], cfg?: OutputCfg) : string[][] | undefined {
|
|
21
|
+
if (nodes.length === 0) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
let title = ArkSerializer.getNodesTableTitle(cfg);
|
|
25
|
+
let size = 0;
|
|
26
|
+
let body = nodes.reduce((acc, node) => {
|
|
27
|
+
if (cfg?.node?.enableId) {
|
|
28
|
+
acc += `,${node.id}`;
|
|
29
|
+
size++;
|
|
30
|
+
}
|
|
31
|
+
if (cfg?.node?.enableType) {
|
|
32
|
+
acc += `,${node.type}`;
|
|
33
|
+
size++;
|
|
34
|
+
}
|
|
35
|
+
if (cfg?.node?.enableName) {
|
|
36
|
+
acc += `,${node.name}`;
|
|
37
|
+
size++;
|
|
38
|
+
}
|
|
39
|
+
if (cfg?.node?.enableSelfSize) {
|
|
40
|
+
acc += `,${node.self_size}`;
|
|
41
|
+
size++;
|
|
42
|
+
}
|
|
43
|
+
if (cfg?.node?.enableRetainedSize) {
|
|
44
|
+
acc += `,${node.retainedSize}`;
|
|
45
|
+
size++;
|
|
46
|
+
}
|
|
47
|
+
return acc;
|
|
48
|
+
}, '').split(',').filter(item => !!item);
|
|
49
|
+
let arr = title.concat(...body);
|
|
50
|
+
let data = arr.reduce((acc, _, i) => {
|
|
51
|
+
if (i % size === 0) {
|
|
52
|
+
acc.push(arr.slice(i, i + size));
|
|
53
|
+
}
|
|
54
|
+
return acc;
|
|
55
|
+
}, [] as string[][]);
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private static getNodesJsonArray(nodes: IHeapNode[], cfg?: OutputCfg) : any[] {
|
|
60
|
+
if (nodes.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const result: any[] = [];
|
|
64
|
+
nodes.forEach(node => {
|
|
65
|
+
const jsonNode: any = {};
|
|
66
|
+
if (cfg?.node?.enableId) {
|
|
67
|
+
jsonNode["id"] = node.id;
|
|
68
|
+
}
|
|
69
|
+
if (cfg?.node?.enableType) {
|
|
70
|
+
jsonNode["type"] = node.type;
|
|
71
|
+
}
|
|
72
|
+
if (cfg?.node?.enableName) {
|
|
73
|
+
jsonNode["name"] = node.name;
|
|
74
|
+
}
|
|
75
|
+
if (cfg?.node?.enableSelfSize) {
|
|
76
|
+
jsonNode["selfSize"] = node.self_size;
|
|
77
|
+
}
|
|
78
|
+
if (cfg?.node?.enableRetainedSize) {
|
|
79
|
+
jsonNode["retainedSize"] = node.retainedSize;
|
|
80
|
+
}
|
|
81
|
+
result.push(jsonNode);
|
|
82
|
+
});
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public static getTextDataByNodes(nodes: IHeapNode[], cfg?: OutputCfg) : TextData | undefined {
|
|
87
|
+
if (nodes.length === 0) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
let desc = cfg?.desc || '';
|
|
91
|
+
let data = nodes.reduce((acc, node) => {
|
|
92
|
+
if (cfg?.node?.enableName) {
|
|
93
|
+
acc += `[${node.name}]`;
|
|
94
|
+
}
|
|
95
|
+
if (cfg?.node?.enableType) {
|
|
96
|
+
acc += ` (${node.type})`;
|
|
97
|
+
}
|
|
98
|
+
if (cfg?.node?.enableId) {
|
|
99
|
+
acc += ` @${node.id}`;
|
|
100
|
+
}
|
|
101
|
+
if (cfg?.node?.enableSelfSize) {
|
|
102
|
+
acc += ` [${node.self_size}]`;
|
|
103
|
+
}
|
|
104
|
+
if (cfg?.node?.enableRetainedSize) {
|
|
105
|
+
acc += ` [${node.retainedSize}]`;
|
|
106
|
+
}
|
|
107
|
+
acc += '\n';
|
|
108
|
+
return acc;
|
|
109
|
+
}, '');
|
|
110
|
+
return new TextData(desc, data);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public static getJsonDataByNodes(
|
|
114
|
+
nodes: IHeapNode[],
|
|
115
|
+
cfg?: OutputCfg
|
|
116
|
+
): JsonData {
|
|
117
|
+
let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
|
|
118
|
+
let desc = cfg?.desc || {};
|
|
119
|
+
return new JsonData(desc, jsonArr);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public static getConsoleDataByNodes(
|
|
123
|
+
isTable: boolean,
|
|
124
|
+
nodes: IHeapNode[],
|
|
125
|
+
cfg?: OutputCfg) : ConsoleData {
|
|
126
|
+
if (isTable) {
|
|
127
|
+
let titleArr = ArkSerializer.getNodesTableTitle(cfg);
|
|
128
|
+
let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
|
|
129
|
+
const tabalData = new ConsoleTableData(titleArr, jsonArr);
|
|
130
|
+
return new ConsoleData(isTable, tabalData);
|
|
131
|
+
} else {
|
|
132
|
+
let textData = ArkSerializer.getTextDataByNodes(nodes, cfg);
|
|
133
|
+
return new ConsoleData(isTable, textData?.data);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public static getHtmlDataByNodes(
|
|
138
|
+
nodes: IHeapNode[],
|
|
139
|
+
cfg: OutputCfg) : HtmlData {
|
|
140
|
+
// TODO
|
|
141
|
+
let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
|
|
142
|
+
let desc = cfg?.desc || '';
|
|
143
|
+
return new HtmlData(desc, jsonArr);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public static getNodeTracePathString(path: ArkTracePathItem | undefined) : string {
|
|
147
|
+
if (!path) {
|
|
148
|
+
Log.error('meminsight', ArkSerializer.name, 'trace path not found.');
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
let snapshot = path.node?.snapshot;
|
|
152
|
+
if (!snapshot) {
|
|
153
|
+
Log.error('meminsight', ArkSerializer.name, 'snapshot not found.');
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
return serializer.summarizePath(path, new Set<number>(), snapshot);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public static getArkNodesMapString(arkNodesMap: ArkNodesMap) : string {
|
|
160
|
+
// TODO:
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
}
|