diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-12 22:12:32 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-12 22:12:32 +0300 |
| commit | 8a4cb57703845c1d8ffbc9318a4125818a72a545 (patch) | |
| tree | 5bd9dbc8f77b99de7dced2e867c36ccbf653e533 /internal/tui | |
| parent | a256cbf9f54ab89aeae0aa9408c1c2b25622fa9d (diff) | |
invert dependency: internal no longer imports internal/tui
Introduce internal/runtime as a neutral contract package that both the
core engine (internal) and the TUI layer (internal/tui) depend on.
- internal/runtime: defines TraceStarter, StreamSource, EventSink,
LiveTrieSource, SnapshotSource, ProbeManager, RuntimePublisher,
RuntimeState, TraceRuntimeBindings, and all context key/helper
functions (RuntimeBindingsFromContext, RuntimePublisherFromContext,
ContextWithRuntimeBindings, ContextWithTraceFilters,
TraceFiltersFromContext). These were previously defined in
internal/tui, forcing the core package to import the TUI layer.
- internal/streamrow: add RingBuffer (moved from internal/tui/eventstream)
so the core tracing engine can use the ring buffer without importing
the TUI layer.
- internal/tui/eventstream/ringbuffer.go: change to a thin re-export of
streamrow.RingBuffer via a type alias, preserving the existing API for
all callers within the TUI layer.
- internal/tui/tui.go: replace locally-defined interface declarations
(TraceStarter, SnapshotSource, ProbeManager, RuntimePublisher,
RuntimeState, TraceRuntimeBindings) with type aliases from
internal/runtime. Delegate all context helper functions to runtime.
- internal/ior.go, internal/ior_bpfsetup.go: remove imports of
internal/tui and internal/tui/eventstream; use internal/runtime and
internal/streamrow instead. Add SetTUIRunners injection point so the
concrete TUI runner functions are wired in by cmd/ior/main.go.
- cmd/ior/main.go: call internal.SetTUIRunners with the concrete TUI
runner functions before internal.Run, completing the wiring without
creating a cycle.
- Test files (internal/ior_mode_test.go, internal/bench_pipeline_test.go):
updated to use runtime.* and streamrow.* types in place of tui.* and
eventstream.* types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/tui')
| -rw-r--r-- | internal/tui/eventstream/ringbuffer.go | 72 | ||||
| -rw-r--r-- | internal/tui/tui.go | 154 |
2 files changed, 84 insertions, 142 deletions
diff --git a/internal/tui/eventstream/ringbuffer.go b/internal/tui/eventstream/ringbuffer.go index 87dacae..8644b42 100644 --- a/internal/tui/eventstream/ringbuffer.go +++ b/internal/tui/eventstream/ringbuffer.go @@ -1,69 +1,17 @@ package eventstream -import "sync" +import "ior/internal/streamrow" -const ringBufferCapacity = 10000 +// RingBuffer is a type alias for streamrow.RingBuffer. The concrete +// implementation lives in the lower-level streamrow package so the core +// tracing engine can use it without importing internal/tui/eventstream. +type RingBuffer = streamrow.RingBuffer -type RingBuffer struct { - mu sync.RWMutex - buf []StreamEvent - start int - size int - totalPushed uint64 -} +// ringBufferCapacity mirrors the capacity constant for use in this package +// (e.g. stream table rendering). +const ringBufferCapacity = streamrow.RingBufferCapacity +// NewRingBuffer allocates an empty RingBuffer with the default capacity. func NewRingBuffer() *RingBuffer { - return &RingBuffer{buf: make([]StreamEvent, ringBufferCapacity)} -} - -func (r *RingBuffer) Push(ev StreamEvent) { - r.mu.Lock() - defer r.mu.Unlock() - - if r.size < ringBufferCapacity { - idx := (r.start + r.size) % ringBufferCapacity - r.buf[idx] = ev - r.size++ - } else { - r.buf[r.start] = ev - r.start = (r.start + 1) % ringBufferCapacity - } - r.totalPushed++ -} - -func (r *RingBuffer) Snapshot() []StreamEvent { - r.mu.RLock() - defer r.mu.RUnlock() - - if r.size == 0 { - return make([]StreamEvent, 0) - } - - out := make([]StreamEvent, r.size) - for i := 0; i < r.size; i++ { - out[i] = r.buf[(r.start+i)%ringBufferCapacity] - } - return out -} - -func (r *RingBuffer) Len() int { - r.mu.RLock() - defer r.mu.RUnlock() - return r.size -} - -func (r *RingBuffer) TotalPushed() uint64 { - r.mu.RLock() - defer r.mu.RUnlock() - return r.totalPushed -} - -func (r *RingBuffer) Reset() { - r.mu.Lock() - defer r.mu.Unlock() - - clear(r.buf) - r.start = 0 - r.size = 0 - r.totalPushed = 0 + return streamrow.NewRingBuffer() } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 997fe22..06fafea 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -14,13 +14,12 @@ import ( "ior/internal/flags" "ior/internal/globalfilter" "ior/internal/parquet" - "ior/internal/probemanager" + "ior/internal/runtime" "ior/internal/statsengine" common "ior/internal/tui/common" dashboardui "ior/internal/tui/dashboard" "ior/internal/tui/eventstream" tuiexport "ior/internal/tui/export" - flamegraphtui "ior/internal/tui/flamegraph" "ior/internal/tui/messages" "ior/internal/tui/pidpicker" "ior/internal/tui/probes" @@ -43,70 +42,61 @@ const ( ) // TraceStarter starts tracing and returns when startup succeeds or fails. +// It is a type alias for runtime.TraceStarter so TUI callers need not import +// the runtime package directly. // Long-lived tracing work should continue in background goroutines. -type TraceStarter func(context.Context) error +type TraceStarter = runtime.TraceStarter // SnapshotSource provides dashboard snapshots for TUI rendering. -type SnapshotSource interface { - Snapshot() *statsengine.Snapshot -} +// It is a type alias for runtime.SnapshotSource. +type SnapshotSource = runtime.SnapshotSource // ProbeManager exposes runtime probe controls to TUI layers. -type ProbeManager interface { - States() []probemanager.ProbeState - Toggle(syscall string) error - ActiveCount() (int, int) -} +// It is a type alias for runtime.ProbeManager. +type ProbeManager = runtime.ProbeManager // RuntimePublisher is the write side of the TUI runtime contract. -// A trace starter calls these methods to inject live data into the active TUI. -type RuntimePublisher interface { - SetDashboardSnapshotSource(source SnapshotSource) - SetEventStreamSource(source eventstream.Source) - SetLiveTrie(liveTrie flamegraphtui.LiveTrieSource) - SetProbeManager(manager ProbeManager) - // SetLiveFilterSetter registers (or, with nil, unregisters) a callback that - // applies a new global filter to the running trace pipeline in place. The - // trace starter passes its eventloop's SetFilter; the TUI calls it on every - // filter change to avoid restarting the BPF probes. - SetLiveFilterSetter(setter func(globalfilter.Filter)) -} +// It is a type alias for runtime.RuntimePublisher; the runtime package owns +// the canonical definition so the core tracing layer can depend on it without +// importing internal/tui. +type RuntimePublisher = runtime.RuntimePublisher // RuntimeState is the read side of the TUI runtime contract. -// A trace starter calls these methods to obtain persistent state owned by the TUI. -type RuntimeState interface { - StreamBuffer() eventstream.Source - Recorder() *parquet.Recorder - StreamSequencer() *eventstream.Sequencer - FilterEpoch() uint64 -} +// It is a type alias for runtime.RuntimeState. +type RuntimeState = runtime.RuntimeState // TraceRuntimeBindings composes RuntimePublisher and RuntimeState so a trace // starter can both inject live data and read persistent TUI-owned state. -type TraceRuntimeBindings interface { - RuntimePublisher - RuntimeState -} - -type runtimeBindingsContextKey struct{} -type traceFiltersContextKey struct{} +// It is a type alias for runtime.TraceRuntimeBindings. +type TraceRuntimeBindings = runtime.TraceRuntimeBindings +// runtimeBindings is the TUI-owned concrete implementation of +// runtime.TraceRuntimeBindings. It guards all fields with a read-write mutex so +// the trace starter goroutine and the Bubble Tea update loop can safely exchange +// live data. type runtimeBindings struct { mu sync.RWMutex - snapshotSource SnapshotSource - streamSource eventstream.Source - streamBuffer *eventstream.RingBuffer - streamSeq *eventstream.Sequencer - recorder *parquet.Recorder - liveTrieSource flamegraphtui.LiveTrieSource - probeManager ProbeManager + // snapshotSource is the stats engine injected by the trace starter. + snapshotSource runtime.SnapshotSource + // streamSource is the active read-side source (may be swapped on reset). + streamSource runtime.StreamSource + // streamBuffer is the TUI-owned ring buffer; it always satisfies both + // runtime.StreamSource (Len/Snapshot) and runtime.EventSink (Push). + streamBuffer *eventstream.RingBuffer + // streamSeq is the shared monotonic counter for stream row sequencing. + streamSeq *eventstream.Sequencer + // recorder handles optional parquet stream recording. + recorder *parquet.Recorder + // liveTrieSource is the flamegraph trie injected by the trace starter. + liveTrieSource runtime.LiveTrieSource + // probeManager is the BPF probe manager injected by the trace starter. + probeManager runtime.ProbeManager + // liveFilterSetter, when non-nil, applies filter changes to the running + // event loop in-place so BPF probes need not be restarted. liveFilterSetter func(globalfilter.Filter) - filterEpoch atomic.Uint64 -} - -type traceFilters struct { - filter globalfilter.Filter + // filterEpoch increments on every filter change and is stored in parquet rows. + filterEpoch atomic.Uint64 } func newRuntimeBindings() *runtimeBindings { @@ -119,52 +109,62 @@ func newRuntimeBindings() *runtimeBindings { } } -func (r *runtimeBindings) SetDashboardSnapshotSource(source SnapshotSource) { +// SetDashboardSnapshotSource wires the stats engine into the dashboard. +func (r *runtimeBindings) SetDashboardSnapshotSource(source runtime.SnapshotSource) { r.mu.Lock() r.snapshotSource = source r.mu.Unlock() } -func (r *runtimeBindings) SetEventStreamSource(source eventstream.Source) { +// SetEventStreamSource wires the stream buffer into the TUI stream view. +func (r *runtimeBindings) SetEventStreamSource(source runtime.StreamSource) { r.mu.Lock() r.streamSource = source r.mu.Unlock() } -func (r *runtimeBindings) StreamBuffer() eventstream.Source { +// StreamBuffer returns the TUI-owned ring buffer, which satisfies runtime.StreamSource. +func (r *runtimeBindings) StreamBuffer() runtime.StreamSource { r.mu.RLock() defer r.mu.RUnlock() return r.streamBuffer } +// Recorder returns the parquet recorder for optional stream recording. func (r *runtimeBindings) Recorder() *parquet.Recorder { r.mu.RLock() defer r.mu.RUnlock() return r.recorder } +// StreamSequencer returns the shared monotonic counter for stream row sequencing. func (r *runtimeBindings) StreamSequencer() *eventstream.Sequencer { r.mu.RLock() defer r.mu.RUnlock() return r.streamSeq } +// FilterEpoch returns the current filter epoch used for parquet recording. func (r *runtimeBindings) FilterEpoch() uint64 { return r.filterEpoch.Load() } -func (r *runtimeBindings) SetLiveTrie(liveTrie flamegraphtui.LiveTrieSource) { +// SetLiveTrie wires the live flamegraph trie into the TUI flamegraph view. +func (r *runtimeBindings) SetLiveTrie(liveTrie runtime.LiveTrieSource) { r.mu.Lock() r.liveTrieSource = liveTrie r.mu.Unlock() } -func (r *runtimeBindings) SetProbeManager(manager ProbeManager) { +// SetProbeManager wires the BPF probe manager into the TUI probes modal. +func (r *runtimeBindings) SetProbeManager(manager runtime.ProbeManager) { r.mu.Lock() r.probeManager = manager r.mu.Unlock() } +// SetLiveFilterSetter registers (or, with nil, unregisters) the live filter +// callback so the TUI can update the running trace pipeline in-place. func (r *runtimeBindings) SetLiveFilterSetter(setter func(globalfilter.Filter)) { r.mu.Lock() r.liveFilterSetter = setter @@ -186,25 +186,29 @@ func (r *runtimeBindings) applyLiveFilter(filter globalfilter.Filter) bool { return true } -func (r *runtimeBindings) dashboardSnapshotSource() SnapshotSource { +// dashboardSnapshotSource returns the currently wired stats engine source. +func (r *runtimeBindings) dashboardSnapshotSource() runtime.SnapshotSource { r.mu.RLock() defer r.mu.RUnlock() return r.snapshotSource } -func (r *runtimeBindings) eventStreamSource() eventstream.Source { +// eventStreamSource returns the currently active stream read source. +func (r *runtimeBindings) eventStreamSource() runtime.StreamSource { r.mu.RLock() defer r.mu.RUnlock() return r.streamSource } -func (r *runtimeBindings) liveTrie() flamegraphtui.LiveTrieSource { +// liveTrie returns the currently wired flamegraph trie source. +func (r *runtimeBindings) liveTrie() runtime.LiveTrieSource { r.mu.RLock() defer r.mu.RUnlock() return r.liveTrieSource } -func (r *runtimeBindings) currentProbeManager() ProbeManager { +// currentProbeManager returns the currently wired probe manager. +func (r *runtimeBindings) currentProbeManager() runtime.ProbeManager { r.mu.RLock() defer r.mu.RUnlock() return r.probeManager @@ -241,44 +245,34 @@ func (r *runtimeBindings) resetDashboardSnapshotSource() *statsengine.Snapshot { // RuntimeBindingsFromContext returns the full TraceRuntimeBindings when the // context was created by the TUI. Use RuntimePublisherFromContext when only -// write access is needed. +// write access is needed. Delegates to runtime.RuntimeBindingsFromContext. func RuntimeBindingsFromContext(ctx context.Context) (TraceRuntimeBindings, bool) { - bindings, ok := ctx.Value(runtimeBindingsContextKey{}).(TraceRuntimeBindings) - if !ok || bindings == nil { - return nil, false - } - return bindings, true + return runtime.RuntimeBindingsFromContext(ctx) } -// RuntimePublisherFromContext returns only the RuntimePublisher side of the -// TUI bindings. Use this when the caller only injects data and does not need -// to read persistent TUI state. +// RuntimePublisherFromContext returns only the RuntimePublisher side of the TUI +// bindings. Use this when the caller only injects data and does not need to +// read persistent TUI state. Delegates to runtime.RuntimePublisherFromContext. func RuntimePublisherFromContext(ctx context.Context) (RuntimePublisher, bool) { - bindings, ok := ctx.Value(runtimeBindingsContextKey{}).(RuntimePublisher) - if !ok || bindings == nil { - return nil, false - } - return bindings, true + return runtime.RuntimePublisherFromContext(ctx) } // ContextWithRuntimeBindings stores trace runtime bindings on the context. +// Delegates to runtime.ContextWithRuntimeBindings. func ContextWithRuntimeBindings(ctx context.Context, bindings TraceRuntimeBindings) context.Context { - return context.WithValue(ctx, runtimeBindingsContextKey{}, bindings) + return runtime.ContextWithRuntimeBindings(ctx, bindings) } // ContextWithTraceFilters stores the active trace filters for the trace starter. +// Delegates to runtime.ContextWithTraceFilters. func ContextWithTraceFilters(ctx context.Context, filter globalfilter.Filter) context.Context { - filters := traceFilters{filter: filter.Clone()} - return context.WithValue(ctx, traceFiltersContextKey{}, filters) + return runtime.ContextWithTraceFilters(ctx, filter) } // TraceFiltersFromContext returns the active trace filters when provided by the TUI model. +// Delegates to runtime.TraceFiltersFromContext. func TraceFiltersFromContext(ctx context.Context) (globalfilter.Filter, bool) { - filters, ok := ctx.Value(traceFiltersContextKey{}).(traceFilters) - if !ok { - return globalfilter.Filter{}, false - } - return filters.filter.Clone(), true + return runtime.TraceFiltersFromContext(ctx) } // RunWithTraceStarterConfig starts the TUI with explicit runtime flags. |
