1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package flamegraph
import "fmt"
// FrameLayout captures renderer-agnostic flamegraph geometry for a single frame.
//
// The layout is reusable by non-SVG renderers (for example SDL or WASM UIs) so
// they can render the same hierarchy without depending on SVG internals.
type FrameLayout struct {
Name string
Title string
Fill string
X float64
Y float64
Width float64
Height float64
Depth int
Total uint64
Percent float64
}
func sanitizeSVGConfig(cfg SVGConfig) SVGConfig {
if cfg.Width <= 0 || cfg.FrameHeight <= 0 || cfg.FontSize <= 0 || cfg.MinWidthPx <= 0 {
return defaultSVGConfig()
}
if cfg.Title == "" {
cfg.Title = defaultSVGConfig().Title
}
return cfg
}
func canvasHeightFor(cfg SVGConfig, t *trie) int {
return cfg.FrameHeight*(t.maxDepth+1) + 80
}
// BuildFrameLayout builds renderer-agnostic frame coordinates from a flamegraph trie.
func BuildFrameLayout(t *trie, cfg SVGConfig) []FrameLayout {
if t == nil || t.root == nil || t.root.total == 0 {
return nil
}
cfg = sanitizeSVGConfig(cfg)
canvasHeight := canvasHeightFor(cfg, t)
out := make([]FrameLayout, 0, len(t.root.children))
collectFrameLayout(&out, t.root, t.root.total, cfg, 0, 0, canvasHeight, true)
return out
}
func collectFrameLayout(out *[]FrameLayout, node *trieNode, rootTotal uint64,
cfg SVGConfig, x float64, depth int, canvasHeight int, isRoot bool) {
if !isRoot {
w := float64(cfg.Width) * (float64(node.total) / float64(rootTotal))
if w < cfg.MinWidthPx {
return
}
y := float64(canvasHeight - (depth+1)*cfg.FrameHeight)
pct := 100 * float64(node.total) / float64(rootTotal)
*out = append(*out, FrameLayout{
Name: node.name,
Title: fmt.Sprintf("%s (%d, %.2f%%)", node.name, node.total, pct),
Fill: frameColor(node.name),
X: x,
Y: y,
Width: w,
Height: float64(cfg.FrameHeight - 1),
Depth: depth,
Total: node.total,
Percent: pct,
})
}
cursor := x
for _, child := range node.children {
cw := float64(cfg.Width) * (float64(child.total) / float64(rootTotal))
collectFrameLayout(out, child, rootTotal, cfg, cursor, depth+1, canvasHeight, false)
cursor += cw
}
}
|