{"spec_id":"flamegraph-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# flamegraph-basic: Flame Graph for Performance Profiling\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 88/100 | Created: 2026-06-08\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens ----------------------------------------------------------------\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint warm subset — semantic exception (the conventional flame-graph\n# aesthetic is yellow → orange → red, so brand green sits out for this spec).\nconst FLAME_COLORS = [\n    colorant\"#DDCC77\",  # amber (Imprint anchor — warning / heat)\n    colorant\"#BD8233\",  # ochre (Imprint #4)\n    colorant\"#AE3030\",  # matte red (Imprint #5)\n]\n\n# In-bar label ink chosen per fill by relative luminance — dark ink on the\n# light amber / ochre bars, light ink on the matte-red bars where dark text\n# would lose contrast.\nfunction contrast_ink(c)\n    r, g, b = red(c), green(c), blue(c)\n    0.2126 * r + 0.7152 * g + 0.0722 * b > 0.5 ?\n        colorant\"#1A1A17\" : colorant\"#FAF8F1\"\nend\nconst FLAME_LABEL_INK = [contrast_ink(c) for c in FLAME_COLORS]\n\n# Theme() hoists chrome tokens into a single declarative block — the per-Axis\n# kwargs below only need to override plot-specific knobs (title, limits, etc).\nset_theme!(Theme(\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n    Axis = (\n        backgroundcolor    = PAGE_BG,\n        titlecolor         = INK,\n        xlabelcolor        = INK,\n        ylabelcolor        = INK_SOFT,\n        xticklabelcolor    = INK_SOFT,\n        bottomspinecolor   = INK_SOFT,\n        xtickcolor         = INK_SOFT,\n        topspinevisible    = false,\n        rightspinevisible  = false,\n        leftspinevisible   = false,\n        yticksvisible      = false,\n        yticklabelsvisible = false,\n        xgridvisible       = false,\n        ygridvisible       = false,\n    ),\n))\n\n# Simulated CPU profile of a web request handler.\n# Each entry: (semicolon-delimited stack from root to leaf, sample count).\nprofile = [\n    (\"main;server.handle_request;parse_request;read_headers\", 18),\n    (\"main;server.handle_request;parse_request;parse_body\", 12),\n    (\"main;server.handle_request;app.route;auth.verify;jwt.decode\", 22),\n    (\"main;server.handle_request;app.route;auth.verify;cache.get\", 9),\n    (\"main;server.handle_request;app.route;user_handler;db.query;db.connect\", 14),\n    (\"main;server.handle_request;app.route;user_handler;db.query;db.execute;db.fetch_rows\", 86),\n    (\"main;server.handle_request;app.route;user_handler;db.query;db.execute;db.parse_result\", 32),\n    (\"main;server.handle_request;app.route;user_handler;serializer.to_json\", 27),\n    (\"main;server.handle_request;app.route;user_handler;serializer.escape_html\", 11),\n    (\"main;server.handle_request;app.route;product_handler;db.query;db.execute;db.fetch_rows\", 41),\n    (\"main;server.handle_request;app.route;product_handler;serializer.to_json\", 15),\n    (\"main;server.handle_request;app.route;product_handler;recommend;model.predict;matmul\", 48),\n    (\"main;server.handle_request;app.route;product_handler;recommend;model.predict;softmax\", 9),\n    (\"main;server.handle_request;app.route;product_handler;recommend;feature_lookup;cache.get\", 7),\n    (\"main;server.handle_request;send_response;write_headers\", 5),\n    (\"main;server.handle_request;send_response;write_body;gzip.compress\", 19),\n    (\"main;server.handle_request;send_response;write_body;tcp.send\", 8),\n    (\"main;server.poll_events;epoll_wait\", 24),\n    (\"main;runtime.gc;mark_phase;walk_heap\", 31),\n    (\"main;runtime.gc;sweep_phase\", 12),\n]\n\ntotal_samples = sum(samples for (_, samples) in profile)\n\n# Aggregate each (depth, prefix) into total samples; record children sets.\ncounts = Dict{Tuple{Int,String},Int}()\nchildren = Dict{Tuple{Int,String},Set{String}}()\nfor (stack, samples) in profile\n    parts = String.(split(stack, ';'))\n    for i in 1:length(parts)\n        prefix = join(parts[1:i], ';')\n        key = (i - 1, prefix)\n        counts[key] = get(counts, key, 0) + samples\n        if i > 1\n            pkey = (i - 2, join(parts[1:i-1], ';'))\n            push!(get!(children, pkey, Set{String}()), prefix)\n        end\n    end\nend\n\n# Lay out rectangles top-down from the root, children sorted alphabetically.\n# Iterative DFS keeps the implementation top-level — no recursive function.\nNodeT = NamedTuple{\n    (:depth, :x0, :w, :name, :prefix),\n    Tuple{Int,Float64,Float64,String,String},\n}\nnodes = NodeT[]\nqueue = [(\"main\", 0, 0.0)]\nwhile !isempty(queue)\n    prefix, depth, x0 = pop!(queue)\n    width = counts[(depth, prefix)] / total_samples\n    name = String(split(prefix, ';')[end])\n    push!(nodes, (depth = depth, x0 = x0, w = width, name = name, prefix = prefix))\n\n    kids = sort!(collect(get(children, (depth, prefix), Set{String}())))\n    child_starts = Float64[]\n    cursor = x0\n    for c in kids\n        push!(child_starts, cursor)\n        cursor += counts[(depth + 1, c)] / total_samples\n    end\n    for i in length(kids):-1:1\n        push!(queue, (kids[i], depth + 1, child_starts[i]))\n    end\nend\n\nmax_depth = maximum(n.depth for n in nodes)\n\n# Widest leaf = dominant CPU hot path; gets a focal-point accent below.\nleaves = filter(n -> !haskey(children, (n.depth, n.prefix)), nodes)\nhot = leaves[argmax([l.w for l in leaves])]\n\n# Title scaled to fit when prefixed with a descriptive subtitle.\ntitle_text = \"CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai\"\ntitle_default = 20\ntitle_size = length(title_text) > 67 ?\n    max(round(Int, title_default * 67 / length(title_text)), 13) :\n    title_default\n\nfig = Figure(resolution = (1600, 900))\n\nax = Axis(\n    fig[1, 1];\n    title          = title_text,\n    titlesize      = title_size,\n    xlabel         = \"Proportion of CPU samples\",\n    ylabel         = \"Stack depth (caller → callee)\",\n    xlabelsize     = 14,\n    ylabelsize     = 13,\n    xticklabelsize = 12,\n    limits         = ((-0.002, 1.002), (-0.15, max_depth + 1.75)),\n    xticks         = (0:0.2:1.0, [\"0%\", \"20%\", \"40%\", \"60%\", \"80%\", \"100%\"]),\n)\n\n# Draw flame bars: one rectangle per node, hairline page-bg stroke between\n# adjacent siblings keeps same-color neighbours visually distinct.\nbar_height = 0.93\nrects = [Rect2f(n.x0, n.depth, n.w, bar_height) for n in nodes]\nflame_idx = [(abs(hash(n.name)) % length(FLAME_COLORS)) + 1 for n in nodes]\nfill_colors = [FLAME_COLORS[i] for i in flame_idx]\npoly!(ax, rects;\n    color       = fill_colors,\n    strokecolor = PAGE_BG,\n    strokewidth = 1.5,\n)\n\n# Focal-point cue: a thicker INK outline on the dominant hot-path leaf, plus\n# a short label above it stating the share of CPU samples. Subtle enough to\n# preserve the flame aesthetic, explicit enough to direct the eye.\npoly!(ax, Rect2f(hot.x0, hot.depth, hot.w, bar_height);\n    color       = (:white, 0.0),\n    strokecolor = INK,\n    strokewidth = 2.5,\n)\nhot_pct = round(Int, hot.w * 100)\ntext!(ax, hot.x0 + hot.w / 2, hot.depth + bar_height + 0.18;\n    text     = \"▼ hot path · $(hot_pct)% of CPU samples\",\n    align    = (:center, :bottom),\n    color    = INK_SOFT,\n    fontsize = 12,\n)\n\n# Function-name labels, only where the bar is wide enough to fit the text.\n# Label ink is chosen per fill color: dark on amber/ochre, light on red.\nlabel_fontsize = 12\nfor (n, fc_idx) in zip(nodes, flame_idx)\n    needed = length(n.name) * 0.0058 + 0.012\n    if n.w >= needed\n        text!(ax, n.x0 + 0.005, n.depth + bar_height / 2;\n            text     = n.name,\n            align    = (:left, :center),\n            color    = FLAME_LABEL_INK[fc_idx],\n            fontsize = label_fontsize,\n        )\n    end\nend\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}