Skip to content

fix(frontend): apply validated max-nodes default to v1 flame graph output - #3

Open
1linkovdim wants to merge 1 commit into
mainfrom
fix/frontend-flamegraph-default-maxnodes
Open

fix(frontend): apply validated max-nodes default to v1 flame graph output#3
1linkovdim wants to merge 1 commit into
mainfrom
fix/frontend-flamegraph-default-maxnodes

Conversation

@1linkovdim

Copy link
Copy Markdown
Owner

Problem

On the v1 read path, SelectMergeStacktraces and SelectMergeSpanProfile validate max-nodes (applying the per-tenant max_flamegraph_nodes_default and clamping to max_flamegraph_nodes_max) and use the validated value for the per-query fan-out — but then build the final merged flame graph / tree with the raw request value c.Msg.GetMaxNodes().

When a client omits maxNodes (e.g. Grafana Explore Profiles, which only sends the parameter when it's set), the raw value is 0. Tree.minValue treats maxNodes < 1 as "no limit", so the final flame graph is emitted untruncated:

  • the configured default (max_flamegraph_nodes_default) is ignored, and
  • the max ceiling is bypassed — the merged tree can exceed max_flamegraph_nodes_max, because that limit lives inside ValidateMaxNodes, which the final assembly skips.

User-visible effect

An omitted maxNodes renders a far larger tree than an explicit one. So on a broad/deep query (worst on transformed-profiles, where the injected service-name root makes a very wide tree), the top-table's other bucket grows when you raise maxNodes toward the configured default — the opposite of what you'd expect — because the "default" view is actually unbounded.

Repro on a fleet query ({"nf.app"=~".*dgw.*"}, transformed-profiles): omit → 234k-node tree / 6.4M other; explicit 16384 → 26k nodes / 8.7M other; explicit 65536 → 7.67M; 131073 → correctly rejected. Total samples conserved throughout — it's purely truncation of the final assembly.

Fix

  • selectMergeStacktracesTree now returns the validated maxNodes; SelectMergeStacktraces uses it for both NewFlameGraph and Tree.Bytes.
  • SelectMergeSpanProfile uses its in-scope validated maxNodes for the final assembly.
  • Diff was already correct (it passes the validated maxNodes to NewFlamegraphDiff); its call sites were updated for the new helper signature.

The v2 read path already threads the validated value through TreeQuery, so this aligns v1 with v2.

Tests

Adds Test_Frontend_SelectMergeStacktraces_MaxNodesDefault:

  • omitted maxNodes truncates to the configured default (produces an other node, bounded node count, not the full tree);
  • an explicit value above the max is rejected.

Verified the test fails without the fix and passes with it. go test ./pkg/frontend/... ./pkg/model/ all green; gofmt clean.

🤖 Generated with Claude Code

…tput

The v1 frontend SelectMergeStacktraces and SelectMergeSpanProfile handlers
validated max-nodes (applying the per-tenant `max_flamegraph_nodes_default`
and clamping to `max_flamegraph_nodes_max`) and used the validated value for
the per-query fan-out — but then built the final merged flame graph / tree
with the raw request value `c.Msg.GetMaxNodes()`.

When a client omits max-nodes (e.g. Grafana Explore Profiles, which only
sends the parameter when set), the raw value is 0. `Tree.minValue` treats
maxNodes < 1 as "no limit", so the final flame graph was emitted untruncated:
the configured default was ignored and the max ceiling was bypassed (the
merged tree could exceed `max_flamegraph_nodes_max`, since that limit lives in
ValidateMaxNodes which the final assembly skipped). The user-visible effect is
that an omitted max-nodes renders a far larger tree than an explicit one, so
"other" appears to grow when you *raise* max-nodes toward the default.

Fix: return the validated maxNodes from selectMergeStacktracesTree and use it
for both NewFlameGraph and Tree.Bytes; use the in-scope validated maxNodes in
the span-profile handler. The v2 read path already threads the validated value
through TreeQuery, so this aligns v1 with v2. Diff was already correct.

Adds a regression test asserting an omitted max-nodes truncates to the
configured default (produces an "other" node, bounded node count) and that an
explicit value above the max is rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant