summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-05 23:30:38 +0200
committerPaul Buetow <paul@buetow.org>2026-03-05 23:30:38 +0200
commit33dbb917be1e30d3de9640bec18d0c619a1a7f67 (patch)
tree8a00d9d646f895a6049ce57c200493a3e06235fc
parent642e3eca14b839c9412a092564f6a76d5777455d (diff)
Reduce flamegraph hot-path allocations
-rw-r--r--internal/tui/flamegraph/bench_test.go8
-rw-r--r--internal/tui/flamegraph/renderer.go17
-rw-r--r--internal/tui/flamegraph/search.go20
3 files changed, 31 insertions, 14 deletions
diff --git a/internal/tui/flamegraph/bench_test.go b/internal/tui/flamegraph/bench_test.go
index e908fed..c000ac0 100644
--- a/internal/tui/flamegraph/bench_test.go
+++ b/internal/tui/flamegraph/bench_test.go
@@ -220,9 +220,11 @@ func BenchmarkAnimationTick(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if !state.Tick(0) {
- state.SetTargets(base)
- state.SetTargets(target)
- _ = state.Tick(0)
+ for idx := range state.springs {
+ state.springs[idx].targetCol += 3
+ state.springs[idx].targetW += 2
+ }
+ state.settled = false
}
frames := state.CurrentFrames()
benchIntSink = frames[len(frames)-1].Width
diff --git a/internal/tui/flamegraph/renderer.go b/internal/tui/flamegraph/renderer.go
index 6fba0b6..ad74173 100644
--- a/internal/tui/flamegraph/renderer.go
+++ b/internal/tui/flamegraph/renderer.go
@@ -14,6 +14,7 @@ import (
)
const pathSeparator = "\x1f"
+const pathSeparatorByte = '\x1f'
const minFlameWidth = 60
// BuildTerminalLayout converts a live trie snapshot into terminal frame cells.
@@ -224,7 +225,7 @@ func renderRow(frames []indexedFrame, width int, selectedPath string, subtreeSet
continue
}
label := padOrTrim(frame.Name, cellWidth)
- style := styleForFrame(item.idx, frame, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive).Width(cellWidth)
+ style := styleForFrame(item.idx, frame, selectedPath, subtreeSet, matchSet, selectedIdx, isDark, searchActive)
cell := style.Render(label)
b.WriteString(cell)
cursor = frame.Col + cellWidth
@@ -255,14 +256,24 @@ func computeSubtreeSetInto(frames []tuiFrame, selectedIdx int, subtree map[int]b
for idx, frame := range frames {
path := frame.Path
if path == selectedPath ||
- strings.HasPrefix(path, selectedPath+pathSeparator) ||
- strings.HasPrefix(selectedPath, path+pathSeparator) {
+ hasPathBoundaryPrefix(path, selectedPath) ||
+ hasPathBoundaryPrefix(selectedPath, path) {
subtree[idx] = true
}
}
return subtree
}
+func hasPathBoundaryPrefix(value, prefix string) bool {
+ if len(value) <= len(prefix) {
+ return false
+ }
+ if !strings.HasPrefix(value, prefix) {
+ return false
+ }
+ return value[len(prefix)] == pathSeparatorByte
+}
+
func styleForFrame(idx int, frame tuiFrame, selectedPath string, subtreeSet, matchSet map[int]bool, selectedIdx int, isDark, searchActive bool) lipgloss.Style {
base := lipgloss.NewStyle().
Foreground(common.ColorBackground).
diff --git a/internal/tui/flamegraph/search.go b/internal/tui/flamegraph/search.go
index 8e392d1..f42c36e 100644
--- a/internal/tui/flamegraph/search.go
+++ b/internal/tui/flamegraph/search.go
@@ -92,21 +92,25 @@ func (m Model) searchFooter() string {
}
func replaceFooterLine(content, footer string) string {
- lines := strings.Split(content, "\n")
- if len(lines) == 0 {
+ if content == "" {
return footer
}
- lines[len(lines)-1] = footer
- return strings.Join(lines, "\n")
+ lastNewline := strings.LastIndexByte(content, '\n')
+ if lastNewline == -1 {
+ return footer
+ }
+ return content[:lastNewline+1] + footer
}
func replaceHeaderLine(content, header string) string {
- lines := strings.Split(content, "\n")
- if len(lines) == 0 {
+ if content == "" {
+ return header
+ }
+ firstNewline := strings.IndexByte(content, '\n')
+ if firstNewline == -1 {
return header
}
- lines[0] = header
- return strings.Join(lines, "\n")
+ return header + content[firstNewline:]
}
func clearBoolMap[K comparable](values map[K]bool) {