Skip to content

Commit 990e110

Browse files
committed
fix(callgrind-utils): bound flamegraph folding on large graphs
Folding expanded every root-to-leaf path; on a real graph with heavily-shared subtrees (a Node/V8 profile: ~9.5k nodes, 30k edges) this blew up exponentially and never terminated. Prune any branch whose budget falls below a small fraction of the total. Because budget is conserved and splits across a node's children, a relative floor bounds the surviving paths to ~1/fraction, so the same profile now folds in ~70ms. Small graphs are unaffected (the absolute 1-instruction floor still dominates).
1 parent a4b11f4 commit 990e110

1 file changed

Lines changed: 16 additions & 4 deletions

File tree

callgrind-utils/src/flamegraph.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ use inferno::flamegraph::{self, Options};
33
use super::{error::FlamegraphError, model::CallGraph};
44

55
/// Below this incoming budget a frame rounds to zero cost, so descending
6-
/// further would only emit empty lines. Pruning here also bounds traversal of
7-
/// deep or heavily-shared subtrees.
6+
/// further would only emit empty lines.
87
const MIN_BUDGET: f64 = 1.0;
98

9+
/// Fraction of the total cost below which a branch is pruned. Budget is
10+
/// conserved and splits across a node's children, so a relative floor bounds
11+
/// the number of surviving paths to ~1/this, keeping the fold tractable on
12+
/// large graphs with heavily-shared subtrees (e.g. a real Node/V8 profile).
13+
const MIN_BUDGET_FRACTION: f64 = 0.0005;
14+
1015
impl CallGraph {
1116
/// Fold the graph into Brendan-Gregg "collapsed stack" lines
1217
/// (`root;child;leaf <count>`), the input format for flamegraph tools.
@@ -38,13 +43,18 @@ impl CallGraph {
3843
.map(|i| self.self_cost(i) + out[i].iter().map(|(_, c)| *c).sum::<u64>())
3944
.collect();
4045

46+
let roots = self.roots(&incoming_incl, &incl);
47+
let total: f64 = roots.iter().map(|(_, b)| *b).sum();
48+
let min_budget = MIN_BUDGET.max(total * MIN_BUDGET_FRACTION);
49+
4150
let mut lines = Vec::new();
4251
let mut stack: Vec<usize> = Vec::new();
4352
let mut on_path = vec![false; n];
44-
for (root, budget) in self.roots(&incoming_incl, &incl) {
53+
for (root, budget) in roots {
4554
fold_dfs(
4655
root,
4756
budget,
57+
min_budget,
4858
&mut stack,
4959
&mut on_path,
5060
&out,
@@ -120,6 +130,7 @@ impl CallGraph {
120130
fn fold_dfs(
121131
node: usize,
122132
budget: f64,
133+
min_budget: f64,
123134
stack: &mut Vec<usize>,
124135
on_path: &mut [bool],
125136
out: &[Vec<(usize, u64)>],
@@ -128,7 +139,7 @@ fn fold_dfs(
128139
names: &[&str],
129140
lines: &mut Vec<String>,
130141
) {
131-
if budget < MIN_BUDGET || incl[node] == 0 {
142+
if budget < min_budget || incl[node] == 0 {
132143
return;
133144
}
134145

@@ -151,6 +162,7 @@ fn fold_dfs(
151162
fold_dfs(
152163
child,
153164
child_budget,
165+
min_budget,
154166
stack,
155167
on_path,
156168
out,

0 commit comments

Comments
 (0)