hiperf_txt_parser 1.0.5 → 1.0.7

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
@@ -33,6 +33,9 @@ import {
33
33
  - `formatPerfDataToText(data: PerfData): string`
34
34
  - `formatPerfDataToJson(data: PerfData): Array<{ issuce: "unknow"; call_chain: string }>`
35
35
  - `filterByTgid(data: PerfData, tgid: number): PerfData`(仅保留 `pid === tgid` 的 RecordSample)
36
+ - `parseTraceFormat(text: string): ParsedTraceFormat`(解析 `sample/trace_format` 风格文本)
37
+ - `parseCommonFieldsFromRaw(raw: Uint8Array, format: ParsedTraceFormat): Record<string, number | bigint>`(按 format 仅解析 `common_*` 字段,小端)
38
+ - `rawHexLinesToBuffer(lines): Uint8Array`(将 perf 文本里 raw 段的 hex 行拼成字节缓冲,便于喂给 `parseCommonFieldsFromRaw`)
36
39
 
37
40
  ## 快速示例
38
41
 
package/dist/backtrace.js CHANGED
@@ -32,6 +32,10 @@ export function toBackTraceStacks(data) {
32
32
  return sample;
33
33
  }
34
34
  const frameLines = backtrace ? backtrace.split("\n") : [];
35
+ if (frameLines.length === 0) {
36
+ const { callchainFrames, ...rest } = sample;
37
+ return rest;
38
+ }
35
39
  return {
36
40
  ...sample,
37
41
  callchainFrames: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { parsePerfData, filterByTgid } from "./parser.js";
2
2
  export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
3
3
  export { toBackTraceStack, toBackTraceStacks } from "./backtrace.js";
4
+ export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, buildTraceParserRegistry, decodeRawByRegistry, decodePerfRawData, } from "./traceFormat.js";
5
+ export type { ParsedTraceFormat, TraceFormatField, TraceParserRegistry, DecodedRawSample, } from "./traceFormat.js";
4
6
  export type { PerfData, RecordSample } from "./types.js";
5
7
  export type { RecordSampleJsonExportItem } from "./serializer.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { parsePerfData, filterByTgid } from "./parser.js";
2
2
  export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
3
3
  export { toBackTraceStack, toBackTraceStacks } from "./backtrace.js";
4
+ export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, buildTraceParserRegistry, decodeRawByRegistry, decodePerfRawData, } from "./traceFormat.js";
@@ -0,0 +1,70 @@
1
+ import type { PerfData } from "./types.js";
2
+ /**
3
+ * perf trace 事件 format 段中的单字段描述(与 sample/trace_format 一致)
4
+ */
5
+ export interface TraceFormatField {
6
+ /** 完整类型串,如 "unsigned short"、"int" */
7
+ typeName: string;
8
+ /** 字段名,如 common_type、args[6] */
9
+ name: string;
10
+ offset: number;
11
+ size: number;
12
+ signed: boolean;
13
+ }
14
+ export interface ParsedTraceFormat {
15
+ eventName?: string;
16
+ eventId?: number;
17
+ printFmt?: string;
18
+ printArgs?: string[];
19
+ fields: TraceFormatField[];
20
+ }
21
+ export interface TraceParserRegistry {
22
+ byEventId: Map<number, ParsedTraceFormat>;
23
+ commonFormat?: ParsedTraceFormat;
24
+ }
25
+ export interface DecodedRawSample {
26
+ sampleIndex: number;
27
+ commonType?: number;
28
+ commonFields: Record<string, number | bigint>;
29
+ eventName?: string;
30
+ renderedText?: string;
31
+ skipped: boolean;
32
+ }
33
+ /**
34
+ * 解析 sample/trace_format 风格的文本,得到字段列表
35
+ */
36
+ export declare function parseTraceFormat(text: string): ParsedTraceFormat;
37
+ /**
38
+ * 仅解析 format 中 common_* 字段(按 offset/size/signed,小端)
39
+ */
40
+ export declare function parseCommonFieldsFromRaw(raw: Uint8Array, format: ParsedTraceFormat): Record<string, number | bigint>;
41
+ /**
42
+ * 解析 format 中全部字段(含数组),数组字段名会去掉 [],如 args[6] => args: [...]
43
+ */
44
+ export declare function parseAllFieldsFromRaw(raw: Uint8Array, format: ParsedTraceFormat): Record<string, number | bigint | Array<number | bigint>>;
45
+ /**
46
+ * 将 perf 文本里 raw 段的 hex 行拼成连续字节(小端:每行一个数值,宽度由 hex 位数决定,4→2 字节,8→4 字节,16→8 字节)
47
+ */
48
+ export declare function rawHexLinesToBuffer(lines: Array<{
49
+ hex: string;
50
+ }>): Uint8Array;
51
+ /**
52
+ * 从多个 trace_format 文本构建解析器集合(按 event ID 索引)
53
+ */
54
+ export declare function buildTraceParserRegistry(formatTexts: string[]): TraceParserRegistry;
55
+ /**
56
+ * 对一条 raw 数据先解 common_*,再按 common_type 选择解析器,最后按 print fmt 渲染。
57
+ * 找不到解析器时返回 skipped=true,并打印 common_type。
58
+ */
59
+ export declare function decodeRawByRegistry(raw: Uint8Array, registry: TraceParserRegistry): Omit<DecodedRawSample, "sampleIndex">;
60
+ /**
61
+ * 批量处理 perfData 的 raw 数据。仅处理有 raw 的 sample。
62
+ */
63
+ /**
64
+ * 对 PerfData 中每个 record sample 的 raw 段进行 trace 解码,并将解码结果“替换回 raw 段内容”。
65
+ *
66
+ * 规则:
67
+ * - 若找到 common_type 对应解析器:将 print fmt 渲染结果写入 `sample.raw.lines`(一行)。
68
+ * - 若找不到解析器:放弃解析,并将 `common_type` 写入 `sample.raw.lines`(一行)。
69
+ */
70
+ export declare function decodePerfRawData(perfData: PerfData, registry: TraceParserRegistry): PerfData;
@@ -0,0 +1,328 @@
1
+ const FIELD_LINE_RE = /^\s*field:([^;]+);\s*offset:(\d+);\s*size:(\d+);\s*signed:([01]);/;
2
+ function splitTypeAndName(rest) {
3
+ const trimmed = rest.trim();
4
+ const m = trimmed.match(/^(.+?)\s+(\w+(?:\[[^\]]*])?)$/);
5
+ if (!m) {
6
+ return { typeName: trimmed, name: trimmed };
7
+ }
8
+ return { typeName: m[1].trim(), name: m[2] };
9
+ }
10
+ /**
11
+ * 解析 sample/trace_format 风格的文本,得到字段列表
12
+ */
13
+ export function parseTraceFormat(text) {
14
+ const lines = text.split(/\r?\n/);
15
+ let eventName;
16
+ let eventId;
17
+ const fields = [];
18
+ let printFmt;
19
+ let printArgs;
20
+ let inFormat = false;
21
+ for (const line of lines) {
22
+ const t = line.trim();
23
+ if (!t)
24
+ continue;
25
+ if (!inFormat) {
26
+ const nameM = t.match(/^name:\s*(.+)$/);
27
+ if (nameM) {
28
+ eventName = nameM[1].trim();
29
+ continue;
30
+ }
31
+ const idM = t.match(/^ID:\s*(\d+)\s*$/);
32
+ if (idM) {
33
+ eventId = parseInt(idM[1], 10);
34
+ continue;
35
+ }
36
+ if (t.startsWith("format:")) {
37
+ inFormat = true;
38
+ continue;
39
+ }
40
+ continue;
41
+ }
42
+ if (t.startsWith("print fmt:")) {
43
+ const pf = t.match(/^print fmt:\s*"([^"]*)"\s*(?:,\s*(.*))?$/);
44
+ if (pf) {
45
+ printFmt = pf[1];
46
+ const argsPart = pf[2];
47
+ if (argsPart) {
48
+ const exprs = argsPart.match(/REC->[^,]+/g);
49
+ printArgs = exprs ? exprs.map((s) => s.trim()) : [];
50
+ }
51
+ else {
52
+ printArgs = [];
53
+ }
54
+ }
55
+ break;
56
+ }
57
+ const fm = line.match(FIELD_LINE_RE);
58
+ if (!fm)
59
+ continue;
60
+ const { typeName, name } = splitTypeAndName(fm[1]);
61
+ fields.push({
62
+ typeName,
63
+ name,
64
+ offset: parseInt(fm[2], 10),
65
+ size: parseInt(fm[3], 10),
66
+ signed: fm[4] === "1",
67
+ });
68
+ }
69
+ return { eventName, eventId, printFmt, printArgs, fields };
70
+ }
71
+ function normalizeType(t) {
72
+ return t.replace(/\s+/g, " ").trim().toLowerCase();
73
+ }
74
+ /**
75
+ * 从 little-endian 原始缓冲区按字段描述读一个标量
76
+ */
77
+ function readFieldScalar(view, field) {
78
+ const { offset, size, signed, typeName } = field;
79
+ if (offset + size > view.byteLength)
80
+ return undefined;
81
+ const t = normalizeType(typeName);
82
+ if (size === 1) {
83
+ if (signed || t === "signed char") {
84
+ return view.getInt8(offset);
85
+ }
86
+ return view.getUint8(offset);
87
+ }
88
+ if (size === 2) {
89
+ if (signed || t === "short") {
90
+ return view.getInt16(offset, true);
91
+ }
92
+ return view.getUint16(offset, true);
93
+ }
94
+ if (size === 4) {
95
+ if (t.includes("float")) {
96
+ return view.getFloat32(offset, true);
97
+ }
98
+ if (signed || t === "int" || t === "long" /* 32-bit 内核上 */) {
99
+ return view.getInt32(offset, true);
100
+ }
101
+ return view.getUint32(offset, true);
102
+ }
103
+ if (size === 8) {
104
+ if (t.includes("double")) {
105
+ return view.getFloat64(offset, true);
106
+ }
107
+ if (signed || t === "long" || t === "long long" || t === "__s64") {
108
+ return view.getBigInt64(offset, true);
109
+ }
110
+ return view.getBigUint64(offset, true);
111
+ }
112
+ return undefined;
113
+ }
114
+ function parseArrayName(name) {
115
+ const m = name.match(/^(\w+)\[(\d+)\]$/);
116
+ if (!m)
117
+ return undefined;
118
+ return { baseName: m[1], len: parseInt(m[2], 10) };
119
+ }
120
+ function readFieldValue(view, field) {
121
+ const arr = parseArrayName(field.name);
122
+ if (!arr)
123
+ return readFieldScalar(view, field);
124
+ if (arr.len <= 0)
125
+ return [];
126
+ const elemSize = Math.floor(field.size / arr.len);
127
+ if (elemSize <= 0)
128
+ return undefined;
129
+ const values = [];
130
+ for (let i = 0; i < arr.len; i++) {
131
+ const elemField = {
132
+ ...field,
133
+ name: arr.baseName,
134
+ offset: field.offset + i * elemSize,
135
+ size: elemSize,
136
+ };
137
+ const v = readFieldScalar(view, elemField);
138
+ if (v === undefined)
139
+ return undefined;
140
+ values.push(v);
141
+ }
142
+ return values;
143
+ }
144
+ /**
145
+ * 仅解析 format 中 common_* 字段(按 offset/size/signed,小端)
146
+ */
147
+ export function parseCommonFieldsFromRaw(raw, format) {
148
+ const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
149
+ const out = {};
150
+ for (const field of format.fields) {
151
+ if (!field.name.startsWith("common_"))
152
+ continue;
153
+ const v = readFieldScalar(view, field);
154
+ if (v !== undefined) {
155
+ out[field.name] = v;
156
+ }
157
+ }
158
+ return out;
159
+ }
160
+ /**
161
+ * 解析 format 中全部字段(含数组),数组字段名会去掉 [],如 args[6] => args: [...]
162
+ */
163
+ export function parseAllFieldsFromRaw(raw, format) {
164
+ const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
165
+ const out = {};
166
+ for (const field of format.fields) {
167
+ const arr = parseArrayName(field.name);
168
+ const key = arr ? arr.baseName : field.name;
169
+ const v = readFieldValue(view, field);
170
+ if (v !== undefined) {
171
+ out[key] = v;
172
+ }
173
+ }
174
+ return out;
175
+ }
176
+ /**
177
+ * 将 perf 文本里 raw 段的 hex 行拼成连续字节(小端:每行一个数值,宽度由 hex 位数决定,4→2 字节,8→4 字节,16→8 字节)
178
+ */
179
+ export function rawHexLinesToBuffer(lines) {
180
+ const chunks = [];
181
+ for (const { hex } of lines) {
182
+ const s = hex.replace(/^0x/i, "").trim();
183
+ if (!s)
184
+ continue;
185
+ const byteLen = Math.ceil(s.length / 2);
186
+ const width = byteLen <= 2 ? 2 : byteLen <= 4 ? 4 : 8;
187
+ let value = BigInt("0x" + s);
188
+ const mask = (1n << BigInt(width * 8)) - 1n;
189
+ value &= mask;
190
+ for (let i = 0; i < width; i++) {
191
+ chunks.push(Number((value >> BigInt(8 * i)) & 0xffn));
192
+ }
193
+ }
194
+ return Uint8Array.from(chunks);
195
+ }
196
+ /**
197
+ * 从多个 trace_format 文本构建解析器集合(按 event ID 索引)
198
+ */
199
+ export function buildTraceParserRegistry(formatTexts) {
200
+ const byEventId = new Map();
201
+ let commonFormat;
202
+ for (const text of formatTexts) {
203
+ const fmt = parseTraceFormat(text);
204
+ if (fmt.eventId !== undefined) {
205
+ byEventId.set(fmt.eventId, fmt);
206
+ if (!commonFormat && fmt.fields.some((f) => f.name === "common_type")) {
207
+ commonFormat = fmt;
208
+ }
209
+ }
210
+ }
211
+ return { byEventId, commonFormat };
212
+ }
213
+ function formatArgBySpecifier(value, spec) {
214
+ const isBig = typeof value === "bigint";
215
+ const lower = spec.toLowerCase();
216
+ if (lower === "x") {
217
+ return isBig ? value.toString(16) : Math.trunc(value).toString(16);
218
+ }
219
+ if (lower === "u") {
220
+ if (isBig)
221
+ return (value < 0n ? 0n : value).toString(10);
222
+ return Math.max(0, Math.trunc(value)).toString(10);
223
+ }
224
+ return isBig ? value.toString(10) : Math.trunc(value).toString(10);
225
+ }
226
+ function renderPrintFmt(printFmt, printArgs, fieldMap) {
227
+ const values = [];
228
+ for (const expr of printArgs ?? []) {
229
+ const em = expr.match(/^REC->(\w+)(?:\[(\d+)])?$/);
230
+ if (!em) {
231
+ values.push(0);
232
+ continue;
233
+ }
234
+ const name = em[1];
235
+ const idxRaw = em[2];
236
+ const v = fieldMap[name];
237
+ if (idxRaw !== undefined) {
238
+ const idx = parseInt(idxRaw, 10);
239
+ if (Array.isArray(v) && idx >= 0 && idx < v.length) {
240
+ values.push(v[idx]);
241
+ }
242
+ else {
243
+ values.push(0);
244
+ }
245
+ }
246
+ else if (Array.isArray(v)) {
247
+ values.push(v[0] ?? 0);
248
+ }
249
+ else if (v !== undefined) {
250
+ values.push(v);
251
+ }
252
+ else {
253
+ values.push(0);
254
+ }
255
+ }
256
+ let valueIdx = 0;
257
+ return printFmt.replace(/%[0-9]*[lh]*([duxX])/g, (_all, spec) => {
258
+ const v = values[valueIdx++] ?? 0;
259
+ return formatArgBySpecifier(v, spec);
260
+ });
261
+ }
262
+ /**
263
+ * 对一条 raw 数据先解 common_*,再按 common_type 选择解析器,最后按 print fmt 渲染。
264
+ * 找不到解析器时返回 skipped=true,并打印 common_type。
265
+ */
266
+ export function decodeRawByRegistry(raw, registry) {
267
+ const commonType = raw.length >= 2 ? Number(new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint16(0, true)) : undefined;
268
+ const commonFields = registry.commonFormat !== undefined
269
+ ? parseCommonFieldsFromRaw(raw, registry.commonFormat)
270
+ : {};
271
+ const commonTypeFromFields = commonFields.common_type;
272
+ const eventId = typeof commonTypeFromFields === "bigint"
273
+ ? Number(commonTypeFromFields)
274
+ : typeof commonTypeFromFields === "number"
275
+ ? commonTypeFromFields
276
+ : commonType;
277
+ if (eventId === undefined || !registry.byEventId.has(eventId)) {
278
+ console.info(`[hiperf_txt_parser] trace parser not found for common_type=${eventId ?? "unknown"}`);
279
+ return {
280
+ commonType: eventId,
281
+ commonFields,
282
+ skipped: true,
283
+ };
284
+ }
285
+ const fmt = registry.byEventId.get(eventId);
286
+ const allFields = parseAllFieldsFromRaw(raw, fmt);
287
+ const renderedText = fmt.printFmt
288
+ ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields)
289
+ : undefined;
290
+ return {
291
+ commonType: eventId,
292
+ commonFields,
293
+ eventName: fmt.eventName,
294
+ renderedText,
295
+ skipped: false,
296
+ };
297
+ }
298
+ /**
299
+ * 批量处理 perfData 的 raw 数据。仅处理有 raw 的 sample。
300
+ */
301
+ /**
302
+ * 对 PerfData 中每个 record sample 的 raw 段进行 trace 解码,并将解码结果“替换回 raw 段内容”。
303
+ *
304
+ * 规则:
305
+ * - 若找到 common_type 对应解析器:将 print fmt 渲染结果写入 `sample.raw.lines`(一行)。
306
+ * - 若找不到解析器:放弃解析,并将 `common_type` 写入 `sample.raw.lines`(一行)。
307
+ */
308
+ export function decodePerfRawData(perfData, registry) {
309
+ return {
310
+ recordSamples: perfData.recordSamples.map((sample) => {
311
+ if (!sample.raw || sample.raw.lines.length === 0)
312
+ return sample;
313
+ const raw = rawHexLinesToBuffer(sample.raw.lines);
314
+ const decoded = decodeRawByRegistry(raw, registry);
315
+ const replacement = decoded.renderedText ??
316
+ (decoded.commonType !== undefined ? String(decoded.commonType) : "");
317
+ if (!replacement)
318
+ return sample;
319
+ return {
320
+ ...sample,
321
+ raw: {
322
+ ...sample.raw,
323
+ lines: [{ hex: replacement }],
324
+ },
325
+ };
326
+ }),
327
+ };
328
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hiperf_txt_parser",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Parse perf data.txt and output structured TypeScript data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",