diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-26 22:45:23 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-26 22:45:23 +0300 |
| commit | fb5a9c1f5c99559cb013a6ff396eb56a7d1f7be6 (patch) | |
| tree | d1f7bc3667ea20799893148b40bc936303d60f67 /internal/tui/flamegraph/renderer.go | |
| parent | 5533d521ae2183342771ace001624c89e75a994f (diff) | |
flamegraph: variable leaf bar heights for height metric (uo)
Diffstat (limited to 'internal/tui/flamegraph/renderer.go')
| -rw-r--r-- | internal/tui/flamegraph/renderer.go | 70 |
1 files changed, 63 insertions, 7 deletions
diff --git a/internal/tui/flamegraph/renderer.go b/internal/tui/flamegraph/renderer.go index 3a35de9..e5bbae2 100644 --- a/internal/tui/flamegraph/renderer.go +++ b/internal/tui/flamegraph/renderer.go @@ -227,6 +227,7 @@ type renderViewParams struct { rowOffset int maxRow int barHeight int + leafBarHeight int availableRows int visibleFrames int truncated bool @@ -234,12 +235,17 @@ type renderViewParams struct { // computeRenderParams derives the row-layout parameters for a given frame set // and viewport height. -func computeRenderParams(frames []tuiFrame, height int) renderViewParams { +func computeRenderParams(frames []tuiFrame, height int, heightMetricActive bool) renderViewParams { availableRows := height - 2 // toolbar + frame-status line maxRow := maxFrameRowForSet(frames, nil) totalDepthRows := maxRow + 1 barHeight := computeBarHeight(availableRows, totalDepthRows, maxBarVisualHeight) + leafBarHeight := barHeight visibleDepthRows := availableRows / barHeight + if heightMetricActive { + barHeight = 1 + visibleDepthRows = availableRows + } if visibleDepthRows < 1 { visibleDepthRows = 1 } @@ -249,10 +255,18 @@ func computeRenderParams(frames []tuiFrame, height int) renderViewParams { rowOffset = maxRow + 1 - visibleDepthRows truncated = true } + if heightMetricActive { + visibleNonLeafRows := max(0, maxRow-rowOffset) + leafBarHeight = availableRows - visibleNonLeafRows + if leafBarHeight < 1 { + leafBarHeight = 1 + } + } return renderViewParams{ rowOffset: rowOffset, maxRow: maxRow, barHeight: barHeight, + leafBarHeight: leafBarHeight, availableRows: availableRows, visibleFrames: countVisibleFrames(frames, nil), truncated: truncated, @@ -313,7 +327,7 @@ func buildNormalStatus(selected tuiFrame, metricLabel string, globalTotal uint64 // RenderTerminalView renders a terminal flamegraph viewport from laid out frames. // The function is split into helpers (computeRenderParams, buildToolbar, // buildFilteredStatus, buildNormalStatus) to keep each piece under 50 lines. -func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtreeSet, matchSet, filterSet map[int]bool, globalTotal uint64, metricLabel string, isDark, searchActive bool, searchQuery string) string { +func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtreeSet, matchSet, filterSet map[int]bool, globalTotal uint64, metricLabel string, heightMetricActive, isDark, searchActive bool, searchQuery string) string { if width < minFlameWidth { return common.PanelStyle.Render("Flame: terminal too narrow (need >= 60 columns)") } @@ -342,7 +356,7 @@ func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtr if subtreeSet == nil { subtreeSet = computeSubtreeSet(frames, selectedIdx) } - params := computeRenderParams(frames, height) + params := computeRenderParams(frames, height, heightMetricActive) toolbar := buildToolbar(frames, width, params) var status string if filterIsActive { @@ -350,11 +364,11 @@ func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtr } else { status = buildNormalStatus(selected, metricLabel, globalTotal) } - return renderViewRows(toolbar, status, rowsForRender(frames, width, params.rowOffset, params.maxRow, params.barHeight, params.availableRows, selected.Path, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterIsActive), width) + return renderViewRows(toolbar, status, rowsForRender(frames, width, params.rowOffset, params.maxRow, params.barHeight, params.leafBarHeight, params.availableRows, selected.Path, subtreeSet, matchSet, selectedIdx, heightMetricActive, isDark, searchActive, filterIsActive), width) } -func rowsForRender(frames []tuiFrame, width, rowOffset, maxRow, barHeight, availableRows int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) []string { - return buildRenderRows(frames, width, rowOffset, maxRow, barHeight, availableRows, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive) +func rowsForRender(frames []tuiFrame, width, rowOffset, maxRow, barHeight, leafBarHeight, availableRows int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, heightMetricActive, isDark, searchActive, filterActive bool) []string { + return buildRenderRows(frames, width, rowOffset, maxRow, barHeight, leafBarHeight, availableRows, selectedPath, subtreeSet, matchSet, selectedIdx, heightMetricActive, isDark, searchActive, filterActive) } func renderViewRows(toolbar, status string, rows []string, width int) string { @@ -376,7 +390,7 @@ type indexedFrame struct { frame tuiFrame } -func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow, barHeight, availableRows int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) []string { +func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow, barHeight, leafBarHeight, availableRows int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, heightMetricActive, isDark, searchActive, filterActive bool) []string { rowsByDepth := make(map[int][]indexedFrame) for idx, frame := range frames { if frame.Row < rowOffset || frame.Row > maxRow { @@ -395,6 +409,14 @@ func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow, barHeight, ava slices.SortFunc(framesAtRow, func(a, b indexedFrame) int { return cmp.Compare(a.frame.Col, b.frame.Col) }) + if heightMetricActive && row == maxRow { + frameHeights := leafFrameHeights(framesAtRow, leafBarHeight) + for h := leafBarHeight - 1; h >= 0; h-- { + showLabels := h == 0 + rows = append(rows, renderLeafRowBand(framesAtRow, frameHeights, h, width, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive, showLabels)) + } + continue + } for repeat := 0; repeat < barHeight; repeat++ { showLabels := repeat == barHeight/2 rows = append(rows, renderRow(framesAtRow, width, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive, showLabels)) @@ -418,6 +440,40 @@ func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow, barHeight, ava return rows } +func leafFrameHeights(frames []indexedFrame, leafBarHeight int) map[int]int { + heights := make(map[int]int, len(frames)) + if leafBarHeight < 1 { + leafBarHeight = 1 + } + maxHeightTotal := uint64(0) + for _, item := range frames { + if item.frame.HeightTotal > maxHeightTotal { + maxHeightTotal = item.frame.HeightTotal + } + } + for _, item := range frames { + frameHeight := 1 + if maxHeightTotal > 0 { + scaled := math.Round(float64(leafBarHeight) * (float64(item.frame.HeightTotal) / float64(maxHeightTotal))) + frameHeight = int(scaled) + } + frameHeight = max(1, frameHeight) + frameHeight = min(leafBarHeight, frameHeight) + heights[item.idx] = frameHeight + } + return heights +} + +func renderLeafRowBand(frames []indexedFrame, frameHeights map[int]int, band, width int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive, showLabels bool) string { + visible := make([]indexedFrame, 0, len(frames)) + for _, item := range frames { + if frameHeights[item.idx] > band { + visible = append(visible, item) + } + } + return renderRow(visible, width, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive, showLabels) +} + func renderRow(frames []indexedFrame, width int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive, showLabels bool) string { if len(frames) == 0 { return strings.Repeat(" ", width) |
