UNPKG

@flamedeck/flamechart-mcp

Version:

MCP server for debugging and analyzing flamegraphs using Model Context Protocol

1,560 lines (1,541 loc) 187 kB
#!/usr/bin/env node var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/index.ts import { FastMCP } from "fastmcp"; // src/tools/top-functions.ts import { z } from "zod"; import { UserError } from "fastmcp"; // src/utils/profile-loader.ts import { readFile } from "node:fs/promises"; import { existsSync as existsSync2 } from "node:fs"; // ../speedscope-core/src/lib-utils.ts function lastOf(ts) { return ts[ts.length - 1] || null; } __name(lastOf, "lastOf"); function sortBy(ts, key) { function comparator(a, b) { const keyA = key(a); const keyB = key(b); return keyA < keyB ? -1 : keyA > keyB ? 1 : 0; } __name(comparator, "comparator"); ts.sort(comparator); } __name(sortBy, "sortBy"); function getOrInsert(map, k, fallback) { if (!map.has(k)) map.set(k, fallback(k)); return map.get(k); } __name(getOrInsert, "getOrInsert"); function getOrElse(map, k, fallback) { if (!map.has(k)) return fallback(k); return map.get(k); } __name(getOrElse, "getOrElse"); var _KeyedSet = class _KeyedSet { constructor() { __publicField(this, "map", /* @__PURE__ */ new Map()); } getOrInsert(t) { const key = t.key; const existing = this.map.get(key); if (existing) return existing; this.map.set(key, t); return t; } forEach(fn) { this.map.forEach(fn); } [Symbol.iterator]() { return this.map.values(); } }; __name(_KeyedSet, "KeyedSet"); var KeyedSet = _KeyedSet; function* itMap(it, f) { for (const t of it) { yield f(t); } } __name(itMap, "itMap"); function itForEach(it, f) { for (const t of it) { f(t); } } __name(itForEach, "itForEach"); function zeroPad(s, width) { return new Array(Math.max(width - s.length, 0) + 1).join("0") + s; } __name(zeroPad, "zeroPad"); function formatPercent(percent) { let formattedPercent = `${percent.toFixed(0)}%`; if (percent === 100) formattedPercent = "100%"; else if (percent > 99) formattedPercent = ">99%"; else if (percent < 0.01) formattedPercent = "<0.01%"; else if (percent < 1) formattedPercent = `${percent.toFixed(2)}%`; else if (percent < 10) formattedPercent = `${percent.toFixed(1)}%`; return formattedPercent; } __name(formatPercent, "formatPercent"); function fract(x) { return x - Math.floor(x); } __name(fract, "fract"); function triangle(x) { return 2 * Math.abs(fract(x) - 0.5) - 1; } __name(triangle, "triangle"); function lazyStatic(cb) { let last = null; return () => { if (last == null) { last = { result: cb() }; } return last.result; }; } __name(lazyStatic, "lazyStatic"); var base64lookupTable = lazyStatic(() => { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const ret = /* @__PURE__ */ new Map(); for (let i = 0; i < alphabet.length; i++) { ret.set(alphabet.charAt(i), i); } ret.set("=", -1); return ret; }); // ../speedscope-core/src/value-formatters.ts var _RawValueFormatter = class _RawValueFormatter { constructor() { __publicField(this, "unit", "none"); } format(v) { return v.toLocaleString(); } }; __name(_RawValueFormatter, "RawValueFormatter"); var RawValueFormatter = _RawValueFormatter; var _TimeFormatter = class _TimeFormatter { constructor(unit) { __publicField(this, "unit"); __publicField(this, "multiplier"); this.unit = unit; if (unit === "nanoseconds") this.multiplier = 1e-9; else if (unit === "microseconds") this.multiplier = 1e-6; else if (unit === "milliseconds") this.multiplier = 1e-3; else this.multiplier = 1; } formatUnsigned(v) { const s = v * this.multiplier; if (s / 60 >= 1) { const minutes = Math.floor(s / 60); const seconds = Math.floor(s - minutes * 60).toString(); return `${minutes}:${zeroPad(seconds, 2)}`; } if (s / 1 >= 1) return `${s.toFixed(2)}s`; if (s / 1e-3 >= 1) return `${(s / 1e-3).toFixed(2)}ms`; if (s / 1e-6 >= 1) return `${(s / 1e-6).toFixed(2)}\xB5s`; else return `${(s / 1e-9).toFixed(2)}ns`; } format(v) { return `${v < 0 ? "-" : ""}${this.formatUnsigned(Math.abs(v))}`; } }; __name(_TimeFormatter, "TimeFormatter"); var TimeFormatter = _TimeFormatter; var _ByteFormatter = class _ByteFormatter { constructor() { __publicField(this, "unit", "bytes"); } format(v) { if (v < 1024) return `${v.toFixed(0)} B`; v /= 1024; if (v < 1024) return `${v.toFixed(2)} KB`; v /= 1024; if (v < 1024) return `${v.toFixed(2)} MB`; v /= 1024; return `${v.toFixed(2)} GB`; } }; __name(_ByteFormatter, "ByteFormatter"); var ByteFormatter = _ByteFormatter; // ../speedscope-core/src/profile.ts var _HasWeights = class _HasWeights { constructor() { __publicField(this, "selfWeight", 0); __publicField(this, "totalWeight", 0); } getSelfWeight() { return this.selfWeight; } getTotalWeight() { return this.totalWeight; } addToTotalWeight(delta) { this.totalWeight += delta; } addToSelfWeight(delta) { this.selfWeight += delta; } overwriteWeightWith(other) { this.selfWeight = other.selfWeight; this.totalWeight = other.totalWeight; } }; __name(_HasWeights, "HasWeights"); var HasWeights = _HasWeights; var _Frame = class _Frame extends HasWeights { constructor(info) { super(); __publicField(this, "key"); // Name of the frame. May be a method name, e.g. // "ActiveRecord##to_hash" __publicField(this, "name"); // File path of the code corresponding to this // call stack frame. __publicField(this, "file"); // Line in the given file where this frame occurs __publicField(this, "line"); // Column in the file __publicField(this, "col"); this.key = info.key; this.name = info.name; this.file = info.file; this.line = info.line; this.col = info.col; } static getOrInsert(set, info) { return set.getOrInsert(new _Frame(info)); } }; __name(_Frame, "Frame"); __publicField(_Frame, "root", new _Frame({ key: "(speedscope root)", name: "(speedscope root)" })); var Frame = _Frame; var _CallTreeNode = class _CallTreeNode extends HasWeights { constructor(frame, parent) { super(); __publicField(this, "frame"); __publicField(this, "parent"); __publicField(this, "children"); // If a node is "frozen", it means it should no longer be mutated. __publicField(this, "frozen"); this.frame = frame, this.parent = parent, this.children = [], this.frozen = false; } isRoot() { return this.frame === Frame.root; } isFrozen() { return this.frozen; } freeze() { this.frozen = true; } }; __name(_CallTreeNode, "CallTreeNode"); var CallTreeNode = _CallTreeNode; var _Profile = class _Profile { constructor(totalWeight = 0) { __publicField(this, "name", ""); __publicField(this, "totalWeight"); __publicField(this, "frames", new KeyedSet()); // Profiles store two call-trees. // // The "append order" call tree is the one in which nodes are ordered in // whatever order they were appended to their parent. // // The "grouped" call tree is one in which each node has at most one child per // frame. Nodes are ordered in decreasing order of weight __publicField(this, "appendOrderCalltreeRoot", new CallTreeNode(Frame.root, null)); __publicField(this, "groupedCalltreeRoot", new CallTreeNode(Frame.root, null)); // List of references to CallTreeNodes at the top of the // stack at the time of the sample. __publicField(this, "samples", []); __publicField(this, "weights", []); __publicField(this, "valueFormatter", new RawValueFormatter()); __publicField(this, "totalNonIdleWeight", null); this.totalWeight = totalWeight; } getAppendOrderCalltreeRoot() { return this.appendOrderCalltreeRoot; } getGroupedCalltreeRoot() { return this.groupedCalltreeRoot; } shallowClone() { const profile = new _Profile(this.totalWeight); Object.assign(profile, this); return profile; } formatValue(v) { return this.valueFormatter.format(v); } setValueFormatter(f) { this.valueFormatter = f; } getWeightUnit() { return this.valueFormatter.unit; } getName() { return this.name; } setName(name) { this.name = name; } getTotalWeight() { return this.totalWeight; } getTotalNonIdleWeight() { if (this.totalNonIdleWeight === null) { this.totalNonIdleWeight = this.groupedCalltreeRoot.children.reduce((n, c) => n + c.getTotalWeight(), 0); } return this.totalNonIdleWeight; } // This is private because it should only be called in the ProfileBuilder // classes. Once a Profile instance has been constructed, it should be treated // as immutable. sortGroupedCallTree() { function visit(node) { node.children.sort((a, b) => -(a.getTotalWeight() - b.getTotalWeight())); node.children.forEach(visit); } __name(visit, "visit"); visit(this.groupedCalltreeRoot); } forEachCallGrouped(openFrame, closeFrame) { function visit(node, start) { if (node.frame !== Frame.root) { openFrame(node, start); } let childTime = 0; node.children.forEach(function(child) { visit(child, start + childTime); childTime += child.getTotalWeight(); }); if (node.frame !== Frame.root) { closeFrame(node, start + node.getTotalWeight()); } } __name(visit, "visit"); visit(this.groupedCalltreeRoot, 0); } forEachCall(openFrame, closeFrame) { let prevStack = []; let value = 0; let sampleIndex = 0; for (const stackTop of this.samples) { let lca = null; for (lca = stackTop; lca && lca.frame != Frame.root && prevStack.indexOf(lca) === -1; lca = lca.parent) { } while (prevStack.length > 0 && lastOf(prevStack) != lca) { const node = prevStack.pop(); closeFrame(node, value); } const toOpen = []; for (let node = stackTop; node && node.frame != Frame.root && node != lca; node = node.parent) { toOpen.push(node); } toOpen.reverse(); for (const node of toOpen) { openFrame(node, value); } prevStack = prevStack.concat(toOpen); value += this.weights[sampleIndex++]; } for (let i = prevStack.length - 1; i >= 0; i--) { closeFrame(prevStack[i], value); } } forEachFrame(fn) { this.frames.forEach(fn); } getProfileWithRecursionFlattened() { const builder = new CallTreeProfileBuilder(); const stack = []; const framesInStack = /* @__PURE__ */ new Set(); function openFrame(node, value) { if (framesInStack.has(node.frame)) { stack.push(null); } else { framesInStack.add(node.frame); stack.push(node); builder.enterFrame(node.frame, value); } } __name(openFrame, "openFrame"); function closeFrame(node, value) { const stackTop = stack.pop(); if (stackTop) { framesInStack.delete(stackTop.frame); builder.leaveFrame(stackTop.frame, value); } } __name(closeFrame, "closeFrame"); this.forEachCall(openFrame, closeFrame); const flattenedProfile = builder.build(); flattenedProfile.name = this.name; flattenedProfile.valueFormatter = this.valueFormatter; this.forEachFrame((f) => { flattenedProfile.frames.getOrInsert(f).overwriteWeightWith(f); }); return flattenedProfile; } getInvertedProfileForCallersOf(focalFrameInfo) { const focalFrame = Frame.getOrInsert(this.frames, focalFrameInfo); const builder = new StackListProfileBuilder(); const nodes = []; function visit(node) { if (node.frame === focalFrame) { nodes.push(node); } else { for (const child of node.children) { visit(child); } } } __name(visit, "visit"); visit(this.appendOrderCalltreeRoot); for (const node of nodes) { const stack = []; for (let n = node; n != null && n.frame !== Frame.root; n = n.parent) { stack.push(n.frame); } builder.appendSampleWithWeight(stack, node.getTotalWeight()); } const ret = builder.build(); ret.name = this.name; ret.valueFormatter = this.valueFormatter; return ret; } getProfileForCalleesOf(focalFrameInfo) { const focalFrame = Frame.getOrInsert(this.frames, focalFrameInfo); const builder = new StackListProfileBuilder(); function recordSubtree(focalFrameNode) { const stack = []; function visit(node) { stack.push(node.frame); builder.appendSampleWithWeight(stack, node.getSelfWeight()); for (const child of node.children) { visit(child); } stack.pop(); } __name(visit, "visit"); visit(focalFrameNode); } __name(recordSubtree, "recordSubtree"); function findCalls(node) { if (node.frame === focalFrame) { recordSubtree(node); } else { for (const child of node.children) { findCalls(child); } } } __name(findCalls, "findCalls"); findCalls(this.appendOrderCalltreeRoot); const ret = builder.build(); ret.name = this.name; ret.valueFormatter = this.valueFormatter; return ret; } // Demangle symbols for readability async demangle() { return Promise.resolve(); } remapSymbols(callback) { for (const frame of this.frames) { const remapped = callback(frame); if (remapped == null) { continue; } const { name, file, line, col } = remapped; if (name != null) { frame.name = name; } if (file != null) { frame.file = file; } if (line != null) { frame.line = line; } if (col != null) { frame.col = col; } } } }; __name(_Profile, "Profile"); var Profile = _Profile; var _StackListProfileBuilder = class _StackListProfileBuilder extends Profile { constructor() { super(...arguments); __publicField(this, "pendingSample", null); } _appendSample(stack, weight, useAppendOrder) { if (isNaN(weight)) throw new Error("invalid weight"); let node = useAppendOrder ? this.appendOrderCalltreeRoot : this.groupedCalltreeRoot; const framesInStack = /* @__PURE__ */ new Set(); for (const frame of stack) { const last = useAppendOrder ? lastOf(node.children) : node.children.find((c) => c.frame === frame); if (last && !last.isFrozen() && last.frame == frame) { node = last; } else { const parent = node; node = new CallTreeNode(frame, node); parent.children.push(node); } node.addToTotalWeight(weight); framesInStack.add(node.frame); } node.addToSelfWeight(weight); if (useAppendOrder) { for (const child of node.children) { child.freeze(); } } if (useAppendOrder) { node.frame.addToSelfWeight(weight); for (const frame of framesInStack) { frame.addToTotalWeight(weight); } if (node === lastOf(this.samples)) { this.weights[this.weights.length - 1] += weight; } else { this.samples.push(node); this.weights.push(weight); } } } appendSampleWithWeight(stack, weight) { if (weight === 0) { return; } if (weight < 0) { throw new Error("Samples must have positive weights"); } const frames = stack.map((fr) => Frame.getOrInsert(this.frames, fr)); this._appendSample(frames, weight, true); this._appendSample(frames, weight, false); } appendSampleWithTimestamp(stack, timestamp) { if (this.pendingSample) { if (timestamp < this.pendingSample.centralTimestamp) { throw new Error("Timestamps received out of order"); } const endTimestamp = (timestamp + this.pendingSample.centralTimestamp) / 2; this.appendSampleWithWeight(this.pendingSample.stack, endTimestamp - this.pendingSample.startTimestamp); this.pendingSample = { stack, startTimestamp: endTimestamp, centralTimestamp: timestamp }; } else { this.pendingSample = { stack, startTimestamp: timestamp, centralTimestamp: timestamp }; } } build() { if (this.pendingSample) { if (this.samples.length > 0) { this.appendSampleWithWeight(this.pendingSample.stack, this.pendingSample.centralTimestamp - this.pendingSample.startTimestamp); } else { this.appendSampleWithWeight(this.pendingSample.stack, 1); this.setValueFormatter(new RawValueFormatter()); } } this.totalWeight = Math.max(this.totalWeight, this.weights.reduce((a, b) => a + b, 0)); this.sortGroupedCallTree(); return this; } }; __name(_StackListProfileBuilder, "StackListProfileBuilder"); var StackListProfileBuilder = _StackListProfileBuilder; var _CallTreeProfileBuilder = class _CallTreeProfileBuilder extends Profile { constructor() { super(...arguments); __publicField(this, "appendOrderStack", [ this.appendOrderCalltreeRoot ]); __publicField(this, "groupedOrderStack", [ this.groupedCalltreeRoot ]); __publicField(this, "framesInStack", /* @__PURE__ */ new Map()); __publicField(this, "stack", []); __publicField(this, "lastValue", 0); } addWeightsToFrames(value) { const delta = value - this.lastValue; for (const frame of this.framesInStack.keys()) { frame.addToTotalWeight(delta); } const stackTop = lastOf(this.stack); if (stackTop) { stackTop.addToSelfWeight(delta); } } addWeightsToNodes(value, stack) { const delta = value - this.lastValue; for (const node of stack) { node.addToTotalWeight(delta); } const stackTop = lastOf(stack); if (stackTop) { stackTop.addToSelfWeight(delta); } } _enterFrame(frame, value, useAppendOrder) { const stack = useAppendOrder ? this.appendOrderStack : this.groupedOrderStack; this.addWeightsToNodes(value, stack); const prevTop = lastOf(stack); if (prevTop) { if (useAppendOrder) { const delta = value - this.lastValue; if (delta > 0) { this.samples.push(prevTop); this.weights.push(value - this.lastValue); } else if (delta < 0) { throw new Error(`Samples must be provided in increasing order of cumulative value. Last sample was ${this.lastValue}, this sample was ${value}`); } } const last = useAppendOrder ? lastOf(prevTop.children) : prevTop.children.find((c) => c.frame === frame); let node; if (last && !last.isFrozen() && last.frame == frame) { node = last; } else { node = new CallTreeNode(frame, prevTop); prevTop.children.push(node); } stack.push(node); } } enterFrame(frameInfo, value) { const frame = Frame.getOrInsert(this.frames, frameInfo); this.addWeightsToFrames(value); this._enterFrame(frame, value, true); this._enterFrame(frame, value, false); this.stack.push(frame); const frameCount = this.framesInStack.get(frame) || 0; this.framesInStack.set(frame, frameCount + 1); this.lastValue = value; this.totalWeight = Math.max(this.totalWeight, this.lastValue); } _leaveFrame(frame, value, useAppendOrder) { const stack = useAppendOrder ? this.appendOrderStack : this.groupedOrderStack; this.addWeightsToNodes(value, stack); if (useAppendOrder) { const leavingStackTop = this.appendOrderStack.pop(); if (leavingStackTop == null) { throw new Error(`Trying to leave ${frame.key} when stack is empty`); } if (this.lastValue == null) { throw new Error(`Trying to leave a ${frame.key} before any have been entered`); } leavingStackTop.freeze(); if (leavingStackTop.frame.key !== frame.key) { throw new Error(`Tried to leave frame "${frame.name}" while frame "${leavingStackTop.frame.name}" was at the top at ${value}`); } const delta = value - this.lastValue; if (delta > 0) { this.samples.push(leavingStackTop); this.weights.push(value - this.lastValue); } else if (delta < 0) { throw new Error(`Samples must be provided in increasing order of cumulative value. Last sample was ${this.lastValue}, this sample was ${value}`); } } else { this.groupedOrderStack.pop(); } } leaveFrame(frameInfo, value) { const frame = Frame.getOrInsert(this.frames, frameInfo); this.addWeightsToFrames(value); this._leaveFrame(frame, value, true); this._leaveFrame(frame, value, false); this.stack.pop(); const frameCount = this.framesInStack.get(frame); if (frameCount == null) return; if (frameCount === 1) { this.framesInStack.delete(frame); } else { this.framesInStack.set(frame, frameCount - 1); } this.lastValue = value; this.totalWeight = Math.max(this.totalWeight, this.lastValue); } build() { if (this.appendOrderStack.length > 1 || this.groupedOrderStack.length > 1) { throw new Error("Tried to complete profile construction with a non-empty stack"); } this.sortGroupedCallTree(); return this; } }; __name(_CallTreeProfileBuilder, "CallTreeProfileBuilder"); var CallTreeProfileBuilder = _CallTreeProfileBuilder; // ../speedscope-import/src/speedscope-import/v8cpuFormatter.ts function treeToArray(root) { const nodes = []; function visit(node) { nodes.push({ id: node.id, callFrame: { columnNumber: 0, functionName: node.functionName, lineNumber: node.lineNumber, scriptId: node.scriptId, url: node.url }, hitCount: node.hitCount, children: node.children.map((child) => child.id) }); node.children.forEach(visit); } __name(visit, "visit"); visit(root); return nodes; } __name(treeToArray, "treeToArray"); function timestampsToDeltas(timestamps, startTime) { return timestamps.map((timestamp, index) => { const lastTimestamp = index === 0 ? startTime * 1e6 : timestamps[index - 1]; return timestamp - lastTimestamp; }); } __name(timestampsToDeltas, "timestampsToDeltas"); function chromeTreeToNodes(content) { return { samples: content.samples, startTime: content.startTime * 1e6, endTime: content.endTime * 1e6, nodes: treeToArray(content.head), timeDeltas: timestampsToDeltas(content.timestamps, content.startTime) }; } __name(chromeTreeToNodes, "chromeTreeToNodes"); // ../speedscope-import/src/speedscope-import/chrome.ts function isChromeTimeline(rawProfile) { if (!Array.isArray(rawProfile)) return false; if (rawProfile.length < 1) return false; const first = rawProfile[0]; if (!("pid" in first && "tid" in first && "ph" in first && "cat" in first)) return false; if (!rawProfile.find((e) => e.name === "CpuProfile" || e.name === "Profile" || e.name === "ProfileChunk")) return false; return true; } __name(isChromeTimeline, "isChromeTimeline"); function isChromeTimelineObject(rawProfile) { if (!("traceEvents" in rawProfile)) return false; return isChromeTimeline(rawProfile.traceEvents); } __name(isChromeTimelineObject, "isChromeTimelineObject"); function importFromChromeTimeline(events, fileName) { const cpuProfileByID = /* @__PURE__ */ new Map(); const pidTidById = /* @__PURE__ */ new Map(); const threadNameByPidTid = /* @__PURE__ */ new Map(); sortBy(events, (e) => e.ts); for (const event of events) { if (event.name === "CpuProfile") { const pidTid = `${event.pid}:${event.tid}`; const id = event.id || pidTid; cpuProfileByID.set(id, event.args.data.cpuProfile); pidTidById.set(id, pidTid); } if (event.name === "Profile") { const pidTid = `${event.pid}:${event.tid}`; cpuProfileByID.set(event.id || pidTid, { startTime: 0, endTime: 0, nodes: [], samples: [], timeDeltas: [], ...event.args.data }); if (event.id) { pidTidById.set(event.id, `${event.pid}:${event.tid}`); } } if (event.name === "thread_name") { threadNameByPidTid.set(`${event.pid}:${event.tid}`, event.args.name); } if (event.name === "ProfileChunk") { const pidTid = `${event.pid}:${event.tid}`; const cpuProfile = cpuProfileByID.get(event.id || pidTid); if (cpuProfile) { const chunk = event.args.data; if (chunk.cpuProfile) { if (chunk.cpuProfile.nodes) { cpuProfile.nodes = cpuProfile.nodes.concat(chunk.cpuProfile.nodes); } if (chunk.cpuProfile.samples) { cpuProfile.samples = cpuProfile.samples.concat(chunk.cpuProfile.samples); } } if (chunk.timeDeltas) { cpuProfile.timeDeltas = cpuProfile.timeDeltas.concat(chunk.timeDeltas); } if (chunk.startTime != null) { cpuProfile.startTime = chunk.startTime; } if (chunk.endTime != null) { cpuProfile.endTime = chunk.endTime; } } else { console.warn(`Ignoring ProfileChunk for undeclared Profile with id ${event.id || pidTid}`); } } } if (cpuProfileByID.size > 0) { const profiles = []; let indexToView = 0; itForEach(cpuProfileByID.keys(), (profileId) => { let threadName = null; const pidTid = pidTidById.get(profileId); if (pidTid) { threadName = threadNameByPidTid.get(pidTid) || null; if (threadName) { } } const profile = importFromChromeCPUProfile(cpuProfileByID.get(profileId)); if (threadName && cpuProfileByID.size > 1) { profile.setName(`${fileName} - ${threadName}`); if (threadName === "CrRendererMain") { indexToView = profiles.length; } } else { profile.setName(`${fileName}`); } profiles.push(profile); }); return { name: fileName, indexToView, profiles }; } else { throw new Error("Could not find CPU profile in Timeline"); } } __name(importFromChromeTimeline, "importFromChromeTimeline"); var callFrameToFrameInfo = /* @__PURE__ */ new Map(); function frameInfoForCallFrame(callFrame) { return getOrInsert(callFrameToFrameInfo, callFrame, (callFrame2) => { const file = callFrame2.url; let line = callFrame2.lineNumber; if (line != null) line++; let col = callFrame2.columnNumber; if (col != null) col++; const name = callFrame2.functionName || (file ? `(anonymous ${file.split("/").pop()}:${line})` : "(anonymous)"); return { key: `${name}:${file}:${line}:${col}`, name, file, line, col }; }); } __name(frameInfoForCallFrame, "frameInfoForCallFrame"); function shouldIgnoreFunction(callFrame) { const { functionName, url } = callFrame; if (url === "native dummy.js") { return true; } return functionName === "(root)" || functionName === "(idle)"; } __name(shouldIgnoreFunction, "shouldIgnoreFunction"); function shouldPlaceOnTopOfPreviousStack(functionName) { return functionName === "(garbage collector)" || functionName === "(program)"; } __name(shouldPlaceOnTopOfPreviousStack, "shouldPlaceOnTopOfPreviousStack"); function importFromChromeCPUProfile(chromeProfile) { const profile = new CallTreeProfileBuilder(chromeProfile.endTime - chromeProfile.startTime); const nodeById = /* @__PURE__ */ new Map(); for (const node of chromeProfile.nodes) { nodeById.set(node.id, node); } for (const node of chromeProfile.nodes) { if (typeof node.parent === "number") { node.parent = nodeById.get(node.parent); } if (!node.children) continue; for (const childId of node.children) { const child = nodeById.get(childId); if (!child) continue; child.parent = node; } } const samples = []; const sampleTimes = []; let elapsed = chromeProfile.timeDeltas[0]; let lastValidElapsed = elapsed; let lastNodeId = NaN; for (let i = 0; i < chromeProfile.samples.length; i++) { const nodeId = chromeProfile.samples[i]; if (nodeId != lastNodeId) { samples.push(nodeId); if (elapsed < lastValidElapsed) { sampleTimes.push(lastValidElapsed); } else { sampleTimes.push(elapsed); lastValidElapsed = elapsed; } } if (i === chromeProfile.samples.length - 1) { if (!isNaN(lastNodeId)) { samples.push(lastNodeId); if (elapsed < lastValidElapsed) { sampleTimes.push(lastValidElapsed); } else { sampleTimes.push(elapsed); lastValidElapsed = elapsed; } } } else { const timeDelta = chromeProfile.timeDeltas[i + 1]; elapsed += timeDelta; lastNodeId = nodeId; } } let prevStack = []; for (let i = 0; i < samples.length; i++) { const value = sampleTimes[i]; const nodeId = samples[i]; const stackTop = nodeById.get(nodeId); if (!stackTop) continue; let lca = null; for (lca = stackTop; lca && prevStack.indexOf(lca) === -1; lca = shouldPlaceOnTopOfPreviousStack(lca.callFrame.functionName) ? lastOf(prevStack) : lca.parent || null) { } while (prevStack.length > 0 && lastOf(prevStack) != lca) { const closingNode = prevStack.pop(); const frame = frameInfoForCallFrame(closingNode.callFrame); profile.leaveFrame(frame, value); } const toOpen = []; for ( let node = stackTop; node && node != lca && !shouldIgnoreFunction(node.callFrame); // Place Chrome internal functions on top of the previous call stack node = shouldPlaceOnTopOfPreviousStack(node.callFrame.functionName) ? lastOf(prevStack) : node.parent || null ) { toOpen.push(node); } toOpen.reverse(); for (const node of toOpen) { profile.enterFrame(frameInfoForCallFrame(node.callFrame), value); } prevStack = prevStack.concat(toOpen); } for (let i = prevStack.length - 1; i >= 0; i--) { profile.leaveFrame(frameInfoForCallFrame(prevStack[i].callFrame), lastOf(sampleTimes)); } profile.setValueFormatter(new TimeFormatter("microseconds")); return profile.build(); } __name(importFromChromeCPUProfile, "importFromChromeCPUProfile"); function importFromOldV8CPUProfile(content) { return importFromChromeCPUProfile(chromeTreeToNodes(content)); } __name(importFromOldV8CPUProfile, "importFromOldV8CPUProfile"); // ../speedscope-import/src/speedscope-import/stackprof.ts function importFromStackprof(stackprofProfile) { const { frames, mode, raw, raw_timestamp_deltas, interval } = stackprofProfile; const profile = new StackListProfileBuilder(); profile.setValueFormatter(new TimeFormatter("microseconds")); let sampleIndex = 0; let prevStack = []; for (let i = 0; i < raw.length; ) { const stackHeight = raw[i++]; let stack = []; for (let j = 0; j < stackHeight; j++) { const id = raw[i++]; let frameName = frames[id].name; if (frameName == null) { frameName = "(unknown)"; } const frame = { key: id, ...frames[id], name: frameName }; stack.push(frame); } if (stack.length === 1 && stack[0].name === "(garbage collection)") { stack = prevStack.concat(stack); } const nSamples = raw[i++]; switch (mode) { case "object": profile.appendSampleWithWeight(stack, nSamples); profile.setValueFormatter(new RawValueFormatter()); break; case "cpu": profile.appendSampleWithWeight(stack, nSamples * interval); break; default: let sampleDuration = 0; for (let j = 0; j < nSamples; j++) { sampleDuration += raw_timestamp_deltas[sampleIndex++]; } profile.appendSampleWithWeight(stack, sampleDuration); } prevStack = stack; } return profile.build(); } __name(importFromStackprof, "importFromStackprof"); // ../speedscope-import/src/speedscope-import/importer-utils.ts var TEXT_FILE_CHUNK_SIZE = 1 << 27; function permissivelyParseJSONString(content) { content = content.trim(); if (content[0] === "[") { content = content.replace(/,\s*$/, ""); if (content[content.length - 1] !== "]") { content += "]"; } } return JSON.parse(content); } __name(permissivelyParseJSONString, "permissivelyParseJSONString"); var _StringBackedTextFileContent = class _StringBackedTextFileContent { constructor(s) { __publicField(this, "s"); this.s = s; } splitLines() { return this.s.split("\n"); } firstChunk() { return this.s; } parseAsJSON(deps) { return permissivelyParseJSONString(this.s); } }; __name(_StringBackedTextFileContent, "StringBackedTextFileContent"); var StringBackedTextFileContent = _StringBackedTextFileContent; var _TextProfileDataSource = class _TextProfileDataSource { constructor(fileName, contents) { __publicField(this, "fileName"); __publicField(this, "contents"); this.fileName = fileName; this.contents = contents; } async name() { return this.fileName; } async readAsArrayBuffer() { return new ArrayBuffer(0); } async readAsText(deps) { return new StringBackedTextFileContent(this.contents); } }; __name(_TextProfileDataSource, "TextProfileDataSource"); var TextProfileDataSource = _TextProfileDataSource; // ../speedscope-import/src/speedscope-import/instruments.ts function parseTSV(contents) { const lines = [ ...contents.splitLines() ].map((l) => l.split(" ")); const headerLine = lines.shift(); if (!headerLine) return []; const indexToField = /* @__PURE__ */ new Map(); for (let i = 0; i < headerLine.length; i++) { indexToField.set(i, headerLine[i]); } const ret = []; for (const line of lines) { const row = {}; for (let i = 0; i < line.length; i++) { row[indexToField.get(i)] = line[i]; } ret.push(row); } return ret; } __name(parseTSV, "parseTSV"); function getWeight(deepCopyRow) { if ("Bytes Used" in deepCopyRow) { const bytesUsedString = deepCopyRow["Bytes Used"]; const parts = /\s*(\d+(?:[.]\d+)?) (\w+)\s+(?:\d+(?:[.]\d+))%/.exec(bytesUsedString); if (!parts) return 0; const value = parseInt(parts[1], 10); const units = parts[2]; switch (units) { case "Bytes": return value; case "KB": return 1024 * value; case "MB": return 1024 * 1024 * value; case "GB": return 1024 * 1024 * 1024 * value; } throw new Error(`Unrecognized units ${units}`); } if ("Weight" in deepCopyRow || "Running Time" in deepCopyRow) { const weightString = deepCopyRow["Weight"] || deepCopyRow["Running Time"]; const parts = /\s*(\d+(?:[.]\d+)?) ?(\w+)\s+(?:\d+(?:[.]\d+))%/.exec(weightString); if (!parts) return 0; const value = parseInt(parts[1], 10); const units = parts[2]; switch (units) { case "ms": return value; case "s": return 1e3 * value; case "min": return 60 * 1e3 * value; case "cycles": return value; case "Kc": return 1e3 * value; case "Mc": return 1e3 * 1e3 * value; case "Gc": return 1e3 * 1e3 * 1e3 * value; } throw new Error(`Unrecognized units ${units}`); } return -1; } __name(getWeight, "getWeight"); function importFromInstrumentsDeepCopy(contents) { const profile = new CallTreeProfileBuilder(); const rows = parseTSV(contents); const stack = []; let cumulativeValue = 0; for (const row of rows) { const symbolName = row["Symbol Name"]; if (!symbolName) continue; const trimmedSymbolName = symbolName.trim(); const stackDepth = symbolName.length - trimmedSymbolName.length; if (stack.length - stackDepth < 0) { throw new Error("Invalid format"); } const framesToLeave = []; while (stackDepth < stack.length) { const stackTop = stack.pop(); framesToLeave.push(stackTop); } for (const frameToLeave of framesToLeave) { cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); profile.leaveFrame(frameToLeave, cumulativeValue); } const newFrameInfo = { key: `${row["Source Path"] || ""}:${trimmedSymbolName}`, name: trimmedSymbolName, file: row["Source Path"], endValue: cumulativeValue + getWeight(row) }; profile.enterFrame(newFrameInfo, cumulativeValue); stack.push(newFrameInfo); } while (stack.length > 0) { const frameToLeave = stack.pop(); cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); profile.leaveFrame(frameToLeave, cumulativeValue); } if ("Bytes Used" in rows[0]) { profile.setValueFormatter(new ByteFormatter()); } else if ("Weight" in rows[0] || "Running Time" in rows[0]) { profile.setValueFormatter(new TimeFormatter("milliseconds")); } return profile.build(); } __name(importFromInstrumentsDeepCopy, "importFromInstrumentsDeepCopy"); // ../speedscope-import/src/speedscope-import/bg-flamegraph.ts function parseBGFoldedStacks(contents) { const samples = []; for (const line of contents.splitLines()) { const match = /^(.*) (\d+)$/gm.exec(line); if (!match) continue; const stack = match[1]; const n = match[2]; samples.push({ stack: stack.split(";").map((name) => ({ key: name, name })), duration: parseInt(n, 10) }); } return samples; } __name(parseBGFoldedStacks, "parseBGFoldedStacks"); function importFromBGFlameGraph(contents) { const parsed = parseBGFoldedStacks(contents); const duration = parsed.reduce((prev, cur) => prev + cur.duration, 0); const profile = new StackListProfileBuilder(duration); if (parsed.length === 0) { return null; } for (const sample of parsed) { profile.appendSampleWithWeight(sample.stack, sample.duration); } return profile.build(); } __name(importFromBGFlameGraph, "importFromBGFlameGraph"); // ../speedscope-import/src/speedscope-import/firefox.ts function importFromFirefox(firefoxProfile) { const cpuProfile = firefoxProfile.profile; const thread = cpuProfile.threads.length === 1 ? cpuProfile.threads[0] : cpuProfile.threads.filter((t) => t.name === "GeckoMain")[0]; const frameKeyToFrameInfo = /* @__PURE__ */ new Map(); function extractStack(sample) { let stackFrameId = sample[0]; const ret = []; while (stackFrameId != null) { const nextStackFrame = thread.stackTable.data[stackFrameId]; const [nextStackId, frameId] = nextStackFrame; ret.push(frameId); stackFrameId = nextStackId; } ret.reverse(); return ret.map((f) => { const frameData = thread.frameTable.data[f]; const location = thread.stringTable[frameData[0]]; const match = /(.*)\s+\((.*?)(?::(\d+))?(?::(\d+))?\)$/.exec(location); if (!match) return null; if (match[2].startsWith("resource:") || match[2] === "self-hosted" || match[2].startsWith("self-hosted:")) { return null; } return getOrInsert(frameKeyToFrameInfo, location, () => ({ key: location, name: match[1], file: match[2], // In Firefox profiles, line numbers are 1-based, but columns are // 0-based. Let's normalize both to be 1-based. line: match[3] ? parseInt(match[3]) : void 0, col: match[4] ? parseInt(match[4]) + 1 : void 0 })); }).filter((f) => f != null); } __name(extractStack, "extractStack"); const profile = new CallTreeProfileBuilder(firefoxProfile.duration); let prevStack = []; for (const sample of thread.samples.data) { const stack = extractStack(sample); const value = sample[1]; let lcaIndex = -1; for (let i = 0; i < Math.min(stack.length, prevStack.length); i++) { if (prevStack[i] !== stack[i]) { break; } lcaIndex = i; } for (let i = prevStack.length - 1; i > lcaIndex; i--) { profile.leaveFrame(prevStack[i], value); } for (let i = lcaIndex + 1; i < stack.length; i++) { profile.enterFrame(stack[i], value); } prevStack = stack; } profile.setValueFormatter(new TimeFormatter("milliseconds")); return profile.build(); } __name(importFromFirefox, "importFromFirefox"); // ../speedscope-core/src/file-format-spec.ts (function(FileFormat2) { (function(ProfileType) { ProfileType["EVENTED"] = "evented"; ProfileType["SAMPLED"] = "sampled"; })(FileFormat2.ProfileType || (FileFormat2.ProfileType = {})); (function(EventType) { EventType["OPEN_FRAME"] = "O"; EventType["CLOSE_FRAME"] = "C"; })(FileFormat2.EventType || (FileFormat2.EventType = {})); })(FileFormat || (FileFormat = {})); var FileFormat; // ../speedscope-import/src/speedscope-import/file-format.ts function importSpeedscopeProfile(serialized, frames) { function setCommonProperties(p) { const { name, unit } = serialized; switch (unit) { case "nanoseconds": case "microseconds": case "milliseconds": case "seconds": p.setValueFormatter(new TimeFormatter(unit)); break; case "bytes": p.setValueFormatter(new ByteFormatter()); break; case "none": p.setValueFormatter(new RawValueFormatter()); break; } p.setName(name); } __name(setCommonProperties, "setCommonProperties"); function importEventedProfile(evented) { const { startValue, endValue, events } = evented; const profile = new CallTreeProfileBuilder(endValue - startValue); setCommonProperties(profile); const frameInfos = frames.map((frame, i) => ({ key: i, ...frame })); for (const ev of events) { switch (ev.type) { case FileFormat.EventType.OPEN_FRAME: { profile.enterFrame(frameInfos[ev.frame], ev.at - startValue); break; } case FileFormat.EventType.CLOSE_FRAME: { profile.leaveFrame(frameInfos[ev.frame], ev.at - startValue); break; } } } return profile.build(); } __name(importEventedProfile, "importEventedProfile"); function importSampledProfile(sampled) { const { startValue, endValue, samples, weights } = sampled; const profile = new StackListProfileBuilder(endValue - startValue); setCommonProperties(profile); const frameInfos = frames.map((frame, i) => ({ key: i, ...frame })); if (samples.length !== weights.length) { throw new Error(`Expected samples.length (${samples.length}) to equal weights.length (${weights.length})`); } for (let i = 0; i < samples.length; i++) { const stack = samples[i]; const weight = weights[i]; profile.appendSampleWithWeight(stack.map((n) => frameInfos[n]), weight); } return profile.build(); } __name(importSampledProfile, "importSampledProfile"); switch (serialized.type) { case FileFormat.ProfileType.EVENTED: return importEventedProfile(serialized); case FileFormat.ProfileType.SAMPLED: return importSampledProfile(serialized); } } __name(importSpeedscopeProfile, "importSpeedscopeProfile"); function importSpeedscopeProfiles(serialized) { return { name: serialized.name || serialized.profiles[0].name || "profile", indexToView: serialized.activeProfileIndex || 0, profiles: serialized.profiles.map((p) => importSpeedscopeProfile(p, serialized.shared.frames)) }; } __name(importSpeedscopeProfiles, "importSpeedscopeProfiles"); // ../speedscope-import/src/speedscope-import/v8proflog.ts function codeToFrameInfo(code, v8log) { if (!code || !code.type) { return { key: "(unknown type)", name: "(unknown type)" }; } let name = code.name; switch (code.type) { case "CPP": { const matches = name.match(/[tT] ([^(<]*)/); if (matches) name = `(c++) ${matches[1]}`; break; } case "SHARED_LIB": name = "(LIB) " + name; break; case "JS": { const matches = name.match(/([a-zA-Z0-9\._\-$]*) ([a-zA-Z0-9\.\-_\/$]*):(\d+):(\d+)/); if (matches) { const file = matches[2]; const line = parseInt(matches[3], 10); const col = parseInt(matches[4], 10); const functionName = matches[1].length > 0 ? matches[1] : file ? `(anonymous ${file.split("/").pop()}:${line})` : "(anonymous)"; return { key: name, name: functionName, file: file.length > 0 ? file : "(unknown file)", line, col }; } break; } case "CODE": { switch (code.kind) { case "LoadIC": case "StoreIC": case "KeyedStoreIC": case "KeyedLoadIC": case "LoadGlobalIC": case "Handler": name = "(IC) " + name; break; case "BytecodeHandler": name = "(bytecode) ~" + name; break; case "Stub": name = "(stub) " + name; break; case "Builtin": name = "(builtin) " + name; break; case "RegExp": name = "(regexp) " + name; break; } break; } default: { name = `(${code.type}) ${name}`; break; } } return { key: name, name }; } __name(codeToFrameInfo, "codeToFrameInfo"); function importFromV8ProfLog(v8log) { const profile = new StackListProfileBuilder(); const sToFrameInfo = /* @__PURE__ */ new Map(); function getFrameInfo(t) { return getOrInsert(sToFrameInfo, t, (t2) => { const code = v8log.code[t2]; return codeToFrameInfo(code, v8log); }); } __name(getFrameInfo, "getFrameInfo"); let lastTm = 0; sortBy(v8log.ticks, (tick) => tick.tm); for (const tick of v8log.ticks) { const stack = []; for (let i = tick.s.length - 2; i >= 0; i -= 2) { const id = tick.s[i]; if (id === -1) continue; if (id > v8log.code.length) { stack.push({ key: id, name: `0x${id.toString(16)}` }); continue; } stack.push(getFrameInfo(id)); } profile.appendSampleWithWeight(stack, tick.tm - lastTm); lastTm = tick.tm; } profile.setValueFormatter(new TimeFormatter("microseconds")); return profile.build(); } __name(importFromV8ProfLog, "importFromV8ProfLog"); // ../speedscope-import/src/speedscope-import/linux-tools-perf.ts function* parseEvents(contents) { let buffer = []; for (const line of contents.splitLines()) { if (line === "") { yield parseEvent(buffer); buffer = []; } else buffer.push(line); } if (buffer.length > 0) yield parseEvent(buffer); } __name(parseEvents, "parseEvents"); function parseEvent(rawEvent) { const lines = rawEvent.filter((l) => !/^\s*#/.exec(l)); const event = { command: null, processID: null, threadID: null, time: null, eventType: "", stack: [] }; const firstLine = lines.shift(); if (!firstLine) return null; const eventStartMatch = /^(\S.+?)\s+(\d+)(?:\/?(\d+))?\s+/.exec(firstLine); if (!eventStartMatch) return null; event.command = eventStartMatch[1]; if (eventStartMatch[3]) { event.processID = parseInt(eventStartMatch[2], 10); event.threadID = parseInt(eventStartMatch[3], 10); } else { event.threadID = parseInt(eventStartMatch[2], 10); } const timeMatch = /\s+(\d+\.\d+):\s+/.exec(firstLine); if (timeMatch) { event.time = parseFloat(timeMatch[1]); } const evName = /(\S+):\s*$/.exec(firstLine); if (evName) { event.eventType = evName[1]; } for (const line of lines) { const lineMatch = /^\s*(\w+)\s*(.+) \((\S*)\)/.exec(line); if (!lineMatch) continue; let [, address, symbolName, file] = lineMatch; symbolName = symbolName.replace(/\+0x[\da-f]+$/, ""); event.stack.push({ address: `0x${address}`, symbolName, file }); } event.stack.reverse(); return event; } __name(parseEvent, "parseEvent"); function importFromLinuxPerf(contents) { const profiles = /* @__PURE__ */ new Map(); let eventType = null; for (const event of parseEvents(contents)) { if (event == null) continue; if (eventType != null && eventType != event.eventType) continue; if (event.time == null) continue; eventType = event.eventType; const profileNameParts = []; if (event.command) profileNameParts.push(event.command); if (event.processID) profileNameParts.push(`pid: ${event.processID}`); if (event.threadID) profileNameParts.push(`tid: ${event.threadID}`); const profileName = profileNameParts.join(" "); const builderState = getOrInsert(profiles, profileName, () => { const builder2 = new StackListProfileBuilder(); builder2.setName(profileName); builder2.setValueFormatter(new TimeFormatter("seconds")); return builder2; }); const builder = builderState; builder.appendSampleWithTimestamp(event.stack.map(({ symbolName, file }) => { return { key: `${symbolName} (${file})`, name: symbolName === "[unknown]" ? `??? (${file})` : symbolName, file }; }), event.time); } if (profiles.size === 0) { return null; } return { name: profiles.size === 1 ? Array.from(profiles.keys())[0] : "", indexToView: 0, profiles: Array.from(itMap(profiles.values(), (builder) => builder.build())) }; } __name(importFromLinuxPerf, "importFromLinuxPerf"); // ../speedscope-import/src/speedscope-import/haskell.ts function addToProfile(tree, startVal, profile, infos, attribute) { if (tree.ticks === 0 && tree.entries