(function (exports) {
"use strict";
const MAX_EVENTS = Infinity;
function getTraceDecoder() {
if (typeof require !== "undefined") {
const path = require("path");
return require(path.resolve(__dirname, "decode.js")).TraceDecoder;
}
if (typeof TraceDecoder !== "undefined") return TraceDecoder;
throw new Error(
"TraceDecoder not found. Load decode.js before trace_parser.js"
);
}
function num(v) {
if (typeof v === "number") return v;
if (typeof v === "bigint") return Number(v);
if (typeof v === "string" && v !== "")
if (!isNaN(Number(v))) return Number(v);
throw new Error(`Invalid number: ${v}`);
}
async function maybeGunzip(buf) {
const b = buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf;
if (b.length < 2 || b[0] !== 0x1f || b[1] !== 0x8b) {
return buf;
}
if (typeof DecompressionStream !== "undefined") {
return await new Response(
new Blob([b]).stream().pipeThrough(new DecompressionStream("gzip"))
).arrayBuffer();
}
const zlib = require("zlib");
const decompressed = zlib.gunzipSync(Buffer.from(b));
return decompressed.buffer.slice(
decompressed.byteOffset,
decompressed.byteOffset + decompressed.byteLength
);
}
const EVENT_TYPES = {
PollStart: 0,
PollEnd: 1,
WorkerPark: 2,
WorkerUnpark: 3,
QueueSample: 4,
WakeEvent: 9,
};
async function parseTrace(buffer, options) {
buffer = await maybeGunzip(buffer);
const maxEvents = (options && options.maxEvents != null) ? options.maxEvents : MAX_EVENTS;
const startTime = (options && options.startTime != null) ? options.startTime : 0;
const endTime = (options && options.endTime != null) ? options.endTime : Infinity;
const onProgress = (options && options.onProgress) || null;
const YIELD_BYTES = 100 * 1024; const TD = getTraceDecoder();
const dec = new TD(
buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer
);
if (!dec.decodeHeader()) throw new Error("Invalid trace header");
const totalBytes = dec.byteLength;
const events = [];
const spawnLocations = new Map();
const taskSpawnLocs = new Map();
const taskSpawnTimes = new Map();
const taskTerminateTimes = new Map();
const callframeSymbols = new Map();
const cpuSamples = [];
const threadNames = new Map();
const runtimeWorkers = new Map(); const customEvents = []; const clockSyncAnchors = [];
const LEGACY_EPOCH_FLOOR_MS = 1_577_836_800_000; let legacySegmentMetaWallNs = null;
let minMonoTs = null;
const capped = () => events.length >= maxEvents;
const UNCAPPED_FRAMES = new Set([
"TaskSpawnEvent",
"TaskTerminateEvent",
"CpuSampleEvent",
"SymbolTableEntry",
"SegmentMetadataEvent",
"ClockSyncEvent",
]);
let lastYieldPos = 0;
let frame;
while ((frame = dec.nextFrame()) !== null) {
if (onProgress && dec.position - lastYieldPos >= YIELD_BYTES) {
lastYieldPos = dec.position;
onProgress({ bytesRead: dec.position, totalBytes, eventCount: events.length });
await new Promise((r) => setTimeout(r, 0));
}
if (frame.type !== "event") continue;
const v = frame.values;
const ts = num(frame.timestamp_ns);
if (
ts != null &&
frame.name !== "SegmentMetadataEvent" &&
frame.name !== "SymbolTableEntry" &&
(minMonoTs == null || ts < minMonoTs)
) {
minMonoTs = ts;
}
if (capped() && !UNCAPPED_FRAMES.has(frame.name)) continue;
const inTimeRange = ts >= startTime && ts <= endTime;
if (!inTimeRange && !UNCAPPED_FRAMES.has(frame.name)) continue;
switch (frame.name) {
case "PollStartEvent": {
const spawnLoc = v.spawn_loc || null;
if (spawnLoc) spawnLocations.set(spawnLoc, spawnLoc);
const taskId = num(v.task_id);
if (taskId && spawnLoc && !taskSpawnLocs.has(taskId)) {
taskSpawnLocs.set(taskId, spawnLoc);
}
events.push({
eventType: 0,
timestamp: ts,
workerId: num(v.worker_id),
localQueue: num(v.local_queue),
globalQueue: 0,
cpuTime: 0,
schedWait: 0,
taskId,
spawnLocId: spawnLoc,
spawnLoc,
});
break;
}
case "PollEndEvent":
events.push({
eventType: 1,
timestamp: ts,
workerId: num(v.worker_id),
globalQueue: 0,
localQueue: 0,
cpuTime: 0,
schedWait: 0,
taskId: 0,
spawnLocId: null,
spawnLoc: null,
});
break;
case "WorkerParkEvent":
events.push({
eventType: 2,
timestamp: ts,
workerId: num(v.worker_id),
localQueue: num(v.local_queue),
cpuTime: num(v.cpu_time_ns),
globalQueue: 0,
schedWait: 0,
taskId: 0,
spawnLocId: null,
spawnLoc: null,
});
break;
case "WorkerUnparkEvent":
events.push({
eventType: 3,
timestamp: ts,
workerId: num(v.worker_id),
localQueue: num(v.local_queue),
cpuTime: num(v.cpu_time_ns),
schedWait: num(v.sched_wait_ns),
globalQueue: 0,
taskId: 0,
spawnLocId: null,
spawnLoc: null,
});
break;
case "QueueSampleEvent":
events.push({
eventType: 4,
timestamp: ts,
globalQueue: num(v.global_queue),
workerId: 0,
localQueue: 0,
cpuTime: 0,
schedWait: 0,
taskId: 0,
spawnLocId: null,
spawnLoc: null,
});
break;
case "TaskSpawnEvent": {
const taskId = num(v.task_id);
const spawnLoc = v.spawn_loc || null;
if (spawnLoc) spawnLocations.set(spawnLoc, spawnLoc);
taskSpawnLocs.set(taskId, spawnLoc);
taskSpawnTimes.set(taskId, ts);
break;
}
case "TaskTerminateEvent":
taskTerminateTimes.set(num(v.task_id), ts);
break;
case "WakeEventEvent":
events.push({
eventType: 9,
timestamp: ts,
workerId: num(v.target_worker),
wakerTaskId: num(v.waker_task_id),
wokenTaskId: num(v.woken_task_id),
targetWorker: num(v.target_worker),
globalQueue: 0,
localQueue: 0,
cpuTime: 0,
schedWait: 0,
taskId: 0,
spawnLocId: null,
spawnLoc: null,
});
break;
case "CpuSampleEvent": {
const chain = (v.callchain || []).map(
(addr) => "0x" + BigInt(addr).toString(16)
);
cpuSamples.push({
timestamp: ts,
workerId: num(v.worker_id),
tid: num(v.tid),
source: num(v.source),
callchain: chain,
});
const tn = v.thread_name;
if (tn) {
threadNames.set(num(v.tid), tn);
}
break;
}
case "ClockSyncEvent": {
const real = num(v.realtime_ns);
if (real > 0) {
clockSyncAnchors.push({ monotonicNs: ts, realtimeNs: real });
}
break;
}
case "SegmentMetadataEvent": {
if (
legacySegmentMetaWallNs == null &&
ts != null &&
ts / 1e6 >= LEGACY_EPOCH_FLOOR_MS
) {
legacySegmentMetaWallNs = ts;
}
const entries = v.entries || {};
for (const [key, val] of Object.entries(entries)) {
if (key.startsWith("runtime.")) {
const name = key.slice("runtime.".length);
const ids = val
.split(",")
.map(Number)
.filter((n) => !isNaN(n));
if (ids.length > 0) runtimeWorkers.set(name, ids);
}
}
break;
}
case "SymbolTableEntry": {
const addrKey = "0x" + BigInt(v.addr).toString(16);
const depth = Number(v.inline_depth || 0);
const sf = v.source_file || "";
const sl = Number(v.source_line || 0);
const location = sf ? (sl ? `${sf}:${sl}` : sf) : null;
const entry = { symbol: v.symbol_name, location };
if (depth === 0) {
const existing = callframeSymbols.get(addrKey);
if (Array.isArray(existing)) {
existing[0] = entry;
} else {
callframeSymbols.set(addrKey, entry);
}
} else {
let arr = callframeSymbols.get(addrKey);
if (!Array.isArray(arr)) {
arr = [arr || { symbol: addrKey, location: null }];
callframeSymbols.set(addrKey, arr);
}
arr[depth] = entry;
}
break;
}
default: {
if (ts != null) {
customEvents.push({
name: frame.name,
timestamp: ts,
fields: v,
});
}
break;
}
}
}
if (
clockSyncAnchors.length === 0 &&
legacySegmentMetaWallNs != null &&
minMonoTs != null
) {
clockSyncAnchors.push({
monotonicNs: minMonoTs,
realtimeNs: legacySegmentMetaWallNs,
});
}
clockSyncAnchors.sort((a, b) => {
if (a.monotonicNs < b.monotonicNs) return -1;
if (a.monotonicNs > b.monotonicNs) return 1;
return 0;
});
let clockOffsetNs = null;
if (clockSyncAnchors.length > 0) {
const a0 = clockSyncAnchors[0];
clockOffsetNs = a0.realtimeNs - a0.monotonicNs;
}
const hasTimeFilter = startTime > 0 || endTime < Infinity;
return {
magic: "D9TF",
version: dec.version,
events,
truncated: events.length >= maxEvents,
timeFiltered: hasTimeFilter,
filterStartTime: hasTimeFilter ? startTime : null,
filterEndTime: hasTimeFilter ? endTime : null,
hasCpuTime: true,
hasSchedWait: true,
hasTaskTracking: true,
spawnLocations,
taskSpawnLocs,
taskSpawnTimes,
cpuSamples,
callframeSymbols,
threadNames,
taskTerminateTimes,
runtimeWorkers,
customEvents,
clockSyncAnchors,
clockOffsetNs,
};
}
function _stripBoringGenerics(s) {
const boring = /^[A-Z]$|^(Fut|Req|Res|Bs|InnerFuture)$/;
return s.replace(/<([^<>]*)>/g, (match, inner) => {
const params = inner.split(",").map((p) => p.trim());
if (params.every((p) => boring.test(p))) return "";
const kept = params.filter((p) => !boring.test(p));
return kept.length ? `<${kept.join(",")}>` : "";
});
}
function _lastSeg(s) {
return s.split("::").pop();
}
function _shortenPath(s) {
const parts = s.split("::");
let closures = 0;
for (let i = parts.length - 1; i >= 0; i--) {
if (parts[i] === "{{closure}}") closures++;
else break;
}
const meaningful = parts.length - closures;
if (meaningful <= 3) return s;
return parts.slice(meaningful - 3).join("::");
}
function _docsRsUrl(location) {
if (!location) return null;
const m = location.match(
/\/([a-z][a-z0-9_-]*)-(\d+\.\d+[^/]*)\/(.+?)(?::(\d+))?$/
);
if (!m) return null;
const [, crate_, version, rawPath, line] = m;
const crateSrc = crate_.replace(/-/g, "_");
const path = rawPath.replace(/^src\//, "");
let url = `https://docs.rs/${crate_}/${version}/src/${crateSrc}/${path}.html`;
if (line) url += `#${line}`;
return url;
}
function _fileName(location) {
if (!location) return null;
const m = location.match(/([^/]+\.rs)(?::\d+)?$/);
return m ? m[1] : null;
}
function formatFrame(frame, callframeSymbols) {
if (typeof frame === "string") {
if (!callframeSymbols)
throw new Error(
"formatFrame requires callframeSymbols when given an address string"
);
const entry = callframeSymbols.get(frame);
if (!entry) return { text: frame || "(unknown)", docsUrl: null };
frame = Array.isArray(entry) ? entry[0] : entry;
}
const { symbol: sym, location } = frame;
if (!sym || sym.startsWith("0x"))
return { text: sym || "(unknown)", docsUrl: null };
let result = sym;
const traitImplMatch = result.match(/^<(.+?) as (.+?)>::(.+)$/);
if (traitImplMatch) {
let [, implType, trait_, method] = traitImplMatch;
const shortType = _lastSeg(_stripBoringGenerics(implType));
result =
shortType.length <= 2
? `${_lastSeg(_stripBoringGenerics(trait_))}::${method}`
: `${shortType}::${method}`;
} else if (result.includes("::")) {
result = _shortenPath(_stripBoringGenerics(result));
}
const fileName = _fileName(location);
if (location) {
const m = location.match(/:(\d+)$/);
if (m) result += ` ${fileName || ""}:${m[1]}`;
}
return { text: result, docsUrl: _docsRsUrl(location) };
}
function symbolizeChain(callchain, callframeSymbols) {
const result = [];
for (const addr of callchain) {
const entry = callframeSymbols.get(addr);
if (!entry) {
result.push({ symbol: addr, location: null });
continue;
}
if (Array.isArray(entry)) {
for (const e of entry) result.push(e);
continue;
}
if (typeof entry === "string") {
result.push({ symbol: entry, location: null });
continue;
}
result.push(entry);
}
return result;
}
function deduplicateSamples(samples, callframeSymbols) {
const groups = new Map();
for (const sample of samples) {
const frames = symbolizeChain(sample.callchain, callframeSymbols);
const key = frames.map((f) => f.symbol).join("\0");
if (!groups.has(key)) {
groups.set(key, {
count: 0,
frames,
leaf: frames[0] ? formatFrame(frames[0]).text : "(unknown)",
leafRaw: frames[0] ? frames[0].symbol : "",
});
}
groups.get(key).count++;
}
return [...groups.values()].sort((a, b) => b.count - a.count);
}
if (typeof module !== "undefined" && module.exports) {
module.exports = {
EVENT_TYPES,
parseTrace,
formatFrame,
symbolizeChain,
deduplicateSamples,
};
} else {
exports.TraceParser = {
EVENT_TYPES,
parseTrace,
formatFrame,
symbolizeChain,
deduplicateSamples,
};
}
})(typeof exports === "undefined" ? this : exports);