summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/flags/flags.go154
-rw-r--r--internal/flags/flags_test.go37
2 files changed, 128 insertions, 63 deletions
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index b2d9dce..bd21768 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -13,20 +13,14 @@ import (
)
var (
- singleton = Flags{
- TUIExportEnable: true,
- }
- once sync.Once
- parseErr error
- pidFilter atomic.Int64
- tidFilter atomic.Int64
- tuiExportEnable atomic.Bool
+ current atomic.Pointer[Flags]
+ once sync.Once
+ parseErr error
)
func init() {
- pidFilter.Store(-1)
- tidFilter.Store(-1)
- tuiExportEnable.Store(true)
+ defaults := NewFlags()
+ current.Store(&defaults)
}
var (
@@ -77,27 +71,91 @@ type Flags struct {
CountField string
}
-func Get() Flags {
- out := singleton
- out.PidFilter = int(pidFilter.Load())
- out.TidFilter = int(tidFilter.Load())
- out.TUIExportEnable = tuiExportEnable.Load()
+// NewFlags returns a configuration instance initialized with project defaults.
+func NewFlags() Flags {
+ return Flags{
+ PidFilter: -1,
+ TidFilter: -1,
+ EventMapSize: 4096 * 16,
+ Duration: 900,
+ LiveInterval: 200 * time.Millisecond,
+ FlamegraphName: "default",
+ TUIExportEnable: true,
+ CollapsedFields: []string{"comm", "path", "tracepoint"},
+ CountField: "count",
+ }
+}
+
+// GetPidFilter returns the active process filter.
+func (f Flags) GetPidFilter() int {
+ return f.PidFilter
+}
+
+// GetTidFilter returns the active thread filter.
+func (f Flags) GetTidFilter() int {
+ return f.TidFilter
+}
+
+// GetTUIExportEnable reports whether TUI CSV export is enabled.
+func (f Flags) GetTUIExportEnable() bool {
+ return f.TUIExportEnable
+}
+
+func (f Flags) clone() Flags {
+ out := f
+ out.TracepointsToAttach = slices.Clone(f.TracepointsToAttach)
+ out.TracepointsToExclude = slices.Clone(f.TracepointsToExclude)
+ out.CollapsedFields = slices.Clone(f.CollapsedFields)
return out
}
+func Get() Flags {
+ cfg := current.Load()
+ if cfg == nil {
+ return NewFlags()
+ }
+ return cfg.clone()
+}
+
+func setCurrent(cfg Flags) {
+ snapshot := cfg.clone()
+ current.Store(&snapshot)
+}
+
+func updateCurrent(update func(*Flags)) {
+ for {
+ old := current.Load()
+ next := NewFlags()
+ if old != nil {
+ next = old.clone()
+ }
+ update(&next)
+ snapshot := next.clone()
+ if current.CompareAndSwap(old, &snapshot) {
+ return
+ }
+ }
+}
+
// SetPidFilter updates the active PID filter used for subsequent tracing runs.
func SetPidFilter(pid int) {
- pidFilter.Store(int64(pid))
+ updateCurrent(func(cfg *Flags) {
+ cfg.PidFilter = pid
+ })
}
// SetTidFilter updates the active TID filter used for subsequent tracing runs.
func SetTidFilter(tid int) {
- tidFilter.Store(int64(tid))
+ updateCurrent(func(cfg *Flags) {
+ cfg.TidFilter = tid
+ })
}
// SetTUIExportEnable toggles TUI snapshot export file writing.
func SetTUIExportEnable(enabled bool) {
- tuiExportEnable.Store(enabled)
+ updateCurrent(func(cfg *Flags) {
+ cfg.TUIExportEnable = enabled
+ })
}
func Parse() error {
@@ -108,48 +166,47 @@ func Parse() error {
}
func parse() error {
- flag.IntVar(&singleton.PidFilter, "pid", -1, "Filter for processes ID")
- flag.IntVar(&singleton.TidFilter, "tid", -1, "Filter for thread ID")
- flag.IntVar(&singleton.EventMapSize, "mapSize", 4096*16, "BPF FD event ring buffer map size")
- flag.IntVar(&singleton.Duration, "duration", 900, "Probe duration in seconds")
+ cfg := NewFlags()
+
+ flag.IntVar(&cfg.PidFilter, "pid", cfg.PidFilter, "Filter for processes ID")
+ flag.IntVar(&cfg.TidFilter, "tid", cfg.TidFilter, "Filter for thread ID")
+ flag.IntVar(&cfg.EventMapSize, "mapSize", cfg.EventMapSize, "BPF FD event ring buffer map size")
+ flag.IntVar(&cfg.Duration, "duration", cfg.Duration, "Probe duration in seconds")
- flag.StringVar(&singleton.CommFilter, "comm", "", "Command to filter for")
- flag.StringVar(&singleton.PathFilter, "path", "", "Path to filter for")
+ flag.StringVar(&cfg.CommFilter, "comm", "", "Command to filter for")
+ flag.StringVar(&cfg.PathFilter, "path", "", "Path to filter for")
- flag.BoolVar(&singleton.PprofEnable, "pprof", false, "Enable profiling")
+ flag.BoolVar(&cfg.PprofEnable, "pprof", false, "Enable profiling")
tracepointsToAttach := flag.String("tps", "", "Comma separated list regexes for tracepoints to load")
tracepointsToExclude := flag.String("tpsExclude", "", "Comma separated list regexes for tracepoints to exclude")
- flag.BoolVar(&singleton.PlainMode, "plain", false, "Enable plain CSV output mode (disable TUI)")
- flag.BoolVar(&singleton.FlamegraphEnable, "flamegraph", false, "Enable flamegraph builder")
- flag.BoolVar(&singleton.LiveFlamegraph, "live", false, "Enable live flamegraph mode")
- flag.DurationVar(&singleton.LiveInterval, "live-interval", 200*time.Millisecond, "Live flamegraph refresh interval")
- flag.StringVar(&singleton.OpenCommand, "open", "", "Command to open live flamegraph URL (used with -live); use {url} placeholder or URL is appended")
- flag.StringVar(&singleton.FlamegraphName, "name", "default", "Name of the flamegraph, used to generate the SVG file")
- flag.BoolVar(&singleton.FlamegraphJSON, "flamegraphJson", false, "Also export flamegraph tree as JSON in -ior mode (experimental WASM-ready output)")
- flag.BoolVar(&singleton.TUIExportEnable, "tuiExport", true, "Enable writing TUI snapshot export files")
-
- flag.StringVar(&singleton.IorDataFile, "ior", "", "IOR data file to convert into native SVG flamegraph")
- flag.DurationVar(&singleton.IorWatchInterval, "iorWatchInterval", 0,
+ flag.BoolVar(&cfg.PlainMode, "plain", false, "Enable plain CSV output mode (disable TUI)")
+ flag.BoolVar(&cfg.FlamegraphEnable, "flamegraph", false, "Enable flamegraph builder")
+ flag.BoolVar(&cfg.LiveFlamegraph, "live", false, "Enable live flamegraph mode")
+ flag.DurationVar(&cfg.LiveInterval, "live-interval", cfg.LiveInterval, "Live flamegraph refresh interval")
+ flag.StringVar(&cfg.OpenCommand, "open", "", "Command to open live flamegraph URL (used with -live); use {url} placeholder or URL is appended")
+ flag.StringVar(&cfg.FlamegraphName, "name", cfg.FlamegraphName, "Name of the flamegraph, used to generate the SVG file")
+ flag.BoolVar(&cfg.FlamegraphJSON, "flamegraphJson", false, "Also export flamegraph tree as JSON in -ior mode (experimental WASM-ready output)")
+ flag.BoolVar(&cfg.TUIExportEnable, "tuiExport", cfg.TUIExportEnable, "Enable writing TUI snapshot export files")
+
+ flag.StringVar(&cfg.IorDataFile, "ior", "", "IOR data file to convert into native SVG flamegraph")
+ flag.DurationVar(&cfg.IorWatchInterval, "iorWatchInterval", 0,
"In -ior mode, poll input file for changes and regenerate outputs; also enables auto-reloading viewer")
fields := flag.String("fields", "",
fmt.Sprintf("Comma separated list of fields to collapse, valid are: %v", validCollapsedFields))
- flag.StringVar(&singleton.CountField, "count", "count",
+ flag.StringVar(&cfg.CountField, "count", cfg.CountField,
fmt.Sprintf("Count field to collapse, valid are: %v", validCollapsedCounts))
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
return err
}
- pidFilter.Store(int64(singleton.PidFilter))
- tidFilter.Store(int64(singleton.TidFilter))
- tuiExportEnable.Store(singleton.TUIExportEnable)
var err error
- singleton.TracepointsToAttach, err = extractTracepointFlags(*tracepointsToAttach)
+ cfg.TracepointsToAttach, err = extractTracepointFlags(*tracepointsToAttach)
if err != nil {
return err
}
- singleton.TracepointsToExclude, err = extractTracepointFlags(*tracepointsToExclude)
+ cfg.TracepointsToExclude, err = extractTracepointFlags(*tracepointsToExclude)
if err != nil {
return err
}
@@ -160,21 +217,22 @@ func parse() error {
// If future kernels regress, add targeted exclusions here.
if *fields == "" {
- singleton.CollapsedFields = []string{"comm", "path", "tracepoint"}
+ cfg.CollapsedFields = []string{"comm", "path", "tracepoint"}
} else {
- singleton.CollapsedFields = strings.Split(*fields, ",")
+ cfg.CollapsedFields = strings.Split(*fields, ",")
}
- for _, field := range singleton.CollapsedFields {
+ for _, field := range cfg.CollapsedFields {
if !slices.Contains(validCollapsedFields, field) {
return fmt.Errorf("invalid field for collapse: %s", field)
}
}
- if !slices.Contains(validCollapsedCounts, singleton.CountField) {
- return fmt.Errorf("invalid count field: %s", singleton.CountField)
+ if !slices.Contains(validCollapsedCounts, cfg.CountField) {
+ return fmt.Errorf("invalid count field: %s", cfg.CountField)
}
+ setCurrent(cfg)
return nil
}
diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go
index 54c65b8..3534916 100644
--- a/internal/flags/flags_test.go
+++ b/internal/flags/flags_test.go
@@ -14,34 +14,25 @@ func parseForTest(t *testing.T, args ...string) (Flags, error) {
oldCommandLine := flag.CommandLine
oldArgs := os.Args
- oldSingleton := singleton
+ oldCurrent := Get()
oldParseErr := parseErr
- oldPID := pidFilter.Load()
- oldTID := tidFilter.Load()
- oldTUIExport := tuiExportEnable.Load()
fs := flag.NewFlagSet("ior-test", flag.ContinueOnError)
fs.SetOutput(io.Discard)
flag.CommandLine = fs
os.Args = append([]string{"ior"}, args...)
- singleton = Flags{TUIExportEnable: true}
+ setCurrent(NewFlags())
parseErr = nil
- pidFilter.Store(-1)
- tidFilter.Store(-1)
- tuiExportEnable.Store(true)
err := parse()
- cfg := singleton
+ cfg := Get()
t.Cleanup(func() {
flag.CommandLine = oldCommandLine
os.Args = oldArgs
- singleton = oldSingleton
+ setCurrent(oldCurrent)
parseErr = oldParseErr
- pidFilter.Store(oldPID)
- tidFilter.Store(oldTID)
- tuiExportEnable.Store(oldTUIExport)
})
return cfg, err
@@ -62,14 +53,30 @@ func TestParseLiveFlagsAndInterval(t *testing.T) {
if cfg.PidFilter != 1234 {
t.Fatalf("pid filter = %d, want 1234", cfg.PidFilter)
}
- if got := int(pidFilter.Load()); got != 1234 {
- t.Fatalf("global pid filter = %d, want 1234", got)
+ if got := Get().GetPidFilter(); got != 1234 {
+ t.Fatalf("Get().GetPidFilter() = %d, want 1234", got)
}
if cfg.OpenCommand != "" {
t.Fatalf("expected empty open command by default")
}
}
+func TestNewFlagsDefaultsAndGetters(t *testing.T) {
+ cfg := NewFlags()
+ if cfg.GetPidFilter() != -1 {
+ t.Fatalf("GetPidFilter() = %d, want -1", cfg.GetPidFilter())
+ }
+ if cfg.GetTidFilter() != -1 {
+ t.Fatalf("GetTidFilter() = %d, want -1", cfg.GetTidFilter())
+ }
+ if !cfg.GetTUIExportEnable() {
+ t.Fatalf("GetTUIExportEnable() = false, want true")
+ }
+ if cfg.CountField != "count" {
+ t.Fatalf("CountField = %q, want count", cfg.CountField)
+ }
+}
+
func TestParseLiveDefaults(t *testing.T) {
cfg, err := parseForTest(t)
if err != nil {