summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ior/main.go5
-rw-r--r--internal/ior.go50
-rw-r--r--internal/tui/tui.go93
-rw-r--r--internal/tui/tui_test.go41
4 files changed, 41 insertions, 148 deletions
diff --git a/cmd/ior/main.go b/cmd/ior/main.go
index 2b852f0..8d93d28 100644
--- a/cmd/ior/main.go
+++ b/cmd/ior/main.go
@@ -23,8 +23,9 @@ func main() {
os.Exit(2)
}
- // Run the internal logic of the application
- if err := internal.Run(); err != nil {
+ // Run the internal logic of the application.
+ // flags.Get() is called here at the CLI boundary so internal code never reads the singleton.
+ if err := internal.Run(flags.Get()); err != nil {
fmt.Printf("Failed to run: %v\n", err)
os.Exit(2)
}
diff --git a/internal/ior.go b/internal/ior.go
index d88f0e3..dba90c3 100644
--- a/internal/ior.go
+++ b/internal/ior.go
@@ -1,7 +1,5 @@
package internal
-import "C"
-
import (
"context"
"errors"
@@ -38,7 +36,7 @@ var (
runTraceWithContextFn = runTraceWithContext
runTUIFn = tui.RunWithTraceStarterConfig
runTUITestFlamesFn = tui.RunTestFlamesWithTraceStarterConfig
- runTUITestLiveFlamesFn = tui.RunTestFlamesWithTraceStarterConfig
+ runTUITestLiveFlamesFn = tui.RunTestFlamesWithTraceStarterConfig // same runner; starter differs (static vs live)
getEUID = os.Geteuid
errRootPrivilegesRequired = errors.New("tracing requires root privileges (run with sudo)")
@@ -70,9 +68,10 @@ func (m libbpfTracepointModule) GetProgram(progName string) (probemanager.Progra
}
// Run is the main entry point for the ior binary.
-func Run() error {
+// cfg must be provided by the caller; it should not be fetched from the global singleton here.
+func Run(cfg flags.Config) error {
flags.PrintVersion()
- return dispatchRun(flags.Get())
+ return dispatchRun(cfg)
}
func dispatchRun(cfg flags.Config) error {
@@ -303,21 +302,14 @@ func applyTraceScopeFromGlobalFilter(cfg *flags.Config, filter globalfilter.Filt
}
cfg.PidFilter = -1
cfg.TidFilter = -1
- if pid, ok := eqFilterValue(filter.PID); ok {
+ if pid, ok := filter.PID.EqValue(); ok {
cfg.PidFilter = pid
}
- if tid, ok := eqFilterValue(filter.TID); ok {
+ if tid, ok := filter.TID.EqValue(); ok {
cfg.TidFilter = tid
}
}
-func eqFilterValue(filter *globalfilter.NumericFilter) (int, bool) {
- if filter == nil || filter.Op != globalfilter.OpEq || filter.Value <= 0 {
- return 0, false
- }
- return int(filter.Value), true
-}
-
func runTrace(cfg flags.Config) error {
return runTraceWithContext(context.Background(), cfg, nil, nil)
}
@@ -398,24 +390,9 @@ func newEventLoopConfig(cfg flags.Config) eventLoopConfig {
}
}
+// traceFilterFromConfig delegates to the canonical Config.TraceFilter method.
func traceFilterFromConfig(cfg flags.Config) globalfilter.Filter {
- filter := cfg.GlobalFilter.Clone()
- if filter.IsActive() {
- return filter
- }
- if cfg.CommFilter != "" {
- filter.Comm = &globalfilter.StringFilter{Pattern: cfg.CommFilter}
- }
- if cfg.PathFilter != "" {
- filter.File = &globalfilter.StringFilter{Pattern: cfg.PathFilter}
- }
- if cfg.PidFilter > 0 {
- filter.PID = &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: int64(cfg.PidFilter)}
- }
- if cfg.TidFilter > 0 {
- filter.TID = &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: int64(cfg.TidFilter)}
- }
- return filter
+ return cfg.TraceFilter()
}
type profilingControl struct {
@@ -730,16 +707,9 @@ func headlessParquetTraceConfig(cfg flags.Config) flags.Config {
return out
}
+// parquetMetadata delegates to the canonical parquet.NewFileMetadata.
func parquetMetadata(mode string) parquet.FileMetadata {
- meta := parquet.FileMetadata{
- StartedAtUnixNano: uint64(time.Now().UnixNano()),
- Mode: mode,
- IORVersion: flags.Version,
- }
- if hostname, err := os.Hostname(); err == nil {
- meta.Hostname = hostname
- }
- return meta
+ return parquet.NewFileMetadata(mode)
}
type headlessParquetSink struct {
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
index ef94748..432fc14 100644
--- a/internal/tui/tui.go
+++ b/internal/tui/tui.go
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"log"
- "os"
"strconv"
"strings"
"sync"
@@ -231,16 +230,6 @@ func TraceFiltersFromContext(ctx context.Context) (globalfilter.Filter, bool) {
return filters.filter.Clone(), true
}
-// Run starts the TUI program in alternate screen mode.
-func Run() error {
- return RunWithTraceStarter(defaultTraceStarter)
-}
-
-// RunWithTraceStarter starts the TUI program with a custom trace starter.
-func RunWithTraceStarter(starter TraceStarter) error {
- return RunWithTraceStarterConfig(flags.Get(), starter)
-}
-
// RunWithTraceStarterConfig starts the TUI with explicit runtime flags.
func RunWithTraceStarterConfig(cfg flags.Config, starter TraceStarter) error {
model := newModelWithRuntimeConfig(cfg.PidFilter, filterFromConfig(cfg), cfg.PidFilter, cfg.TidFilter, cfg.TUIExportEnable, starter)
@@ -249,12 +238,6 @@ func RunWithTraceStarterConfig(cfg flags.Config, starter TraceStarter) error {
return err
}
-// RunTestFlamesWithTraceStarter starts the TUI directly on dashboard/flame view
-// with a synthetic static flamegraph source.
-func RunTestFlamesWithTraceStarter(starter TraceStarter) error {
- return RunTestFlamesWithTraceStarterConfig(flags.Get(), starter)
-}
-
// RunTestFlamesWithTraceStarterConfig starts test-flames mode with explicit runtime flags.
func RunTestFlamesWithTraceStarterConfig(cfg flags.Config, starter TraceStarter) error {
model := newModelWithRuntimeConfig(1, filterFromConfig(cfg), 1, -1, cfg.TUIExportEnable, starter)
@@ -317,9 +300,10 @@ type pickerReturnState struct {
tidFilter int
}
-// NewModel creates the top-level TUI model.
+// NewModel creates the top-level TUI model with default runtime flags.
+// Prefer NewModelWithConfig to pass parsed CLI config explicitly.
func NewModel(initialPID int, startTrace TraceStarter) Model {
- return NewModelWithConfig(flags.Get(), initialPID, startTrace)
+ return NewModelWithConfig(flags.NewFlags(), initialPID, startTrace)
}
// NewModelWithConfig creates the top-level TUI model with explicit runtime flags.
@@ -500,17 +484,6 @@ func (m Model) canHandleDashboardShortcut(msg tea.KeyPressMsg) bool {
!m.dashboard.BlocksGlobalShortcuts(msg)
}
-func (m Model) canQuitFromMainDashboard(msg tea.KeyPressMsg) bool {
- return m.screen == ScreenDashboard &&
- !m.attaching &&
- m.lastErr == nil &&
- !m.filterModal.Visible() &&
- !m.exporter.Visible() &&
- !m.recordModal.Visible() &&
- !m.probeModal.Visible() &&
- !m.dashboard.BlocksGlobalShortcuts(msg)
-}
-
func (m Model) shouldCancelPickerToDashboard(msg tea.KeyPressMsg) bool {
return m.screen == ScreenPIDPicker &&
m.pickerReturn != nil &&
@@ -537,7 +510,7 @@ func (m Model) handleGlobalKeyPress(msg tea.KeyPressMsg) (tea.Model, tea.Cmd, bo
return next, cmd, true
}
if key.Matches(msg, m.keys.Quit) {
- if m.canQuitFromMainDashboard(msg) {
+ if m.canHandleDashboardShortcut(msg) {
if err := m.stopRecording(); err != nil {
m.lastErr = err
return m, nil, true
@@ -848,31 +821,9 @@ func defaultTraceStarter(context.Context) error {
return nil
}
+// filterFromConfig delegates to the canonical Config.TraceFilter method.
func filterFromConfig(cfg flags.Config) globalfilter.Filter {
- filter := cfg.GlobalFilter.Clone()
- if filter.IsActive() {
- return filter
- }
- if cfg.CommFilter != "" {
- filter.Comm = &globalfilter.StringFilter{Pattern: cfg.CommFilter}
- }
- if cfg.PathFilter != "" {
- filter.File = &globalfilter.StringFilter{Pattern: cfg.PathFilter}
- }
- if cfg.PidFilter > 0 {
- filter.PID = eqNumericFilter(cfg.PidFilter)
- }
- if cfg.TidFilter > 0 {
- filter.TID = eqNumericFilter(cfg.TidFilter)
- }
- return filter
-}
-
-func eqNumericFilter(value int) *globalfilter.NumericFilter {
- if value <= 0 {
- return nil
- }
- return &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: int64(value)}
+ return cfg.TraceFilter()
}
func (m *Model) setProcessFilters(pid, tid int) {
@@ -887,10 +838,12 @@ func (m *Model) setProcessFilters(pid, tid int) {
func (m *Model) setGlobalFilter(filter globalfilter.Filter) {
m.globalFilter = filter.Clone()
- pid, _ := eqNumericFilterValue(m.globalFilter.PID)
- tid, _ := eqNumericFilterValue(m.globalFilter.TID)
- m.pidFilter = pid
- m.tidFilter = tid
+ // EqValue returns (0, false) when no equality filter is set;
+ // selectedPIDFilter maps non-positive values to -1 ("no filter").
+ pid, _ := m.globalFilter.PID.EqValue()
+ tid, _ := m.globalFilter.TID.EqValue()
+ m.pidFilter = selectedPIDFilter(pid)
+ m.tidFilter = selectedPIDFilter(tid)
m.syncDashboardFilterState()
}
@@ -903,8 +856,8 @@ func (m *Model) syncDashboardFilterState() {
func applyProcessFilters(filter globalfilter.Filter, pid, tid int) globalfilter.Filter {
out := filter.Clone()
- out.PID = eqNumericFilter(pid)
- out.TID = eqNumericFilter(tid)
+ out.PID = globalfilter.NewEqFilter(int64(pid))
+ out.TID = globalfilter.NewEqFilter(int64(tid))
return out
}
@@ -950,13 +903,6 @@ func (m Model) undoGlobalFilter() (tea.Model, tea.Cmd) {
return m, tea.Batch(m.spin.Tick, m.beginTraceCmd())
}
-func eqNumericFilterValue(filter *globalfilter.NumericFilter) (int, bool) {
- if filter == nil || filter.Op != globalfilter.OpEq || filter.Value <= 0 {
- return -1, false
- }
- return int(filter.Value), true
-}
-
func globalFilterActionLabel(prev, next globalfilter.Filter, action string) string {
if strings.TrimSpace(action) != "" {
return action
@@ -1196,16 +1142,9 @@ func defaultParquetRecordingFilename() string {
return fmt.Sprintf("ior-recording-%s.parquet", time.Now().Format("20060102-150405"))
}
+// tuiParquetMetadata delegates to the canonical parquet.NewFileMetadata.
func tuiParquetMetadata() parquet.FileMetadata {
- meta := parquet.FileMetadata{
- StartedAtUnixNano: uint64(time.Now().UnixNano()),
- Mode: "tui",
- IORVersion: flags.Version,
- }
- if hostname, err := os.Hostname(); err == nil {
- meta.Hostname = hostname
- }
- return meta
+ return parquet.NewFileMetadata("tui")
}
func shortenRecordingPath(path string) string {
diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go
index 6ce16e6..7f43961 100644
--- a/internal/tui/tui_test.go
+++ b/internal/tui/tui_test.go
@@ -88,8 +88,6 @@ func TestRuntimeBindingsContextRoundTrip(t *testing.T) {
}
func TestPidSelectedTransitionsToDashboardAndSetsPIDFilter(t *testing.T) {
- flags.SetPidFilter(-1)
- flags.SetTidFilter(99)
m := NewModel(-1, func(context.Context) error { return nil })
next, cmd := m.Update(PidSelectedMsg{Pid: 42})
@@ -113,7 +111,6 @@ func TestPidSelectedTransitionsToDashboardAndSetsPIDFilter(t *testing.T) {
}
func TestInitialPIDSkipsPickerAndStartsTracing(t *testing.T) {
- flags.SetPidFilter(-1)
m := NewModel(7, func(context.Context) error { return nil })
if m.screen != ScreenDashboard {
@@ -127,7 +124,6 @@ func TestInitialPIDSkipsPickerAndStartsTracing(t *testing.T) {
}
func TestPidSelectedAllSetsNoFilter(t *testing.T) {
- flags.SetPidFilter(999)
m := NewModel(-1, func(context.Context) error { return nil })
next, _ := m.Update(PidSelectedMsg{Pid: 0})
@@ -696,9 +692,6 @@ func TestTracingStartedAppliesViewportWhenModelSizeIsUnset(t *testing.T) {
}
func TestExportKeyOpensModalOnDashboard(t *testing.T) {
- flags.SetTUIExportEnable(true)
- t.Cleanup(func() { flags.SetTUIExportEnable(true) })
-
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.attaching = false
@@ -1118,8 +1111,6 @@ func TestPidSelectedClearsPersistentStreamBuffer(t *testing.T) {
}
func TestSelectTIDKeyReturnsToPickerWhenPIDFilterIsAll(t *testing.T) {
- flags.SetPidFilter(-1)
- flags.SetTidFilter(-1)
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.attaching = false
@@ -1146,9 +1137,9 @@ func TestSelectTIDKeyReturnsToPickerWhenPIDFilterIsAll(t *testing.T) {
}
func TestSelectTIDKeyReturnsToPickerWhenSinglePIDSelected(t *testing.T) {
- flags.SetPidFilter(1234)
- flags.SetTidFilter(-1)
- m := NewModel(-1, func(context.Context) error { return nil })
+ cfg := flags.NewFlags()
+ cfg.PidFilter = 1234
+ m := NewModelWithConfig(cfg, -1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.attaching = false
m.width = 120
@@ -1174,9 +1165,9 @@ func TestSelectTIDKeyReturnsToPickerWhenSinglePIDSelected(t *testing.T) {
}
func TestTidSelectedTransitionsToDashboardAndSetsTIDFilter(t *testing.T) {
- flags.SetPidFilter(2222)
- flags.SetTidFilter(-1)
- m := NewModel(-1, func(context.Context) error { return nil })
+ cfg := flags.NewFlags()
+ cfg.PidFilter = 2222
+ m := NewModelWithConfig(cfg, -1, func(context.Context) error { return nil })
next, cmd := m.Update(TidSelectedMsg{Pid: 0, Tid: 3333})
if cmd == nil {
@@ -1198,8 +1189,6 @@ func TestTidSelectedTransitionsToDashboardAndSetsTIDFilter(t *testing.T) {
}
func TestTidSelectedFromAllPIDModeSetsOwningPID(t *testing.T) {
- flags.SetPidFilter(-1)
- flags.SetTidFilter(-1)
m := NewModel(-1, func(context.Context) error { return nil })
next, cmd := m.Update(TidSelectedMsg{Pid: 4444, Tid: 5555})
@@ -1219,10 +1208,9 @@ func TestTidSelectedFromAllPIDModeSetsOwningPID(t *testing.T) {
}
func TestExportKeyIgnoredWhenExportDisabled(t *testing.T) {
- flags.SetTUIExportEnable(false)
- t.Cleanup(func() { flags.SetTUIExportEnable(true) })
-
- m := NewModel(-1, func(context.Context) error { return nil })
+ cfg := flags.NewFlags()
+ cfg.TUIExportEnable = false
+ m := NewModelWithConfig(cfg, -1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.attaching = false
@@ -1234,8 +1222,6 @@ func TestExportKeyIgnoredWhenExportDisabled(t *testing.T) {
}
func TestStreamFilterModalConsumesEKeyInsteadOfOpeningExport(t *testing.T) {
- flags.SetTUIExportEnable(true)
- t.Cleanup(func() { flags.SetTUIExportEnable(true) })
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
@@ -1925,8 +1911,6 @@ func advanceFlameSelection(t *testing.T, m *Model) string {
}
func TestQuestionMarkDoesNotBreakExportModalInput(t *testing.T) {
- flags.SetTUIExportEnable(true)
- t.Cleanup(func() { flags.SetTUIExportEnable(true) })
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
@@ -1951,10 +1935,9 @@ func TestQuestionMarkDoesNotBreakExportModalInput(t *testing.T) {
}
func TestStatusBarHidesExportBindingWhenExportDisabled(t *testing.T) {
- flags.SetTUIExportEnable(false)
- t.Cleanup(func() { flags.SetTUIExportEnable(true) })
-
- m := NewModel(-1, func(context.Context) error { return nil })
+ cfg := flags.NewFlags()
+ cfg.TUIExportEnable = false
+ m := NewModelWithConfig(cfg, -1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
m.width = 100
m.height = 30