diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-24 08:45:38 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-24 08:45:38 +0200 |
| commit | e5116514c33b2fce6ce2fc7904d6720c5236221f (patch) | |
| tree | e5bc8c9ba37836305ec1d095573535fb8344cbe7 /internal/tui | |
| parent | b01e24374398eb3d343e9472f3262668039db56c (diff) | |
tui: wire eventloop stats engine into dashboard snapshots
Diffstat (limited to 'internal/tui')
| -rw-r--r-- | internal/tui/tui.go | 62 | ||||
| -rw-r--r-- | internal/tui/tui_test.go | 25 |
2 files changed, 83 insertions, 4 deletions
diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 49d2365..b54875a 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "ior/internal/flags" + "ior/internal/statsengine" "ior/internal/tui/pidpicker" + "sync" + "time" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" @@ -26,6 +29,30 @@ const ( // Long-lived tracing work should continue in background goroutines. type TraceStarter func(context.Context) error +type snapshotSource interface { + Snapshot() *statsengine.Snapshot +} + +type dashboardTickMsg struct{} + +var dashboardSourceState struct { + mu sync.RWMutex + source snapshotSource +} + +// SetDashboardSnapshotSource sets the snapshot source used by dashboard mode. +func SetDashboardSnapshotSource(source snapshotSource) { + dashboardSourceState.mu.Lock() + dashboardSourceState.source = source + dashboardSourceState.mu.Unlock() +} + +func getDashboardSnapshotSource() snapshotSource { + dashboardSourceState.mu.RLock() + defer dashboardSourceState.mu.RUnlock() + return dashboardSourceState.source +} + // Run starts the TUI program in alternate screen mode. func Run() error { return RunWithTraceStarter(defaultTraceStarter) @@ -70,7 +97,7 @@ func NewModel(initialPID int, startTrace TraceStarter) Model { model := Model{ screen: ScreenPIDPicker, pidPicker: pidpicker.New(), - dashboard: newDashboardModel(), + dashboard: newDashboardModel(getDashboardSnapshotSource()), keys: Keys, spin: spin, startTrace: startTrace, @@ -111,11 +138,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.handlePidSelected(msg) case TracingStartedMsg: m.attaching = false - return m, nil + return m, dashboardTickCmd() case TracingErrorMsg: m.attaching = false m.lastErr = msg.Err return m, nil + case dashboardTickMsg: + m.dashboard.refresh() + return m, dashboardTickCmd() } if m.attaching { @@ -216,10 +246,15 @@ func (m Model) View() string { type dashboardModel struct { selectedPID int + source snapshotSource + latest *statsengine.Snapshot } -func newDashboardModel() dashboardModel { - return dashboardModel{selectedPID: -1} +func newDashboardModel(source snapshotSource) dashboardModel { + return dashboardModel{ + selectedPID: -1, + source: source, + } } func (d dashboardModel) Init() tea.Cmd { @@ -231,8 +266,27 @@ func (d dashboardModel) Update(tea.Msg) (tea.Model, tea.Cmd) { } func (d dashboardModel) View() string { + if d.latest != nil { + return PanelStyle.Render( + fmt.Sprintf("Dashboard (%d syscalls, %.1f/s)", d.latest.TotalSyscalls, d.latest.SyscallRatePerSec), + ) + } if d.selectedPID > 0 { return PanelStyle.Render(fmt.Sprintf("Dashboard (PID %d)", d.selectedPID)) } return PanelStyle.Render("Dashboard (All PIDs)") } + +func (d *dashboardModel) refresh() { + if source := getDashboardSnapshotSource(); source != nil { + d.source = source + } + if d.source == nil { + return + } + d.latest = d.source.Snapshot() +} + +func dashboardTickCmd() tea.Cmd { + return tea.Tick(time.Second, func(time.Time) tea.Msg { return dashboardTickMsg{} }) +} diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go index 3801813..40ea67b 100644 --- a/internal/tui/tui_test.go +++ b/internal/tui/tui_test.go @@ -3,6 +3,7 @@ package tui import ( "context" "errors" + "ior/internal/statsengine" "strings" "testing" "time" @@ -164,3 +165,27 @@ func TestQuitInvokesTraceStop(t *testing.T) { t.Fatalf("expected stopTrace to be invoked on quit") } } + +type fakeDashboardSource struct { + snap *statsengine.Snapshot +} + +func (f fakeDashboardSource) Snapshot() *statsengine.Snapshot { + return f.snap +} + +func TestDashboardRefreshPicksLateBoundSource(t *testing.T) { + orig := getDashboardSnapshotSource() + defer SetDashboardSnapshotSource(orig) + + SetDashboardSnapshotSource(nil) + d := newDashboardModel(nil) + + want := &statsengine.Snapshot{TotalSyscalls: 77} + SetDashboardSnapshotSource(fakeDashboardSource{snap: want}) + + d.refresh() + if d.latest != want { + t.Fatalf("expected dashboard refresh to bind and use latest global source") + } +} |
