summaryrefslogtreecommitdiff
path: root/internal/tracepoints
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tracepoints')
-rw-r--r--internal/tracepoints/selector.go85
-rw-r--r--internal/tracepoints/selector_test.go84
2 files changed, 169 insertions, 0 deletions
diff --git a/internal/tracepoints/selector.go b/internal/tracepoints/selector.go
new file mode 100644
index 0000000..af2f39e
--- /dev/null
+++ b/internal/tracepoints/selector.go
@@ -0,0 +1,85 @@
+package tracepoints
+
+import (
+ "fmt"
+ "regexp"
+ "slices"
+ "strings"
+)
+
+// Selector holds compiled include and exclude regexes for choosing which
+// tracepoints to attach at BPF probe registration time. It is the single
+// authoritative home for tracepoint-selection logic, keeping that concern
+// out of the top-level flags.Config.
+type Selector struct {
+ // Attach is the list of compiled regexes that select which tracepoints to
+ // load. An empty list means "attach all non-excluded tracepoints".
+ Attach []*regexp.Regexp
+ // Exclude is the list of compiled regexes that suppress specific
+ // tracepoints even when they match the Attach list.
+ Exclude []*regexp.Regexp
+}
+
+// ParseSelector parses the comma-separated regex strings for the -tps and
+// -tpsExclude CLI flags into a Selector. Either string may be empty, which
+// leaves the corresponding list nil (i.e. "match all" for Attach, "exclude
+// nothing" for Exclude). An error is returned if any regex fails to compile.
+func ParseSelector(attach, exclude string) (Selector, error) {
+ attachRegexes, err := parseRegexList(attach)
+ if err != nil {
+ return Selector{}, err
+ }
+ excludeRegexes, err := parseRegexList(exclude)
+ if err != nil {
+ return Selector{}, err
+ }
+ return Selector{Attach: attachRegexes, Exclude: excludeRegexes}, nil
+}
+
+// parseRegexList splits a comma-separated string of regex patterns and
+// compiles each one. Returns nil (not an error) when the input is empty.
+func parseRegexList(patterns string) ([]*regexp.Regexp, error) {
+ if len(patterns) == 0 {
+ return nil, nil
+ }
+ var regexes []*regexp.Regexp
+ for _, pattern := range strings.Split(patterns, ",") {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, fmt.Errorf("unable to compile regex %q: %w", pattern, err)
+ }
+ regexes = append(regexes, re)
+ }
+ return regexes, nil
+}
+
+// ShouldAttach reports whether the given tracepoint name passes the
+// selector's attach/exclude regex filters. Exclusions are checked first; if
+// the name matches any exclude pattern it is rejected regardless of the attach
+// list. When the attach list is empty, all non-excluded tracepoints are
+// accepted.
+func (s Selector) ShouldAttach(tracepointName string) bool {
+ for _, re := range s.Exclude {
+ if re.MatchString(tracepointName) {
+ return false
+ }
+ }
+ if len(s.Attach) == 0 {
+ return true
+ }
+ for _, re := range s.Attach {
+ if re.MatchString(tracepointName) {
+ return true
+ }
+ }
+ return false
+}
+
+// Clone returns a deep copy of the Selector so that modifications to the
+// 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),
+ }
+}
diff --git a/internal/tracepoints/selector_test.go b/internal/tracepoints/selector_test.go
new file mode 100644
index 0000000..d12f24b
--- /dev/null
+++ b/internal/tracepoints/selector_test.go
@@ -0,0 +1,84 @@
+package tracepoints
+
+import "testing"
+
+func TestParseSelectorEmpty(t *testing.T) {
+ // An empty attach and exclude string means accept everything.
+ sel, err := ParseSelector("", "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Fatal("expected ShouldAttach=true for empty selector")
+ }
+}
+
+func TestParseSelectorAttachFilter(t *testing.T) {
+ // Only openat tracepoints should be accepted when an explicit attach list
+ // is provided.
+ sel, err := ParseSelector("^sys_enter_openat$,^sys_exit_openat$", "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Error("expected ShouldAttach=true for sys_enter_openat")
+ }
+ if !sel.ShouldAttach("sys_exit_openat") {
+ t.Error("expected ShouldAttach=true for sys_exit_openat")
+ }
+ if sel.ShouldAttach("sys_enter_write") {
+ t.Error("expected ShouldAttach=false for sys_enter_write (not in attach list)")
+ }
+}
+
+func TestParseSelectorExcludeFilter(t *testing.T) {
+ // Excluded tracepoints are rejected even when no explicit attach list exists.
+ sel, err := ParseSelector("", "name_to_handle_at,open_by_handle_at")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if sel.ShouldAttach("sys_enter_name_to_handle_at") {
+ t.Error("expected ShouldAttach=false for excluded tracepoint")
+ }
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Error("expected ShouldAttach=true for non-excluded tracepoint")
+ }
+}
+
+func TestParseSelectorExcludeTakesPrecedence(t *testing.T) {
+ // An exclude pattern beats an attach pattern when both match.
+ sel, err := ParseSelector("openat", "openat")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if sel.ShouldAttach("sys_enter_openat") {
+ t.Error("expected ShouldAttach=false: exclude must beat attach")
+ }
+}
+
+func TestParseSelectorInvalidRegexReturnsError(t *testing.T) {
+ _, err := ParseSelector("[", "")
+ if err == nil {
+ t.Fatal("expected error for invalid attach regex")
+ }
+}
+
+func TestParseSelectorInvalidExcludeRegexReturnsError(t *testing.T) {
+ _, err := ParseSelector("", "[")
+ if err == nil {
+ t.Fatal("expected error for invalid exclude regex")
+ }
+}
+
+func TestSelectorCloneIsIndependent(t *testing.T) {
+ // Modifications to the clone's Attach slice must not affect the original.
+ sel, err := ParseSelector("openat", "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ clone := sel.Clone()
+ clone.Attach = nil
+ if !sel.ShouldAttach("sys_enter_openat") {
+ t.Error("original Selector was mutated through clone")
+ }
+}