diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-11 20:02:47 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-11 20:02:47 +0300 |
| commit | 933be1ba2dbb7f6397a4112969bc85a4eac9d155 (patch) | |
| tree | 1c9f66ee8321880f322b0ddf8030e64dc2af976b /internal/tui/dashboard | |
| parent | 662dcfd7ca96d0d4157f9d30b04518db5adfbe45 (diff) | |
speed up flame graph TUI under heavy event load
Move the per-tick snapshot refresh off the Bubble Tea update goroutine,
add a frame ancestry index so navigation and filter helpers run in
O(subtree) instead of O(frames), skip refresh and animation while the
user is actively pressing keys, and memoize View() output. Keystrokes
(pause, filter, navigate) now land within one frame even when the live
trie ingests thousands of events per tick.
- new SnapshotTree() on LiveTrie bypasses JSON marshal+unmarshal
- RefreshFromLiveTrieCmd runs SnapshotTree + layout + ancestry on a
background goroutine, coalesced via refreshInFlight, and returns a
flameSnapshotReadyMsg the Update loop applies cheaply
- driveWindow gate (250 ms after last key press) skips refresh dispatch
and snaps frames directly to target without animation while the user
is driving
- View() caches its rendered string keyed on the inputs that affect
output; cache is bypassed during animation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'internal/tui/dashboard')
| -rw-r--r-- | internal/tui/dashboard/model.go | 17 |
1 files changed, 10 insertions, 7 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index 79e3b38..df8f9f1 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -232,14 +232,17 @@ func (m Model) handleFlameTick() (tea.Model, tea.Cmd) { if !m.focused || m.activeTab != TabFlame { return m, nil } - var animCmd tea.Cmd - if m.liveTrie != nil && m.flamegraphModel.RefreshFromLiveTrie() { - animCmd = m.flamegraphModel.AnimationCmd() - } - if animCmd == nil { - return m, flameTickCmd() + // Always re-arm the 200 ms tick. The snapshot refresh itself runs on a + // background goroutine via RefreshFromLiveTrieCmd, so even when a previous + // refresh is still in flight (the cmd returns nil and skips), the tick + // channel stays alive. + cmds := []tea.Cmd{flameTickCmd()} + if m.liveTrie != nil { + if refreshCmd := m.flamegraphModel.RefreshFromLiveTrieCmd(); refreshCmd != nil { + cmds = append(cmds, refreshCmd) + } } - return m, tea.Batch(flameTickCmd(), animCmd) + return m, tea.Batch(cmds...) } func (m Model) handleBubbleTick() (tea.Model, tea.Cmd) { |
