From 1bf3f90198d29bfb015d91ffb708959ebe54a885 Mon Sep 17 00:00:00 2001 From: dmitriimaksimovdevelop <227611064+dmitriimaksimovdevelop@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:02:10 +0300 Subject: [PATCH] fix: remove duration arg from 22 event-based BCC tools (fixes #19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Event-based BCC tracers (tcpretrans, tcpdrop, killsnoop, etc.) run until killed — they don't accept a duration positional argument. Passing formatDuration(d) caused "unrecognized arguments: 10" errors. Fixed tools (22 total): - Network: tcpretrans, tcpdrop, tcpstates, tcpconnect, tcpaccept, tcplife, udpconnect, sofdsnoop, skbdrop - Process: killsnoop, threadsnoop, syncsnoop, exitsnoop, capable - Disk: filelife, mountsnoop, mdflush - Memory: oomkill, shmsnoop, drsnoop, numamove - CPU: cpufreq - Filter-based (positional arg is threshold, not duration): tcpconnlat (min_ms), gethostlatency (no positional args) Note: biolatency failure in #19 is a BCC/kernel compat issue (blk_account_io_done kprobe removed in kernel 6.x). Requires updated bcc-tools package, not fixable in melisai. Tests: - TestEventBasedToolsNoArgs: verifies all 22 event tools pass no args - TestDurationToolsHaveArgs: guards that histogram/sampling tools still pass duration correctly Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/executor/registry.go | 48 +++++++++++------------ internal/executor/registry_test.go | 61 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/internal/executor/registry.go b/internal/executor/registry.go index 3a79768..bcbc178 100644 --- a/internal/executor/registry.go +++ b/internal/executor/registry.go @@ -115,7 +115,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpconnlat", Binary: "tcpconnlat", Category: "network", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { return ParseTcpconnlat(raw, 1000) }, }, @@ -123,7 +123,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpretrans", Binary: "tcpretrans", Category: "network", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { return ParseTcpretrans(raw, 1000) }, }, @@ -139,7 +139,7 @@ var Registry = map[string]*ToolSpec{ Name: "gethostlatency", Binary: "gethostlatency", Category: "network", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { return ParseGethostlatency(raw, 1000) }, }, @@ -147,7 +147,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpdrop", Binary: "tcpdrop", Category: "network", NeedsRoot: true, OutputType: TRACING, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { return ParseTcpdrop(raw, 1000) }, }, @@ -155,7 +155,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpstates", Binary: "tcpstates", Category: "network", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { // tcpstates output is complex, treat as tabular for now @@ -234,7 +234,7 @@ var Registry = map[string]*ToolSpec{ Name: "killsnoop", Binary: "killsnoop", Category: "process", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -245,7 +245,7 @@ var Registry = map[string]*ToolSpec{ Name: "threadsnoop", Binary: "threadsnoop", Category: "process", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -256,7 +256,7 @@ var Registry = map[string]*ToolSpec{ Name: "syncsnoop", Binary: "syncsnoop", Category: "process", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -267,7 +267,7 @@ var Registry = map[string]*ToolSpec{ Name: "exitsnoop", Binary: "exitsnoop", Category: "process", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -289,7 +289,7 @@ var Registry = map[string]*ToolSpec{ Name: "capable", Binary: "capable", Category: "process", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -302,7 +302,7 @@ var Registry = map[string]*ToolSpec{ Name: "filelife", Binary: "filelife", Category: "disk", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -313,7 +313,7 @@ var Registry = map[string]*ToolSpec{ Name: "mountsnoop", Binary: "mountsnoop", Category: "disk", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -370,7 +370,7 @@ var Registry = map[string]*ToolSpec{ Name: "mdflush", Binary: "mdflush", Category: "disk", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -394,7 +394,7 @@ var Registry = map[string]*ToolSpec{ Name: "cpufreq", Binary: "cpufreq", Category: "cpu", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -418,7 +418,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpconnect", Binary: "tcpconnect", Category: "network", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -429,7 +429,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcpaccept", Binary: "tcpaccept", Category: "network", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -440,7 +440,7 @@ var Registry = map[string]*ToolSpec{ Name: "tcplife", Binary: "tcplife", Category: "network", NeedsRoot: true, OutputType: TABULAR, PIDFlag: "-p", BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -451,7 +451,7 @@ var Registry = map[string]*ToolSpec{ Name: "udpconnect", Binary: "udpconnect", Category: "network", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -462,7 +462,7 @@ var Registry = map[string]*ToolSpec{ Name: "sofdsnoop", Binary: "sofdsnoop", Category: "network", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -484,7 +484,7 @@ var Registry = map[string]*ToolSpec{ Name: "skbdrop", Binary: "skbdrop", Category: "network", NeedsRoot: true, OutputType: TRACING, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -498,7 +498,7 @@ var Registry = map[string]*ToolSpec{ Name: "oomkill", Binary: "oomkill", Category: "memory", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -509,7 +509,7 @@ var Registry = map[string]*ToolSpec{ Name: "shmsnoop", Binary: "shmsnoop", Category: "memory", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -520,7 +520,7 @@ var Registry = map[string]*ToolSpec{ Name: "drsnoop", Binary: "drsnoop", Category: "memory", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) @@ -533,7 +533,7 @@ var Registry = map[string]*ToolSpec{ Name: "numamove", Binary: "numamove", Category: "memory", NeedsRoot: true, OutputType: TABULAR, BuildArgs: func(d time.Duration) []string { - return []string{formatDuration(d)} + return nil // event-based tool — no duration arg, killed by context timeout }, Parser: func(raw string) (*model.Result, error) { events, trunc := ParseTabularEvents(raw, 1000) diff --git a/internal/executor/registry_test.go b/internal/executor/registry_test.go index 6379435..09e9a95 100644 --- a/internal/executor/registry_test.go +++ b/internal/executor/registry_test.go @@ -303,3 +303,64 @@ func TestSkbdropParsesStacksAndEvents(t *testing.T) { t.Error("expected kernel stacks from skbdrop") } } + +// TestEventBasedToolsNoArgs verifies that event-based BCC tools +// (tcpretrans, tcpdrop, oomkill, tcpstates) do NOT pass duration +// as a positional argument. These tools don't accept duration — they +// run until killed by context timeout. Passing duration causes errors +// like "unrecognized arguments: 10". See issue #19. +func TestEventBasedToolsNoArgs(t *testing.T) { + eventTools := []string{ + // Issue #19: these tools don't accept duration as positional arg + "tcpretrans", "tcpdrop", "oomkill", "tcpstates", + // Event tracers — run until killed + "killsnoop", "threadsnoop", "syncsnoop", "exitsnoop", + "capable", "filelife", "mountsnoop", "mdflush", "cpufreq", + "tcpconnect", "tcpaccept", "tcplife", "udpconnect", "sofdsnoop", + "skbdrop", "shmsnoop", "drsnoop", "numamove", + // Positional arg is filter threshold, not duration + "tcpconnlat", // arg = min_ms filter + "gethostlatency", // no positional args + } + + for _, name := range eventTools { + t.Run(name, func(t *testing.T) { + tool, ok := Registry[name] + if !ok { + t.Fatalf("tool %q not in registry", name) + } + if tool.BuildArgs == nil { + t.Fatalf("tool %q has nil BuildArgs", name) + } + + args := tool.BuildArgs(10 * time.Second) + if len(args) > 0 { + t.Errorf("tool %q BuildArgs(10s) = %v, want nil/empty (event-based tool should not pass duration)", + name, args) + } + }) + } +} + +// TestDurationToolsHaveArgs verifies that duration-based BCC tools +// DO pass arguments (regression guard for the opposite direction). +func TestDurationToolsHaveArgs(t *testing.T) { + durationTools := []string{"runqlat", "biolatency", "biosnoop", "opensnoop", "hardirqs", "softirqs"} + + for _, name := range durationTools { + t.Run(name, func(t *testing.T) { + tool, ok := Registry[name] + if !ok { + t.Skipf("tool %q not in registry", name) + } + if tool.BuildArgs == nil { + t.Fatalf("tool %q has nil BuildArgs", name) + } + + args := tool.BuildArgs(10 * time.Second) + if len(args) == 0 { + t.Errorf("tool %q BuildArgs(10s) returned empty args, expected duration arg", name) + } + }) + } +}