hiperf_txt_parser 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -25,12 +25,14 @@ import {
25
25
  parsePerfData,
26
26
  formatPerfDataToText,
27
27
  formatPerfDataToJson,
28
+ filterByTgid,
28
29
  } from "hiperf_txt_parser";
29
30
  ```
30
31
 
31
32
  - `parsePerfData(text: string): PerfData`
32
33
  - `formatPerfDataToText(data: PerfData): string`
33
34
  - `formatPerfDataToJson(data: PerfData): Array<{ issuce: "unknow"; call_chain: string }>`
35
+ - `filterByTgid(data: PerfData, tgid: number): PerfData`(仅保留 `pid === tgid` 的 RecordSample)
34
36
 
35
37
  ## 快速示例
36
38
 
@@ -40,7 +42,8 @@ import { parsePerfData, formatPerfDataToJson, formatPerfDataToText } from "hiper
40
42
  const input = `record sample: type 9, misc 2, size 520\n sample_type: 0x8000107e7\n ID 13`;
41
43
 
42
44
  const parsed = parsePerfData(input);
43
- const jsonArray = formatPerfDataToJson(parsed);
45
+ const filtered = filterByTgid(parsed, 1234);
46
+ const jsonArray = formatPerfDataToJson(filtered);
44
47
  const txt = formatPerfDataToText(parsed);
45
48
  ```
46
49
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { parsePerfData } from "./parser.js";
1
+ export { parsePerfData, filterByTgid } from "./parser.js";
2
2
  export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
3
3
  export type { PerfData, RecordSample } from "./types.js";
4
4
  export type { RecordSampleJsonExportItem } from "./serializer.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { parsePerfData } from "./parser.js";
1
+ export { parsePerfData, filterByTgid } from "./parser.js";
2
2
  export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
package/dist/parser.d.ts CHANGED
@@ -3,3 +3,7 @@ import type { PerfData } from "./types.js";
3
3
  * 解析 perf data 文本,仅返回 record sample 结构数组
4
4
  */
5
5
  export declare function parsePerfData(text: string): PerfData;
6
+ /**
7
+ * 按 tgid 过滤 RecordSample(当前以 pid 字段作为 tgid)
8
+ */
9
+ export declare function filterByTgid(data: PerfData, tgid: number): PerfData;
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 indent = getIndent(line);
48
- const trimmed = line.trimStart();
49
- if (indent === 0 && trimmed.length > 0) {
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 (indent === 2) {
53
- if (trimmed.startsWith("sample_type:")) {
54
- sample.sample_type = trimmed.replace(/^sample_type:\s*/, "").trim();
55
- }
56
- else if (trimmed.startsWith("ID ")) {
57
- sample.id = parseInt(trimmed.replace(/^ID\s+/, ""), 10) || 0;
58
- }
59
- else if (trimmed.startsWith("ip ")) {
60
- sample.ip = trimmed.replace(/^ip\s+/, "").trim();
61
- }
62
- else if (trimmed.startsWith("pid ") && trimmed.includes(", tid ")) {
63
- const pidMatch = trimmed.match(/pid\s+(\d+).*tid\s+(\d+)/);
64
- if (pidMatch) {
65
- sample.pid = parseInt(pidMatch[1], 10);
66
- sample.tid = parseInt(pidMatch[2], 10);
67
- }
68
- }
69
- else if (trimmed.startsWith("time ")) {
70
- sample.time = parseInt(trimmed.replace(/^time\s+/, ""), 10) || 0;
71
- }
72
- else if (trimmed.startsWith("stream_id ")) {
73
- sample.stream_id = parseInt(trimmed.replace(/^stream_id\s+/, ""), 10) || 0;
74
- }
75
- else if (trimmed.startsWith("cpu ") && trimmed.includes(", res ")) {
76
- const cpuMatch = trimmed.match(/cpu\s+(\d+).*res\s+(\d+)/);
77
- if (cpuMatch) {
78
- sample.cpu = parseInt(cpuMatch[1], 10);
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
- else if (trimmed.startsWith("period ")) {
83
- sample.period = parseInt(trimmed.replace(/^period\s+/, ""), 10) || 0;
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
- else if (trimmed.startsWith("callchain nr=")) {
86
- const nrMatch = trimmed.match(/callchain\s+nr=(\d+)/);
87
- const nr = nrMatch ? parseInt(nrMatch[1], 10) : 0;
88
- const addresses = [];
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
- else if (trimmed.startsWith("raw size=")) {
101
- const sizeMatch = trimmed.match(/raw\s+size=(\d+)/);
102
- const size = sizeMatch ? parseInt(sizeMatch[1], 10) : 0;
103
- const entries = [];
104
- i++;
105
- while (i < lines.length && getIndent(lines[i]) >= 4) {
106
- const rawLine = lines[i].trim();
107
- const hexShort = rawLine.match(/^(0x[0-9a-fA-F]+)\s*\(([0-9a-fA-F]+)\)$/);
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
- sample.raw = { size, lines: entries };
117
- continue;
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
- else if (trimmed.startsWith("callchain: ")) {
135
- const countMatch = trimmed.match(/callchain:\s*(\d+)/);
136
- const count = countMatch ? parseInt(countMatch[1], 10) : 0;
137
- const frames = [];
138
- i++;
139
- while (i < lines.length && getIndent(lines[i]) >= 4) {
140
- frames.push(lines[i].trim());
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
- sample.callchainFrames = { count, frames };
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
- const indent = getIndent(line);
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) && indent === 0) {
224
+ if (trimmed.startsWith(RECORD_COMM_PREFIX)) {
170
225
  if (current.length > 0) {
171
226
  blocks.push(current);
172
227
  current = [];
@@ -202,3 +257,11 @@ export function parsePerfData(text) {
202
257
  }
203
258
  return { recordSamples };
204
259
  }
260
+ /**
261
+ * 按 tgid 过滤 RecordSample(当前以 pid 字段作为 tgid)
262
+ */
263
+ export function filterByTgid(data, tgid) {
264
+ return {
265
+ recordSamples: data.recordSamples.filter((sample) => sample.pid === tgid),
266
+ };
267
+ }
@@ -33,9 +33,9 @@ function serializeOneSample(sample) {
33
33
  }
34
34
  if (sample.callchainFrames) {
35
35
  lines.push(` `);
36
- lines.push(` callchain: ${sample.callchainFrames.count}`);
36
+ lines.push(` callchain: ${sample.callchainFrames.count}`);
37
37
  for (const frame of sample.callchainFrames.frames) {
38
- lines.push(` ${frame}`);
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.0",
3
+ "version": "1.0.2",
4
4
  "description": "Parse perf data.txt and output structured TypeScript data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,9 +16,14 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "build": "tsc",
19
- "test": "npm run build && node --test dist/__tests__/parser.test.js"
19
+ "prepublishOnly": "npm run build",
20
+ "test": "npm run build && node --test tests/*.test.mjs"
20
21
  },
21
- "keywords": ["perf", "parser", "library"],
22
+ "keywords": [
23
+ "perf",
24
+ "parser",
25
+ "library"
26
+ ],
22
27
  "author": "",
23
28
  "license": "MIT",
24
29
  "devDependencies": {