diff options
Diffstat (limited to 'internal/flags')
| -rw-r--r-- | internal/flags/flags.go | 16 | ||||
| -rw-r--r-- | internal/flags/sampling.go | 17 | ||||
| -rw-r--r-- | internal/flags/sampling_test.go | 86 |
3 files changed, 119 insertions, 0 deletions
diff --git a/internal/flags/flags.go b/internal/flags/flags.go index cc39638..c20707b 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -85,6 +85,15 @@ type Config struct { ShowVersion bool } +// IsRawOutputMode reports whether the config selects a headless output path +// (-plain, -flamegraph, or headless -parquet) that lacks a TUI aggregate +// sink. In these modes, aggregate-only sampling (rate 0) would silently +// suppress ring-buffer events, so callers should promote default aggregate- +// only rates to 1. +func (f Config) IsRawOutputMode() bool { + return f.PlainMode || f.FlamegraphOutput || strings.TrimSpace(f.ParquetPath) != "" +} + // DefaultResetTimer is the default cadence for the dashboard's auto-reset // timer. It periodically clears aggregate state (live flamegraph trie and // stats engine) — the same effect as pressing `r` — to prevent unbounded @@ -266,6 +275,13 @@ func resolveSamplingRates(cfg *Config, familySampling, syscallSampling *string) } cfg.SyscallFamilySamplingRates = familyRates cfg.SyscallSamplingRates = mergeSyscallSamplingRates(syscallRates) + // In raw output modes (-plain, -flamegraph, headless -parquet) there is + // no aggregate sink, so aggregate-only defaults (rate 0) would silently + // suppress ring-buffer events. Promote those defaults to rate 1 unless + // the user explicitly requested rate 0 via -syscall-sampling-syscalls. + if cfg.IsRawOutputMode() { + promoteAggregateOnlyForRawOutput(cfg.SyscallSamplingRates, syscallRates) + } return nil } diff --git a/internal/flags/sampling.go b/internal/flags/sampling.go index 0f0af2a..5656f0a 100644 --- a/internal/flags/sampling.go +++ b/internal/flags/sampling.go @@ -49,6 +49,23 @@ func mergeSyscallSamplingRates(overrides map[string]uint32) map[string]uint32 { return out } +// promoteAggregateOnlyForRawOutput replaces default aggregate-only rates (0) +// with rate 1 (emit every event) when running in a raw output mode that lacks +// an aggregate sink. Without this promotion, BPF suppresses ring-buffer +// events for these syscalls and no rows appear in -plain, -flamegraph, or +// headless -parquet output. User-explicit overrides (present in userOverrides) +// are preserved unchanged. +func promoteAggregateOnlyForRawOutput(merged map[string]uint32, userOverrides map[string]uint32) { + for _, syscall := range defaultAggregateOnlySyscalls { + if _, explicit := userOverrides[syscall]; explicit { + continue + } + if merged[syscall] == 0 { + merged[syscall] = 1 + } + } +} + func parseFamilySamplingRates(raw string) (map[types.SyscallFamily]uint32, error) { entries, err := parseSamplingEntries(raw) if err != nil { diff --git a/internal/flags/sampling_test.go b/internal/flags/sampling_test.go index c43c2fc..6ae95c4 100644 --- a/internal/flags/sampling_test.go +++ b/internal/flags/sampling_test.go @@ -102,3 +102,89 @@ func TestParseSamplingRatesOverrideDefaultFutexRate(t *testing.T) { t.Fatalf("futex rate = %d, want 7", got) } } + +func TestPlainModePromotesAggregateOnlyDefaults(t *testing.T) { + cfg, err := parseForTest(t, "-plain") + if err != nil { + t.Fatalf("parse returned error: %v", err) + } + for _, syscall := range []string{"futex", "futex_wait", "futex_wake", "futex_requeue", "futex_waitv", "clock_gettime"} { + rate, ok := cfg.SyscallSamplingRates[syscall] + if !ok { + t.Fatalf("expected sampling entry for %s in plain mode", syscall) + } + if rate != 1 { + t.Fatalf("%s rate in plain mode = %d, want 1 (promoted from aggregate-only)", syscall, rate) + } + } +} + +func TestFlamegraphModePromotesAggregateOnlyDefaults(t *testing.T) { + cfg, err := parseForTest(t, "-flamegraph") + if err != nil { + t.Fatalf("parse returned error: %v", err) + } + if got := cfg.SyscallSamplingRates["clock_gettime"]; got != 1 { + t.Fatalf("clock_gettime rate in flamegraph mode = %d, want 1", got) + } +} + +func TestParquetModePromotesAggregateOnlyDefaults(t *testing.T) { + cfg, err := parseForTest(t, "-parquet", "trace.parquet") + if err != nil { + t.Fatalf("parse returned error: %v", err) + } + if got := cfg.SyscallSamplingRates["futex"]; got != 1 { + t.Fatalf("futex rate in parquet mode = %d, want 1", got) + } +} + +func TestPlainModePreservesExplicitAggregateOnly(t *testing.T) { + cfg, err := parseForTest(t, "-plain", "-syscall-sampling-syscalls", "futex=0") + if err != nil { + t.Fatalf("parse returned error: %v", err) + } + // User explicitly requested aggregate-only for futex; it should stay 0. + if got := cfg.SyscallSamplingRates["futex"]; got != 0 { + t.Fatalf("futex rate = %d, want 0 (explicit override preserved)", got) + } + // clock_gettime was not overridden, so it should be promoted to 1. + if got := cfg.SyscallSamplingRates["clock_gettime"]; got != 1 { + t.Fatalf("clock_gettime rate = %d, want 1 (default promoted)", got) + } +} + +func TestTUIModeKeepsAggregateOnlyDefaults(t *testing.T) { + cfg, err := parseForTest(t) + if err != nil { + t.Fatalf("parse returned error: %v", err) + } + // In TUI mode (no -plain, -flamegraph, or -parquet), defaults should + // remain aggregate-only (rate 0) because the aggregate sink is present. + for _, syscall := range []string{"futex", "clock_gettime"} { + if got := cfg.SyscallSamplingRates[syscall]; got != 0 { + t.Fatalf("%s rate in TUI mode = %d, want 0 (aggregate-only default)", syscall, got) + } + } +} + +func TestIsRawOutputMode(t *testing.T) { + cases := []struct { + name string + cfg Config + want bool + }{ + {"default TUI", Config{}, false}, + {"plain", Config{PlainMode: true}, true}, + {"flamegraph", Config{FlamegraphOutput: true}, true}, + {"parquet", Config{ParquetPath: "trace.parquet"}, true}, + {"parquet whitespace", Config{ParquetPath: " "}, false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.cfg.IsRawOutputMode(); got != tc.want { + t.Fatalf("IsRawOutputMode() = %v, want %v", got, tc.want) + } + }) + } +} |
