{"spec_id":"flamegraph-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// flamegraph-basic: Flame Graph for Performance Profiling\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-08-20\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: folded call-stack samples (self-time per stack frame) -----------\n// Each row is the *own* (exclusive) sample count captured for that exact\n// stack path — the standard \"folded stack\" input format flame graphs use.\nconst samples = [\n  { stack: \"main\", value: 25 },\n  { stack: \"main;acceptConnections\", value: 15 },\n  { stack: \"main;acceptConnections;tlsHandshake\", value: 8 },\n  { stack: \"main;acceptConnections;tlsHandshake;certValidation\", value: 12 },\n  { stack: \"main;acceptConnections;tlsHandshake;cipherNegotiation\", value: 10 },\n  { stack: \"main;acceptConnections;parseHeaders\", value: 45 },\n  { stack: \"main;acceptConnections;readSocketBuffer\", value: 10 },\n  { stack: \"main;acceptConnections;readSocketBuffer;parseFrameHeader\", value: 18 },\n  { stack: \"main;acceptConnections;readSocketBuffer;bufferAlloc\", value: 14 },\n  { stack: \"main;routeRequest\", value: 12 },\n  { stack: \"main;routeRequest;authMiddleware\", value: 20 },\n  { stack: \"main;routeRequest;authMiddleware;verifyToken\", value: 35 },\n  { stack: \"main;routeRequest;authMiddleware;loadUserSession\", value: 28 },\n  { stack: \"main;routeRequest;rateLimitCheck\", value: 10 },\n  { stack: \"main;routeRequest;rateLimitCheck;checkBucket\", value: 16 },\n  { stack: \"main;routeRequest;rateLimitCheck;updateBucket\", value: 12 },\n  { stack: \"main;routeRequest;handleApi\", value: 8 },\n  { stack: \"main;routeRequest;handleApi;validateInput\", value: 10 },\n  { stack: \"main;routeRequest;handleApi;validateInput;schemaCheck\", value: 30 },\n  { stack: \"main;routeRequest;handleApi;validateInput;sanitizeFields\", value: 22 },\n  { stack: \"main;routeRequest;handleApi;queryDatabase\", value: 6 },\n  { stack: \"main;routeRequest;handleApi;queryDatabase;acquireConnection\", value: 14 },\n  { stack: \"main;routeRequest;handleApi;queryDatabase;executeQuery\", value: 20 },\n  { stack: \"main;routeRequest;handleApi;queryDatabase;executeQuery;planQuery\", value: 55 },\n  { stack: \"main;routeRequest;handleApi;queryDatabase;executeQuery;fetchRows\", value: 40 },\n  {\n    stack: \"main;routeRequest;handleApi;queryDatabase;executeQuery;fetchRows;deserializeRow\",\n    value: 65,\n  },\n  {\n    stack: \"main;routeRequest;handleApi;queryDatabase;executeQuery;fetchRows;rowToObject\",\n    value: 48,\n  },\n  { stack: \"main;routeRequest;handleApi;queryDatabase;parseResults\", value: 70 },\n  { stack: \"main;routeRequest;handleApi;cacheLookup\", value: 5 },\n  { stack: \"main;routeRequest;handleApi;cacheLookup;cacheHit\", value: 18 },\n  { stack: \"main;routeRequest;handleApi;cacheLookup;cacheMiss\", value: 24 },\n  { stack: \"main;routeRequest;handleApi;businessLogic\", value: 8 },\n  { stack: \"main;routeRequest;handleApi;businessLogic;computeDiscounts\", value: 42 },\n  { stack: \"main;routeRequest;handleApi;businessLogic;applyTaxRules\", value: 33 },\n  { stack: \"main;routeRequest;handleApi;businessLogic;aggregateTotals\", value: 27 },\n  { stack: \"main;routeRequest;handleApi;serializeResponse\", value: 12 },\n  { stack: \"main;routeRequest;handleApi;serializeResponse;jsonEncode\", value: 38 },\n  { stack: \"main;routeRequest;handleApi;serializeResponse;compressGzip\", value: 30 },\n  { stack: \"main;routeRequest;handleStatic\", value: 6 },\n  { stack: \"main;routeRequest;handleStatic;readFile\", value: 44 },\n  { stack: \"main;routeRequest;handleStatic;setCacheHeaders\", value: 9 },\n  { stack: \"main;backgroundJobs\", value: 10 },\n  { stack: \"main;backgroundJobs;garbageCollect\", value: 20 },\n  { stack: \"main;backgroundJobs;garbageCollect;markPhase\", value: 32 },\n  { stack: \"main;backgroundJobs;garbageCollect;sweepPhase\", value: 28 },\n  { stack: \"main;backgroundJobs;logFlush\", value: 15 },\n  { stack: \"main;backgroundJobs;metricsExport\", value: 5 },\n  { stack: \"main;backgroundJobs;metricsExport;pushToStatsd\", value: 12 },\n  { stack: \"main;backgroundJobs;metricsExport;formatMetrics\", value: 10 },\n  { stack: \"main;logRequest\", value: 6 },\n  { stack: \"main;logRequest;formatLogLine\", value: 8 },\n  { stack: \"main;logRequest;formatLogLine;escapeChars\", value: 11 },\n  { stack: \"main;logRequest;writeToDisk\", value: 7 },\n  { stack: \"main;logRequest;writeToDisk;flushDisk\", value: 13 },\n  { stack: \"main;logRequest;writeToDisk;fsync\", value: 9 },\n];\n\n// --- Build the call tree: own time per node, then roll up totals -----------\nconst root = { name: \"main\", children: {}, own: 0 };\nfor (const { stack, value } of samples) {\n  const parts = stack.split(\";\");\n  let node = root;\n  for (let i = 1; i < parts.length; i++) {\n    const name = parts[i];\n    if (!node.children[name]) node.children[name] = { name, children: {}, own: 0 };\n    node = node.children[name];\n  }\n  node.own += value;\n}\n\nlet maxDepth = 0;\nconst rollUp = (node, depth) => {\n  maxDepth = Math.max(maxDepth, depth);\n  const kids = Object.values(node.children);\n  node.total = node.own + kids.reduce((sum, kid) => sum + rollUp(kid, depth + 1), 0);\n  return node.total;\n};\nrollUp(root, 0);\nconst rootTotal = root.total;\n\n// --- Flatten the tree into left-to-right stacked bars, one series per frame\n// A stacked bar chart tiles series strictly by declaration order per row, so\n// a depth-first walk reproduces the classic flame-graph layout. Any node that\n// doesn't fully cover its own width in the row below it (self time, or a\n// leaf ending before the deepest row) needs an invisible spacer series to\n// keep every deeper row aligned under the correct parent.\nconst frames = [];\n\nconst padDown = (value, depth) => {\n  if (depth > maxDepth) return;\n  frames.push({ depth, value, name: null, real: false });\n  padDown(value, depth + 1);\n};\n\nconst walk = (node, depth) => {\n  frames.push({ depth, value: node.total, name: node.name, real: true });\n  const kids = Object.values(node.children).sort((a, b) => a.name.localeCompare(b.name));\n  if (kids.length) {\n    for (const kid of kids) walk(kid, depth + 1);\n    if (node.own > 0) padDown(node.own, depth + 1);\n  } else if (node.total > 0) {\n    padDown(node.total, depth + 1);\n  }\n};\nwalk(root, 0);\n\n// --- Color: conventional warm flame-graph palette, built from Imprint's ----\n// warm-family anchors only (amber -> ochre -> matte-red). The spec calls for\n// yellows/oranges/reds by domain convention (Brendan Gregg's original flame\n// graph aesthetic), which is the semantic-exception case in the style guide\n// — so frames don't follow the usual \"first series is brand green\" rule.\nconst warmStops = [t.amber, t.palette[3], t.palette[4]];\nconst lerp = (hexA, hexB, f) => {\n  const a = Highcharts.color(hexA).rgba;\n  const b = Highcharts.color(hexB).rgba;\n  const mix = [0, 1, 2].map((i) => Math.round(a[i] + (b[i] - a[i]) * f));\n  return `rgb(${mix[0]}, ${mix[1]}, ${mix[2]})`;\n};\nconst hash = (str) => {\n  let h = 0;\n  for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) >>> 0;\n  return (h % 1000) / 1000;\n};\n// Blend two signals into the ramp position: `heat` (log-scaled share of total\n// samples) pulls hot frames toward the matte-red end, adding a second layer of\n// storytelling beyond bar width; `hash(name)` keeps same-share siblings visually\n// distinct instead of collapsing them onto one shade.\nconst warmColor = (name, share) => {\n  const heat = Math.min(1, Math.log1p(share * 12) / Math.log1p(12));\n  const f = heat * 0.65 + hash(name) * 0.35;\n  return f < 0.5 ? lerp(warmStops[0], warmStops[1], f * 2) : lerp(warmStops[1], warmStops[2], (f - 0.5) * 2);\n};\n\nconst categories = Array.from({ length: maxDepth + 1 }, (_, i) => (i === 0 ? \"Depth 0 · root\" : `Depth ${i}`));\n\nconst series = frames.map((frame) => {\n  const data = new Array(categories.length).fill(null);\n  data[frame.depth] = frame.value;\n  const widthFraction = frame.value / rootTotal;\n  return {\n    type: \"bar\",\n    name: frame.real ? frame.name : undefined,\n    data,\n    stack: \"flame\",\n    color: frame.real ? warmColor(frame.name, frame.value / rootTotal) : \"transparent\",\n    borderWidth: frame.real ? 1 : 0,\n    borderColor: t.pageBg,\n    enableMouseTracking: frame.real,\n    showInLegend: false,\n    // Fixed dark ink, not theme-adaptive: the label is only ever shown once\n    // the post-render fit check below (chart.events.render) confirms it sits\n    // fully inside the warm data fill, which is constant across themes — it\n    // never sits on the theme-flipped page background/spacer border.\n    dataLabels: {\n      enabled: frame.real && widthFraction > 0.02,\n      inside: true,\n      align: \"center\",\n      verticalAlign: \"middle\",\n      format: frame.name,\n      style: { color: \"#1A1A17\", fontSize: \"13px\", fontWeight: \"500\", textOutline: \"none\" },\n    },\n  };\n});\n\n// Highcharts doesn't hide inside bar/column labels that are wider than their\n// own point box — it just lets them overflow into whatever sits next door\n// (here, the transparent spacer/border, which renders as the theme's page\n// background). A width-fraction heuristic can't predict that reliably because\n// label pixel width depends on the actual rendered glyphs. Instead, measure\n// each label against its bar's real rendered box after every render and hide\n// any label that doesn't fit — the only way to guarantee it never bleeds past\n// the warm fill it depends on for contrast.\n// `type: \"bar\"` charts render inverted: each point's SVG rect keeps its\n// pre-rotation local coordinates, so `getBBox().width` is the constant row\n// thickness and the bar's actual on-screen length is `getBBox().height`.\nconst FIT_PADDING = 12;\nfunction hideOverflowingLabels() {\n  for (const s of this.series) {\n    for (const point of s.points) {\n      if (!point.dataLabel || !point.graphic) continue;\n      const fits = point.dataLabel.getBBox().width + FIT_PADDING <= point.graphic.getBBox().height;\n      point.dataLabel.attr({ visibility: fits ? \"inherit\" : \"hidden\" });\n    }\n  }\n}\n\n// --- Chart -------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"bar\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: { render: hideOverflowingLabels },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"flamegraph-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"CPU profile of a request-handling call stack — bar width is share of total samples\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    categories,\n    reversed: false,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Call stack depth\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n  },\n  yAxis: { visible: false, min: 0, max: rootTotal, reversed: true },\n  legend: { enabled: false },\n  plotOptions: {\n    series: { animation: false, stacking: \"normal\", pointPadding: 0.02, groupPadding: 0, borderRadius: 0 },\n  },\n  tooltip: {\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    style: { color: t.ink, fontSize: \"14px\" },\n    formatter() {\n      const pct = ((this.y / rootTotal) * 100).toFixed(1);\n      return `<b>${this.series.name}</b><br/>${this.y.toLocaleString()} samples (${pct}%)`;\n    },\n  },\n  series,\n});\n"}