| Age | Commit message (Collapse) | Author |
|
Replace three near-identical trimOldest* functions with a single generic
trimLRU[K comparable, V any] helper; the three wrappers now delegate to it,
with trimOldestPendingPairs passing a cleanup callback for Recycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
values
Introduces statsengine.DefaultTopN = 64 as the canonical named constant for
the top-N capacity used when constructing an Engine. Removes the local
defaultEngineCapacity constant from runtime_builder.go and updates all
call sites to reference statsengine.DefaultTopN instead of bare 64.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add var _ Interface = (*ConcreteType)(nil) assertions for:
- *flamegraph.LiveTrie → runtime.{Snapshotter,Configurator,LiveTrieSource}
and tui/flamegraph.{Snapshotter,Configurator,LiveTrieSource}
- *probemanager.Manager → runtime.ProbeManager
- *statsengine.Engine → runtime.SnapshotSource
- *streamrow.RingBuffer → runtime.EventSink
- *runtimeBindings (tui) → runtime.TraceRuntimeBindings
- *lateBoundDashboardSource → dashboard.SnapshotSource
- libbpfTracepointProgram/Module → probemanager.{Program,Attacher}
Assertions are grouped close to their interface definitions to avoid
introducing new import cycles (runtime already imports all affected
packages; tui/flamegraph already imports coreflamegraph).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
The receiver name flags shadowed the package name flags, making the
package inaccessible inside the method body. Rename to f to match all
other receivers in flags.go and add a doc comment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Document every exported field in Config (flags package) and Filter,
NumericFilter, StringFilter (globalfilter package) with a concise one-line
doc comment describing its purpose and behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Applies the Interface Segregation Principle to LiveTrieSource in both
internal/runtime and internal/tui/flamegraph. The monolithic interface is
replaced by two focused sub-interfaces — Snapshotter (read-only: Version,
SnapshotJSON, SnapshotTree) and Configurator (mutating: Fields, CountField,
Reconfigure, SetCountField, Reset) — which LiveTrieSource then embeds so all
existing consumers continue to compile unchanged. The buildSnapshotMsg helper
is narrowed to Snapshotter since it only reads snapshot data, preventing
accidental mutation from the background goroutine.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduce tabDescriptor struct and tabDescriptors map in new
tabregistry.go. Each tab registers its name, short name, ordered
position, allowed viz modes, render function, scroll handler, and
optional init tick command. Adding a new tab now requires only a
single registry entry — no existing switch/if chains need editing.
Key changes:
- Tab.String() and tabLabel() use lookupTab() instead of a switch
- renderActiveTabContent() dispatches via d.Render (replaces renderActiveTab switch)
- handleScrollKey() dispatches via d.HandleScroll (replaces tab switch)
- Init() and postKeyTransitionCmd() use d.InitCmd (replaces stream/flame tab checks)
- allowedVizModes() delegates to tabAllowedVizModes() from registry
- orderedTabs() replaces hardcoded allTabs slice, derived from Position field
- bubbleChartFor() helper eliminates 5 repeated switch-on-tab for chart ops
- toggleBubbleMetric, tickActiveBubbleChart, moveBubbleSelection,
activeBubbleChartHasNodes all use bubbleChartFor()
- refreshBubbleData split into two focused functions under 50 lines
- Two pre-existing test functions over 50 lines refactored
All tests pass; go build ./internal/tui/... clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduce a ModeRegistry pattern so new execution modes can be added
without modifying the dispatch or validation switch/if chains. Each mode
is a self-contained modeHandler (match + validate + run); the ordered
defaultRegistry replaces the open if-chains in dispatchRun and
validateRunConfig, which become thin wrappers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Split the 1389-line tui.go Model into three focused sub-controllers
that each own a single concern:
- filterstack.go (filterStack): owns the filter chain, undo history,
and label stack; provides push/pop/rebindProcessFilters API so the
Model never manipulates filter slices directly.
- tracelifecycle.go (traceLifecycle): owns trace start/stop and the
active context.CancelFunc; provides beginCmd/stop API; also houses
the recorder helpers (recorderStart/Stop/Active/Status) and the
auto-reset cycle logic (nextAutoResetInterval, autoResetCycle).
- screenrouter.go (screenRouter): owns the picker-return bookmark
(pickerReturn) and the applyWindowSizeToPicker helper so screen
transition code in tui.go delegates to it.
The Model.Update switch is split into dispatchTypedMsg (framework
messages) and dispatchAppMsg (app messages) to keep each helper
under the 50-line limit. View is split into viewPickerScreen and
viewDashboardScreen for the same reason.
All functions are ≤50 lines. go test ./internal/tui/... passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
RuntimeBuilder encapsulates allocation of per-trace-session components
(statsengine.Engine, streamrow.RingBuffer, Sequencer, flamegraph.LiveTrie);
buildTUIRuntime, buildTestFlamesRuntime, and buildTestLiveFlamesRuntime now
delegate construction to RuntimeBuilder.Build() and focus only on wiring.
wireRuntimeBindings is extracted to isolate the context-binding wiring step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Break the god-class Model in internal/tui/flamegraph/model.go into four
focused sub-controllers that each own a single concern:
- ZoomNavigator : zoom path, stack, root node, and reset/undo logic
- SelectionManager: selected frame index, clamp, traversal, navigation
- FrameAnimator : spring animation, frame layout swap, ancestry index
- SearchController: search query, match indices, filter-visible set
All four are embedded in Model so existing field access (m.selectedIdx,
m.zoomPath, etc.) is promoted unchanged, keeping tests and callers intact.
Model methods now delegate to the sub-controllers rather than holding all
logic inline.
Also split RenderTerminalView (88 lines) into computeRenderParams,
buildToolbar, buildFilteredStatus, and buildNormalStatus helpers, and
extracted buildSnapshotMsg from RefreshFromLiveTrieCmd.
All functions are now ≤ 50 lines. All tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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>
|
|
Extract renderActiveContentViz (treemap/icicle/bubble dispatch) and
renderActiveContentTable (table-with-sort dispatch) from the original
56-line renderActiveContent, reducing it to 15 lines of code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
50-line limit
Extract buildTUIRuntime and makeTUIEventLoopConfigurer from the monolithic
tuiTraceStarterFromRunTrace closure, and extract maybePrependFlamegraphConfigure
and finaliseTrace from runTraceWithContext. All functions are now <= 50 lines.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Extract handleHelpOverlayKeyPress, handleQuitKeyPress, routeQuitAsEsc, and
handleDashboardShortcutKeys from the 89-line handleGlobalKeyPress to comply
with the project's 50-line function limit. All existing behaviour is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Extract captureSnapshotInputs (lock-guarded state copy), buildSubSnapshots
(concurrent per-category builders), and populateSnapshotFields (scalar
field assignment) from the monolithic Snapshot method. The method itself
is now 18 lines; all three helpers are well under 30 lines each.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
50-line limit
Extract buildGoTestCmd, startProgressTicker, and drainTestEvents from
runGoTestWithProgress; extract parquetSchemaCheck, parquetRowCountCheck,
and parquetSanityCheck from runParquetChecks. All six new helpers and both
top-level functions are now well under 50 lines. No behavior changed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Extract handleSearchInput and handleKeyNavigation from Update (111 lines → 33),
then split handleKeyNavigation into handleModeKey and handleMovementKey.
Extract frameCoordToTargetRow and findFrameAtRow from frameIndexAt (66 lines → 32).
All new helpers are ≤ 29 lines; behavior is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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>
|
|
Renders the next-tick countdown ("12s/30s") in the dashboard chrome so
users can see when aggregates will clear, and adds a dedicated H-help
line spelling out the cycle keys (off → 10s → 30s → 1m → 2m → 5m).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
Two follow-up refinements to the auto-reset timer added in 8da473a.
- Hotkey cycle now goes off -> 10s -> 30s -> 60s -> 2m -> 5m -> off,
giving the user finer control between 60s and 5m and a quicker
starting cadence.
- The timer now pauses while the TUI is blurred. SetFocused returns a
tea.Cmd that re-arms a fresh tick on focus regain, and bumps the
generation counter on every focus change so any tick scheduled before
blur is dropped on arrival. autoResetTickCmd and handleAutoResetTick
also gate on m.focused as defense in depth.
- Dashboard chrome shows 'auto-reset: 30s (paused)' while the timer is
enabled but blurred, distinguishing it from the disabled 'off' state.
Tests cover the full preset cycle (including custom-value passthrough)
and the pause-on-blur lifecycle: stale ticks ignored, current-gen ticks
ignored while blurred, focus regain re-arms and fires the reset, no-op
focus calls don't churn the generation counter, and the chrome label
flips to '(paused)' as expected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
Live flamegraph trie and stats engine grow unboundedly during long
traces. Add a periodic auto-reset (same effect as the 'r' key) so they
stay bounded.
- New CLI flag -resetTimer=30s (default 30s, 0 disables).
- Hotkey I cycles the cadence: off -> 15s -> 30s -> 60s -> 5m -> off.
Custom intervals (e.g. -resetTimer=47s) advance to the first preset
greater than the current value, then wrap to off.
- autoResetTickMsg carries a generation counter so changing the cadence
drops in-flight ticks scheduled under the previous interval.
- Dashboard chrome shows 'auto-reset: 30s' or 'auto-reset: off'.
|
|
PrepareForTraceRestart was designed for the full-restart path, where the
dashboard's live-trie reference is rebound when TracingStartedMsg fires.
The in-place filter swap skips that message, leaving the flamegraph tab
stuck on 'Flame: waiting for data...' until the next real trace restart.
Re-bind via SetLiveTrie immediately after PrepareForTraceRestart in both
applyGlobalFilter and undoGlobalFilter.
|
|
Changing the global filter used to call stopTrace + beginTraceCmd, which
detached and re-attached every tracepoint and re-loaded the BPF object.
On heavily loaded I/O systems that took several seconds and showed an
'Attaching tracepoints...' overlay each time. The probe set never depends
on the global filter (ShouldIAttachTracepoint only reads CLI regex flags),
so the restart was gratuitous.
Now the eventloop holds its filter behind atomic.Pointer with SetFilter /
Filter accessors, and the trace starter registers el.SetFilter via the
runtime bindings as a SetLiveFilterSetter callback. applyGlobalFilter
and undoGlobalFilter call runtime.applyLiveFilter first; only if no
trace is running do they fall back to the full restart path.
|
|
- Bubbles, treemap, icicle, and the live flamegraph 'b' cycle now include
syscall duration (sum) as a third metric alongside events and bytes.
Statsengine snapshots expose TotalLatencyNs to support this.
- AttachAll takes an optional warn callback. Production passes one so older
kernels that lack newer tracepoints log a warning and keep going instead
of aborting startup.
- Dockerfile.el8 + scripts/build-with-docker-el8.sh + mage buildDockerEl8
produce ior.el8, a static binary built against Rocky Linux 8 glibc for
RHEL/Rocky/Alma 8 hosts.
- README.md documents installing mage and the new el8 target.
|
|
|
|
|
|
|
|
Adds --platform=linux/amd64 to the Dockerfile FROM directive and to the
docker build/run invocations in scripts/build-with-docker.sh so the ior
binary is always cross-compiled for amd64 regardless of host arch.
|
|
|
|
|
|
- demo/ renamed to docs/tutorial/ (tapes, scripts, TUTORIAL.md)
- docs/tutorial/assets/ added to git (51 MB of GIFs + PNGs); removed
/demo/assets/ from .gitignore so images render on Codeberg
- docs/tui-reference.md removed; its hotkey tables merged into a new
"Hotkey Quick Reference" section at the end of TUTORIAL.md
- TUTORIAL.md: updated install section (mage buildDocker, no
GOTOOLCHAIN=auto), fixed README relative path (../../README.md),
updated internal tapes/scripts/assets path prose
- README.md: updated all demo/ image paths and links to docs/tutorial/;
TUI and recording-modes links now point to TUTORIAL.md anchors
- AGENTS.md: updated demo/ references to docs/tutorial/
- Magefile.go: updated demoDir/demoTapesDir/demoScriptsDir/demoRunTape/
demoSudoKeepers constants to docs/tutorial/ paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduces a Docker-based build path so ior can be compiled on any
Linux host without a native Rocky 9 toolchain setup:
- Dockerfile: Rocky 9 minimal image with Go (version from ARG, default
from go.mod), static libelf/libzstd built from source, libbpfgo at
v0.9.2-libbpf-1.5.1, and mage; CMD runs mage generate + mage all
against the repo root mounted as a volume.
- scripts/build-with-docker.sh: reads GO_VERSION from go.mod, passes it
as --build-arg to docker build, mounts tracefs and BTF into the
container, writes the binary to the repo root.
- Magefile.go: adds BuildDocker target that wraps the script.
- README.md: simplified to the two build paths (Docker + native) with
links to docs/; removed GOTOOLCHAIN=auto throughout.
- docs/build-rocky-linux-9.md: full manual Rocky 9 steps, libbpfgo
toolchain setup/rollback, compile-once-run-everywhere explanation,
and timing semantics.
- docs/tui-reference.md: complete TUI hotkey reference, recording mode
details, and the .ior.zst vs Parquet trade-off table.
- AGENTS.md: removed GOTOOLCHAIN=auto from all build commands.
- internal/c/generated_tracepoints.c: regenerated against the host kernel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Tracks the inputs that drive `mage demo` so the GIFs and screenshots
under demo/assets/ can be reproduced from a checkout. The generated
assets themselves (~50 MB of GIFs and PNGs) are excluded via gitignore
since they're fully reproducible from the tapes in ~10 min.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
The BPF handler generator emitted struct trace_event_raw_sys_enter/
trace_event_raw_sys_exit (the BTF-blessed aliases). RHEL 9 carries an
rt-tree backport that adds preempt_lazy_count to struct trace_entry,
which widens those aliases by 8 bytes and shifts args/ret. The actual
tracepoint context the kernel hands the program is still
syscall_trace_enter / syscall_trace_exit, where the offsets did not
move. Programs typed against the wider alias read past max_ctx_offset
and the verifier rejects the attach with EACCES.
Switching the generator to emit syscall_trace_enter/exit lines up with
the real context on RHEL 9 (and is identical on every other distro,
since the two structs only diverge there). Same fix bcc shipped in
iovisor/bcc#4920 and inspektor-gadget did in inspektor-gadget#2546.
Field accesses (ctx->args[N], ctx->ret) are unchanged.
Verified end-to-end on Rocky Linux 9.7 stock 5.14.0-611.5.1.el9_7
(no kernel-ml needed) and Fedora 6.19. README rewritten accordingly:
drops the elrepo kernel-ml step and the trailing 'permission denied'
troubleshooting paragraph; adds a historical note explaining why the
old workaround existed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
|
|
Relocates the two non-canonical main packages so every binary in the repo
lives at ./cmd/<BINARY>/main.go:
- tools/filewriter/ -> cmd/filewriter/
- integrationtests/cmd/ioworkload/ (20 files) -> cmd/ioworkload/
Consumers updated:
- Magefile.go: workloadSourcePath now ./cmd/ioworkload
- integrationtests/README.md: structure note points at ../cmd/ioworkload
Files moved with git mv so git log --follow history is preserved.
cmd/ior/main.go was already canonical and is untouched.
Verified: mage build produces the ior binary; go build ./cmd/...
builds filewriter and ioworkload; go test ./cmd/ioworkload passes;
go vet ./cmd/filewriter ./cmd/ioworkload is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
runTraceWithContext and runHeadlessParquet opened cpu/mem/exec-trace
profile files via setupProfiling, then relied on the shutdown watcher
goroutine (registered later) to close them. If any intervening step
(newEventLoop, recorder.Start) returned an error, the watcher was never
registered and the profile fds leaked until process exit. Add a
defer profiling.stop(logln) immediately after setupProfiling so the fds
are released on every return path. profiling.stop is idempotent via
sync.Once so the watcher and defer can both run harmlessly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Adds a `mage parquetValidate` target that validates a Parquet recording
via clickhouse-local in Docker (schema presence, row count, seq/time_ns
sanity). Adds docs/parquet-querying.md with schema reference, invocation
pattern, and seven example queries with representative outputs.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
|
tui.Model (task 429)
tui.Model had 33 fields mixing keyboard tracking, filter chain, process
selection, and UI presentation concerns (SRP violation).
Extract three focused sub-structs:
- keyboardState (kb): enhancements, lastEvent{ID,At,WasPress},
suppress{ID,Until} — 7 fields managed by keys_normalize.go
- filterState (filter): global filter, history slice, label stack
— 3 fields for the trace filter chain
- processState (proc): pid, tid, pickerReturn
— 3 fields for PID/TID selection and picker navigation
Model drops from 33 to 23 top-level fields. All field accesses in
tui.go, keys_normalize.go, and tui_test.go are updated accordingly.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
|
(task 427/ISP)
TraceRuntimeBindings mixed 4 setter methods (injecting data into TUI) with
4 getter methods (reading persistent TUI-owned state), violating ISP.
Split into two focused interfaces:
- RuntimePublisher: SetDashboardSnapshotSource, SetEventStreamSource,
SetLiveTrie, SetProbeManager — the write/inject side
- RuntimeState: StreamBuffer, Recorder, StreamSequencer, FilterEpoch
— the read/persistent-state side
TraceRuntimeBindings now embeds both, preserving all existing call sites.
RuntimePublisherFromContext() added so callers that only inject data do not
see the getter surface. Three such callers are narrowed: setupBPFModule,
tuiTestFlamesStarter, tuiTestLiveFlamesStarter.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
|
ior.go had 763 lines covering 9+ concerns. Follow the eventloop_*.go
pattern and extract into three focused files:
- ior_bpfsetup.go: libbpfTracepoint{Program,Module} adapter types,
setupBPFModule, setupBPFModuleError, setupEventChannel
- ior_profiling.go: profilingControl type, setupProfiling,
profilingFilesForMode, stop()
- ior_parquet_sink.go: headlessParquetSink type, runHeadlessParquet,
isHeadlessParquetMode, hasHeadlessParquetContentFilters,
headlessParquetTraceConfig; inline parquetMetadata one-liner
ior.go shrinks from 763 → 453 lines, retaining entry, dispatch,
TUI wiring, and core trace execution.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|