| 1 | import type { CpuProfile, ProfileFrame } from "./rendererDiagnostics.js"; |
| 2 | |
| 3 | export function analyseProfile(profile: CpuProfile): ProfileFrame[] { |
| 4 | const samples = profile.samples ?? []; |
| 5 | if (profile.nodes.length > 20_000 || samples.length > 100_000) throw new Error("profile exceeds analysis budget"); |
| 6 | const nodes = new Map(profile.nodes.map((node) => [node.id, node.callFrame])); |
| 7 | const rows = new Map<number, ProfileFrame>(); |
| 8 | for (let i = 0; i < samples.length; i++) { |
| 9 | const id = samples[i]; |
| 10 | const frame = nodes.get(id); |
| 11 | if (!frame) continue; |
| 12 | // Built application scripts only: omit eval, external URLs and user paths. |
| 13 | if (!frame.url.startsWith("reasonix://app/")) continue; |
| 14 | const file = frame.url.split("/").pop()?.split(/[?#]/)[0] ?? ""; |
| 15 | if (!/^[\w.-]+\.m?js$/.test(file)) continue; |
| 16 | const row = rows.get(id) ?? { label: `${frame.functionName.replace(/[\r\n]/g, " ").slice(0, 100) || "(anonymous)"} (${file}:${frame.lineNumber + 1})`, samples: 0, selfMs: 0 }; |
| 17 | row.samples++; |
| 18 | row.selfMs += Math.max(0, Number.isFinite(profile.timeDeltas?.[i]) ? profile.timeDeltas![i] / 1000 : 0); |
| 19 | rows.set(id, row); |
| 20 | } |
| 21 | return [...rows.values()].sort((a, b) => b.selfMs - a.selfMs || b.samples - a.samples).slice(0, 8); |
| 22 | } |
| 23 |