summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/flags/flags.go28
-rw-r--r--internal/flags/flags_test.go99
-rw-r--r--internal/generate/bpfhandler.go4
-rw-r--r--internal/generate/classify.go55
-rw-r--r--internal/generate/tracepointsgo.go107
-rw-r--r--internal/generate/tracepointsgo_test.go22
-rw-r--r--internal/probemanager/manager_test.go41
-rw-r--r--internal/tracepoints/dimension_selector.go219
-rw-r--r--internal/tracepoints/dimension_selector_test.go147
-rw-r--r--internal/tracepoints/generated_tracepoints.go740
-rw-r--r--internal/tracepoints/selector.go46
-rw-r--r--internal/tracepoints/selector_test.go14
12 files changed, 1507 insertions, 15 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 {
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index 9365c52..dce776a 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -23,9 +23,9 @@ func generateBPFHandler(tp GeneratedTracepoint) string {
}
eventStruct := eventStructName(tp.Classification.Kind)
- comment := eventStruct
+ comment := fmt.Sprintf("%s (kind=%s)", eventStruct, tp.Classification.Kind.MetadataName())
if tp.Classification.Kind == KindRet {
- comment = fmt.Sprintf("%s (%s)", eventStruct, ClassifyRet(f.Name))
+ comment = fmt.Sprintf("%s (%s) (kind=%s)", eventStruct, ClassifyRet(f.Name), tp.Classification.Kind.MetadataName())
}
eventTypeConst := eventTypeConstant(tp.Classification.Kind, isEnter)
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index af7d78d..9e9df9c 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -32,6 +32,61 @@ const (
KindPerfOpen
)
+func (k TracepointKind) MetadataName() string {
+ switch k {
+ case KindFd:
+ return "fd"
+ case KindOpen:
+ return "open"
+ case KindMqOpen:
+ return "mq-open"
+ case KindExec:
+ return "exec"
+ case KindPathname:
+ return "pathname"
+ case KindName:
+ return "name"
+ case KindRet:
+ return "ret"
+ case KindFcntl:
+ return "fcntl"
+ case KindNull:
+ return "null"
+ case KindDup3:
+ return "dup3"
+ case KindOpenByHandleAt:
+ return "open-by-handle-at"
+ case KindSocket:
+ return "socket"
+ case KindSocketpair:
+ return "socketpair"
+ case KindAccept:
+ return "accept"
+ case KindPipe:
+ return "pipe"
+ case KindEventfd:
+ return "eventfd"
+ case KindEpollCtl:
+ return "epoll-ctl"
+ case KindTwoFd:
+ return "two-fd"
+ case KindPoll:
+ return "poll"
+ case KindMem:
+ return "mem"
+ case KindSleep:
+ return "sleep"
+ case KindKeyctl:
+ return "keyctl"
+ case KindPtrace:
+ return "ptrace"
+ case KindPerfOpen:
+ return "perf-open"
+ default:
+ return "none"
+ }
+}
+
type RetClassification string
const (
diff --git a/internal/generate/tracepointsgo.go b/internal/generate/tracepointsgo.go
index 0542f64..9d92047 100644
--- a/internal/generate/tracepointsgo.go
+++ b/internal/generate/tracepointsgo.go
@@ -5,14 +5,35 @@ import (
"fmt"
"io"
"regexp"
+ "sort"
"strings"
)
var secRe = regexp.MustCompile(`^SEC.*sys_((?:enter|exit)_[a-z_0-9]+)`)
+var kindLineRe = regexp.MustCompile(`^(sys_enter_[a-z0-9_]+)\s+is a struct\s+([a-z0-9_]+)\s*$`)
// ExtractTracepoints reads generated C code and extracts tracepoint names from
// SEC annotations, producing the generated_tracepoints.go content.
func ExtractTracepoints(r io.Reader) (string, error) {
+ return ExtractTracepointsWithKinds(r, strings.NewReader(""))
+}
+
+// ExtractTracepointsWithKinds reads generated C code and the
+// generated_tracepoints_result.txt metadata to produce
+// internal/tracepoints/generated_tracepoints.go.
+func ExtractTracepointsWithKinds(cReader io.Reader, kindsReader io.Reader) (string, error) {
+ tracepoints, err := extractTracepoints(cReader)
+ if err != nil {
+ return "", err
+ }
+ syscallKinds, err := extractSyscallKinds(kindsReader)
+ if err != nil {
+ return "", err
+ }
+ return formatTracepointsGo(tracepoints, syscallKinds), nil
+}
+
+func extractTracepoints(r io.Reader) ([]string, error) {
scanner := bufio.NewScanner(r)
var tracepoints []string
@@ -24,13 +45,58 @@ func ExtractTracepoints(r io.Reader) (string, error) {
}
if err := scanner.Err(); err != nil {
- return "", fmt.Errorf("scanning input: %w", err)
+ return nil, fmt.Errorf("scanning input: %w", err)
}
+ return tracepoints, nil
+}
- return formatTracepointsGo(tracepoints), nil
+func extractSyscallKinds(r io.Reader) (map[string]string, error) {
+ kinds := make(map[string]string)
+ if r == nil {
+ return kinds, nil
+ }
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ continue
+ }
+ m := kindLineRe.FindStringSubmatch(line)
+ if m == nil {
+ continue
+ }
+ syscall := strings.TrimPrefix(m[1], "sys_enter_")
+ kind := normalizeStructKind(m[2])
+ if kind == "" {
+ continue
+ }
+ kinds[syscall] = kind
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("scan kind metadata: %w", err)
+ }
+ return kinds, nil
+}
+
+func normalizeStructKind(structName string) string {
+ base := strings.TrimSuffix(structName, "_event")
+ switch base {
+ case "mq_open":
+ return "mq-open"
+ case "open_by_handle_at":
+ return "open-by-handle-at"
+ case "two_fd":
+ return "two-fd"
+ case "epoll_ctl":
+ return "epoll-ctl"
+ case "perf_open":
+ return "perf-open"
+ default:
+ return base
+ }
}
-func formatTracepointsGo(tracepoints []string) string {
+func formatTracepointsGo(tracepoints []string, syscallKinds map[string]string) string {
var b strings.Builder
b.WriteString("// Code generated - don't change manually!\n")
b.WriteString("package tracepoints\n\n")
@@ -39,5 +105,40 @@ func formatTracepointsGo(tracepoints []string) string {
fmt.Fprintf(&b, "\t%q,\n", tp)
}
b.WriteString("}\n")
+ b.WriteString("\n")
+ b.WriteString("var syscallFamilies = map[string]string{\n")
+ syscallFamilies := make(map[string]string)
+ for _, tp := range tracepoints {
+ syscall := strings.TrimPrefix(tp, "sys_enter_")
+ syscall = strings.TrimPrefix(syscall, "sys_exit_")
+ if syscall == "" {
+ continue
+ }
+ if _, ok := syscallFamilies[syscall]; ok {
+ continue
+ }
+ family := ClassifySyscallFamily("sys_enter_" + syscall)
+ syscallFamilies[syscall] = string(family)
+ }
+ familySyscalls := make([]string, 0, len(syscallFamilies))
+ for syscall := range syscallFamilies {
+ familySyscalls = append(familySyscalls, syscall)
+ }
+ sort.Strings(familySyscalls)
+ for _, syscall := range familySyscalls {
+ fmt.Fprintf(&b, "\t%q: %q,\n", syscall, syscallFamilies[syscall])
+ }
+ b.WriteString("}\n")
+ b.WriteString("\n")
+ b.WriteString("var syscallKinds = map[string]string{\n")
+ syscalls := make([]string, 0, len(syscallKinds))
+ for syscall := range syscallKinds {
+ syscalls = append(syscalls, syscall)
+ }
+ sort.Strings(syscalls)
+ for _, syscall := range syscalls {
+ fmt.Fprintf(&b, "\t%q: %q,\n", syscall, syscallKinds[syscall])
+ }
+ b.WriteString("}\n")
return b.String()
}
diff --git a/internal/generate/tracepointsgo_test.go b/internal/generate/tracepointsgo_test.go
index 978633b..ebad63d 100644
--- a/internal/generate/tracepointsgo_test.go
+++ b/internal/generate/tracepointsgo_test.go
@@ -51,6 +51,8 @@ func TestExtractTracepoints(t *testing.T) {
requireContains(t, output, `"sys_enter_close",`)
requireContains(t, output, `"sys_exit_close",`)
requireContains(t, output, "var List = []string{")
+ requireContains(t, output, "var syscallFamilies = map[string]string{")
+ requireContains(t, output, "var syscallKinds = map[string]string{")
// Should NOT contain ignore comments or defines
if strings.Contains(output, "kill") {
@@ -78,6 +80,8 @@ func TestExtractTracepointsEmpty(t *testing.T) {
t.Fatal(err)
}
requireContains(t, output, "var List = []string{")
+ requireContains(t, output, "var syscallFamilies = map[string]string{")
+ requireContains(t, output, "var syscallKinds = map[string]string{")
requireContains(t, output, "}")
}
@@ -115,7 +119,25 @@ func TestExtractTracepointsNoSECLines(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
requireContains(t, output, "var List = []string{")
+ requireContains(t, output, "var syscallFamilies = map[string]string{")
+ requireContains(t, output, "var syscallKinds = map[string]string{")
if strings.Contains(output, `"sys_`) {
t.Error("input with no SEC lines should produce empty list")
}
}
+
+func TestExtractTracepointsWithKinds(t *testing.T) {
+ kindData := `sys_enter_read is a struct fd_event
+sys_enter_open_by_handle_at is a struct open_by_handle_at_event
+sys_enter_mq_open is a struct mq_open_event
+sys_enter_epoll_ctl is a struct epoll_ctl_event
+`
+ output, err := ExtractTracepointsWithKinds(strings.NewReader(sampleGeneratedC), strings.NewReader(kindData))
+ if err != nil {
+ t.Fatalf("ExtractTracepointsWithKinds failed: %v", err)
+ }
+ requireContains(t, output, `"read": "fd",`)
+ requireContains(t, output, `"open_by_handle_at": "open-by-handle-at",`)
+ requireContains(t, output, `"mq_open": "mq-open",`)
+ requireContains(t, output, `"epoll_ctl": "epoll-ctl",`)
+}
diff --git a/internal/probemanager/manager_test.go b/internal/probemanager/manager_test.go
index b75a579..ee436b9 100644
--- a/internal/probemanager/manager_test.go
+++ b/internal/probemanager/manager_test.go
@@ -2,6 +2,7 @@ package probemanager
import (
"errors"
+ "ior/internal/tracepoints"
"strings"
"sync"
"testing"
@@ -127,6 +128,46 @@ func TestManagerAttachAllToggleAndCounts(t *testing.T) {
}
}
+func TestManagerAttachAllWithDimensionSelectorAttachesOnlyEnabledSyscalls(t *testing.T) {
+ attacher := &fakeAttacher{
+ programs: map[string]*fakeProgram{
+ "handle_sys_enter_openat": {},
+ "handle_sys_exit_openat": {},
+ "handle_sys_enter_write": {},
+ "handle_sys_exit_write": {},
+ },
+ errs: map[string]error{},
+ }
+ mgr := NewManager(attacher)
+ selector, err := tracepoints.ParseSelectorWithDimensions("", "", tracepoints.DimensionSelectorConfig{
+ TraceSyscalls: "openat",
+ })
+ if err != nil {
+ t.Fatalf("build selector: %v", err)
+ }
+
+ err = mgr.AttachAll(selector.ShouldAttach, []string{
+ "sys_enter_openat", "sys_exit_openat",
+ "sys_enter_write", "sys_exit_write",
+ }, nil)
+ if err != nil {
+ t.Fatalf("AttachAll returned error: %v", err)
+ }
+
+ if got := attacher.programs["handle_sys_enter_openat"].attachCalls(); got != 1 {
+ t.Fatalf("openat enter attach calls = %d, want 1", got)
+ }
+ if got := attacher.programs["handle_sys_exit_openat"].attachCalls(); got != 1 {
+ t.Fatalf("openat exit attach calls = %d, want 1", got)
+ }
+ if got := attacher.programs["handle_sys_enter_write"].attachCalls(); got != 0 {
+ t.Fatalf("write enter attach calls = %d, want 0", got)
+ }
+ if got := attacher.programs["handle_sys_exit_write"].attachCalls(); got != 0 {
+ t.Fatalf("write exit attach calls = %d, want 0", got)
+ }
+}
+
func TestManagerAttachSerializesConcurrentCalls(t *testing.T) {
enterBlocked := make(chan struct{})
releaseEnter := make(chan struct{})
diff --git a/internal/tracepoints/dimension_selector.go b/internal/tracepoints/dimension_selector.go
new file mode 100644
index 0000000..22a70fb
--- /dev/null
+++ b/internal/tracepoints/dimension_selector.go
@@ -0,0 +1,219 @@
+package tracepoints
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "ior/internal/types"
+)
+
+// DimensionSelectorConfig holds attach-time syscall-dimension selection inputs.
+// Each field accepts comma-separated values.
+type DimensionSelectorConfig struct {
+ TraceFamilies string
+ TraceKinds string
+ TraceSyscalls string
+ NoTraceFamilies string
+ NoTraceKinds string
+ NoTraceSyscalls string
+}
+
+// ParseSelectorWithDimensions compiles regex-based attach/exclude filters and
+// applies attach-time syscall dimension gating.
+func ParseSelectorWithDimensions(attach, exclude string, dims DimensionSelectorConfig) (Selector, error) {
+ sel, err := ParseSelector(attach, exclude)
+ if err != nil {
+ return Selector{}, err
+ }
+
+ allow, err := buildAllowedSyscalls(dims)
+ if err != nil {
+ return Selector{}, err
+ }
+ sel.RestrictSyscalls = true
+ sel.Syscalls = allow
+ return sel, nil
+}
+
+func buildAllowedSyscalls(dims DimensionSelectorConfig) (map[string]struct{}, error) {
+ knownSyscalls := allKnownSyscalls()
+ knownKinds := allKnownKinds()
+
+ includeFamilies, familyFilterProvided, err := parseFamiliesCSV(dims.TraceFamilies)
+ if err != nil {
+ return nil, err
+ }
+ includeKinds, kindFilterProvided, err := parseKindsCSV(dims.TraceKinds, knownKinds)
+ if err != nil {
+ return nil, err
+ }
+ includeSyscalls, syscallFilterProvided, err := parseSyscallsCSV(dims.TraceSyscalls, knownSyscalls)
+ if err != nil {
+ return nil, err
+ }
+
+ allow := make(map[string]struct{})
+ hasPositive := familyFilterProvided || kindFilterProvided || syscallFilterProvided
+ if hasPositive {
+ for syscall, family := range syscallFamilies {
+ if _, ok := includeFamilies[family]; ok {
+ allow[syscall] = struct{}{}
+ }
+ }
+ for syscall, kind := range syscallKinds {
+ if _, ok := includeKinds[kind]; ok {
+ allow[syscall] = struct{}{}
+ }
+ }
+ for syscall := range includeSyscalls {
+ allow[syscall] = struct{}{}
+ }
+ } else {
+ // Backward compatibility default: keep existing file-I/O coverage on and
+ // leave newly-expanded non-IO families disabled unless explicitly opted in.
+ for syscall, family := range syscallFamilies {
+ if family == string(types.FamilyFS) {
+ allow[syscall] = struct{}{}
+ }
+ }
+ }
+
+ excludeFamilies, _, err := parseFamiliesCSV(dims.NoTraceFamilies)
+ if err != nil {
+ return nil, err
+ }
+ excludeKinds, _, err := parseKindsCSV(dims.NoTraceKinds, knownKinds)
+ if err != nil {
+ return nil, err
+ }
+ excludeSyscalls, _, err := parseSyscallsCSV(dims.NoTraceSyscalls, knownSyscalls)
+ if err != nil {
+ return nil, err
+ }
+
+ for syscall := range allow {
+ if _, ok := excludeSyscalls[syscall]; ok {
+ delete(allow, syscall)
+ continue
+ }
+ if family, ok := syscallFamilies[syscall]; ok {
+ if _, excluded := excludeFamilies[family]; excluded {
+ delete(allow, syscall)
+ continue
+ }
+ }
+ if kind, ok := syscallKinds[syscall]; ok {
+ if _, excluded := excludeKinds[kind]; excluded {
+ delete(allow, syscall)
+ }
+ }
+ }
+
+ return allow, nil
+}
+
+func parseFamiliesCSV(raw string) (map[string]struct{}, bool, error) {
+ values, provided := splitCSV(raw)
+ if !provided {
+ return map[string]struct{}{}, false, nil
+ }
+ out := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ family, ok := types.ParseSyscallFamily(value)
+ if !ok {
+ return nil, false, fmt.Errorf("invalid syscall family in trace selector: %q", value)
+ }
+ out[string(family)] = struct{}{}
+ }
+ return out, true, nil
+}
+
+func parseKindsCSV(raw string, knownKinds map[string]struct{}) (map[string]struct{}, bool, error) {
+ values, provided := splitCSV(raw)
+ if !provided {
+ return map[string]struct{}{}, false, nil
+ }
+ out := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ kind := normalizeKind(value)
+ if _, ok := knownKinds[kind]; !ok {
+ return nil, false, fmt.Errorf("invalid syscall kind in trace selector: %q", value)
+ }
+ out[kind] = struct{}{}
+ }
+ return out, true, nil
+}
+
+func parseSyscallsCSV(raw string, knownSyscalls map[string]struct{}) (map[string]struct{}, bool, error) {
+ values, provided := splitCSV(raw)
+ if !provided {
+ return map[string]struct{}{}, false, nil
+ }
+ out := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ syscall := strings.ToLower(strings.TrimSpace(value))
+ if _, ok := knownSyscalls[syscall]; !ok {
+ return nil, false, fmt.Errorf("invalid syscall in trace selector: %q", value)
+ }
+ out[syscall] = struct{}{}
+ }
+ return out, true, nil
+}
+
+func splitCSV(raw string) ([]string, bool) {
+ raw = strings.TrimSpace(raw)
+ if raw == "" {
+ return nil, false
+ }
+ parts := strings.Split(raw, ",")
+ values := make([]string, 0, len(parts))
+ for _, part := range parts {
+ part = strings.TrimSpace(part)
+ if part == "" {
+ continue
+ }
+ values = append(values, part)
+ }
+ if len(values) == 0 {
+ return nil, false
+ }
+ return values, true
+}
+
+func normalizeKind(raw string) string {
+ normalized := strings.ToLower(strings.TrimSpace(raw))
+ normalized = strings.ReplaceAll(normalized, "_", "-")
+ return normalized
+}
+
+func allKnownSyscalls() map[string]struct{} {
+ out := make(map[string]struct{}, len(syscallFamilies))
+ for syscall := range syscallFamilies {
+ out[syscall] = struct{}{}
+ }
+ return out
+}
+
+func allKnownKinds() map[string]struct{} {
+ out := make(map[string]struct{})
+ for _, kind := range syscallKinds {
+ if kind == "" {
+ continue
+ }
+ out[kind] = struct{}{}
+ }
+ return out
+}
+
+// KnownKinds returns the sorted, normalized attach-time kind names accepted by
+// -trace-kinds and -no-trace-kinds.
+func KnownKinds() []string {
+ kindSet := allKnownKinds()
+ kinds := make([]string, 0, len(kindSet))
+ for kind := range kindSet {
+ kinds = append(kinds, kind)
+ }
+ sort.Strings(kinds)
+ return kinds
+}
diff --git a/internal/tracepoints/dimension_selector_test.go b/internal/tracepoints/dimension_selector_test.go
new file mode 100644
index 0000000..cd7b0f8
--- /dev/null
+++ b/internal/tracepoints/dimension_selector_test.go
@@ -0,0 +1,147 @@
+package tracepoints
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestParseSelectorWithDimensionsDefaultFSOnly(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{})
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected FS syscall openat to be attached by default")
+ }
+ if sel.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected non-FS syscall nanosleep to be excluded by default")
+ }
+}
+
+func TestParseSelectorWithDimensionsFamilyOnly(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceFamilies: "Time",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep to be attached when Time family is enabled")
+ }
+ if sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat to be excluded when only Time family is enabled")
+ }
+}
+
+func TestParseSelectorWithDimensionsKindOnly(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceKinds: "sleep",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep to be attached for sleep kind")
+ }
+ if sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat to be excluded when only sleep kind is enabled")
+ }
+}
+
+func TestParseSelectorWithDimensionsSyscallOnly(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceSyscalls: "openat",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") || !sel.ShouldAttach("sys_exit_openat") {
+ t.Fatal("expected both openat enter/exit tracepoints to be attached")
+ }
+ if sel.ShouldAttach("sys_enter_write") {
+ t.Fatal("expected write to be excluded when only openat is selected")
+ }
+}
+
+func TestParseSelectorWithDimensionsUnionSemantics(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceFamilies: "Time",
+ TraceSyscalls: "openat",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat from syscall selector")
+ }
+ if !sel.ShouldAttach("sys_enter_nanosleep") {
+ t.Fatal("expected nanosleep from family selector")
+ }
+}
+
+func TestParseSelectorWithDimensionsExclusionsOverridePositives(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceFamilies: "FS",
+ NoTraceSyscalls: "openat",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat to be excluded by -no-trace-syscalls")
+ }
+ if !sel.ShouldAttach("sys_enter_read") {
+ t.Fatal("expected other FS syscall (read) to remain attached")
+ }
+}
+
+func TestParseSelectorWithDimensionsRegexStillApplies(t *testing.T) {
+ sel, err := ParseSelectorWithDimensions("^sys_enter_openat$,^sys_exit_openat$", "", DimensionSelectorConfig{
+ TraceFamilies: "FS",
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected openat to pass regex+dimension filters")
+ }
+ if sel.ShouldAttach("sys_enter_read") {
+ t.Fatal("expected read to fail regex attach filters")
+ }
+}
+
+func TestParseSelectorWithDimensionsRejectsInvalidFamily(t *testing.T) {
+ _, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceFamilies: "Nope",
+ })
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall family") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestParseSelectorWithDimensionsRejectsInvalidKind(t *testing.T) {
+ _, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceKinds: "not-a-kind",
+ })
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall kind") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestParseSelectorWithDimensionsRejectsInvalidSyscall(t *testing.T) {
+ _, err := ParseSelectorWithDimensions("", "", DimensionSelectorConfig{
+ TraceSyscalls: "not_a_syscall",
+ })
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !strings.Contains(err.Error(), "invalid syscall in trace selector") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
diff --git a/internal/tracepoints/generated_tracepoints.go b/internal/tracepoints/generated_tracepoints.go
index 8277cbf..11913c8 100644
--- a/internal/tracepoints/generated_tracepoints.go
+++ b/internal/tracepoints/generated_tracepoints.go
@@ -737,3 +737,743 @@ var List = []string{
"sys_enter_rt_sigreturn",
"sys_exit_rt_sigreturn",
}
+
+var syscallFamilies = map[string]string{
+ "accept": "Network",
+ "accept4": "Network",
+ "access": "FS",
+ "acct": "Misc",
+ "add_key": "Security",
+ "adjtimex": "Misc",
+ "alarm": "Misc",
+ "arch_prctl": "Process",
+ "bind": "Network",
+ "bpf": "Security",
+ "brk": "Memory",
+ "cachestat": "FS",
+ "capget": "Security",
+ "capset": "Security",
+ "chdir": "FS",
+ "chmod": "FS",
+ "chown": "FS",
+ "chroot": "FS",
+ "clock_adjtime": "Time",
+ "clock_getres": "Time",
+ "clock_gettime": "Time",
+ "clock_nanosleep": "Time",
+ "clock_settime": "Time",
+ "clone": "Process",
+ "clone3": "Process",
+ "close": "FS",
+ "close_range": "FS",
+ "connect": "Network",
+ "copy_file_range": "FS",
+ "creat": "FS",
+ "delete_module": "Security",
+ "dup": "FS",
+ "dup2": "FS",
+ "dup3": "FS",
+ "epoll_create": "Polling",
+ "epoll_create1": "Polling",
+ "epoll_ctl": "Polling",
+ "epoll_pwait": "Polling",
+ "epoll_pwait2": "Polling",
+ "epoll_wait": "Polling",
+ "eventfd": "IPC",
+ "eventfd2": "IPC",
+ "execve": "Process",
+ "execveat": "Process",
+ "exit": "Process",
+ "exit_group": "Process",
+ "faccessat": "FS",
+ "faccessat2": "FS",
+ "fadvise64": "FS",
+ "fallocate": "FS",
+ "fanotify_init": "Misc",
+ "fanotify_mark": "Misc",
+ "fchdir": "FS",
+ "fchmod": "FS",
+ "fchmodat": "FS",
+ "fchmodat2": "FS",
+ "fchown": "FS",
+ "fchownat": "FS",
+ "fcntl": "FS",
+ "fdatasync": "FS",
+ "fgetxattr": "FS",
+ "file_getattr": "Misc",
+ "file_setattr": "Misc",
+ "finit_module": "Security",
+ "flistxattr": "FS",
+ "flock": "FS",
+ "fork": "Process",
+ "fremovexattr": "FS",
+ "fsconfig": "FS",
+ "fsetxattr": "FS",
+ "fsmount": "FS",
+ "fsopen": "FS",
+ "fspick": "FS",
+ "fstatfs": "FS",
+ "fsync": "FS",
+ "ftruncate": "FS",
+ "futex": "Misc",
+ "futex_requeue": "Misc",
+ "futex_wait": "Misc",
+ "futex_waitv": "Misc",
+ "futex_wake": "Misc",
+ "futimesat": "FS",
+ "get_mempolicy": "Security",
+ "get_robust_list": "Misc",
+ "getcpu": "Misc",
+ "getcwd": "FS",
+ "getdents": "FS",
+ "getdents64": "FS",
+ "getegid": "Process",
+ "geteuid": "Process",
+ "getgid": "Process",
+ "getgroups": "Process",
+ "getitimer": "Time",
+ "getpeername": "Network",
+ "getpgid": "Process",
+ "getpgrp": "Process",
+ "getpid": "Process",
+ "getppid": "Process",
+ "getpriority": "Process",
+ "getrandom": "Security",
+ "getresgid": "Process",
+ "getresuid": "Process",
+ "getrlimit": "Process",
+ "getrusage": "Process",
+ "getsid": "Process",
+ "getsockname": "Network",
+ "getsockopt": "Network",
+ "gettid": "Process",
+ "gettimeofday": "Time",
+ "getuid": "Process",
+ "getxattr": "FS",
+ "getxattrat": "FS",
+ "init_module": "Security",
+ "inotify_add_watch": "IPC",
+ "inotify_init": "IPC",
+ "inotify_init1": "IPC",
+ "inotify_rm_watch": "IPC",
+ "io_cancel": "AIO",
+ "io_destroy": "AIO",
+ "io_getevents": "AIO",
+ "io_pgetevents": "AIO",
+ "io_setup": "AIO",
+ "io_submit": "AIO",
+ "io_uring_enter": "AIO",
+ "io_uring_register": "AIO",
+ "io_uring_setup": "AIO",
+ "ioctl": "FS",
+ "ioperm": "Misc",
+ "iopl": "Misc",
+ "ioprio_get": "Misc",
+ "ioprio_set": "Misc",
+ "kcmp": "Process",
+ "kexec_file_load": "Security",
+ "kexec_load": "Misc",
+ "keyctl": "Security",
+ "kill": "Signals",
+ "landlock_add_rule": "Security",
+ "landlock_create_ruleset": "Security",
+ "landlock_restrict_self": "Security",
+ "lchown": "FS",
+ "lgetxattr": "FS",
+ "link": "FS",
+ "linkat": "FS",
+ "listen": "Network",
+ "listmount": "FS",
+ "listns": "FS",
+ "listxattr": "FS",
+ "listxattrat": "FS",
+ "llistxattr": "FS",
+ "lremovexattr": "FS",
+ "lseek": "FS",
+ "lsetxattr": "FS",
+ "lsm_get_self_attr": "Misc",
+ "lsm_list_modules": "Misc",
+ "lsm_set_self_attr": "Misc",
+ "madvise": "Memory",
+ "map_shadow_stack": "Memory",
+ "mbind": "Memory",
+ "membarrier": "Memory",
+ "memfd_create": "IPC",
+ "memfd_secret": "IPC",
+ "migrate_pages": "Memory",
+ "mincore": "Memory",
+ "mkdir": "FS",
+ "mkdirat": "FS",
+ "mknod": "FS",
+ "mknodat": "FS",
+ "mlock": "Memory",
+ "mlock2": "Memory",
+ "mlockall": "Memory",
+ "mmap": "Memory",
+ "modify_ldt": "Misc",
+ "mount": "FS",
+ "mount_setattr": "FS",
+ "move_mount": "FS",
+ "move_pages": "Memory",
+ "mprotect": "Memory",
+ "mq_getsetattr": "IPC",
+ "mq_notify": "IPC",
+ "mq_open": "IPC",
+ "mq_timedreceive": "IPC",
+ "mq_timedsend": "IPC",
+ "mq_unlink": "IPC",
+ "mremap": "Memory",
+ "mseal": "Memory",
+ "msgctl": "IPC",
+ "msgget": "IPC",
+ "msgrcv": "IPC",
+ "msgsnd": "IPC",
+ "msync": "FS",
+ "munlock": "Memory",
+ "munlockall": "Memory",
+ "munmap": "Memory",
+ "name_to_handle_at": "FS",
+ "nanosleep": "Time",
+ "newfstat": "FS",
+ "newfstatat": "FS",
+ "newlstat": "FS",
+ "newstat": "FS",
+ "newuname": "Misc",
+ "open": "FS",
+ "open_by_handle_at": "FS",
+ "open_tree": "FS",
+ "open_tree_attr": "FS",
+ "openat": "FS",
+ "openat2": "FS",
+ "pause": "Signals",
+ "perf_event_open": "Security",
+ "personality": "Process",
+ "pidfd_getfd": "IPC",
+ "pidfd_open": "IPC",
+ "pidfd_send_signal": "IPC",
+ "pipe": "IPC",
+ "pipe2": "IPC",
+ "pivot_root": "Process",
+ "pkey_alloc": "Memory",
+ "pkey_free": "Memory",
+ "pkey_mprotect": "Memory",
+ "poll": "Polling",
+ "ppoll": "Polling",
+ "prctl": "Process",
+ "pread64": "FS",
+ "preadv": "FS",
+ "preadv2": "FS",
+ "prlimit64": "Process",
+ "process_madvise": "Memory",
+ "process_mrelease": "Memory",
+ "process_vm_readv": "Memory",
+ "process_vm_writev": "Memory",
+ "pselect6": "Polling",
+ "ptrace": "Security",
+ "pwrite64": "FS",
+ "pwritev": "FS",
+ "pwritev2": "FS",
+ "quotactl": "FS",
+ "quotactl_fd": "FS",
+ "read": "FS",
+ "readahead": "FS",
+ "readlink": "FS",
+ "readlinkat": "FS",
+ "readv": "FS",
+ "reboot": "Process",
+ "recvfrom": "Network",
+ "recvmmsg": "Network",
+ "recvmsg": "Network",
+ "remap_file_pages": "Memory",
+ "removexattr": "FS",
+ "removexattrat": "FS",
+ "rename": "FS",
+ "renameat": "FS",
+ "renameat2": "FS",
+ "request_key": "Security",
+ "restart_syscall": "Process",
+ "rmdir": "FS",
+ "rseq": "Misc",
+ "rt_sigaction": "Signals",
+ "rt_sigpending": "Signals",
+ "rt_sigprocmask": "Signals",
+ "rt_sigqueueinfo": "Signals",
+ "rt_sigreturn": "Signals",
+ "rt_sigsuspend": "Signals",
+ "rt_sigtimedwait": "Signals",
+ "rt_tgsigqueueinfo": "Signals",
+ "sched_get_priority_max": "Sched",
+ "sched_get_priority_min": "Sched",
+ "sched_getaffinity": "Sched",
+ "sched_getattr": "Sched",
+ "sched_getparam": "Sched",
+ "sched_getscheduler": "Sched",
+ "sched_rr_get_interval": "Sched",
+ "sched_setaffinity": "Sched",
+ "sched_setattr": "Sched",
+ "sched_setparam": "Sched",
+ "sched_setscheduler": "Sched",
+ "sched_yield": "Sched",
+ "seccomp": "Security",
+ "select": "Polling",
+ "semctl": "IPC",
+ "semget": "IPC",
+ "semop": "IPC",
+ "semtimedop": "IPC",
+ "sendfile64": "Network",
+ "sendmmsg": "Network",
+ "sendmsg": "Network",
+ "sendto": "Network",
+ "set_mempolicy": "Memory",
+ "set_mempolicy_home_node": "Memory",
+ "set_robust_list": "Misc",
+ "set_tid_address": "Process",
+ "setdomainname": "Misc",
+ "setfsgid": "Process",
+ "setfsuid": "Process",
+ "setgid": "Process",
+ "setgroups": "Process",
+ "sethostname": "Misc",
+ "setitimer": "Time",
+ "setns": "Process",
+ "setpgid": "Process",
+ "setpriority": "Process",
+ "setregid": "Process",
+ "setresgid": "Process",
+ "setresuid": "Process",
+ "setreuid": "Process",
+ "setrlimit": "Process",
+ "setsid": "Process",
+ "setsockopt": "Network",
+ "settimeofday": "Time",
+ "setuid": "Process",
+ "setxattr": "FS",
+ "setxattrat": "FS",
+ "shmat": "IPC",
+ "shmctl": "IPC",
+ "shmdt": "IPC",
+ "shmget": "IPC",
+ "shutdown": "Network",
+ "sigaltstack": "Signals",
+ "signalfd": "IPC",
+ "signalfd4": "IPC",
+ "socket": "Network",
+ "socketpair": "Network",
+ "splice": "Network",
+ "statfs": "FS",
+ "statmount": "FS",
+ "statx": "FS",
+ "swapoff": "FS",
+ "swapon": "FS",
+ "symlink": "FS",
+ "symlinkat": "FS",
+ "sync": "FS",
+ "sync_file_range": "FS",
+ "syncfs": "FS",
+ "sysfs": "Misc",
+ "sysinfo": "Misc",
+ "syslog": "Misc",
+ "tee": "Network",
+ "tgkill": "Signals",
+ "time": "Time",
+ "timer_create": "Time",
+ "timer_delete": "Time",
+ "timer_getoverrun": "Time",
+ "timer_gettime": "Time",
+ "timer_settime": "Time",
+ "timerfd_create": "IPC",
+ "timerfd_gettime": "IPC",
+ "timerfd_settime": "IPC",
+ "times": "Time",
+ "tkill": "Signals",
+ "truncate": "FS",
+ "umask": "Process",
+ "umount": "FS",
+ "unlink": "FS",
+ "unlinkat": "FS",
+ "unshare": "Process",
+ "uprobe": "Misc",
+ "uretprobe": "Misc",
+ "userfaultfd": "IPC",
+ "ustat": "FS",
+ "utime": "Misc",
+ "utimensat": "FS",
+ "utimes": "Misc",
+ "vfork": "Process",
+ "vhangup": "Process",
+ "vmsplice": "Misc",
+ "wait4": "Process",
+ "waitid": "Process",
+ "write": "FS",
+ "writev": "FS",
+}
+
+var syscallKinds = map[string]string{
+ "accept": "accept",
+ "accept4": "accept",
+ "access": "path",
+ "acct": "null",
+ "add_key": "keyctl",
+ "adjtimex": "null",
+ "alarm": "null",
+ "arch_prctl": "null",
+ "bind": "fd",
+ "bpf": "null",
+ "brk": "null",
+ "cachestat": "fd",
+ "capget": "null",
+ "capset": "null",
+ "chdir": "path",
+ "chmod": "path",
+ "chown": "path",
+ "chroot": "path",
+ "clock_adjtime": "null",
+ "clock_getres": "null",
+ "clock_gettime": "null",
+ "clock_nanosleep": "sleep",
+ "clock_settime": "null",
+ "clone": "null",
+ "clone3": "null",
+ "close": "fd",
+ "close_range": "fd",
+ "connect": "fd",
+ "copy_file_range": "fd",
+ "creat": "path",
+ "delete_module": "null",
+ "dup": "fd",
+ "dup2": "fd",
+ "dup3": "dup3",
+ "epoll_create": "null",
+ "epoll_create1": "null",
+ "epoll_ctl": "epoll-ctl",
+ "epoll_pwait": "fd",
+ "epoll_pwait2": "fd",
+ "epoll_wait": "fd",
+ "eventfd": "eventfd",
+ "eventfd2": "eventfd",
+ "execve": "exec",
+ "execveat": "exec",
+ "exit": "null",
+ "exit_group": "null",
+ "faccessat": "path",
+ "faccessat2": "path",
+ "fadvise64": "fd",
+ "fallocate": "fd",
+ "fanotify_init": "null",
+ "fanotify_mark": "path",
+ "fchdir": "fd",
+ "fchmod": "fd",
+ "fchmodat": "path",
+ "fchmodat2": "path",
+ "fchown": "fd",
+ "fchownat": "path",
+ "fcntl": "fcntl",
+ "fdatasync": "fd",
+ "fgetxattr": "fd",
+ "file_getattr": "path",
+ "file_setattr": "path",
+ "finit_module": "fd",
+ "flistxattr": "fd",
+ "flock": "fd",
+ "fork": "null",
+ "fremovexattr": "fd",
+ "fsconfig": "fd",
+ "fsetxattr": "fd",
+ "fsmount": "eventfd",
+ "fsopen": "null",
+ "fspick": "path",
+ "fstatfs": "fd",
+ "fsync": "fd",
+ "ftruncate": "fd",
+ "futex": "null",
+ "futex_requeue": "null",
+ "futex_wait": "null",
+ "futex_waitv": "null",
+ "futex_wake": "null",
+ "futimesat": "path",
+ "get_mempolicy": "null",
+ "get_robust_list": "null",
+ "getcpu": "null",
+ "getcwd": "null",
+ "getdents": "fd",
+ "getdents64": "fd",
+ "getegid": "null",
+ "geteuid": "null",
+ "getgid": "null",
+ "getgroups": "null",
+ "getitimer": "null",
+ "getpeername": "fd",
+ "getpgid": "null",
+ "getpgrp": "null",
+ "getpid": "null",
+ "getppid": "null",
+ "getpriority": "null",
+ "getrandom": "null",
+ "getresgid": "null",
+ "getresuid": "null",
+ "getrlimit": "null",
+ "getrusage": "null",
+ "getsid": "null",
+ "getsockname": "fd",
+ "getsockopt": "fd",
+ "gettid": "null",
+ "gettimeofday": "null",
+ "getuid": "null",
+ "getxattr": "path",
+ "getxattrat": "path",
+ "init_module": "null",
+ "inotify_add_watch": "fd",
+ "inotify_init": "null",
+ "inotify_init1": "null",
+ "inotify_rm_watch": "fd",
+ "io_cancel": "null",
+ "io_destroy": "null",
+ "io_getevents": "null",
+ "io_pgetevents": "null",
+ "io_setup": "null",
+ "io_submit": "null",
+ "io_uring_enter": "fd",
+ "io_uring_register": "fd",
+ "io_uring_setup": "null",
+ "ioctl": "fd",
+ "ioperm": "null",
+ "iopl": "null",
+ "ioprio_get": "null",
+ "ioprio_set": "null",
+ "kcmp": "null",
+ "kexec_file_load": "null",
+ "kexec_load": "null",
+ "keyctl": "keyctl",
+ "kill": "null",
+ "landlock_add_rule": "null",
+ "landlock_create_ruleset": "null",
+ "landlock_restrict_self": "null",
+ "lchown": "path",
+ "lgetxattr": "path",
+ "link": "name",
+ "linkat": "name",
+ "listen": "fd",
+ "listmount": "null",
+ "listns": "null",
+ "listxattr": "path",
+ "listxattrat": "path",
+ "llistxattr": "path",
+ "lremovexattr": "path",
+ "lseek": "fd",
+ "lsetxattr": "path",
+ "lsm_get_self_attr": "null",
+ "lsm_list_modules": "null",
+ "lsm_set_self_attr": "null",
+ "madvise": "null",
+ "map_shadow_stack": "null",
+ "mbind": "null",
+ "membarrier": "null",
+ "memfd_create": "null",
+ "memfd_secret": "null",
+ "migrate_pages": "null",
+ "mincore": "null",
+ "mkdir": "path",
+ "mkdirat": "path",
+ "mknod": "path",
+ "mknodat": "path",
+ "mlock": "null",
+ "mlock2": "null",
+ "mlockall": "null",
+ "mmap": "fd",
+ "modify_ldt": "null",
+ "mount": "path",
+ "mount_setattr": "path",
+ "move_mount": "two-fd",
+ "move_pages": "null",
+ "mprotect": "null",
+ "mq_getsetattr": "fd",
+ "mq_notify": "fd",
+ "mq_open": "open",
+ "mq_timedreceive": "fd",
+ "mq_timedsend": "fd",
+ "mq_unlink": "path",
+ "mremap": "mem",
+ "mseal": "null",
+ "msgctl": "null",
+ "msgget": "null",
+ "msgrcv": "null",
+ "msgsnd": "null",
+ "msync": "null",
+ "munlock": "null",
+ "munlockall": "null",
+ "munmap": "mem",
+ "name_to_handle_at": "path",
+ "nanosleep": "sleep",
+ "newfstat": "fd",
+ "newfstatat": "path",
+ "newlstat": "path",
+ "newstat": "path",
+ "newuname": "null",
+ "open": "open",
+ "open_by_handle_at": "open-by-handle-at",
+ "open_tree": "open",
+ "open_tree_attr": "open",
+ "openat": "open",
+ "openat2": "open",
+ "pause": "null",
+ "perf_event_open": "perf-open",
+ "personality": "null",
+ "pidfd_getfd": "fd",
+ "pidfd_open": "null",
+ "pidfd_send_signal": "null",
+ "pipe": "pipe",
+ "pipe2": "pipe",
+ "pivot_root": "path",
+ "pkey_alloc": "null",
+ "pkey_free": "null",
+ "pkey_mprotect": "null",
+ "poll": "poll",
+ "ppoll": "poll",
+ "prctl": "null",
+ "pread64": "fd",
+ "preadv": "fd",
+ "preadv2": "fd",
+ "prlimit64": "null",
+ "process_madvise": "null",
+ "process_mrelease": "null",
+ "process_vm_readv": "null",
+ "process_vm_writev": "null",
+ "pselect6": "poll",
+ "ptrace": "ptrace",
+ "pwrite64": "fd",
+ "pwritev": "fd",
+ "pwritev2": "fd",
+ "quotactl": "path",
+ "quotactl_fd": "fd",
+ "read": "fd",
+ "readahead": "fd",
+ "readlink": "path",
+ "readlinkat": "path",
+ "readv": "fd",
+ "reboot": "null",
+ "recvfrom": "fd",
+ "recvmmsg": "fd",
+ "recvmsg": "fd",
+ "remap_file_pages": "null",
+ "removexattr": "path",
+ "removexattrat": "path",
+ "rename": "name",
+ "renameat": "name",
+ "renameat2": "name",
+ "request_key": "keyctl",
+ "restart_syscall": "null",
+ "rmdir": "path",
+ "rseq": "null",
+ "rt_sigaction": "null",
+ "rt_sigpending": "null",
+ "rt_sigprocmask": "null",
+ "rt_sigqueueinfo": "null",
+ "rt_sigreturn": "null",
+ "rt_sigsuspend": "null",
+ "rt_sigtimedwait": "null",
+ "rt_tgsigqueueinfo": "null",
+ "sched_get_priority_max": "null",
+ "sched_get_priority_min": "null",
+ "sched_getaffinity": "null",
+ "sched_getattr": "null",
+ "sched_getparam": "null",
+ "sched_getscheduler": "null",
+ "sched_rr_get_interval": "null",
+ "sched_setaffinity": "null",
+ "sched_setattr": "null",
+ "sched_setparam": "null",
+ "sched_setscheduler": "null",
+ "sched_yield": "null",
+ "seccomp": "null",
+ "select": "poll",
+ "semctl": "null",
+ "semget": "null",
+ "semop": "null",
+ "semtimedop": "null",
+ "sendfile64": "null",
+ "sendmmsg": "fd",
+ "sendmsg": "fd",
+ "sendto": "fd",
+ "set_mempolicy": "null",
+ "set_mempolicy_home_node": "null",
+ "set_robust_list": "null",
+ "set_tid_address": "null",
+ "setdomainname": "null",
+ "setfsgid": "null",
+ "setfsuid": "null",
+ "setgid": "null",
+ "setgroups": "null",
+ "sethostname": "null",
+ "setitimer": "null",
+ "setns": "fd",
+ "setpgid": "null",
+ "setpriority": "null",
+ "setregid": "null",
+ "setresgid": "null",
+ "setresuid": "null",
+ "setreuid": "null",
+ "setrlimit": "null",
+ "setsid": "null",
+ "setsockopt": "fd",
+ "settimeofday": "null",
+ "setuid": "null",
+ "setxattr": "path",
+ "setxattrat": "path",
+ "shmat": "null",
+ "shmctl": "null",
+ "shmdt": "null",
+ "shmget": "null",
+ "shutdown": "fd",
+ "sigaltstack": "null",
+ "signalfd": "null",
+ "signalfd4": "null",
+ "socket": "socket",
+ "socketpair": "socketpair",
+ "splice": "null",
+ "statfs": "path",
+ "statmount": "null",
+ "statx": "path",
+ "swapoff": "path",
+ "swapon": "path",
+ "symlink": "name",
+ "symlinkat": "name",
+ "sync": "null",
+ "sync_file_range": "fd",
+ "syncfs": "fd",
+ "sysfs": "null",
+ "sysinfo": "null",
+ "syslog": "null",
+ "tee": "null",
+ "tgkill": "null",
+ "time": "null",
+ "timer_create": "null",
+ "timer_delete": "null",
+ "timer_getoverrun": "null",
+ "timer_gettime": "null",
+ "timer_settime": "null",
+ "timerfd_create": "null",
+ "timerfd_gettime": "null",
+ "timerfd_settime": "null",
+ "times": "null",
+ "tkill": "null",
+ "truncate": "path",
+ "umask": "null",
+ "umount": "path",
+ "unlink": "path",
+ "unlinkat": "path",
+ "unshare": "null",
+ "uprobe": "null",
+ "uretprobe": "null",
+ "userfaultfd": "null",
+ "ustat": "null",
+ "utime": "path",
+ "utimensat": "path",
+ "utimes": "path",
+ "vfork": "null",
+ "vhangup": "null",
+ "vmsplice": "fd",
+ "wait4": "null",
+ "waitid": "null",
+ "write": "fd",
+ "writev": "fd",
+}
diff --git a/internal/tracepoints/selector.go b/internal/tracepoints/selector.go
index af2f39e..91df58f 100644
--- a/internal/tracepoints/selector.go
+++ b/internal/tracepoints/selector.go
@@ -2,6 +2,7 @@ package tracepoints
import (
"fmt"
+ "maps"
"regexp"
"slices"
"strings"
@@ -18,6 +19,12 @@ type Selector struct {
// Exclude is the list of compiled regexes that suppress specific
// tracepoints even when they match the Attach list.
Exclude []*regexp.Regexp
+ // Syscalls optionally restricts attach to an explicit syscall allowlist.
+ // Keys are bare syscall names (for example "openat", not "sys_enter_openat").
+ // When RestrictSyscalls is true, only entries in this map are attached.
+ Syscalls map[string]struct{}
+ // RestrictSyscalls gates whether Syscalls should be enforced.
+ RestrictSyscalls bool
}
// ParseSelector parses the comma-separated regex strings for the -tps and
@@ -65,11 +72,27 @@ func (s Selector) ShouldAttach(tracepointName string) bool {
}
}
if len(s.Attach) == 0 {
- return true
+ if !s.RestrictSyscalls {
+ return true
+ }
+ syscall, ok := SyscallNameFromTracepoint(tracepointName)
+ if !ok {
+ return false
+ }
+ _, allowed := s.Syscalls[syscall]
+ return allowed
}
for _, re := range s.Attach {
if re.MatchString(tracepointName) {
- return true
+ if !s.RestrictSyscalls {
+ return true
+ }
+ syscall, ok := SyscallNameFromTracepoint(tracepointName)
+ if !ok {
+ return false
+ }
+ _, allowed := s.Syscalls[syscall]
+ return allowed
}
}
return false
@@ -79,7 +102,22 @@ func (s Selector) ShouldAttach(tracepointName string) bool {
// copy's slices do not affect the original.
func (s Selector) Clone() Selector {
return Selector{
- Attach: slices.Clone(s.Attach),
- Exclude: slices.Clone(s.Exclude),
+ Attach: slices.Clone(s.Attach),
+ Exclude: slices.Clone(s.Exclude),
+ Syscalls: maps.Clone(s.Syscalls),
+ RestrictSyscalls: s.RestrictSyscalls,
+ }
+}
+
+// SyscallNameFromTracepoint returns the bare syscall name for a tracepoint
+// (for example "openat" from "sys_enter_openat").
+func SyscallNameFromTracepoint(tracepointName string) (string, bool) {
+ switch {
+ case strings.HasPrefix(tracepointName, "sys_enter_"):
+ return strings.TrimPrefix(tracepointName, "sys_enter_"), true
+ case strings.HasPrefix(tracepointName, "sys_exit_"):
+ return strings.TrimPrefix(tracepointName, "sys_exit_"), true
+ default:
+ return "", false
}
}
diff --git a/internal/tracepoints/selector_test.go b/internal/tracepoints/selector_test.go
index d12f24b..dd9084b 100644
--- a/internal/tracepoints/selector_test.go
+++ b/internal/tracepoints/selector_test.go
@@ -82,3 +82,17 @@ func TestSelectorCloneIsIndependent(t *testing.T) {
t.Error("original Selector was mutated through clone")
}
}
+
+func TestSelectorCloneCopiesSyscallAllowlist(t *testing.T) {
+ sel := Selector{
+ Syscalls: map[string]struct{}{
+ "openat": {},
+ },
+ RestrictSyscalls: true,
+ }
+ clone := sel.Clone()
+ delete(clone.Syscalls, "openat")
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("original syscall allowlist mutated through clone")
+ }
+}