From 69e9f9d0b7266778d55b06ad2662d26f62531fcf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 20:47:59 +0000 Subject: [PATCH 1/5] feat(makie): implement flamegraph-basic --- .../implementations/julia/makie.jl | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 plots/flamegraph-basic/implementations/julia/makie.jl diff --git a/plots/flamegraph-basic/implementations/julia/makie.jl b/plots/flamegraph-basic/implementations/julia/makie.jl new file mode 100644 index 0000000000..4fc25bc9db --- /dev/null +++ b/plots/flamegraph-basic/implementations/julia/makie.jl @@ -0,0 +1,159 @@ +# anyplot.ai +# flamegraph-basic: Flame Graph for Performance Profiling +# Library: Makie.jl | Julia 1.11 +# Quality: pending | Created: 2026-06-08 + +using CairoMakie +using Colors +using Random + +Random.seed!(42) + +# Theme tokens +const THEME = get(ENV, "ANYPLOT_THEME", "light") +const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" +const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" +const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" + +# Imprint warm subset — semantic exception (the conventional flame-graph +# aesthetic is yellow → orange → red, so brand green sits out for this spec). +const FLAME_COLORS = [ + colorant"#DDCC77", # amber (Imprint anchor — warning / heat) + colorant"#BD8233", # ochre (Imprint #4) + colorant"#AE3030", # matte red (Imprint #5) +] + +# Simulated CPU profile of a web request handler. +# Each entry: (semicolon-delimited stack from root to leaf, sample count). +profile = [ + ("main;server.handle_request;parse_request;read_headers", 18), + ("main;server.handle_request;parse_request;parse_body", 12), + ("main;server.handle_request;app.route;auth.verify;jwt.decode", 22), + ("main;server.handle_request;app.route;auth.verify;cache.get", 9), + ("main;server.handle_request;app.route;user_handler;db.query;db.connect", 14), + ("main;server.handle_request;app.route;user_handler;db.query;db.execute;db.fetch_rows", 86), + ("main;server.handle_request;app.route;user_handler;db.query;db.execute;db.parse_result", 32), + ("main;server.handle_request;app.route;user_handler;serializer.to_json", 27), + ("main;server.handle_request;app.route;user_handler;serializer.escape_html", 11), + ("main;server.handle_request;app.route;product_handler;db.query;db.execute;db.fetch_rows", 41), + ("main;server.handle_request;app.route;product_handler;serializer.to_json", 15), + ("main;server.handle_request;app.route;product_handler;recommend;model.predict;matmul", 48), + ("main;server.handle_request;app.route;product_handler;recommend;model.predict;softmax", 9), + ("main;server.handle_request;app.route;product_handler;recommend;feature_lookup;cache.get", 7), + ("main;server.handle_request;send_response;write_headers", 5), + ("main;server.handle_request;send_response;write_body;gzip.compress", 19), + ("main;server.handle_request;send_response;write_body;tcp.send", 8), + ("main;server.poll_events;epoll_wait", 24), + ("main;runtime.gc;mark_phase;walk_heap", 31), + ("main;runtime.gc;sweep_phase", 12), +] + +total_samples = sum(samples for (_, samples) in profile) + +# Aggregate each (depth, prefix) into total samples; record children sets. +counts = Dict{Tuple{Int,String},Int}() +children = Dict{Tuple{Int,String},Set{String}}() +for (stack, samples) in profile + parts = String.(split(stack, ';')) + for i in 1:length(parts) + prefix = join(parts[1:i], ';') + key = (i - 1, prefix) + counts[key] = get(counts, key, 0) + samples + if i > 1 + pkey = (i - 2, join(parts[1:i-1], ';')) + push!(get!(children, pkey, Set{String}()), prefix) + end + end +end + +# Lay out rectangles top-down from the root, children sorted alphabetically. +# Iterative DFS keeps the implementation top-level — no recursive function. +NodeT = NamedTuple{(:depth, :x0, :w, :name),Tuple{Int,Float64,Float64,String}} +nodes = NodeT[] +queue = [("main", 0, 0.0)] +while !isempty(queue) + prefix, depth, x0 = pop!(queue) + width = counts[(depth, prefix)] / total_samples + name = String(split(prefix, ';')[end]) + push!(nodes, (depth = depth, x0 = x0, w = width, name = name)) + + kids = sort!(collect(get(children, (depth, prefix), Set{String}()))) + child_starts = Float64[] + cursor = x0 + for c in kids + push!(child_starts, cursor) + cursor += counts[(depth + 1, c)] / total_samples + end + for i in length(kids):-1:1 + push!(queue, (kids[i], depth + 1, child_starts[i])) + end +end + +max_depth = maximum(n.depth for n in nodes) + +# Title scaled to fit when prefixed with a descriptive subtitle. +title_text = "CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai" +title_default = 20 +title_size = length(title_text) > 67 ? + max(round(Int, title_default * 67 / length(title_text)), 13) : + title_default + +fig = Figure( + resolution = (1600, 900), + fontsize = 14, + backgroundcolor = PAGE_BG, +) + +ax = Axis( + fig[1, 1]; + title = title_text, + titlesize = title_size, + titlecolor = INK, + xlabel = "Proportion of CPU samples", + ylabel = "Stack depth (caller → callee)", + xlabelcolor = INK, + ylabelcolor = INK_SOFT, + xlabelsize = 14, + ylabelsize = 13, + xticklabelcolor = INK_SOFT, + xticklabelsize = 12, + backgroundcolor = PAGE_BG, + topspinevisible = false, + rightspinevisible = false, + leftspinevisible = false, + bottomspinecolor = INK_SOFT, + xtickcolor = INK_SOFT, + yticksvisible = false, + yticklabelsvisible = false, + xgridvisible = false, + ygridvisible = false, + limits = ((-0.002, 1.002), (-0.15, max_depth + 1.0)), + xticks = (0:0.2:1.0, ["0%", "20%", "40%", "60%", "80%", "100%"]), +) + +# Draw flame bars: one rectangle per node, hairline page-bg stroke for separation. +bar_height = 0.93 +rects = [Rect2f(n.x0, n.depth, n.w, bar_height) for n in nodes] +fill_colors = [FLAME_COLORS[(abs(hash(n.name)) % length(FLAME_COLORS)) + 1] for n in nodes] +poly!(ax, rects; + color = fill_colors, + strokecolor = PAGE_BG, + strokewidth = 1.5, +) + +# Function-name labels, only where the bar is wide enough to fit the text. +# Approx 0.0058 axis units per char at fontsize 12; small left pad. +label_fontsize = 12 +for n in nodes + needed = length(n.name) * 0.0058 + 0.012 + if n.w >= needed + text!(ax, n.x0 + 0.005, n.depth + bar_height / 2; + text = n.name, + align = (:left, :center), + color = colorant"#1A1A17", + fontsize = label_fontsize, + ) + end +end + +save("plot-$(THEME).png", fig; px_per_unit = 2) From c938f94f4e6fd1e3d33da7336010ad915cb30318 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 20:48:14 +0000 Subject: [PATCH 2/5] chore(makie): add metadata for flamegraph-basic --- .../metadata/julia/makie.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/flamegraph-basic/metadata/julia/makie.yaml diff --git a/plots/flamegraph-basic/metadata/julia/makie.yaml b/plots/flamegraph-basic/metadata/julia/makie.yaml new file mode 100644 index 0000000000..8df2e590a3 --- /dev/null +++ b/plots/flamegraph-basic/metadata/julia/makie.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for makie implementation of flamegraph-basic +# Auto-generated by impl-generate.yml + +library: makie +language: julia +specification_id: flamegraph-basic +created: '2026-06-08T20:48:13Z' +updated: '2026-06-08T20:48:13Z' +generated_by: claude-opus +workflow_run: 27165613659 +issue: 4665 +language_version: 1.11.9 +library_version: 0.22.10 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/flamegraph-basic/julia/makie/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/flamegraph-basic/julia/makie/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: null +review: + strengths: [] + weaknesses: [] From 3d72bb4bc32c5f7a81d2fae97c3ebef2e9c06ea0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 20:53:08 +0000 Subject: [PATCH 3/5] chore(makie): update quality score 87 and review feedback for flamegraph-basic --- .../implementations/julia/makie.jl | 4 +- .../metadata/julia/makie.yaml | 272 +++++++++++++++++- 2 files changed, 267 insertions(+), 9 deletions(-) diff --git a/plots/flamegraph-basic/implementations/julia/makie.jl b/plots/flamegraph-basic/implementations/julia/makie.jl index 4fc25bc9db..776ad47995 100644 --- a/plots/flamegraph-basic/implementations/julia/makie.jl +++ b/plots/flamegraph-basic/implementations/julia/makie.jl @@ -1,7 +1,7 @@ # anyplot.ai # flamegraph-basic: Flame Graph for Performance Profiling -# Library: Makie.jl | Julia 1.11 -# Quality: pending | Created: 2026-06-08 +# Library: makie 0.22.10 | Julia 1.11.9 +# Quality: 87/100 | Created: 2026-06-08 using CairoMakie using Colors diff --git a/plots/flamegraph-basic/metadata/julia/makie.yaml b/plots/flamegraph-basic/metadata/julia/makie.yaml index 8df2e590a3..0072c3cf98 100644 --- a/plots/flamegraph-basic/metadata/julia/makie.yaml +++ b/plots/flamegraph-basic/metadata/julia/makie.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for makie implementation of flamegraph-basic -# Auto-generated by impl-generate.yml - library: makie language: julia specification_id: flamegraph-basic created: '2026-06-08T20:48:13Z' -updated: '2026-06-08T20:48:13Z' +updated: '2026-06-08T20:53:08Z' generated_by: claude-opus workflow_run: 27165613659 issue: 4665 @@ -15,7 +12,268 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/flamegrap preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/flamegraph-basic/julia/makie/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: null +quality_score: 87 review: - strengths: [] - weaknesses: [] + strengths: + - 'Correct flame-graph topology: root ''main'' at bottom, callees stacked upward, + widths proportional to aggregated samples — reads as a textbook Brendan-Gregg + flame graph.' + - 'Semantic-exception palette is well-grounded: warm subset of Imprint (#DDCC77 + amber, #BD8233 ochre, #AE3030 red) keeps the conventional flame aesthetic without + inventing colors outside the brand palette, and a comment explains why brand green + sits out.' + - Theme tokens are threaded cleanly through every chrome element (title/labels/ticks/spine) + so both light and dark renders are fully legible; data colors are byte-identical + across themes as required. + - Iterative DFS layout keeps the script top-level (no recursive helper function) + while still producing a deterministic alphabetical sibling order — KISS done right. + - 'Minimal chrome is intentional: top/right/left spines removed, grid removed, y-ticks/labels + removed (depth is conveyed by stacking position) — restraint matches the visualization''s + needs.' + - In-bar label gating ('only draw when bar width ≥ text width') prevents the classic + flame-graph text-overflow mess at narrow stacks. + - Hairline page-background strokes between sibling rectangles cleanly separate adjacent + same-color bars without visible gaps — matches the spec's 'directly adjacent, + minimal gaps' requirement. + - 'Idiomatic batched CairoMakie usage: a single poly! call with a vector of Rect2f + and per-rectangle fill colors instead of looping rect-by-rect.' + weaknesses: + - 'In-bar text color is hardcoded to colorant"#1A1A17" (dark ink). On amber/ochre + bars contrast is fine, but on the matte-red #AE3030 bars (e.g. ''app.route'', + ''server.handle_request'', ''product_handler'') the dark-on-dark-red contrast + is noticeably weak in both themes — labels read but barely. Consider a luminance-aware + label color: dark ink on amber/ochre, light ink (#FAF8F1) on red. This is a VQ-01 + nick, not a failure.' + - DE-03 storytelling is limited to what the flame shape itself conveys — there is + no extra emphasis on the obvious hot path (db.fetch_rows ≈ 24% of total). A subtle + annotation or thicker stroke on the dominant leaf could promote the focal point + without breaking the flame-graph convention. + - 'LM-02 distinctive Makie features are modest: the implementation uses Figure/Axis/poly!/text! + but does not lean on anything uniquely Makie (no GridLayout composition, no recipe, + no Theme(), no Observable). A more characterful Makie touch (e.g. a Theme() block + for the chrome tokens, or a small inset Axis showing the per-depth sample sum) + would lift the score.' + - Color assignment via abs(hash(name)) % 3 produces a stable mapping but is semantically + arbitrary — adjacent siblings can end up the same color, slightly muting the 'distinct + child' signal that the hairline separators are doing the work to restore. Assigning + color by depth (or by child index within a parent) would give a more readable + color rhythm; this is a minor DE-01/VQ-03 polish, not a correctness issue. + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (~#FAF8F1), matches the spec exactly — no pure white anywhere. + Chrome: Title "CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai" rendered in dark ink, centered above the plot at a comfortable ~80% width (fontsize auto-scaled to ~16pt because the title exceeds the 67-char threshold). X-axis label "Proportion of CPU samples" in dark ink, x-tick labels "0%, 20%, 40%, 60%, 80%, 100%" in softer ink — all sharply readable against the warm bg. Y-axis label "Stack depth (caller → callee)" rotated, in soft ink. Y-axis ticks/labels intentionally hidden (depth is shown by stacking position). Only the bottom spine is drawn (soft ink); top/right/left spines removed. No grid. + Data: Seven stack-depth rows, root 'main' at the bottom spanning the full 0-100% width, then 'server.handle_request' + 'runtime.gc' above it, then 'app.route' + 'walk_heap', etc. — pyramid narrows toward the leaves. Bars colored from the warm Imprint subset: amber #DDCC77, ochre #BD8233, matte-red #AE3030 — no brand green (semantic flame-graph exception, properly noted in the code). Hairline page-bg strokes between adjacent siblings give clean separation. In-bar function-name labels appear only where the bar is wide enough to fit the text — narrow stacks (e.g. small auth.verify subbars at the top right) correctly have no labels. The label text is dark ink: high contrast on amber/ochre, noticeably weaker on the red bars but still legible. + Legibility verdict: PASS — all chrome readable; in-bar labels readable but red-bar labels are at the edge of comfortable contrast. + + Dark render (plot-dark.png): + Background: Warm near-black (~#1A1A17), matches the spec — no pure black. + Chrome: Title in light ink (~#F0EFE8), x-axis label in light ink, x-tick labels in soft light ink, y-axis label in soft light ink — every chrome element flipped correctly via the theme tokens. No "dark-on-dark" failures on any chrome element. + Data: Bar colors are byte-identical to the light render (same amber/ochre/red) — exactly as required (only chrome flips, never data). The hairline separators now read as dark slits between same-color sibling bars (they use PAGE_BG which is dark in this theme) — this works structurally and visually. In-bar labels remain dark ink, which is intentional: the bars are warm-colored, not the page bg, so dark labels on amber/ochre are fine. Dark labels on the red bars (e.g. 'app.route', 'server.handle_request', 'product_handler') have the same weak contrast as in the light render — not a theme-adaptation failure, but a global polish opportunity. + Legibility verdict: PASS — no dark-on-dark chrome; data labels readable with the same caveat as the light render (red-bar contrast). + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: 'All chrome readable in both themes; in-bar labels on the matte-red + #AE3030 bars have weak dark-on-dark-red contrast (-1).' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text/data/legend overlap; in-bar labels are gated by bar width. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Every bar is rendered with a visible fill; hairline separators keep + siblings distinct. + - id: VQ-04 + name: Color Accessibility + score: 1 + max: 2 + passed: true + comment: Warm-only palette is the spec-mandated flame aesthetic; luminance + differences between amber/ochre/red help CVD viewers but red-green axis + isn't ideal (-1). + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas 3200x1800 (landscape, gate passed); proportions clean; no + overflow. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: '''Proportion of CPU samples'' (% units) and ''Stack depth (caller + → callee)'' — descriptive with direction.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: Semantic-exception subset of Imprint (amber/ochre/red) — explicitly + grounded in the spec's 'warm flame palette' requirement; backgrounds correct + on both themes. + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Custom warm palette, intentional restraint, theme tokens — solidly + above the DE-01=4 default. + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: Top/right/left spines removed, no grid, y-ticks/labels hidden, hairline + separators — strong minimal-chrome discipline. + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: Flame shape inherently shows the hot path, but no extra emphasis + (annotation, accent) on the dominant db.fetch_rows leaf. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Genuine flame graph: root ''main'' at bottom, callees stacked upward, + alphabetical sibling order.' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Width ∝ samples, bottom-up caller→callee, in-bar labels when wide + enough, warm palette, adjacent bars with hairline separation — every spec + bullet met. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X = proportion of CPU samples (0-100%), Y = stack depth; axis limits + include all data. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title is 'CPU Profile of a Web Request Handler · flamegraph-basic + · julia · makie · anyplot.ai' — descriptive prefix + mandated suffix, language=julia. + No legend needed (color is decorative, not encoding). + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Covers deep stacks (7 levels), wide leaves, narrow leaves, GC + epoll + branches off main — full surface of flame-graph variability. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Web request handler CPU profile (auth, db.query, serializer, recommend, + gc, send_response) — totally plausible and neutral. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Sample counts 5–86, summing to ~450; proportions reasonable, no single + stack dwarfs the rest. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Top-level imperative script; iterative DFS instead of a recursive + function. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Random.seed!(42) set; data is hardcoded so output is deterministic + across runs. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: CairoMakie, Colors, Random — all used. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Reasonable complexity; clear sections; no fake interactivity. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot-$(THEME).png via CairoMakie save with px_per_unit=2 + — canonical. + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Correct Figure/Axis config, batched poly! with Vector{Rect2f} and + per-rectangle colors, text! for labels — idiomatic CairoMakie. + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: true + comment: Batched poly! is somewhat distinctive, but no GridLayout composition, + no Theme(), no Observable, no recipe — modest above the LM-02=1 default. + verdict: APPROVED +impl_tags: + dependencies: + - colors + techniques: + - annotations + - manual-ticks + patterns: + - iteration-over-groups + dataprep: [] + styling: + - minimal-chrome + - publication-ready From b2dce16da3c5644f4499a568ba65ca6822081e39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 21:01:07 +0000 Subject: [PATCH 4/5] fix(makie): address review feedback for flamegraph-basic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempt 1/3 - fixes based on AI review - VQ-01: luminance-aware in-bar label color (dark ink on amber/ochre, light ink on red) so the matte-red bars get readable contrast - DE-03: focal-point cue on the dominant hot-path leaf (db.fetch_rows, 19% of CPU) — thicker INK outline plus a short annotation above it - LM-02: hoist chrome tokens into a Theme() block for more idiomatic Makie styling vocabulary --- .../implementations/julia/makie.jl | 112 ++++++++++++------ 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/plots/flamegraph-basic/implementations/julia/makie.jl b/plots/flamegraph-basic/implementations/julia/makie.jl index 776ad47995..84e96fecd1 100644 --- a/plots/flamegraph-basic/implementations/julia/makie.jl +++ b/plots/flamegraph-basic/implementations/julia/makie.jl @@ -9,7 +9,7 @@ using Random Random.seed!(42) -# Theme tokens +# Theme tokens ---------------------------------------------------------------- const THEME = get(ENV, "ANYPLOT_THEME", "light") const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" @@ -23,6 +23,39 @@ const FLAME_COLORS = [ colorant"#AE3030", # matte red (Imprint #5) ] +# In-bar label ink chosen per fill by relative luminance — dark ink on the +# light amber / ochre bars, light ink on the matte-red bars where dark text +# would lose contrast. +function contrast_ink(c) + r, g, b = red(c), green(c), blue(c) + 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.5 ? + colorant"#1A1A17" : colorant"#FAF8F1" +end +const FLAME_LABEL_INK = [contrast_ink(c) for c in FLAME_COLORS] + +# Theme() hoists chrome tokens into a single declarative block — the per-Axis +# kwargs below only need to override plot-specific knobs (title, limits, etc). +set_theme!(Theme( + fontsize = 14, + backgroundcolor = PAGE_BG, + Axis = ( + backgroundcolor = PAGE_BG, + titlecolor = INK, + xlabelcolor = INK, + ylabelcolor = INK_SOFT, + xticklabelcolor = INK_SOFT, + bottomspinecolor = INK_SOFT, + xtickcolor = INK_SOFT, + topspinevisible = false, + rightspinevisible = false, + leftspinevisible = false, + yticksvisible = false, + yticklabelsvisible = false, + xgridvisible = false, + ygridvisible = false, + ), +)) + # Simulated CPU profile of a web request handler. # Each entry: (semicolon-delimited stack from root to leaf, sample count). profile = [ @@ -68,14 +101,17 @@ end # Lay out rectangles top-down from the root, children sorted alphabetically. # Iterative DFS keeps the implementation top-level — no recursive function. -NodeT = NamedTuple{(:depth, :x0, :w, :name),Tuple{Int,Float64,Float64,String}} +NodeT = NamedTuple{ + (:depth, :x0, :w, :name, :prefix), + Tuple{Int,Float64,Float64,String,String}, +} nodes = NodeT[] queue = [("main", 0, 0.0)] while !isempty(queue) prefix, depth, x0 = pop!(queue) width = counts[(depth, prefix)] / total_samples name = String(split(prefix, ';')[end]) - push!(nodes, (depth = depth, x0 = x0, w = width, name = name)) + push!(nodes, (depth = depth, x0 = x0, w = width, name = name, prefix = prefix)) kids = sort!(collect(get(children, (depth, prefix), Set{String}()))) child_starts = Float64[] @@ -91,6 +127,10 @@ end max_depth = maximum(n.depth for n in nodes) +# Widest leaf = dominant CPU hot path; gets a focal-point accent below. +leaves = filter(n -> !haskey(children, (n.depth, n.prefix)), nodes) +hot = leaves[argmax([l.w for l in leaves])] + # Title scaled to fit when prefixed with a descriptive subtitle. title_text = "CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai" title_default = 20 @@ -98,59 +138,59 @@ title_size = length(title_text) > 67 ? max(round(Int, title_default * 67 / length(title_text)), 13) : title_default -fig = Figure( - resolution = (1600, 900), - fontsize = 14, - backgroundcolor = PAGE_BG, -) +fig = Figure(resolution = (1600, 900)) ax = Axis( fig[1, 1]; - title = title_text, - titlesize = title_size, - titlecolor = INK, - xlabel = "Proportion of CPU samples", - ylabel = "Stack depth (caller → callee)", - xlabelcolor = INK, - ylabelcolor = INK_SOFT, - xlabelsize = 14, - ylabelsize = 13, - xticklabelcolor = INK_SOFT, - xticklabelsize = 12, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinevisible = false, - bottomspinecolor = INK_SOFT, - xtickcolor = INK_SOFT, - yticksvisible = false, - yticklabelsvisible = false, - xgridvisible = false, - ygridvisible = false, - limits = ((-0.002, 1.002), (-0.15, max_depth + 1.0)), - xticks = (0:0.2:1.0, ["0%", "20%", "40%", "60%", "80%", "100%"]), + title = title_text, + titlesize = title_size, + xlabel = "Proportion of CPU samples", + ylabel = "Stack depth (caller → callee)", + xlabelsize = 14, + ylabelsize = 13, + xticklabelsize = 12, + limits = ((-0.002, 1.002), (-0.15, max_depth + 1.75)), + xticks = (0:0.2:1.0, ["0%", "20%", "40%", "60%", "80%", "100%"]), ) -# Draw flame bars: one rectangle per node, hairline page-bg stroke for separation. +# Draw flame bars: one rectangle per node, hairline page-bg stroke between +# adjacent siblings keeps same-color neighbours visually distinct. bar_height = 0.93 rects = [Rect2f(n.x0, n.depth, n.w, bar_height) for n in nodes] -fill_colors = [FLAME_COLORS[(abs(hash(n.name)) % length(FLAME_COLORS)) + 1] for n in nodes] +flame_idx = [(abs(hash(n.name)) % length(FLAME_COLORS)) + 1 for n in nodes] +fill_colors = [FLAME_COLORS[i] for i in flame_idx] poly!(ax, rects; color = fill_colors, strokecolor = PAGE_BG, strokewidth = 1.5, ) +# Focal-point cue: a thicker INK outline on the dominant hot-path leaf, plus +# a short label above it stating the share of CPU samples. Subtle enough to +# preserve the flame aesthetic, explicit enough to direct the eye. +poly!(ax, Rect2f(hot.x0, hot.depth, hot.w, bar_height); + color = (:white, 0.0), + strokecolor = INK, + strokewidth = 2.5, +) +hot_pct = round(Int, hot.w * 100) +text!(ax, hot.x0 + hot.w / 2, hot.depth + bar_height + 0.18; + text = "▼ hot path · $(hot_pct)% of CPU samples", + align = (:center, :bottom), + color = INK_SOFT, + fontsize = 12, +) + # Function-name labels, only where the bar is wide enough to fit the text. -# Approx 0.0058 axis units per char at fontsize 12; small left pad. +# Label ink is chosen per fill color: dark on amber/ochre, light on red. label_fontsize = 12 -for n in nodes +for (n, fc_idx) in zip(nodes, flame_idx) needed = length(n.name) * 0.0058 + 0.012 if n.w >= needed text!(ax, n.x0 + 0.005, n.depth + bar_height / 2; text = n.name, align = (:left, :center), - color = colorant"#1A1A17", + color = FLAME_LABEL_INK[fc_idx], fontsize = label_fontsize, ) end From 76207d0a432932b8f5cca351c874ee80cc529dc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 21:07:36 +0000 Subject: [PATCH 5/5] chore(makie): update quality score 88 and review feedback for flamegraph-basic --- .../implementations/julia/makie.jl | 2 +- .../metadata/julia/makie.yaml | 226 ++++++++---------- 2 files changed, 107 insertions(+), 121 deletions(-) diff --git a/plots/flamegraph-basic/implementations/julia/makie.jl b/plots/flamegraph-basic/implementations/julia/makie.jl index 84e96fecd1..816bfb07cc 100644 --- a/plots/flamegraph-basic/implementations/julia/makie.jl +++ b/plots/flamegraph-basic/implementations/julia/makie.jl @@ -1,7 +1,7 @@ # anyplot.ai # flamegraph-basic: Flame Graph for Performance Profiling # Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 87/100 | Created: 2026-06-08 +# Quality: 88/100 | Created: 2026-06-08 using CairoMakie using Colors diff --git a/plots/flamegraph-basic/metadata/julia/makie.yaml b/plots/flamegraph-basic/metadata/julia/makie.yaml index 0072c3cf98..4f06d2b57e 100644 --- a/plots/flamegraph-basic/metadata/julia/makie.yaml +++ b/plots/flamegraph-basic/metadata/julia/makie.yaml @@ -2,7 +2,7 @@ library: makie language: julia specification_id: flamegraph-basic created: '2026-06-08T20:48:13Z' -updated: '2026-06-08T20:53:08Z' +updated: '2026-06-08T21:07:36Z' generated_by: claude-opus workflow_run: 27165613659 issue: 4665 @@ -12,146 +12,135 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/flamegrap preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/flamegraph-basic/julia/makie/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: 87 +quality_score: 88 review: strengths: - - 'Correct flame-graph topology: root ''main'' at bottom, callees stacked upward, - widths proportional to aggregated samples — reads as a textbook Brendan-Gregg - flame graph.' - - 'Semantic-exception palette is well-grounded: warm subset of Imprint (#DDCC77 - amber, #BD8233 ochre, #AE3030 red) keeps the conventional flame aesthetic without - inventing colors outside the brand palette, and a comment explains why brand green - sits out.' - - Theme tokens are threaded cleanly through every chrome element (title/labels/ticks/spine) - so both light and dark renders are fully legible; data colors are byte-identical - across themes as required. - - Iterative DFS layout keeps the script top-level (no recursive helper function) - while still producing a deterministic alphabetical sibling order — KISS done right. - - 'Minimal chrome is intentional: top/right/left spines removed, grid removed, y-ticks/labels - removed (depth is conveyed by stacking position) — restraint matches the visualization''s - needs.' - - In-bar label gating ('only draw when bar width ≥ text width') prevents the classic - flame-graph text-overflow mess at narrow stacks. - - Hairline page-background strokes between sibling rectangles cleanly separate adjacent - same-color bars without visible gaps — matches the spec's 'directly adjacent, - minimal gaps' requirement. - - 'Idiomatic batched CairoMakie usage: a single poly! call with a vector of Rect2f - and per-rectangle fill colors instead of looping rect-by-rect.' + - Theme tokens (PAGE_BG, INK, INK_SOFT) hoisted into a single Theme() block — idiomatic + CairoMakie and the chrome flips cleanly between light and dark renders with no + element left behind. + - 'Hot-path focal point is genuinely effective: the dominant db.fetch_rows leaf + gets a thick INK outline plus a ''hot path · 19% of CPU samples'' caption, directing + the eye while preserving the flame aesthetic.' + - 'Warm flame-graph palette uses Imprint members only — amber anchor #DDCC77, ochre + #BD8233 (Imprint #4), matte red #AE3030 (Imprint #5) — and the code explicitly + documents this as a semantic exception driven by the spec''s ''yellow → orange + → red'' note.' + - Luminance-based label ink chosen per fill (dark on amber/ochre, light on matte + red) keeps function-name labels readable on every bar color. + - Hairline PAGE_BG stroke between sibling rectangles solves the same-color-adjacency + problem without using a heavy ink border that would compete with the data. + - Title-length adaptive sizing (round(20 × 67 / len(title))) prevents the long descriptive-prefix + title from overflowing on the landscape canvas. + - Canvas hits the canonical 3200×1800 landscape via resolution=(1600, 900) × px_per_unit=2 + — no gate failure. weaknesses: - - 'In-bar text color is hardcoded to colorant"#1A1A17" (dark ink). On amber/ochre - bars contrast is fine, but on the matte-red #AE3030 bars (e.g. ''app.route'', - ''server.handle_request'', ''product_handler'') the dark-on-dark-red contrast - is noticeably weak in both themes — labels read but barely. Consider a luminance-aware - label color: dark ink on amber/ochre, light ink (#FAF8F1) on red. This is a VQ-01 - nick, not a failure.' - - DE-03 storytelling is limited to what the flame shape itself conveys — there is - no extra emphasis on the obvious hot path (db.fetch_rows ≈ 24% of total). A subtle - annotation or thicker stroke on the dominant leaf could promote the focal point - without breaking the flame-graph convention. - - 'LM-02 distinctive Makie features are modest: the implementation uses Figure/Axis/poly!/text! - but does not lean on anything uniquely Makie (no GridLayout composition, no recipe, - no Theme(), no Observable). A more characterful Makie touch (e.g. a Theme() block - for the chrome tokens, or a small inset Axis showing the per-depth sample sum) - would lift the score.' - - Color assignment via abs(hash(name)) % 3 produces a stable mapping but is semantically - arbitrary — adjacent siblings can end up the same color, slightly muting the 'distinct - child' signal that the hairline separators are doing the work to restore. Assigning - color by depth (or by child index within a parent) would give a more readable - color rhythm; this is a minor DE-01/VQ-03 polish, not a correctness issue. + - Profile contains only 20 unique stack traces but the spec asks for 50-500 — well + below the lower bound. Extend the synthetic profile so the flame graph shows a + richer call-tree (more siblings at depths 3-5, more leaves) and the chart density + better matches the spec's intended scale. + - The `contrast_ink(c)` helper is the only top-level function in the script and + slightly bends CQ-01 KISS. Consider inlining the luminance check as a comprehension + since FLAME_COLORS has only 3 entries — e.g., FLAME_LABEL_INK = [colorant"#1A1A17", + colorant"#1A1A17", colorant"#FAF8F1"] hardcoded alongside FLAME_COLORS. + - 'LM-02 distinctive-features ceiling: the implementation uses Makie''s vectorized + poly! with a Rect2f array and the Theme() block, which is idiomatic but not a + strong showcase. Consider Makie-distinctive touches such as a Colorbar tied to + value, or annotation! arrows that lock to data coordinates, to push library-mastery + higher.' image_description: |- Light render (plot-light.png): - Background: Warm off-white (~#FAF8F1), matches the spec exactly — no pure white anywhere. - Chrome: Title "CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai" rendered in dark ink, centered above the plot at a comfortable ~80% width (fontsize auto-scaled to ~16pt because the title exceeds the 67-char threshold). X-axis label "Proportion of CPU samples" in dark ink, x-tick labels "0%, 20%, 40%, 60%, 80%, 100%" in softer ink — all sharply readable against the warm bg. Y-axis label "Stack depth (caller → callee)" rotated, in soft ink. Y-axis ticks/labels intentionally hidden (depth is shown by stacking position). Only the bottom spine is drawn (soft ink); top/right/left spines removed. No grid. - Data: Seven stack-depth rows, root 'main' at the bottom spanning the full 0-100% width, then 'server.handle_request' + 'runtime.gc' above it, then 'app.route' + 'walk_heap', etc. — pyramid narrows toward the leaves. Bars colored from the warm Imprint subset: amber #DDCC77, ochre #BD8233, matte-red #AE3030 — no brand green (semantic flame-graph exception, properly noted in the code). Hairline page-bg strokes between adjacent siblings give clean separation. In-bar function-name labels appear only where the bar is wide enough to fit the text — narrow stacks (e.g. small auth.verify subbars at the top right) correctly have no labels. The label text is dark ink: high contrast on amber/ochre, noticeably weaker on the red bars but still legible. - Legibility verdict: PASS — all chrome readable; in-bar labels readable but red-bar labels are at the edge of comfortable contrast. + Background: warm off-white (#FAF8F1) — confirmed, not pure white. + Chrome: title "CPU Profile of a Web Request Handler · flamegraph-basic · julia · makie · anyplot.ai" in dark INK at top, x-axis label "Proportion of CPU samples" and rotated y-axis label "Stack depth (caller → callee)" both in dark INK along the bottom and left edges. X-axis tick labels (0%, 20%, 40%, 60%, 80%, 100%) in INK_SOFT mid-gray. Bottom spine in INK_SOFT; top, right and left spines hidden; no grid. All chrome cleanly readable. + Data: stacked flame bars in warm Imprint palette — amber #DDCC77, ochre #BD8233 (Imprint #4), matte red #AE3030 (Imprint #5). First categorical series is NOT brand green #009E73 — this is the documented semantic exception per the spec's "warm yellow/orange/red flame aesthetic" note. Bottom row "main" spans the full width in amber; "server.handle_request" / "runtime.gc" sit one row up; "app.route" / "user_handler" / "product_handler" higher; the dominant "db.fetch_rows" leaf (right side of row 5) carries a thick black INK outline plus a "▼ hot path · 19% of CPU samples" annotation in INK_SOFT — the focal point. In-bar function-name labels are dark on amber/ochre, light on matte-red, all legible. + Legibility verdict: PASS Dark render (plot-dark.png): - Background: Warm near-black (~#1A1A17), matches the spec — no pure black. - Chrome: Title in light ink (~#F0EFE8), x-axis label in light ink, x-tick labels in soft light ink, y-axis label in soft light ink — every chrome element flipped correctly via the theme tokens. No "dark-on-dark" failures on any chrome element. - Data: Bar colors are byte-identical to the light render (same amber/ochre/red) — exactly as required (only chrome flips, never data). The hairline separators now read as dark slits between same-color sibling bars (they use PAGE_BG which is dark in this theme) — this works structurally and visually. In-bar labels remain dark ink, which is intentional: the bars are warm-colored, not the page bg, so dark labels on amber/ochre are fine. Dark labels on the red bars (e.g. 'app.route', 'server.handle_request', 'product_handler') have the same weak contrast as in the light render — not a theme-adaptation failure, but a global polish opportunity. - Legibility verdict: PASS — no dark-on-dark chrome; data labels readable with the same caveat as the light render (red-bar contrast). + Background: warm near-black (#1A1A17) — confirmed, not pure black. + Chrome: same title now in light INK (#F0EFE8), same x-axis label and rotated y-axis label in light INK, x-tick labels in INK_SOFT light gray. Bottom spine in INK_SOFT. No dark-on-dark failures — every text element clearly visible against the dark surface. + Data: identical amber / ochre / matte-red fills to the light render (Imprint identity preserved across themes). Hot-path outline correctly flipped to light INK so it reads on the dark surface. Bar geometry and "hot path · 19%" caption identical to light. + Legibility verdict: PASS criteria_checklist: visual_quality: - score: 28 + score: 30 max: 30 items: - id: VQ-01 name: Text Legibility - score: 7 + score: 8 max: 8 passed: true - comment: 'All chrome readable in both themes; in-bar labels on the matte-red - #AE3030 bars have weak dark-on-dark-red contrast (-1).' + comment: Title fontsize adaptively scaled for long descriptive prefix; all + chrome readable in both themes. - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No text/data/legend overlap; in-bar labels are gated by bar width. + comment: No text overlaps; hot-path annotation sits above the highlighted + leaf with adequate clearance. - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Every bar is rendered with a visible fill; hairline separators keep - siblings distinct. + comment: Narrow bars still visible; hairline PAGE_BG stroke separates same-color + siblings without competing with the data. - id: VQ-04 name: Color Accessibility - score: 1 + score: 2 max: 2 passed: true - comment: Warm-only palette is the spec-mandated flame aesthetic; luminance - differences between amber/ochre/red help CVD viewers but red-green axis - isn't ideal (-1). + comment: Warm yellow→ochre→red ramp is CVD-distinguishable and matches the + conventional flame-graph idiom. - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Canvas 3200x1800 (landscape, gate passed); proportions clean; no - overflow. + comment: Canvas hits 3200×1800; no overflow; title proportion comfortable + for a landscape canvas. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: '''Proportion of CPU samples'' (% units) and ''Stack depth (caller - → callee)'' — descriptive with direction.' + comment: Descriptive title with descriptive prefix; both axes labelled; x-axis + ticks formatted as percentages. - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: Semantic-exception subset of Imprint (amber/ochre/red) — explicitly - grounded in the spec's 'warm flame palette' requirement; backgrounds correct - on both themes. + comment: 'Imprint members only (amber anchor + ochre #4 + matte red #5); brand-green + opt-out properly justified via the spec''s warm-aesthetic note; PAGE_BG + and INK tokens correct on both themes.' design_excellence: - score: 13 + score: 16 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 passed: true - comment: Custom warm palette, intentional restraint, theme tokens — solidly - above the DE-01=4 default. + comment: Theme tokens hoisted, luminance-driven label ink, focal-point cue + — polished but not extraordinary. - id: DE-02 name: Visual Refinement score: 5 max: 6 passed: true - comment: Top/right/left spines removed, no grid, y-ticks/labels hidden, hairline - separators — strong minimal-chrome discipline. + comment: Top/right/left spines hidden, no grid, y-ticks suppressed — appropriate + for flame graphs. - id: DE-03 name: Data Storytelling - score: 3 + score: 5 max: 6 passed: true - comment: Flame shape inherently shows the hot path, but no extra emphasis - (annotation, accent) on the dominant db.fetch_rows leaf. + comment: Hot-path outline + caption gives the reader a clear focal point without + breaking the flame aesthetic. spec_compliance: - score: 15 + score: 14 max: 15 items: - id: SC-01 @@ -159,95 +148,90 @@ review: score: 5 max: 5 passed: true - comment: 'Genuine flame graph: root ''main'' at bottom, callees stacked upward, - alphabetical sibling order.' + comment: Correctly stacks bars bottom-to-top with width proportional to samples + — textbook flame graph. - id: SC-02 name: Required Features - score: 4 + score: 3 max: 4 passed: true - comment: Width ∝ samples, bottom-up caller→callee, in-bar labels when wide - enough, warm palette, adjacent bars with hairline separation — every spec - bullet met. + comment: All visual features present; data size (20 traces) falls short of + spec's 50-500 range. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X = proportion of CPU samples (0-100%), Y = stack depth; axis limits - include all data. + comment: X = proportion of CPU samples 0-100%, Y = stack depth ascending. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title is 'CPU Profile of a Web Request Handler · flamegraph-basic - · julia · makie · anyplot.ai' — descriptive prefix + mandated suffix, language=julia. - No legend needed (color is decorative, not encoding). + comment: Title matches descriptive-prefix + canonical-suffix format; no legend + required for single-encoding flame graph. data_quality: - score: 15 + score: 13 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 6 + score: 4 max: 6 passed: true - comment: Covers deep stacks (7 levels), wide leaves, narrow leaves, GC + epoll - branches off main — full surface of flame-graph variability. + comment: Shows hierarchy, samples, depth, hot path — but only 20 traces vs + spec's 50-500, limiting visual richness. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Web request handler CPU profile (auth, db.query, serializer, recommend, - gc, send_response) — totally plausible and neutral. + comment: Plausible web-request profile (server.handle_request → app.route + → user_handler → db.query → ...); neutral, non-controversial. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Sample counts 5–86, summing to ~450; proportions reasonable, no single - stack dwarfs the rest. + comment: Sample counts 5-86 are sensible for a profile snapshot. code_quality: - score: 10 + score: 9 max: 10 items: - id: CQ-01 name: KISS Structure - score: 3 + score: 2 max: 3 passed: true - comment: Top-level imperative script; iterative DFS instead of a recursive - function. + comment: One `contrast_ink` helper function slightly bends KISS for a 3-element + FLAME_COLORS array — could be inlined. - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Random.seed!(42) set; data is hardcoded so output is deterministic - across runs. + comment: Random.seed!(42) set. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: CairoMakie, Colors, Random — all used. + comment: CairoMakie + Colors + Random — all used. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Reasonable complexity; clear sections; no fake interactivity. + comment: Iterative DFS layout avoids recursion; aggregation + child-set bookkeeping + is clean. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot-$(THEME).png via CairoMakie save with px_per_unit=2 - — canonical. - library_mastery: - score: 6 + comment: Saves as plot-$(THEME).png; px_per_unit=2 hits the canonical canvas. + library_features: + score: 7 max: 10 items: - id: LM-01 @@ -255,25 +239,27 @@ review: score: 4 max: 5 passed: true - comment: Correct Figure/Axis config, batched poly! with Vector{Rect2f} and - per-rectangle colors, text! for labels — idiomatic CairoMakie. + comment: Theme() block, Axis kwargs, vectorized poly!(rects; color=array) + — clean Makie idioms. - id: LM-02 name: Distinctive Features - score: 2 + score: 3 max: 5 passed: true - comment: Batched poly! is somewhat distinctive, but no GridLayout composition, - no Theme(), no Observable, no recipe — modest above the LM-02=1 default. + comment: Vectorized poly! with Rect2f array is efficient; Theme() block hoisting + is idiomatic — but no stand-out Makie-only feature. verdict: APPROVED impl_tags: - dependencies: - - colors + dependencies: [] techniques: - annotations - manual-ticks + - patches patterns: + - data-generation - iteration-over-groups - dataprep: [] + dataprep: + - normalization styling: - minimal-chrome - - publication-ready + - edge-highlighting