summaryrefslogtreecommitdiff
path: root/internal/flags/sampling.go
blob: 5656f0a5fb8b8904208a0eb796c6f46099749ce0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package flags

import (
	"fmt"
	"strconv"
	"strings"

	"ior/internal/types"
)

var defaultAggregateOnlySyscalls = []string{
	"futex",
	"futex_wait",
	"futex_wake",
	"futex_requeue",
	"futex_waitv",
	"clock_gettime",
}

func cloneFamilySamplingRates(in map[types.SyscallFamily]uint32) map[types.SyscallFamily]uint32 {
	out := make(map[types.SyscallFamily]uint32, len(in))
	for family, rate := range in {
		out[family] = rate
	}
	return out
}

func cloneSyscallSamplingRates(in map[string]uint32) map[string]uint32 {
	out := make(map[string]uint32, len(in))
	for syscall, rate := range in {
		out[syscall] = rate
	}
	return out
}

func defaultSyscallSamplingRates() map[string]uint32 {
	out := make(map[string]uint32, len(defaultAggregateOnlySyscalls))
	for _, syscall := range defaultAggregateOnlySyscalls {
		out[syscall] = 0
	}
	return out
}

func mergeSyscallSamplingRates(overrides map[string]uint32) map[string]uint32 {
	out := defaultSyscallSamplingRates()
	for syscall, rate := range overrides {
		out[syscall] = rate
	}
	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 {
		return nil, err
	}
	out := make(map[types.SyscallFamily]uint32, len(entries))
	for key, rate := range entries {
		family, ok := types.ParseSyscallFamily(key)
		if !ok {
			return nil, fmt.Errorf("invalid syscall family in sampling map: %q", key)
		}
		out[family] = rate
	}
	return out, nil
}

func parseSyscallSamplingRates(raw string) (map[string]uint32, error) {
	entries, err := parseSamplingEntries(raw)
	if err != nil {
		return nil, err
	}
	out := make(map[string]uint32, len(entries))
	for syscall, rate := range entries {
		syscall = strings.ToLower(strings.TrimSpace(syscall))
		if syscall == "" {
			return nil, fmt.Errorf("invalid syscall sampling key %q", syscall)
		}
		if _, ok := types.EnterTraceIDByName(syscall); !ok {
			return nil, fmt.Errorf("invalid syscall in sampling map: %q", syscall)
		}
		out[syscall] = rate
	}
	return out, nil
}

func parseSamplingEntries(raw string) (map[string]uint32, error) {
	out := make(map[string]uint32)
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return out, nil
	}
	for _, part := range strings.Split(raw, ",") {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		key, valueRaw, ok := strings.Cut(part, "=")
		if !ok {
			return nil, fmt.Errorf("invalid sampling entry %q: expected name=rate", part)
		}
		key = strings.TrimSpace(key)
		if key == "" {
			return nil, fmt.Errorf("invalid sampling entry %q: empty name", part)
		}
		rate, err := strconv.ParseUint(strings.TrimSpace(valueRaw), 10, 32)
		if err != nil {
			return nil, fmt.Errorf("invalid sampling rate for %q: %w", key, err)
		}
		out[key] = uint32(rate)
	}
	return out, nil
}