hiperf_txt_parser 1.0.1 → 1.0.3
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/dist/backtrace.d.ts +13 -0
- package/dist/backtrace.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/parser.js +148 -93
- package/dist/serializer.js +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PerfData, RecordSample } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 将 RecordSample 原始栈中符合 pattern 的帧转为 hstack 可解析的栈格式。
|
|
4
|
+
*
|
|
5
|
+
* 输出示例:
|
|
6
|
+
* #26 at triggerBinder (entry|entry|1.0.0|src/main/ets/myabilitystage/PreloadHook.ts:4:21)
|
|
7
|
+
*/
|
|
8
|
+
export declare function toBackTraceStack(sample: RecordSample): string;
|
|
9
|
+
/**
|
|
10
|
+
* 批量将 PerfData 中所有 RecordSample 转为 hstack 可解析栈。
|
|
11
|
+
* 返回数组长度与 recordSamples 一致,元素顺序一一对应。
|
|
12
|
+
*/
|
|
13
|
+
export declare function toBackTraceStacks(data: PerfData): string[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// 例: 26:0x0000005ab8b4beee : triggerBinder:[url:entry|entry|1.0.0|src/main/ets/x.ts:4:21][...]
|
|
2
|
+
const RAW_STACK_PATTERN = /^\s*(\d+):0x[0-9A-Za-z]+\s*:\s*([^\[]+?)\s*:\[url:([^\]]+)\].*$/;
|
|
3
|
+
/**
|
|
4
|
+
* 将 RecordSample 原始栈中符合 pattern 的帧转为 hstack 可解析的栈格式。
|
|
5
|
+
*
|
|
6
|
+
* 输出示例:
|
|
7
|
+
* #26 at triggerBinder (entry|entry|1.0.0|src/main/ets/myabilitystage/PreloadHook.ts:4:21)
|
|
8
|
+
*/
|
|
9
|
+
export function toBackTraceStack(sample) {
|
|
10
|
+
const frames = sample.callchainFrames?.frames ?? [];
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const frame of frames) {
|
|
13
|
+
const m = frame.match(RAW_STACK_PATTERN);
|
|
14
|
+
if (!m)
|
|
15
|
+
continue;
|
|
16
|
+
const index = m[1];
|
|
17
|
+
const funcName = m[2].trim();
|
|
18
|
+
const urlInfo = m[3].trim();
|
|
19
|
+
out.push(`#${index} at ${funcName} (${urlInfo})`);
|
|
20
|
+
}
|
|
21
|
+
return out.join("\n");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 批量将 PerfData 中所有 RecordSample 转为 hstack 可解析栈。
|
|
25
|
+
* 返回数组长度与 recordSamples 一致,元素顺序一一对应。
|
|
26
|
+
*/
|
|
27
|
+
export function toBackTraceStacks(data) {
|
|
28
|
+
return data.recordSamples.map((sample) => toBackTraceStack(sample));
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { parsePerfData, filterByTgid } from "./parser.js";
|
|
2
2
|
export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
|
|
3
|
+
export { toBackTraceStack, toBackTraceStacks } from "./backtrace.js";
|
|
3
4
|
export type { PerfData, RecordSample } from "./types.js";
|
|
4
5
|
export type { RecordSampleJsonExportItem } from "./serializer.js";
|
package/dist/index.js
CHANGED
package/dist/parser.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
const RECORD_SAMPLE_PREFIX = "record sample:";
|
|
2
2
|
const RECORD_COMM_PREFIX = "record comm";
|
|
3
|
-
/**
|
|
4
|
-
* 获取行首空格数量(缩进)
|
|
5
|
-
*/
|
|
6
|
-
function getIndent(line) {
|
|
7
|
-
const m = line.match(/^(\s*)/);
|
|
8
|
-
return m ? m[1].length : 0;
|
|
9
|
-
}
|
|
10
3
|
/**
|
|
11
4
|
* 解析 "record sample: type 9, misc 2, size 520" 行
|
|
12
5
|
*/
|
|
@@ -23,6 +16,20 @@ function parseHeaderLine(line) {
|
|
|
23
16
|
/**
|
|
24
17
|
* 解析单个 record sample 块(已按行切分好的行数组)
|
|
25
18
|
*/
|
|
19
|
+
function isTopLevelField(trimmed) {
|
|
20
|
+
return (trimmed.startsWith("sample_type:") ||
|
|
21
|
+
trimmed.startsWith("ID ") ||
|
|
22
|
+
trimmed.startsWith("ip ") ||
|
|
23
|
+
(trimmed.startsWith("pid ") && trimmed.includes(", tid ")) ||
|
|
24
|
+
trimmed.startsWith("time ") ||
|
|
25
|
+
trimmed.startsWith("stream_id ") ||
|
|
26
|
+
(trimmed.startsWith("cpu ") && trimmed.includes(", res ")) ||
|
|
27
|
+
trimmed.startsWith("period ") ||
|
|
28
|
+
trimmed.startsWith("callchain nr=") ||
|
|
29
|
+
trimmed.startsWith("raw size=") ||
|
|
30
|
+
trimmed.startsWith("server nr=") ||
|
|
31
|
+
trimmed.startsWith("callchain: "));
|
|
32
|
+
}
|
|
26
33
|
function parseOneBlock(lines) {
|
|
27
34
|
if (lines.length === 0 || !lines[0].trimStart().startsWith(RECORD_SAMPLE_PREFIX)) {
|
|
28
35
|
throw new Error("Invalid record sample block");
|
|
@@ -41,108 +48,157 @@ function parseOneBlock(lines) {
|
|
|
41
48
|
res: 0,
|
|
42
49
|
period: 0,
|
|
43
50
|
};
|
|
51
|
+
let mode = null;
|
|
44
52
|
let i = 1;
|
|
45
53
|
while (i < lines.length) {
|
|
46
54
|
const line = lines[i];
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
if (!trimmed) {
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (trimmed.startsWith(RECORD_SAMPLE_PREFIX) ||
|
|
61
|
+
trimmed.startsWith(RECORD_COMM_PREFIX)) {
|
|
50
62
|
break;
|
|
51
63
|
}
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
sample.res = parseInt(cpuMatch[2], 10);
|
|
80
|
-
}
|
|
64
|
+
if (mode && isTopLevelField(trimmed)) {
|
|
65
|
+
mode = null;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (trimmed.startsWith("sample_type:")) {
|
|
69
|
+
sample.sample_type = trimmed.replace(/^sample_type:\s*/, "").trim();
|
|
70
|
+
mode = null;
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (trimmed.startsWith("ID ")) {
|
|
75
|
+
sample.id = parseInt(trimmed.replace(/^ID\s+/, ""), 10) || 0;
|
|
76
|
+
mode = null;
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (trimmed.startsWith("ip ")) {
|
|
81
|
+
sample.ip = trimmed.replace(/^ip\s+/, "").trim();
|
|
82
|
+
mode = null;
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (trimmed.startsWith("pid ") && trimmed.includes(", tid ")) {
|
|
87
|
+
const pidMatch = trimmed.match(/pid\s+(\d+).*tid\s+(\d+)/);
|
|
88
|
+
if (pidMatch) {
|
|
89
|
+
sample.pid = parseInt(pidMatch[1], 10);
|
|
90
|
+
sample.tid = parseInt(pidMatch[2], 10);
|
|
81
91
|
}
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
mode = null;
|
|
93
|
+
i++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (trimmed.startsWith("time ")) {
|
|
97
|
+
sample.time = parseInt(trimmed.replace(/^time\s+/, ""), 10) || 0;
|
|
98
|
+
mode = null;
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (trimmed.startsWith("stream_id ")) {
|
|
103
|
+
sample.stream_id = parseInt(trimmed.replace(/^stream_id\s+/, ""), 10) || 0;
|
|
104
|
+
mode = null;
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (trimmed.startsWith("cpu ") && trimmed.includes(", res ")) {
|
|
109
|
+
const cpuMatch = trimmed.match(/cpu\s+(\d+).*res\s+(\d+)/);
|
|
110
|
+
if (cpuMatch) {
|
|
111
|
+
sample.cpu = parseInt(cpuMatch[1], 10);
|
|
112
|
+
sample.res = parseInt(cpuMatch[2], 10);
|
|
84
113
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
mode = null;
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (trimmed.startsWith("period ")) {
|
|
119
|
+
sample.period = parseInt(trimmed.replace(/^period\s+/, ""), 10) || 0;
|
|
120
|
+
mode = null;
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (trimmed.startsWith("callchain nr=")) {
|
|
125
|
+
const nrMatch = trimmed.match(/callchain\s+nr=(\d+)/);
|
|
126
|
+
const nr = nrMatch ? parseInt(nrMatch[1], 10) : 0;
|
|
127
|
+
sample.callchain = { nr, addresses: [] };
|
|
128
|
+
mode = "callchainAddr";
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (trimmed.startsWith("raw size=")) {
|
|
133
|
+
const sizeMatch = trimmed.match(/raw\s+size=(\d+)/);
|
|
134
|
+
const size = sizeMatch ? parseInt(sizeMatch[1], 10) : 0;
|
|
135
|
+
sample.raw = { size, lines: [] };
|
|
136
|
+
mode = "raw";
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (trimmed.startsWith("server nr=")) {
|
|
141
|
+
const nrMatch = trimmed.match(/server\s+nr=(\d+)/);
|
|
142
|
+
const nr = nrMatch ? parseInt(nrMatch[1], 10) : 0;
|
|
143
|
+
sample.server = { nr, pids: [] };
|
|
144
|
+
mode = "server";
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (trimmed.startsWith("callchain: ")) {
|
|
149
|
+
const countMatch = trimmed.match(/callchain:\s*(\d+)/);
|
|
150
|
+
const count = countMatch ? parseInt(countMatch[1], 10) : 0;
|
|
151
|
+
sample.callchainFrames = { count, frames: [] };
|
|
152
|
+
mode = "frames";
|
|
153
|
+
i++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (mode === "callchainAddr") {
|
|
157
|
+
if (/^0x[0-9a-fA-F]+$/.test(trimmed) && sample.callchain) {
|
|
158
|
+
sample.callchain.addresses.push(trimmed);
|
|
89
159
|
i++;
|
|
90
|
-
while (i < lines.length && getIndent(lines[i]) >= 4) {
|
|
91
|
-
const addr = lines[i].trim();
|
|
92
|
-
if (addr && /^0x[0-9a-fA-F]+$/.test(addr)) {
|
|
93
|
-
addresses.push(addr);
|
|
94
|
-
}
|
|
95
|
-
i++;
|
|
96
|
-
}
|
|
97
|
-
sample.callchain = { nr, addresses };
|
|
98
160
|
continue;
|
|
99
161
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (hexShort) {
|
|
109
|
-
entries.push({ hex: hexShort[1], short: hexShort[2] });
|
|
110
|
-
}
|
|
111
|
-
else if (rawLine.startsWith("0x")) {
|
|
112
|
-
entries.push({ hex: rawLine });
|
|
113
|
-
}
|
|
162
|
+
mode = null;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (mode === "raw") {
|
|
166
|
+
if (sample.raw) {
|
|
167
|
+
const hexShort = trimmed.match(/^(0x[0-9a-fA-F]+)\s*\(([0-9a-fA-F]+)\)$/);
|
|
168
|
+
if (hexShort) {
|
|
169
|
+
sample.raw.lines.push({ hex: hexShort[1], short: hexShort[2] });
|
|
114
170
|
i++;
|
|
171
|
+
continue;
|
|
115
172
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
else if (trimmed.startsWith("server nr=")) {
|
|
120
|
-
const nrMatch = trimmed.match(/server\s+nr=(\d+)/);
|
|
121
|
-
const nr = nrMatch ? parseInt(nrMatch[1], 10) : 0;
|
|
122
|
-
const pids = [];
|
|
123
|
-
i++;
|
|
124
|
-
while (i < lines.length && getIndent(lines[i]) >= 4) {
|
|
125
|
-
const pidMatch = lines[i].trim().match(/pid:\s*(\d+)/);
|
|
126
|
-
if (pidMatch) {
|
|
127
|
-
pids.push(parseInt(pidMatch[1], 10));
|
|
128
|
-
}
|
|
173
|
+
if (/^0x[0-9a-fA-F]+$/.test(trimmed)) {
|
|
174
|
+
sample.raw.lines.push({ hex: trimmed });
|
|
129
175
|
i++;
|
|
176
|
+
continue;
|
|
130
177
|
}
|
|
131
|
-
sample.server = { nr, pids };
|
|
132
|
-
continue;
|
|
133
178
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
mode = null;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (mode === "server") {
|
|
183
|
+
if (sample.server) {
|
|
184
|
+
const pidMatch = trimmed.match(/^pid:\s*(\d+)$/);
|
|
185
|
+
if (pidMatch) {
|
|
186
|
+
sample.server.pids.push(parseInt(pidMatch[1], 10));
|
|
141
187
|
i++;
|
|
188
|
+
continue;
|
|
142
189
|
}
|
|
143
|
-
|
|
190
|
+
}
|
|
191
|
+
mode = null;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (mode === "frames") {
|
|
195
|
+
if (sample.callchainFrames) {
|
|
196
|
+
sample.callchainFrames.frames.push(trimmed);
|
|
197
|
+
i++;
|
|
144
198
|
continue;
|
|
145
199
|
}
|
|
200
|
+
mode = null;
|
|
201
|
+
continue;
|
|
146
202
|
}
|
|
147
203
|
i++;
|
|
148
204
|
}
|
|
@@ -158,15 +214,14 @@ function extractRecordSampleBlocks(text) {
|
|
|
158
214
|
for (let i = 0; i < lines.length; i++) {
|
|
159
215
|
const line = lines[i];
|
|
160
216
|
const trimmed = line.trimStart();
|
|
161
|
-
|
|
162
|
-
if (trimmed.startsWith(RECORD_SAMPLE_PREFIX) && indent === 0) {
|
|
217
|
+
if (trimmed.startsWith(RECORD_SAMPLE_PREFIX)) {
|
|
163
218
|
if (current.length > 0) {
|
|
164
219
|
blocks.push(current);
|
|
165
220
|
}
|
|
166
221
|
current = [line];
|
|
167
222
|
continue;
|
|
168
223
|
}
|
|
169
|
-
if (trimmed.startsWith(RECORD_COMM_PREFIX)
|
|
224
|
+
if (trimmed.startsWith(RECORD_COMM_PREFIX)) {
|
|
170
225
|
if (current.length > 0) {
|
|
171
226
|
blocks.push(current);
|
|
172
227
|
current = [];
|
package/dist/serializer.js
CHANGED
|
@@ -33,9 +33,9 @@ function serializeOneSample(sample) {
|
|
|
33
33
|
}
|
|
34
34
|
if (sample.callchainFrames) {
|
|
35
35
|
lines.push(` `);
|
|
36
|
-
lines.push(`
|
|
36
|
+
lines.push(` callchain: ${sample.callchainFrames.count}`);
|
|
37
37
|
for (const frame of sample.callchainFrames.frames) {
|
|
38
|
-
lines.push(`
|
|
38
|
+
lines.push(` ${frame}`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
return lines.join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hiperf_txt_parser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Parse perf data.txt and output structured TypeScript data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"prepublishOnly": "npm run build",
|
|
20
|
-
"test": "npm run build && node --test tests
|
|
20
|
+
"test": "npm run build && node --test tests/*.test.mjs"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"perf",
|