hm-pt-core 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/README.md CHANGED
@@ -19,7 +19,7 @@ npm install hm-pt-core
19
19
 
20
20
  | hm-pt-core | hiperf_txt_parser | hmtrace-parser |
21
21
  |------------|-------------------|----------------|
22
- | 1.0.x | 2.0.x | 0.8.x |
22
+ | 1.0.x(当前 **1.0.3**) | **2.1.x** | **0.8.x** |
23
23
 
24
24
  ## 开发
25
25
 
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@ export type { Endian, BuildTraceParserRegistryOptions } from "./trace/parse.js";
9
9
  export { TraceFieldDecodeError } from "./trace/parse.js";
10
10
  export type { PrintfSpec } from "./trace/printf.js";
11
11
  export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, bufferToRawHexLines, buildTraceParserRegistry, } from "./trace/parse.js";
12
- export { formatPrintfValue, tokenizePrintfFormat } from "./trace/printf.js";
12
+ export { formatPrintfValue, tokenizePrintfFormat, renderPrintFmt, renderPrintFmtAsFieldDict } from "./trace/printf.js";
13
13
  export { decodeRawByRegistry } from "./trace/decode_raw.js";
package/dist/index.js CHANGED
@@ -4,5 +4,5 @@
4
4
  export { isPerfDataLike } from "./perf/is_perf_data_like.js";
5
5
  export { TraceFieldDecodeError } from "./trace/parse.js";
6
6
  export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, bufferToRawHexLines, buildTraceParserRegistry, } from "./trace/parse.js";
7
- export { formatPrintfValue, tokenizePrintfFormat } from "./trace/printf.js";
7
+ export { formatPrintfValue, tokenizePrintfFormat, renderPrintFmt, renderPrintFmtAsFieldDict } from "./trace/printf.js";
8
8
  export { decodeRawByRegistry } from "./trace/decode_raw.js";
@@ -21,9 +21,10 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
21
21
  };
22
22
  }
23
23
  const allFields = parseAllFieldsFromRaw(raw, fmt);
24
+ const renderContext = { eventName: fmt.eventName, eventId };
24
25
  const mode = options.tracePrintMode ?? "printf";
25
26
  if (fmt.printFmt && mode === "fieldDict") {
26
- let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields);
27
+ let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields, renderContext);
27
28
  const transform = registry.transformFieldDictByEventId?.get(eventId);
28
29
  if (transform) {
29
30
  renderedFieldDict = transform({
@@ -42,7 +43,9 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
42
43
  skipped: false,
43
44
  };
44
45
  }
45
- const renderedText = fmt.printFmt ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields) : undefined;
46
+ const renderedText = fmt.printFmt
47
+ ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields, renderContext)
48
+ : undefined;
46
49
  return {
47
50
  commonType: eventId,
48
51
  commonFields,
@@ -138,6 +138,24 @@ function scanBalancedPrintArgs(argsPart) {
138
138
  }
139
139
  return args;
140
140
  }
141
+ /**
142
+ * 从 print fmt 逗号后参数段提取表达式。
143
+ * 内核 trace 常在首个 __data_loc 参数外套 ((char*)((void *)...)),括号平衡切分只得 1 段;
144
+ * 此时回退为按 `REC->` 切分(与 hiperf 1.x 行为一致)。
145
+ */
146
+ function parsePrintArgExpressions(argsPart) {
147
+ const balanced = scanBalancedPrintArgs(argsPart);
148
+ const recExprs = argsPart.match(/REC->[^,]+/g)?.map((s) => s.trim().replace(/\)+$/, "")) ?? [];
149
+ if (recExprs.length > balanced.length) {
150
+ return recExprs;
151
+ }
152
+ if (recExprs.length === 1 &&
153
+ balanced.length === 1 &&
154
+ balanced[0] !== recExprs[0]) {
155
+ return recExprs;
156
+ }
157
+ return balanced;
158
+ }
141
159
  function parsePrintFmtLine(line) {
142
160
  const prefix = line.match(/^print\s+fmt\s*:\s*/i);
143
161
  if (!prefix)
@@ -151,7 +169,7 @@ function parsePrintFmtLine(line) {
151
169
  const argsMatch = rest.match(/^,\s*(.*)$/);
152
170
  if (!argsMatch)
153
171
  return undefined;
154
- return { printFmt: lit.value, printArgs: scanBalancedPrintArgs(argsMatch[1]) };
172
+ return { printFmt: lit.value, printArgs: parsePrintArgExpressions(argsMatch[1]) };
155
173
  }
156
174
  function splitTypeAndName(rest) {
157
175
  const trimmed = rest.trim();
@@ -222,13 +240,89 @@ function isCharType(typeName) {
222
240
  const t = normalizeType(typeName);
223
241
  return t === "char" || t === "signed char" || t === "unsigned char";
224
242
  }
243
+ function isDataLocType(typeName) {
244
+ return normalizeType(typeName).includes("__data_loc");
245
+ }
246
+ function parseDataLocElementType(typeName, signed) {
247
+ const t = normalizeType(typeName);
248
+ const m = t.match(/^__data_loc\s+(.+)\[\]$/);
249
+ if (!m)
250
+ return undefined;
251
+ const elemTypeName = m[1].trim();
252
+ const elemSize = inferScalarSize(elemTypeName, signed);
253
+ if (elemSize === undefined)
254
+ return undefined;
255
+ return { elemTypeName, elemSize, signed: isSignedElementType(elemTypeName, signed) };
256
+ }
257
+ function isSignedElementType(elemTypeName, fieldSigned) {
258
+ const t = normalizeType(elemTypeName);
259
+ if (t.includes("unsigned"))
260
+ return false;
261
+ if (t === "char" && !fieldSigned)
262
+ return false;
263
+ return fieldSigned || t === "signed char" || t === "short" || t === "int" || t === "long" || t === "long long" || t === "__s64";
264
+ }
265
+ function inferScalarSize(typeName, signed) {
266
+ const t = normalizeType(typeName);
267
+ if (t.includes("double"))
268
+ return 8;
269
+ if (t.includes("long long") || t === "__s64")
270
+ return 8;
271
+ if (t.includes("long"))
272
+ return 8;
273
+ if (t.includes("short"))
274
+ return 2;
275
+ if (t.includes("char") || t.includes("int") || t.includes("float"))
276
+ return 4;
277
+ if (t.includes("unsigned") || signed)
278
+ return 4;
279
+ return undefined;
280
+ }
281
+ function assertFieldInBounds(fieldName, offset, size, rawLength) {
282
+ if (offset < 0 || offset + size > rawLength) {
283
+ throw new TraceFieldDecodeError(`Field "${fieldName}" out of bounds: offset=${offset}, size=${size}, raw.length=${rawLength}`, fieldName, offset, size, rawLength);
284
+ }
285
+ }
286
+ function readLocatorField(view, field) {
287
+ assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
288
+ const locField = {
289
+ ...field,
290
+ typeName: "unsigned int",
291
+ signed: false,
292
+ size: 4,
293
+ };
294
+ const loc = readFieldScalarUnchecked(view, locField);
295
+ if (typeof loc !== "number") {
296
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc locator is invalid`, field.name, field.offset, field.size, view.byteLength);
297
+ }
298
+ return loc;
299
+ }
300
+ function readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemTypeName, elemSize, elemSigned) {
301
+ if (byteLen === 0)
302
+ return [];
303
+ if (byteLen % elemSize !== 0) {
304
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc length ${byteLen} is not divisible by element size ${elemSize}`, field.name, dataOffset, byteLen, raw.length);
305
+ }
306
+ assertFieldInBounds(field.name, dataOffset, byteLen, raw.length);
307
+ const count = byteLen / elemSize;
308
+ const values = [];
309
+ for (let i = 0; i < count; i++) {
310
+ const elemField = {
311
+ typeName: elemTypeName,
312
+ name: field.name,
313
+ offset: dataOffset + i * elemSize,
314
+ size: elemSize,
315
+ signed: elemSigned,
316
+ };
317
+ values.push(readFieldScalarUnchecked(view, elemField));
318
+ }
319
+ return values;
320
+ }
225
321
  /**
226
322
  * 从 little-endian 原始缓冲区按字段描述读一个标量
227
323
  */
228
- function readFieldScalar(view, field) {
324
+ function readFieldScalarUnchecked(view, field) {
229
325
  const { offset, size, signed, typeName } = field;
230
- if (offset + size > view.byteLength)
231
- return undefined;
232
326
  const t = normalizeType(typeName);
233
327
  if (size === 1) {
234
328
  if (signed || t === "signed char") {
@@ -260,7 +354,14 @@ function readFieldScalar(view, field) {
260
354
  }
261
355
  return view.getBigUint64(offset, true);
262
356
  }
263
- return undefined;
357
+ throw new TraceFieldDecodeError(`Field "${field.name}" has unsupported scalar size ${size}`, field.name, offset, size, view.byteLength);
358
+ }
359
+ /**
360
+ * 从 little-endian 原始缓冲区按字段描述读一个标量;越界时抛出 TraceFieldDecodeError。
361
+ */
362
+ function readFieldScalar(view, field) {
363
+ assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
364
+ return readFieldScalarUnchecked(view, field);
264
365
  }
265
366
  function parseArrayName(name) {
266
367
  const m = name.match(/^(\w+)\[(\d+)\]$/);
@@ -274,9 +375,10 @@ function readFieldValue(view, field) {
274
375
  return readFieldScalar(view, field);
275
376
  if (arr.len <= 0)
276
377
  return [];
277
- const elemSize = Math.floor(field.size / arr.len);
278
- if (elemSize <= 0)
279
- return undefined;
378
+ if (field.size % arr.len !== 0) {
379
+ throw new TraceFieldDecodeError(`Field "${field.name}" size ${field.size} is not divisible by array length ${arr.len}`, field.name, field.offset, field.size, view.byteLength);
380
+ }
381
+ const elemSize = field.size / arr.len;
280
382
  const values = [];
281
383
  for (let i = 0; i < arr.len; i++) {
282
384
  const elemField = {
@@ -285,10 +387,7 @@ function readFieldValue(view, field) {
285
387
  offset: field.offset + i * elemSize,
286
388
  size: elemSize,
287
389
  };
288
- const v = readFieldScalar(view, elemField);
289
- if (v === undefined)
290
- return undefined;
291
- values.push(v);
390
+ values.push(readFieldScalar(view, elemField));
292
391
  }
293
392
  return values;
294
393
  }
@@ -301,10 +400,9 @@ export function parseCommonFieldsFromRaw(raw, format) {
301
400
  for (const field of format.fields) {
302
401
  if (!field.name.startsWith("common_"))
303
402
  continue;
304
- const v = readFieldScalar(view, field);
305
- if (v !== undefined) {
306
- out[field.name] = v;
307
- }
403
+ if (field.offset + field.size > view.byteLength)
404
+ continue;
405
+ out[field.name] = readFieldScalar(view, field);
308
406
  }
309
407
  return out;
310
408
  }
@@ -318,26 +416,18 @@ export function parseAllFieldsFromRaw(raw, format) {
318
416
  const arr = parseArrayName(field.name);
319
417
  const key = arr ? arr.baseName : field.name;
320
418
  const t = normalizeType(field.typeName);
321
- // __data_loc char[] reason: 实际存一个 u32,低 16 位为 offset,高 16 位为 length
322
- if (t.includes("__data_loc") && t.includes("char")) {
323
- const locField = {
324
- ...field,
325
- typeName: "unsigned int",
326
- signed: false,
327
- size: 4,
328
- };
329
- const loc = readFieldScalar(view, locField);
330
- if (typeof loc === "number") {
331
- const locKey = `__data_loc_${field.name}`;
332
- out[locKey] = loc;
333
- const offset = loc & 0xffff;
334
- const len = (loc >>> 16) & 0xffff;
335
- if (len > 0 && (offset < 0 || offset + len > raw.length)) {
336
- throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc out of bounds: offset=${offset}, len=${len}, raw.length=${raw.length}`, field.name, offset, len, raw.length);
337
- }
338
- if (offset >= 0 && len > 0 && offset + len <= raw.length) {
339
- const bytes = raw.subarray(offset, offset + len);
340
- // 去掉末尾 \0
419
+ if (isDataLocType(field.typeName)) {
420
+ const loc = readLocatorField(view, field);
421
+ const locKey = `__data_loc_${field.name}`;
422
+ out[locKey] = loc;
423
+ const dataOffset = loc & 0xffff;
424
+ const byteLen = (loc >>> 16) & 0xffff;
425
+ if (byteLen > 0 && (dataOffset < 0 || dataOffset + byteLen > raw.length)) {
426
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc out of bounds: offset=${dataOffset}, len=${byteLen}, raw.length=${raw.length}`, field.name, dataOffset, byteLen, raw.length);
427
+ }
428
+ if (t.includes("char")) {
429
+ if (byteLen > 0) {
430
+ const bytes = raw.subarray(dataOffset, dataOffset + byteLen);
341
431
  const nul = bytes.indexOf(0);
342
432
  const slice = nul >= 0 ? bytes.subarray(0, nul) : bytes;
343
433
  out[field.name] = UTF8_DECODER.decode(slice);
@@ -345,17 +435,20 @@ export function parseAllFieldsFromRaw(raw, format) {
345
435
  else {
346
436
  out[field.name] = "";
347
437
  }
438
+ continue;
439
+ }
440
+ const elemInfo = parseDataLocElementType(field.typeName, field.signed);
441
+ if (!elemInfo) {
442
+ throw new TraceFieldDecodeError(`Field "${field.name}" uses unsupported __data_loc element type "${field.typeName}"`, field.name, field.offset, field.size, raw.length);
348
443
  }
444
+ out[key] = readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemInfo.elemTypeName, elemInfo.elemSize, elemInfo.signed);
349
445
  continue;
350
446
  }
351
447
  if (arr && isCharType(field.typeName)) {
352
448
  out[key] = readCharArrayAsString(raw, field.offset, field.size, field.name);
353
449
  continue;
354
450
  }
355
- const v = readFieldValue(view, field);
356
- if (v !== undefined) {
357
- out[key] = v;
358
- }
451
+ out[key] = readFieldValue(view, field);
359
452
  }
360
453
  return out;
361
454
  }
@@ -450,6 +543,9 @@ export function buildTraceParserRegistry(formatTexts, options = {}) {
450
543
  if (typeof src !== "string" && src.transformFieldDict) {
451
544
  transformFieldDictByEventId.set(fmt.eventId, src.transformFieldDict);
452
545
  }
546
+ else {
547
+ transformFieldDictByEventId.delete(fmt.eventId);
548
+ }
453
549
  }
454
550
  });
455
551
  return {
@@ -29,6 +29,10 @@ export declare function tokenizePrintfFormat(printFmt: string): Array<{
29
29
  kind: "spec";
30
30
  spec: PrintfSpec;
31
31
  }>;
32
+ export interface PrintFmtRenderContext {
33
+ eventName?: string;
34
+ eventId?: number;
35
+ }
32
36
  /** 测试专用:重置 printf 解析缓存 */
33
37
  export declare function resetPrintfCachesForTest(): void;
34
38
  /** 测试专用:读取 printf 解析缓存条目数 */
@@ -36,6 +40,6 @@ export declare function getPrintfCacheSizesForTest(): {
36
40
  tokens: number;
37
41
  specs: number;
38
42
  };
39
- export declare function renderPrintFmtAsFieldDict(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap): Record<string, string>;
40
- export declare function renderPrintFmt(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap): string;
43
+ export declare function renderPrintFmtAsFieldDict(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): Record<string, string>;
44
+ export declare function renderPrintFmt(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): string;
41
45
  export {};
@@ -158,11 +158,21 @@ function formatPrintfUnsignedRadix(unsigned, spec, radix, upper) {
158
158
  : unsigned.toString(radix);
159
159
  body = body.padStart(Math.max(prec, body.length), "0");
160
160
  }
161
- if (hasPrintfFlag(spec.flags, "#") && unsigned !== 0n) {
162
- if (radix === 16)
161
+ if (hasPrintfFlag(spec.flags, "#")) {
162
+ if (radix === 16 && unsigned !== 0n) {
163
163
  prefix = upper ? "0X" : "0x";
164
- else if (radix === 8 && body !== "0" && !body.startsWith("0"))
165
- prefix = "0";
164
+ }
165
+ else if (radix === 8) {
166
+ if (unsigned === 0n && (prec === 0 || body === "")) {
167
+ body = "0";
168
+ }
169
+ else if (body === "") {
170
+ body = "0";
171
+ }
172
+ else if (!body.startsWith("0")) {
173
+ body = "0" + body;
174
+ }
175
+ }
166
176
  }
167
177
  const zeroPad = hasPrintfFlag(spec.flags, "0") &&
168
178
  !hasPrintfFlag(spec.flags, "-") &&
@@ -333,6 +343,16 @@ export function tokenizePrintfFormat(printFmt) {
333
343
  flushText();
334
344
  return out;
335
345
  }
346
+ function assertPrintArgCount(printFmt, printArgs, context) {
347
+ const specCount = extractPrintfSpecs(printFmt).length;
348
+ const argCount = printArgs?.length ?? 0;
349
+ if (argCount < specCount) {
350
+ const eventLabel = context?.eventName !== undefined
351
+ ? `"${context.eventName}"${context.eventId !== undefined ? ` (ID ${context.eventId})` : ""}`
352
+ : "unknown event";
353
+ throw new Error(`Print fmt for event ${eventLabel} requires ${specCount} args but got ${argCount} at spec index ${argCount}: "${printFmt}"`);
354
+ }
355
+ }
336
356
  function extractPrintfSpecs(printFmt) {
337
357
  const cached = PRINT_FMT_SPEC_CACHE.get(printFmt);
338
358
  if (cached)
@@ -401,12 +421,20 @@ function evalPrintArgExpr(exprRaw, fieldMap) {
401
421
  return v;
402
422
  throw new Error(`__get_str(${m[1]}) requires a string field, got ${typeof v}`);
403
423
  }
404
- m = expr.match(/^__get_dynamic_array\((\w+)\)$/);
424
+ m = expr.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
405
425
  if (m) {
406
426
  const v = fieldMap[m[1]];
407
- if (Array.isArray(v))
408
- return v;
409
- throw new Error(`__get_dynamic_array(${m[1]}) requires an array field, got ${typeof v}`);
427
+ if (!Array.isArray(v)) {
428
+ throw new Error(`__get_dynamic_array(${m[1]}) requires an array field, got ${typeof v}`);
429
+ }
430
+ if (m[2] !== undefined) {
431
+ const idx = parseInt(m[2], 10);
432
+ if (idx < 0 || idx >= v.length) {
433
+ throw new Error(`Invalid print arg index for __get_dynamic_array(${m[1]})[${m[2]}]`);
434
+ }
435
+ return v[idx];
436
+ }
437
+ return v;
410
438
  }
411
439
  m = expr.match(/^REC->(\w+)&0xffff$/);
412
440
  if (m) {
@@ -481,9 +509,9 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
481
509
  if (mGetStr) {
482
510
  return mGetStr[1];
483
511
  }
484
- const mGetArr = k.match(/^__get_dynamic_array\((\w+)\)$/);
512
+ const mGetArr = k.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
485
513
  if (mGetArr) {
486
- return mGetArr[1];
514
+ return mGetArr[2] !== undefined ? `${mGetArr[1]}__${mGetArr[2]}` : mGetArr[1];
487
515
  }
488
516
  // REC->__data_loc_path&0xffff 在 fieldDict 中收敛为 path 作为键名
489
517
  const mDataLocOff = k.match(/^__data_loc_(\w+)&0xffff$/);
@@ -492,7 +520,8 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
492
520
  }
493
521
  return k.length > 0 ? k : `arg${index}`;
494
522
  }
495
- export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
523
+ export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap, context) {
524
+ assertPrintArgCount(printFmt, printArgs, context);
496
525
  const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
497
526
  const specs = extractPrintfSpecs(printFmt);
498
527
  const out = {};
@@ -506,7 +535,8 @@ export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
506
535
  }
507
536
  return out;
508
537
  }
509
- export function renderPrintFmt(printFmt, printArgs, fieldMap) {
538
+ export function renderPrintFmt(printFmt, printArgs, fieldMap, context) {
539
+ assertPrintArgCount(printFmt, printArgs, context);
510
540
  const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
511
541
  const tokens = getCachedPrintfTokens(printFmt);
512
542
  let argIdx = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hm-pt-core",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "DFX perf trace shared types and trace format decode primitives",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",