diff options
Diffstat (limited to 'internal/flamegraph/layout.go')
| -rw-r--r-- | internal/flamegraph/layout.go | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/internal/flamegraph/layout.go b/internal/flamegraph/layout.go new file mode 100644 index 0000000..c319800 --- /dev/null +++ b/internal/flamegraph/layout.go @@ -0,0 +1,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 + } +} |
