hiperf_txt_parser 1.0.12 → 1.0.14

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/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { parsePerfData, filterByTgid } from "./parser.js";
2
2
  export { formatPerfDataToText, formatPerfDataToJson } from "./serializer.js";
3
3
  export { toBackTraceStack, toBackTraceStacks } from "./backtrace.js";
4
4
  export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, bufferToRawHexLines, buildTraceParserRegistry, decodeRawByRegistry, decodePerfRawData, } from "./traceFormat.js";
5
- export type { ParsedTraceFormat, TraceFormatField, TraceParserRegistry, DecodedRawSample, DecodePerfRawDataOptions, Endian, } from "./traceFormat.js";
5
+ export type { ParsedTraceFormat, TraceFormatField, TraceParserRegistry, DecodedRawSample, DecodePerfRawDataOptions, DecodeRawByRegistryOptions, TracePrintMode, Endian, } from "./traceFormat.js";
6
6
  export type { PerfData, RecordSample } from "./types.js";
7
7
  export type { RecordSampleJsonExportItem } from "./serializer.js";
@@ -3,13 +3,15 @@ import type { PerfData } from "./types.js";
3
3
  * 将 PerfData 导出为与 sample/perf_data.txt 相同格式的文本(含缩进与每行前缀)
4
4
  */
5
5
  export declare function formatPerfDataToText(data: PerfData): string;
6
- /** 导出用 JSON 的单项:issuce 固定为 "unknow",call_chain 为 frames 逐行拼接 */
7
- export interface RecordSampleJsonExportItem {
6
+ /**
7
+ * 导出用 JSON 单项:固定含 `issuce`、`call_chain`;若存在 `traceFieldDict`,其键值对与二者**同级**平铺。
8
+ */
9
+ export type RecordSampleJsonExportItem = {
8
10
  issuce: "unknow";
9
11
  call_chain: string;
10
- }
12
+ } & Record<string, string>;
11
13
  /**
12
- * 将 PerfData 转为导出用 JSON 结构:顶层为数组,每项为 { issuce: "unknow", call_chain: "" },
13
- * call_chain 由对应 record sample 的 callchainFrames.frames 逐行拼接得到,无则为空字符串。
14
+ * 将 PerfData 转为导出用 JSON 结构:顶层为数组。
15
+ * call_chain callchainFrames.frames 拼接;有 traceFieldDict 时各字段平铺在对象顶层(与 call_chain 同级)。
14
16
  */
15
17
  export declare function formatPerfDataToJson(data: PerfData): RecordSampleJsonExportItem[];
@@ -19,7 +19,18 @@ function serializeOneSample(sample) {
19
19
  lines.push(` ${addr}`);
20
20
  }
21
21
  }
22
- if (sample.raw) {
22
+ if (sample.traceFieldDict && Object.keys(sample.traceFieldDict).length > 0 && sample.raw) {
23
+ lines.push(` raw size=${sample.raw.size}`);
24
+ for (const [k, v] of Object.entries(sample.traceFieldDict)) {
25
+ lines.push(` ${k}: ${v}`);
26
+ }
27
+ const n = Object.keys(sample.traceFieldDict).length;
28
+ for (let i = n; i < sample.raw.lines.length; i++) {
29
+ const { hex, short } = sample.raw.lines[i];
30
+ lines.push(short ? ` ${hex} (${short})` : ` ${hex}`);
31
+ }
32
+ }
33
+ else if (sample.raw) {
23
34
  lines.push(` raw size=${sample.raw.size}`);
24
35
  for (const { hex, short } of sample.raw.lines) {
25
36
  lines.push(short ? ` ${hex} (${short})` : ` ${hex}`);
@@ -47,12 +58,20 @@ export function formatPerfDataToText(data) {
47
58
  return data.recordSamples.map(serializeOneSample).join("\n\n");
48
59
  }
49
60
  /**
50
- * 将 PerfData 转为导出用 JSON 结构:顶层为数组,每项为 { issuce: "unknow", call_chain: "" },
51
- * call_chain 由对应 record sample 的 callchainFrames.frames 逐行拼接得到,无则为空字符串。
61
+ * 将 PerfData 转为导出用 JSON 结构:顶层为数组。
62
+ * call_chain callchainFrames.frames 拼接;有 traceFieldDict 时各字段平铺在对象顶层(与 call_chain 同级)。
52
63
  */
53
64
  export function formatPerfDataToJson(data) {
54
- return data.recordSamples.map((s) => ({
55
- issuce: "unknow",
56
- call_chain: s.callchainFrames ? s.callchainFrames.frames.join("\n") : "",
57
- }));
65
+ return data.recordSamples.map((s) => {
66
+ const item = {
67
+ issuce: "unknow",
68
+ call_chain: s.callchainFrames ? s.callchainFrames.frames.join("\n") : "",
69
+ };
70
+ if (s.traceFieldDict) {
71
+ for (const [k, v] of Object.entries(s.traceFieldDict)) {
72
+ item[k] = v;
73
+ }
74
+ }
75
+ return item;
76
+ });
58
77
  }
@@ -28,11 +28,19 @@ export interface DecodedRawSample {
28
28
  commonFields: Record<string, number | bigint>;
29
29
  eventName?: string;
30
30
  renderedText?: string;
31
+ /** 当 tracePrintMode 为 fieldDict 时:各 print 参数按对应格式符格式化后的键值对 */
32
+ renderedFieldDict?: Record<string, string>;
31
33
  skipped: boolean;
32
34
  }
35
+ /** printf:整条 print fmt 渲染为一句;fieldDict:按格式符逐项生成字段名 → 格式化字符串 */
36
+ export type TracePrintMode = "printf" | "fieldDict";
37
+ export interface DecodeRawByRegistryOptions {
38
+ tracePrintMode?: TracePrintMode;
39
+ }
33
40
  export interface DecodePerfRawDataOptions {
34
41
  /** 是否在替换后的 raw 内容中保留 common_* 信息 */
35
42
  keepCommonFields?: boolean;
43
+ tracePrintMode?: TracePrintMode;
36
44
  }
37
45
  export type Endian = "le" | "be";
38
46
  /**
@@ -71,7 +79,7 @@ export declare function buildTraceParserRegistry(formatTexts: string[]): TracePa
71
79
  * 对一条 raw 数据先解 common_*,再按 common_type 选择解析器,最后按 print fmt 渲染。
72
80
  * 找不到解析器时返回 skipped=true,并打印 common_type。
73
81
  */
74
- export declare function decodeRawByRegistry(raw: Uint8Array, registry: TraceParserRegistry): Omit<DecodedRawSample, "sampleIndex">;
82
+ export declare function decodeRawByRegistry(raw: Uint8Array, registry: TraceParserRegistry, options?: DecodeRawByRegistryOptions): Omit<DecodedRawSample, "sampleIndex">;
75
83
  /**
76
84
  * 批量处理 perfData 的 raw 数据。仅处理有 raw 的 sample。
77
85
  */
@@ -80,6 +88,7 @@ export declare function decodeRawByRegistry(raw: Uint8Array, registry: TracePars
80
88
  *
81
89
  * 规则:
82
90
  * - 若找到 common_type 对应解析器:将 print fmt 渲染结果写入 `sample.raw.lines`(一行)。
91
+ * - fieldDict:写入 `traceFieldDict`,`raw.lines` 为每行一条 `key: value`;`raw.size` 为行数(含可选 common 行)。
83
92
  * - 若找不到解析器:放弃解析,并将 `common_type` 写入 `sample.raw.lines`(一行)。
84
93
  */
85
94
  export declare function decodePerfRawData(perfData: PerfData, registry: TraceParserRegistry, options?: DecodePerfRawDataOptions): PerfData;
@@ -274,6 +274,7 @@ export function buildTraceParserRegistry(formatTexts) {
274
274
  }
275
275
  return { byEventId, commonFormat };
276
276
  }
277
+ const PRINT_FMT_SPEC_RE = /%[0-9]*[lh]*([duxXsS])/g;
277
278
  function formatArgBySpecifier(value, spec) {
278
279
  if (spec.toLowerCase() === "s") {
279
280
  return typeof value === "string" ? value : String(value);
@@ -290,79 +291,107 @@ function formatArgBySpecifier(value, spec) {
290
291
  }
291
292
  return isBig ? value.toString(10) : Math.trunc(value).toString(10);
292
293
  }
293
- function renderPrintFmt(printFmt, printArgs, fieldMap) {
294
- const values = [];
295
- function normalizeExpr(exprRaw) {
296
- let expr = exprRaw.trim();
297
- // 去掉外层括号(可能不止一层,也可能只有一侧被 match 捕获到)
298
- while (expr.startsWith("("))
299
- expr = expr.slice(1).trim();
300
- while (expr.endsWith(")"))
301
- expr = expr.slice(0, -1).trim();
302
- expr = expr.replace(/\s+/g, "");
303
- return expr;
294
+ function normalizePrintExpr(exprRaw) {
295
+ let expr = exprRaw.trim();
296
+ while (expr.startsWith("("))
297
+ expr = expr.slice(1).trim();
298
+ while (expr.endsWith(")"))
299
+ expr = expr.slice(0, -1).trim();
300
+ expr = expr.replace(/\s+/g, "");
301
+ return expr;
302
+ }
303
+ function evalPrintArgExpr(exprRaw, fieldMap) {
304
+ const expr = normalizePrintExpr(exprRaw);
305
+ let m = expr.match(/^REC->(\w+)&0xffff$/);
306
+ if (m) {
307
+ const v = fieldMap[m[1]];
308
+ const n = typeof v === "number" ? v : 0;
309
+ return n & 0xffff;
304
310
  }
305
- function evalExpr(exprRaw) {
306
- // 允许表达式带括号与空格
307
- const expr = normalizeExpr(exprRaw);
308
- // (REC->__data_loc_reason&0xffff) / (REC->__data_loc_reason>>16)
309
- let m = expr.match(/^REC->(\w+)&0xffff$/);
310
- if (m) {
311
- const v = fieldMap[m[1]];
312
- const n = typeof v === "number" ? v : 0;
313
- return n & 0xffff;
311
+ m = expr.match(/^REC->(\w+)>>16$/);
312
+ if (m) {
313
+ const v = fieldMap[m[1]];
314
+ const n = typeof v === "number" ? v : 0;
315
+ return (n >>> 16) & 0xffff;
316
+ }
317
+ m = expr.match(/^REC->(\w+)(?:\[(\d+)])?$/);
318
+ if (!m)
319
+ return 0;
320
+ const name = m[1];
321
+ const idxRaw = m[2];
322
+ const v = fieldMap[name];
323
+ if (idxRaw !== undefined) {
324
+ const idx = parseInt(idxRaw, 10);
325
+ if (Array.isArray(v) && idx >= 0 && idx < v.length) {
326
+ return v[idx];
314
327
  }
315
- m = expr.match(/^REC->(\w+)>>16$/);
328
+ return 0;
329
+ }
330
+ if (Array.isArray(v))
331
+ return v[0] ?? 0;
332
+ return v ?? 0;
333
+ }
334
+ function buildPrintArgValues(printArgs, fieldMap) {
335
+ const normalizedArgs = (printArgs ?? []).map(normalizePrintExpr);
336
+ const values = (printArgs ?? []).map((e) => evalPrintArgExpr(e, fieldMap));
337
+ return { normalizedArgs, values };
338
+ }
339
+ /** 与 renderPrintFmt 中 replace 顺序一致:依次取出每个格式符的类型字母 */
340
+ function extractPrintfSpecChars(printFmt) {
341
+ const specs = [];
342
+ printFmt.replace(PRINT_FMT_SPEC_RE, (_all, spec) => {
343
+ specs.push(spec);
344
+ return _all;
345
+ });
346
+ return specs;
347
+ }
348
+ function formatOnePrintArg(idx, spec, fieldMap, normalizedArgs, values) {
349
+ const v = values[idx] ?? 0;
350
+ const argExpr = normalizedArgs[idx] ?? "";
351
+ if (spec.toLowerCase() === "s") {
352
+ const m = argExpr.match(/^REC->__data_loc_(\w+)(?:&0xffff)?(?:>>16)?$/);
316
353
  if (m) {
317
- const v = fieldMap[m[1]];
318
- const n = typeof v === "number" ? v : 0;
319
- return (n >>> 16) & 0xffff;
320
- }
321
- // REC->field 或 REC->field[idx]
322
- m = expr.match(/^REC->(\w+)(?:\[(\d+)])?$/);
323
- if (!m)
324
- return 0;
325
- const name = m[1];
326
- const idxRaw = m[2];
327
- const v = fieldMap[name];
328
- if (idxRaw !== undefined) {
329
- const idx = parseInt(idxRaw, 10);
330
- if (Array.isArray(v) && idx >= 0 && idx < v.length) {
331
- return v[idx];
354
+ const s = fieldMap[m[1]];
355
+ if (typeof s === "string") {
356
+ return s;
332
357
  }
333
- return 0;
334
358
  }
335
- if (Array.isArray(v))
336
- return v[0] ?? 0;
337
- return v ?? 0;
338
359
  }
339
- const normalizedArgs = (printArgs ?? []).map(normalizeExpr);
340
- for (const expr of printArgs ?? []) {
341
- values.push(evalExpr(expr));
360
+ return formatArgBySpecifier(v, spec);
361
+ }
362
+ function fieldKeyFromNormalizedArg(normalizedArg, index) {
363
+ let k = normalizedArg;
364
+ if (k.startsWith("REC->"))
365
+ k = k.slice(5);
366
+ return k.length > 0 ? k : `arg${index}`;
367
+ }
368
+ function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
369
+ const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
370
+ const specs = extractPrintfSpecChars(printFmt);
371
+ const out = {};
372
+ const seen = new Map();
373
+ for (let i = 0; i < specs.length; i++) {
374
+ const base = fieldKeyFromNormalizedArg(normalizedArgs[i] ?? "", i);
375
+ const n = (seen.get(base) ?? 0) + 1;
376
+ seen.set(base, n);
377
+ const key = n === 1 ? base : `${base}__${n}`;
378
+ out[key] = formatOnePrintArg(i, specs[i], fieldMap, normalizedArgs, values);
342
379
  }
380
+ return out;
381
+ }
382
+ function renderPrintFmt(printFmt, printArgs, fieldMap) {
383
+ const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
343
384
  let valueIdx = 0;
344
- return printFmt.replace(/%[0-9]*[lh]*([duxXsS])/g, (_all, spec) => {
385
+ return printFmt.replace(PRINT_FMT_SPEC_RE, (_all, spec) => {
345
386
  const idx = valueIdx++;
346
- const v = values[idx] ?? 0;
347
- const argExpr = normalizedArgs[idx] ?? "";
348
- // %s + REC->__data_loc_xxx 时,优先打印已解析出来的 xxx 字符串
349
- if (spec.toLowerCase() === "s") {
350
- const m = argExpr.match(/^REC->__data_loc_(\w+)(?:&0xffff)?(?:>>16)?$/);
351
- if (m) {
352
- const s = fieldMap[m[1]];
353
- if (typeof s === "string") {
354
- return s;
355
- }
356
- }
357
- }
358
- return formatArgBySpecifier(v, spec);
387
+ return formatOnePrintArg(idx, spec, fieldMap, normalizedArgs, values);
359
388
  });
360
389
  }
361
390
  /**
362
391
  * 对一条 raw 数据先解 common_*,再按 common_type 选择解析器,最后按 print fmt 渲染。
363
392
  * 找不到解析器时返回 skipped=true,并打印 common_type。
364
393
  */
365
- export function decodeRawByRegistry(raw, registry) {
394
+ export function decodeRawByRegistry(raw, registry, options = {}) {
366
395
  const commonType = raw.length >= 2 ? Number(new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint16(0, true)) : undefined;
367
396
  const commonFields = registry.commonFormat !== undefined
368
397
  ? parseCommonFieldsFromRaw(raw, registry.commonFormat)
@@ -383,9 +412,19 @@ export function decodeRawByRegistry(raw, registry) {
383
412
  }
384
413
  const fmt = registry.byEventId.get(eventId);
385
414
  const allFields = parseAllFieldsFromRaw(raw, fmt);
386
- const renderedText = fmt.printFmt
387
- ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields)
388
- : undefined;
415
+ const mode = options.tracePrintMode ?? "printf";
416
+ if (fmt.printFmt && mode === "fieldDict") {
417
+ const renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields);
418
+ return {
419
+ commonType: eventId,
420
+ commonFields,
421
+ eventName: fmt.eventName,
422
+ renderedFieldDict,
423
+ renderedText: JSON.stringify(renderedFieldDict),
424
+ skipped: false,
425
+ };
426
+ }
427
+ const renderedText = fmt.printFmt ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields) : undefined;
389
428
  return {
390
429
  commonType: eventId,
391
430
  commonFields,
@@ -402,6 +441,7 @@ export function decodeRawByRegistry(raw, registry) {
402
441
  *
403
442
  * 规则:
404
443
  * - 若找到 common_type 对应解析器:将 print fmt 渲染结果写入 `sample.raw.lines`(一行)。
444
+ * - fieldDict:写入 `traceFieldDict`,`raw.lines` 为每行一条 `key: value`;`raw.size` 为行数(含可选 common 行)。
405
445
  * - 若找不到解析器:放弃解析,并将 `common_type` 写入 `sample.raw.lines`(一行)。
406
446
  */
407
447
  export function decodePerfRawData(perfData, registry, options = {}) {
@@ -410,7 +450,9 @@ export function decodePerfRawData(perfData, registry, options = {}) {
410
450
  if (!sample.raw || sample.raw.lines.length === 0)
411
451
  return sample;
412
452
  const raw = rawHexLinesToBuffer(sample.raw.lines);
413
- const decoded = decodeRawByRegistry(raw, registry);
453
+ const decoded = decodeRawByRegistry(raw, registry, {
454
+ tracePrintMode: options.tracePrintMode,
455
+ });
414
456
  const base = decoded.renderedText ??
415
457
  (decoded.commonType !== undefined ? String(decoded.commonType) : "");
416
458
  if (!base)
@@ -420,8 +462,30 @@ export function decodePerfRawData(perfData, registry, options = {}) {
420
462
  .map(([k, v]) => `${k}:${typeof v === "bigint" ? v.toString(10) : v}`)
421
463
  .join(" ")
422
464
  : "";
465
+ const dict = decoded.renderedFieldDict;
466
+ const useFieldDict = options.tracePrintMode === "fieldDict" &&
467
+ dict !== undefined &&
468
+ Object.keys(dict).length > 0;
469
+ if (useFieldDict) {
470
+ const kvLines = Object.entries(dict).map(([k, v]) => ({
471
+ hex: `${k}: ${v}`,
472
+ }));
473
+ const lines = options.keepCommonFields && commonLine.length > 0
474
+ ? [...kvLines, { hex: commonLine }]
475
+ : kvLines;
476
+ return {
477
+ ...sample,
478
+ traceFieldDict: dict,
479
+ raw: {
480
+ ...sample.raw,
481
+ size: lines.length,
482
+ lines,
483
+ },
484
+ };
485
+ }
423
486
  return {
424
487
  ...sample,
488
+ traceFieldDict: undefined,
425
489
  raw: {
426
490
  ...sample.raw,
427
491
  lines: options.keepCommonFields && commonLine.length > 0
package/dist/types.d.ts CHANGED
@@ -39,6 +39,8 @@ export interface RecordSample {
39
39
  period: number;
40
40
  callchain?: CallchainEntry;
41
41
  raw?: RawEntry;
42
+ /** tracePrintMode 为 fieldDict 且解码成功时:print 各参数键值(用于导出 txt/json) */
43
+ traceFieldDict?: Record<string, string>;
42
44
  server?: ServerEntry;
43
45
  callchainFrames?: CallchainFramesEntry;
44
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hiperf_txt_parser",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Parse perf data.txt and output structured TypeScript data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",