summaryrefslogtreecommitdiff
path: root/internal/flags
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-21 08:16:08 +0300
committerPaul Buetow <paul@buetow.org>2026-05-21 08:16:08 +0300
commitbe8735fe701f7398c19c17c394f4827614eab875 (patch)
treeaba59890563edb6e03f2eb82fee5d89b49fa2c81 /internal/flags
parent3a5706f21d30258577a5934efb93c400dad723db (diff)
p7 add attach-time trace dimension gating
Diffstat (limited to 'internal/flags')
-rw-r--r--internal/flags/flags.go28
-rw-r--r--internal/flags/flags_test.go99
2 files changed, 121 insertions, 6 deletions
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 3a6456a..547ba8e 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -148,12 +148,12 @@ func Parse() (Config, error) {
// fresh FlagSet and custom argument slices without touching global state.
func parseFromFlagSet(fs *flag.FlagSet, args []string) (Config, error) {
cfg := NewFlags()
- tpsAttach, tpsExclude, fields, familySampling, syscallSampling := registerFlags(fs, &cfg)
+ tpsAttach, tpsExclude, fields, familySampling, syscallSampling, dims := registerFlags(fs, &cfg)
if err := fs.Parse(args); err != nil {
return Config{}, err
}
- if err := resolvePostParseFields(&cfg, tpsAttach, tpsExclude, fields); err != nil {
+ if err := resolvePostParseFields(&cfg, tpsAttach, tpsExclude, fields, dims); err != nil {
return Config{}, err
}
if err := resolveSamplingRates(&cfg, familySampling, syscallSampling); err != nil {
@@ -167,9 +167,10 @@ func parseFromFlagSet(fs *flag.FlagSet, args []string) (Config, error) {
// registerFlags binds all CLI flags to cfg and returns the string pointers for
// fields that require post-parse resolution (tracepoint regexes, collapse fields).
-func registerFlags(fs *flag.FlagSet, cfg *Config) (tpsAttach, tpsExclude, fields, familySampling, syscallSampling *string) {
+func registerFlags(fs *flag.FlagSet, cfg *Config) (tpsAttach, tpsExclude, fields, familySampling, syscallSampling *string, dims *tracepoints.DimensionSelectorConfig) {
validFields := collapse.ValidFields()
validCounts := collapse.ValidCountFields()
+ dimensionCfg := &tracepoints.DimensionSelectorConfig{}
fs.IntVar(&cfg.PidFilter, "pid", cfg.PidFilter, "Filter for processes ID")
fs.IntVar(&cfg.TidFilter, "tid", cfg.TidFilter, "Filter for thread ID")
@@ -182,6 +183,18 @@ func registerFlags(fs *flag.FlagSet, cfg *Config) (tpsAttach, tpsExclude, fields
tpsAttach = fs.String("tps", "", "Comma separated list regexes for tracepoints to load")
tpsExclude = fs.String("tpsExclude", "", "Comma separated list regexes for tracepoints to exclude")
+ fs.StringVar(&dimensionCfg.TraceFamilies, "trace-families", "",
+ "Comma separated syscall families to attach (for example FS,Time,Network)")
+ fs.StringVar(&dimensionCfg.TraceKinds, "trace-kinds", "",
+ "Comma separated tracepoint kinds to attach (for example fd,open,sleep,epoll-ctl)")
+ fs.StringVar(&dimensionCfg.TraceSyscalls, "trace-syscalls", "",
+ "Comma separated syscall names to attach (for example openat,read,nanosleep)")
+ fs.StringVar(&dimensionCfg.NoTraceFamilies, "no-trace-families", "",
+ "Comma separated syscall families to exclude from attachment")
+ fs.StringVar(&dimensionCfg.NoTraceKinds, "no-trace-kinds", "",
+ "Comma separated tracepoint kinds to exclude from attachment")
+ fs.StringVar(&dimensionCfg.NoTraceSyscalls, "no-trace-syscalls", "",
+ "Comma separated syscall names to exclude from attachment")
fs.BoolVar(&cfg.PlainMode, "plain", false, "Enable plain CSV output mode (disable TUI)")
fs.BoolVar(&cfg.FlamegraphOutput, "flamegraph", false, "Write aggregated .ior.zst output for trace/integration workflows")
@@ -204,15 +217,18 @@ func registerFlags(fs *flag.FlagSet, cfg *Config) (tpsAttach, tpsExclude, fields
fmt.Sprintf("Comma separated list of fields to collapse, valid are: %v", validFields))
fs.StringVar(&cfg.CountField, "count", cfg.CountField,
fmt.Sprintf("Count field to collapse, valid are: %v", validCounts))
- return tpsAttach, tpsExclude, fields, familySampling, syscallSampling
+ return tpsAttach, tpsExclude, fields, familySampling, syscallSampling, dimensionCfg
}
// resolvePostParseFields compiles the tracepoint selector and collapse field
// list from the raw string flags that cannot be bound directly to cfg fields.
-func resolvePostParseFields(cfg *Config, tpsAttach, tpsExclude, fields *string) error {
+func resolvePostParseFields(cfg *Config, tpsAttach, tpsExclude, fields *string, dims *tracepoints.DimensionSelectorConfig) error {
// Parse the tracepoint include/exclude regex lists into a Selector.
// The Selector owns all matching logic; Config is purely a data carrier.
- sel, err := tracepoints.ParseSelector(*tpsAttach, *tpsExclude)
+ if dims == nil {
+ dims = &tracepoints.DimensionSelectorConfig{}
+ }
+ sel, err := tracepoints.ParseSelectorWithDimensions(*tpsAttach, *tpsExclude, *dims)
if err != nil {
return err
}
diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go
index 1feeafd..93df7b0 100644
--- a/internal/flags/flags_test.go
+++ b/internal/flags/flags_test.go
@@ -152,6 +152,105 @@ func TestParseInvalidTracepointRegexReturnsError(t *testing.T) {
}
}
+func TestParseDefaultTraceDimensionsFSOnly(t *testing.T) {
+ cfg, err := parseForTest(t)
+ if err != nil {
+ t.Fatalf("parse returned error: %v", err)
+ }
+ if !cfg.TracepointSelector.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat attached by default")
+ }
+ if cfg.TracepointSelector.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep excluded by default")
+ }
+}
+
+func TestParseTraceFamiliesFlag(t *testing.T) {
+ cfg, err := parseForTest(t, "-trace-families", "Time")
+ if err != nil {
+ t.Fatalf("parse returned error: %v", err)
+ }
+ if !cfg.TracepointSelector.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep attached for Time family")
+ }
+ if cfg.TracepointSelector.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat excluded when only Time family enabled")
+ }
+}
+
+func TestParseTraceKindsFlag(t *testing.T) {
+ cfg, err := parseForTest(t, "-trace-kinds", "sleep")
+ if err != nil {
+ t.Fatalf("parse returned error: %v", err)
+ }
+ if !cfg.TracepointSelector.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep attached for sleep kind")
+ }
+ if cfg.TracepointSelector.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat excluded for sleep-only selector")
+ }
+}
+
+func TestParseTraceSyscallsFlag(t *testing.T) {
+ cfg, err := parseForTest(t, "-trace-syscalls", "openat")
+ if err != nil {
+ t.Fatalf("parse returned error: %v", err)
+ }
+ if !cfg.TracepointSelector.ShouldAttach("sys_enter_openat") || !cfg.TracepointSelector.ShouldAttach("sys_exit_openat") {
+ t.Fatal("expected openat enter/exit attached")
+ }
+ if cfg.TracepointSelector.ShouldAttach("sys_enter_write") {
+ t.Fatal("expected write excluded when only openat enabled")
+ }
+}
+
+func TestParseTraceDimensionsUnionAndExclusions(t *testing.T) {
+ cfg, err := parseForTest(t,
+ "-trace-families", "Time",
+ "-trace-syscalls", "openat",
+ "-no-trace-syscalls", "openat",
+ )
+ if err != nil {
+ t.Fatalf("parse returned error: %v", err)
+ }
+ if cfg.TracepointSelector.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat excluded by no-trace-syscalls")
+ }
+ if !cfg.TracepointSelector.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep still attached from trace-families")
+ }
+}
+
+func TestParseTraceFamiliesRejectsUnknown(t *testing.T) {
+ _, err := parseForTest(t, "-trace-families", "Nope")
+ if err == nil {
+ t.Fatal("expected parse error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall family") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestParseTraceKindsRejectsUnknown(t *testing.T) {
+ _, err := parseForTest(t, "-trace-kinds", "not-a-kind")
+ if err == nil {
+ t.Fatal("expected parse error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall kind") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestParseTraceSyscallsRejectsUnknown(t *testing.T) {
+ _, err := parseForTest(t, "-trace-syscalls", "definitely_not_syscall")
+ if err == nil {
+ t.Fatal("expected parse error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall in trace selector") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
func TestParseResetTimerDefault(t *testing.T) {
cfg, err := parseForTest(t)
if err != nil {