hiperf_txt_parser 1.0.12 → 1.0.13

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";
@@ -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
  */
@@ -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,
@@ -410,7 +449,9 @@ export function decodePerfRawData(perfData, registry, options = {}) {
410
449
  if (!sample.raw || sample.raw.lines.length === 0)
411
450
  return sample;
412
451
  const raw = rawHexLinesToBuffer(sample.raw.lines);
413
- const decoded = decodeRawByRegistry(raw, registry);
452
+ const decoded = decodeRawByRegistry(raw, registry, {
453
+ tracePrintMode: options.tracePrintMode,
454
+ });
414
455
  const base = decoded.renderedText ??
415
456
  (decoded.commonType !== undefined ? String(decoded.commonType) : "");
416
457
  if (!base)
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.13",
4
4
  "description": "Parse perf data.txt and output structured TypeScript data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",