hiperf_txt_parser
v2.3.3
Published
Parse perf data.txt and output structured TypeScript data
Readme
hiperf_txt_parser
将 perf 文本中的 record sample 段解析为结构化数据,并支持导出为:
- perf 原文本格式(保留缩进和行前缀;若存在
traceFieldDict,会在 raw hex 行前输出key: value行) - JSON 数组格式(每项为
{ "call_chain": "...", ...traceFields }) .xlsx(formatPerfDataToExcel,依赖包内exceljs)
本项目为 纯 lib 库:
src/为库实现,tests/为回归与性能用例,scripts/为本地基准;无 CLI、无 demo 子项目。发布到 npm 时仅包含dist/(见package.json的files)。
安装
npm install hiperf_txt_parser在仓库根目录本地联调:
npm install .
npm run build对外 API
import {
parsePerfData,
parsePerfDataFromFile,
loadPerfData,
formatPerfDataToText,
formatPerfDataToJson,
formatPerfDataToExcel,
savePerfDataToText,
savePerfDataToJson,
savePerfDataToExcel,
importPerfDataFromExportedJson,
normalizeRecordSampleFromExportedText,
filterByTgid,
toBackTraceStack,
toBackTraceStacks,
buildTraceParserRegistry,
decodePerfRawData,
decodePerfRawDataSync,
findHitraceSnapshot,
findHiperfRecordsForHitraceSnapshot,
hitraceThreadKeyForSample,
} from "hiperf_txt_parser";
import type {
HitraceSidecarOptions,
HitraceThreadIndexSnapshot,
} from "hiperf_txt_parser";parsePerfData(text: string): PerfDataparsePerfDataFromFile(filePath: string): PerfData(扩展名为.json不区分大小写时按导出 JSON 解析,其它按 perf 文本解析)formatPerfDataToText(data: PerfData): stringformatPerfDataToJson(data: PerfData): RecordSampleJsonExportItem[](call_chain与formatPerfDataToExcel的callchain列同源:由callchainFrames.frames以换行拼接)formatPerfDataToExcel(data: PerfData): Promise<Buffer>(.xlsx:traceFieldDict的键集合相同的样本归到同一工作表;无字典或空字典时仅一列callchain;有字典时表头为键名排序后的列并在末尾追加callchain;recordSamples为空会抛错)importPerfDataFromExportedJson(jsonText: string): PerfDatanormalizeRecordSampleFromExportedText(sample: RecordSample): RecordSample(从导出 txt 的 rawkey: value行回填traceFieldDict)filterByTgid(data: PerfData, tgid: number): PerfData(仅保留pid === tgid的 RecordSample)toBackTraceStack(sample, options?): stringtoBackTraceStacks(data, options?): PerfDataparseTraceFormat(text: string): ParsedTraceFormat(解析sample/trace_format风格文本)parseCommonFieldsFromRaw(raw: Uint8Array, format: ParsedTraceFormat): Record<string, number | bigint>(按 format 仅解析common_*字段,小端)rawHexLinesToBuffer(lines): Uint8Array(将 perf 文本里 raw 段的 hex 行拼成字节缓冲,便于喂给parseCommonFieldsFromRaw)buildTraceParserRegistry(formatTexts)(从 trace format 文本构建 event 解析注册表,支持按 format 绑定 fieldDict 回调)decodePerfRawData(perfData, registry, options)(按注册表解码 raw,并可在fieldDict模式写回 key/value;成功时写入traceEventName/tracePrintContent;字段解码失败降级为decode_failed而不中断整批;可选options.hitrace合并预处理 hitrace 索引;workerCount > 1时返回Promise)decodePerfRawDataSync(perfData, registry, options)(始终在主线程解码,等价于workerCount <= 1的decodePerfRawData)decodePerfRawDataOneRow(sample, registry, options)/decodePerfRawDataOneRowWithHitrace(...)(单条 sample 解码,供自定义流水线复用)findHiperfRecordsForHitraceSnapshot(samples, snap, thread, index)(反向查找:返回会被findHitraceSnapshot命中的 hiperf 记录)loadPerfData(filePath, options?)/savePerfDataToText/savePerfDataToJson/savePerfDataToExcel(大文件流式读写;workerCount > 1时走 Worker 池。TXT 解析在返回前会关闭并等待底层ReadStream,调用方可立即覆盖或 rename 源文件)traceFormatTextsNeedMainThreadDecode/traceFormatTextsToWorkerStrings(判断formatTexts是否必须主线程或可否传入 Worker)compareAll/createCompositeCompare/defaultRecordSampleCompare/pickPerfDataMatchingOther/filterPerfData(RecordSample比较与过滤)dedupePerfData/dedupeItemsByCompare(按比较函数去重)
完整导出见 src/index.ts。
快速示例
import {
parsePerfData,
filterByTgid,
formatPerfDataToJson,
formatPerfDataToText,
} from "hiperf_txt_parser";
const input = `record sample: type 9, misc 2, size 520\n sample_type: 0x8000107e7\n ID 13`;
const parsed = parsePerfData(input);
const filtered = filterByTgid(parsed, 1234);
const jsonArray = formatPerfDataToJson(filtered);
const txt = formatPerfDataToText(parsed);按文件路径解析(txt/json 自动分发)
import { parsePerfDataFromFile } from "hiperf_txt_parser";
const fromTxt = parsePerfDataFromFile("./sample/perf_data.txt");
const fromJson = parsePerfDataFromFile("./out/decoded.json");fieldDict 回调示例(按 format 绑定)
import {
buildTraceParserRegistry,
decodePerfRawData,
formatPerfDataToJson,
} from "hiperf_txt_parser";
const traceFormatText = `name: foo
ID: 77
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:__data_loc char[] path; offset:8; size:4; signed:0;
field:u32 len; offset:12; size:4; signed:0;
print fmt: "path=%s len=%u", REC->path, REC->len
`;
// 将回调与 format 直接绑定:只影响该 eventId(77)
const registry = buildTraceParserRegistry([
{
text: traceFormatText,
transformFieldDict: ({ fieldDict }) => {
// 仅在 tracePrintMode: "fieldDict" 时触发
return {
path: fieldDict.path ?? "",
len: fieldDict.len ?? "0",
};
},
},
]);
// perfData 为 parsePerfData(...) 的结果
const decoded = decodePerfRawData(perfData, registry, {
tracePrintMode: "fieldDict",
});
// JSON 导出时,traceFieldDict 会平铺到 call_chain 同级
const jsonOut = formatPerfDataToJson(decoded);hitrace 侧车合并(可选)
当 perf 样本的 raw 解码失败或需补充字段时,可传入由外部工具(如 hmtrace-parser 的 buildHitraceIndexSnapshot)预处理好的线程索引,在解码阶段按时间戳对齐合并。
- 索引键:
${sample.pid}:${sample.tid}(hiperf 中pid为进程 / tgid,tid为线程;与 hitrace 桶键一致)。可用hitraceThreadKeyForSample(sample)生成。 - 对齐:
findHitraceSnapshot(index, sample)在同线程桶内取timestampNs <= sample.time的最后一条(二分)。 - 反向查找:
findHiperfRecordsForHitraceSnapshot(samples, snap, thread, index)按线程 → 时间窗口[snap.ts, nextSnap.ts)→ fieldDict 子集,找出会被正向对齐命中的 hiperf 记录。 - 策略
HitraceSidecarOptions(显式字段,无strategy枚举):
| 字段 | 含义 |
|------|------|
| index | 线程 → 按时间升序的快照列表 |
| order | perfOnly | hitraceFirst | perfFirst:路径执行顺序 |
| afterPerf | 仅 order: "perfFirst":perf 之后何时 try hitrace(见下) |
| overrideDecodedPerf | afterPerf: "always" 且 perf 已 decoded 时是否允许 hitrace 覆盖 |
| timeThreshold | 默认 none;否则校验 sample.time - snap.timestampNs(ms),超出可 drop(视为未命中)或 ignore |
hitracePolicyPerfFirst 默认 afterPerf: whenPerfNotDecoded。其它取值:
never:从不 apply hitracewhenPerfNotDecoded:perf.kind !== "decoded"whenPerfNeedsFallback:无 raw / 解码失败 / 空渲染always:有 snap 即尝试;覆盖已 decoded 的 perf 需overrideDecodedPerf: true
order 语义:
perfOnly:等同不传 hitracehitraceFirst:先 apply,成功则返回(不先解 perf raw)perfFirst:先decodePerfRawDataOneRow,再按afterPerf决定是否 apply
便捷工厂:hitracePolicyPerfOnly、hitracePolicyHitraceFirst、hitracePolicyPerfFirst。
import {
buildTraceParserRegistry,
decodePerfRawData,
hitracePolicyPerfFirst,
hitraceThreadKeyForSample,
} from "hiperf_txt_parser";
import type { HitraceThreadIndexSnapshot } from "hiperf_txt_parser";
const registry = buildTraceParserRegistry([traceFormatText]);
const hitraceIndex: HitraceThreadIndexSnapshot = {
[hitraceThreadKeyForSample(perfData.recordSamples[0]!)]: [
{
timestampNs: perfData.recordSamples[0]!.time,
functionName: "my_event",
fieldDict: { path: "/data", len: "4" },
},
],
};
const decoded = await decodePerfRawData(perfData, registry, {
tracePrintMode: "fieldDict",
formatTexts: [traceFormatText],
workerCount: 4,
hitrace: hitracePolicyPerfFirst(hitraceIndex),
// 或手写:{ index, order: "perfFirst", afterPerf: "whenPerfNeedsFallback" }
});hitrace.index 可 structuredClone 后随 Worker 传入;order === "perfOnly" 时不下发侧车。
批量解码结束后会打 info 汇总(decodePerfRawData hitrace merge results: …),含 rows、snapFound/snapMissing 与各 hitraceMerge 结果计数。单行决策见结果上的 hitraceMerge / hitraceSnapFound;更细逐步日志设 HIPERF_LOG_LEVEL=trace。
Excel 导出(.xlsx)
import { parsePerfData, formatPerfDataToExcel } from "hiperf_txt_parser";
import fs from "node:fs";
const perfData = parsePerfData(perfText);
const buf = await formatPerfDataToExcel(perfData);
fs.writeFileSync("out/samples.xlsx", buf);JSON 导入说明
formatPerfDataToJson 导出的结构可直接被 importPerfDataFromExportedJson 读回:
import {
formatPerfDataToJson,
importPerfDataFromExportedJson,
} from "hiperf_txt_parser";
const arr = formatPerfDataToJson(perfData);
const restored = importPerfDataFromExportedJson(JSON.stringify(arr));Node 并行(worker_threads,经 options 开启)
以下能力 仅适用于 Node.js(依赖 worker_threads),通过原有 API 的 workerCount(默认 1) 选择串行或并行:<= 1 不创建 Worker;> 1 使用 Worker 池。
decodePerfRawData(perfData, registry, options?):options.workerCount > 1时返回Promise,并需options.formatTexts(与buildTraceParserRegistry入参相同)。含不可序列化的transformFieldDict时自动回退主线程。options.hitrace在order !== "perfOnly"时传入 Worker(index须可序列化)。loadPerfData(filePath, options?):perf 文本在workerCount > 1时块级并行parseOneBlock;.json仍一次性读入。串行与并行都会在返回前destroy输入流并等待close(遇到records summary提前结束时尤其重要),避免 Windows 上随后覆盖同一 TXT 失败。savePerfDataToText/savePerfDataToJson:options.workerCount > 1时并行序列化后按序写盘。
实现位于 src/parallel/(decodeParallel、loadParallel、saveParallel)与 src/workers/(decodeWorker、parseBlockWorker、saveSerializeWorker),勿作为稳定公共路径直接引用。
源码布局(src/)
| 路径 | 说明 |
|------|------|
| parser.ts / serializer.ts | perf 文本解析与导入导出 |
| traceFormat.ts | trace format 解析与 raw 解码主逻辑 |
| hitrace/types.ts | hitrace 侧车类型契约 |
| hitrace/merge.ts | perf 与 hitrace 按时间戳合并 |
| parallel/ | Node Worker 池(decode / load / save) |
| workers/ | Worker 线程入口脚本 |
| index.ts | 对外 re-export 聚合 |
对比串行 wall time 与加速比:
npm run bench:parallel -- --samples=8000 --rounds=2 --worker-count=4开发与验证
npm testtests/ 覆盖解析、trace 解码、hitrace 合并、并行一致性、导入导出、TXT 读流生命周期与性能阈值。本地可用 sample/(已 gitignore)放置 perf 样例,通过 loadPerfData / parsePerfDataFromFile 自行验证。
性能基准
提供了一个本地基准脚本,用于评估大数据量下的核心链路吞吐:
npm run bench -- --samples=20000 --rounds=5并行对比(见上一节「Node 并行 API」):
npm run bench:parallel -- --samples=8000 --rounds=2 --worker-count=4多档 samples × worker-count 扫描(输出 Markdown 表,可选 CSV):
npm run bench:scale-sweep
npm run bench:scale-sweep -- --samples=4000,15000,50000,120000 --workers=2,4,8 --rounds=3 --csv=out/bench-scale.csvspeedup = 串行 avg / 并行 avg(> 1 表示并行更快)。合成数据 raw 较小时并行可能慢于串行,属 Worker 调度与拷贝开销;请在真实大 raw 载荷与本机核数下复测。
只跑特定 profile(可逗号分隔):
npm run bench -- --samples=20000 --rounds=5 --profiles=fieldDict跳过 Core Stages(仅跑 decode/pipeline profile):
npm run bench -- --samples=20000 --rounds=5 --profiles=fieldDict --skip-coresamples:每轮构造的record sample数量(默认20000)rounds:测试轮数(默认3)profiles:可选,指定 decode profile;可选值为printf、fieldDict、fieldDict+keepCommon(默认全跑)skip-core:可选,跳过Core Stages(仅输出Decode Profiles)- 输出分为两段:
Core Stages:parsePerfData、formatPerfDataToTextDecode Profiles:printf/fieldDict/fieldDict+keepCommon三种模式下的decodePerfRawData与端到端pipeline
- 每项同时输出
avg、p50、p95、吞吐(samples/s)、heapΔ与heapPeak
输出结构说明
- 解析结构(
parsePerfData):{ recordSamples: RecordSample[] } - 类型
RecordSampleJsonExportItem:{ call_chain: string } & Record<string, string>(除call_chain外的顶层键来自traceFieldDict) - JSON 导出(
formatPerfDataToJson):
[
{
"call_chain": "frame1\\nframe2\\nframe3"
}
]