diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 15:06:22 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 15:06:22 +0200 |
| commit | 1530bf2856bbb32a6e0457596b55c07f3836a0ec (patch) | |
| tree | d699766a2607042de0f8278652b9b7cde2426b84 /internal/tui/flamegraph/renderer.go | |
| parent | 4737786fd4a417ff94e22e4f72a1e924d4e033dd (diff) | |
flamegraph: use full viewport with capped bar height and preserve footer/status
Diffstat (limited to 'internal/tui/flamegraph/renderer.go')
| -rw-r--r-- | internal/tui/flamegraph/renderer.go | 68 |
1 files changed, 57 insertions, 11 deletions
diff --git a/internal/tui/flamegraph/renderer.go b/internal/tui/flamegraph/renderer.go index f2ab08e..3ae9a11 100644 --- a/internal/tui/flamegraph/renderer.go +++ b/internal/tui/flamegraph/renderer.go @@ -17,6 +17,7 @@ import ( const pathSeparator = "\x1f" const pathSeparatorByte = '\x1f' const minFlameWidth = 60 +const maxBarVisualHeight = 3 // BuildTerminalLayout converts a live trie snapshot into terminal frame cells. func BuildTerminalLayout(snapshot *snapshotNode, width, height int) []tuiFrame { @@ -223,10 +224,16 @@ func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtr availableRows := height - 2 // toolbar + status maxRow := maxFrameRowForSet(frames, nil) + totalDepthRows := maxRow + 1 + barHeight := computeBarHeight(availableRows, totalDepthRows, maxBarVisualHeight) + visibleDepthRows := availableRows / barHeight + if visibleDepthRows < 1 { + visibleDepthRows = 1 + } rowOffset := 0 truncated := false - if maxRow+1 > availableRows { - rowOffset = maxRow + 1 - availableRows + if maxRow+1 > visibleDepthRows { + rowOffset = maxRow + 1 - visibleDepthRows truncated = true } @@ -263,16 +270,16 @@ func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtr status := fmt.Sprintf("Filter %q: %.1f%% system (%d/%d matches, %.1f%% frames shown) | Selected: %s total=%d depth=%d %.2f%% filter", searchQuery, filterSystemShare, pos, len(matches), frameCoverage, selected.Name, selected.Total, selected.Depth, selectedFilterShare) - return renderViewRows(toolbar, status, rowsForRender(frames, width, rowOffset, maxRow, selected.Path, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive), width) + return renderViewRows(toolbar, status, rowsForRender(frames, width, rowOffset, maxRow, barHeight, availableRows, selected.Path, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive), width) } else { status := fmt.Sprintf("Selected: %s [%s] total=%d depth=%d col=%d width=%d share=%.2f%%", selected.Name, compactFramePath(selected.Path), selected.Total, selected.Depth, selected.Col, selected.Width, selectedSystemShare) - return renderViewRows(toolbar, status, rowsForRender(frames, width, rowOffset, maxRow, selected.Path, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive), width) + return renderViewRows(toolbar, status, rowsForRender(frames, width, rowOffset, maxRow, barHeight, availableRows, selected.Path, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive), width) } } -func rowsForRender(frames []tuiFrame, width, rowOffset, maxRow int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) []string { - return buildRenderRows(frames, width, rowOffset, maxRow, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive) +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 renderViewRows(toolbar, status string, rows []string, width int) string { @@ -294,7 +301,7 @@ type indexedFrame struct { frame tuiFrame } -func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) []string { +func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow, barHeight, availableRows int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) []string { rowsByDepth := make(map[int][]indexedFrame) for idx, frame := range frames { if frame.Row < rowOffset || frame.Row > maxRow { @@ -303,18 +310,40 @@ func buildRenderRows(frames []tuiFrame, width, rowOffset, maxRow int, selectedPa rowsByDepth[frame.Row] = append(rowsByDepth[frame.Row], indexedFrame{idx: idx, frame: frame}) } - rows := make([]string, 0, maxRow-rowOffset+1) + if barHeight < 1 { + barHeight = 1 + } + + rows := make([]string, 0, (maxRow-rowOffset+1)*barHeight) for row := maxRow; row >= rowOffset; row-- { framesAtRow := rowsByDepth[row] sort.Slice(framesAtRow, func(i, j int) bool { return framesAtRow[i].frame.Col < framesAtRow[j].frame.Col }) - rows = append(rows, renderRow(framesAtRow, width, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive)) + for repeat := 0; repeat < barHeight; repeat++ { + showLabels := repeat == barHeight/2 + rows = append(rows, renderRow(framesAtRow, width, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive, showLabels)) + } + } + + if availableRows > 0 { + if len(rows) > availableRows { + rows = rows[:availableRows] + } + if len(rows) < availableRows { + blank := strings.Repeat(" ", width) + pad := make([]string, 0, availableRows) + for i := 0; i < availableRows-len(rows); i++ { + pad = append(pad, blank) + } + pad = append(pad, rows...) + rows = pad + } } return rows } -func renderRow(frames []indexedFrame, width int, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive, filterActive bool) string { +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) } @@ -339,7 +368,10 @@ func renderRow(frames []indexedFrame, width int, selectedPath string, subtreeSet if cellWidth <= 0 { continue } - label := frameLabel(frame.Name, cellWidth, item.idx == selectedIdx, matchSet != nil && matchSet[item.idx]) + label := strings.Repeat(" ", cellWidth) + if showLabels { + label = frameLabel(frame.Name, cellWidth, item.idx == selectedIdx, matchSet != nil && matchSet[item.idx]) + } style := styleForFrame(item.idx, frame, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive, filterActive) cell := style.Render(label) b.WriteString(cell) @@ -569,6 +601,20 @@ func filterSampleCoverage(frames []tuiFrame, matchSet map[int]bool, totalBase ui return percentOfTotal(coveredTotal, rootTotal) } +func computeBarHeight(availableRows, depthRows, maxHeight int) int { + if availableRows <= 0 || depthRows <= 0 { + return 1 + } + height := availableRows / depthRows + if height < 1 { + height = 1 + } + if maxHeight > 0 && height > maxHeight { + height = maxHeight + } + return height +} + func filterCoverageTotals(frames []tuiFrame, matchSet map[int]bool, totalBase uint64) (coveredTotal uint64, rootTotal uint64) { if len(frames) == 0 || len(matchSet) == 0 { return 0, 0 |
