diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-05 23:47:19 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-05 23:47:19 +0200 |
| commit | 77310af6f292004fbdd11eaa0bcfeaff812a365d (patch) | |
| tree | 02c0c242759efa8a9fa2dfc970515bcc6b77bc1a /internal/tui/flamegraph | |
| parent | b48fb545191be25e9795e79336c45c439466986c (diff) | |
Make flame tab default and fix flame hotkey routing
Diffstat (limited to 'internal/tui/flamegraph')
| -rw-r--r-- | internal/tui/flamegraph/model.go | 28 | ||||
| -rw-r--r-- | internal/tui/flamegraph/renderer.go | 26 | ||||
| -rw-r--r-- | internal/tui/flamegraph/renderer_test.go | 24 |
3 files changed, 78 insertions, 0 deletions
diff --git a/internal/tui/flamegraph/model.go b/internal/tui/flamegraph/model.go index 5f5a83c..5d101c2 100644 --- a/internal/tui/flamegraph/model.go +++ b/internal/tui/flamegraph/model.go @@ -208,6 +208,34 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } +// ConsumesKey reports whether the flamegraph should handle a key press before +// dashboard- or app-level shortcuts. +func (m Model) ConsumesKey(msg tea.KeyPressMsg) bool { + if m.searchActive { + return true + } + switch { + case msg.String() == "/", + msg.String() == "n", + msg.String() == "N", + msg.String() == "p", + msg.String() == "r", + msg.String() == "o", + msg.String() == "?": + return true + case key.Matches(msg, m.keys.ZoomIn), + key.Matches(msg, m.keys.ZoomUndo), + key.Matches(msg, m.keys.ZoomReset), + key.Matches(msg, m.keys.MoveShallower), + key.Matches(msg, m.keys.MoveDeeper), + key.Matches(msg, m.keys.PrevSibling), + key.Matches(msg, m.keys.NextSibling): + return true + default: + return false + } +} + // View renders the flamegraph viewport. func (m Model) View() tea.View { content := RenderTerminalView(m.frames, m.width, m.height, m.selectedIdx, m.subtreeSet, m.matchIndices, m.isDark, m.searchActive, m.searchQuery) diff --git a/internal/tui/flamegraph/renderer.go b/internal/tui/flamegraph/renderer.go index ad74173..9f31023 100644 --- a/internal/tui/flamegraph/renderer.go +++ b/internal/tui/flamegraph/renderer.go @@ -102,6 +102,10 @@ func frameName(name string, depth int) string { } func terminalFrameColor(name string) color.Color { + if semantic, ok := semanticFrameColor(name); ok { + return semantic + } + hasher := fnv.New32a() _, _ = hasher.Write([]byte(name)) h := hasher.Sum32() @@ -113,6 +117,28 @@ func terminalFrameColor(name string) color.Color { } } +func semanticFrameColor(name string) (color.Color, bool) { + label := strings.ToLower(strings.TrimSpace(name)) + switch { + case label == "": + return nil, false + case strings.Contains(label, "read"), strings.Contains(label, "pread"): + return color.RGBA{R: 78, G: 132, B: 201, A: 255}, true // read I/O: blue + case strings.Contains(label, "write"), strings.Contains(label, "pwrite"): + return color.RGBA{R: 222, G: 122, B: 58, A: 255}, true // write I/O: orange + case strings.Contains(label, "open"), strings.Contains(label, "close"), strings.Contains(label, "stat"), strings.Contains(label, "rename"), strings.Contains(label, "link"): + return color.RGBA{R: 196, G: 168, B: 72, A: 255}, true // metadata I/O: amber + case strings.HasPrefix(label, "/"), strings.Contains(label, "path:"), strings.Contains(label, "/"): + return color.RGBA{R: 88, G: 156, B: 84, A: 255}, true // file paths: green + case strings.Contains(label, "pid"), strings.Contains(label, "tid"): + return color.RGBA{R: 67, G: 151, B: 149, A: 255}, true // process/thread dimensions: teal + case strings.HasPrefix(label, "sys_"): + return color.RGBA{R: 191, G: 99, B: 74, A: 255}, true // other syscall buckets: rust + default: + return nil, false + } +} + // RenderTerminalView renders a terminal flamegraph viewport from laid out frames. func RenderTerminalView(frames []tuiFrame, width, height, selectedIdx int, subtreeSet, matchSet map[int]bool, isDark, searchActive bool, searchQuery string) string { if width < minFlameWidth { diff --git a/internal/tui/flamegraph/renderer_test.go b/internal/tui/flamegraph/renderer_test.go index ca837fe..eb111b8 100644 --- a/internal/tui/flamegraph/renderer_test.go +++ b/internal/tui/flamegraph/renderer_test.go @@ -1,6 +1,7 @@ package flamegraph import ( + "image/color" "strings" "testing" ) @@ -108,6 +109,29 @@ func TestBuildTerminalLayoutUsesPathSeparatorAndColor(t *testing.T) { } } +func TestTerminalFrameColorSemanticPalette(t *testing.T) { + tests := []struct { + name string + label string + want color.RGBA + }{ + {name: "read", label: "sys_enter_read", want: color.RGBA{R: 78, G: 132, B: 201, A: 255}}, + {name: "write", label: "sys_enter_write", want: color.RGBA{R: 222, G: 122, B: 58, A: 255}}, + {name: "metadata", label: "sys_enter_openat", want: color.RGBA{R: 196, G: 168, B: 72, A: 255}}, + {name: "path", label: "/var/log/app.log", want: color.RGBA{R: 88, G: 156, B: 84, A: 255}}, + {name: "pid", label: "pid=1234", want: color.RGBA{R: 67, G: 151, B: 149, A: 255}}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := terminalFrameColor(tc.label) + if got != tc.want { + t.Fatalf("unexpected semantic color for %q: got=%v want=%v", tc.label, got, tc.want) + } + }) + } +} + func TestRenderTerminalViewShowsNarrowMessage(t *testing.T) { out := RenderTerminalView(nil, 50, 10, 0, nil, nil, true, false, "") if !strings.Contains(out, "terminal too narrow") { |
