diff --git a/config/sampler_config.go b/config/sampler_config.go index 37917b2a32..dfe21902d6 100644 --- a/config/sampler_config.go +++ b/config/sampler_config.go @@ -149,10 +149,22 @@ type V2SamplerConfig struct { Samplers map[string]*V2SamplerChoice `json:"samplers" yaml:"Samplers,omitempty" validate:"required"` } +type GetSamplingFielder interface { + GetSamplingFields() []string +} + +var _ GetSamplingFielder = (*DeterministicSamplerConfig)(nil) + type DeterministicSamplerConfig struct { SampleRate int `json:"samplerate" yaml:"SampleRate,omitempty" default:"1" validate:"required,gte=1"` } +func (d *DeterministicSamplerConfig) GetSamplingFields() []string { + return nil +} + +var _ GetSamplingFielder = (*DynamicSamplerConfig)(nil) + type DynamicSamplerConfig struct { SampleRate int64 `json:"samplerate" yaml:"SampleRate,omitempty" validate:"required,gte=1"` ClearFrequency Duration `json:"clearfrequency" yaml:"ClearFrequency,omitempty"` @@ -161,6 +173,12 @@ type DynamicSamplerConfig struct { UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"` } +func (d *DynamicSamplerConfig) GetSamplingFields() []string { + return d.FieldList +} + +var _ GetSamplingFielder = (*EMADynamicSamplerConfig)(nil) + type EMADynamicSamplerConfig struct { GoalSampleRate int `json:"goalsamplerate" yaml:"GoalSampleRate,omitempty" validate:"gte=1"` AdjustmentInterval Duration `json:"adjustmentinterval" yaml:"AdjustmentInterval,omitempty"` @@ -173,6 +191,12 @@ type EMADynamicSamplerConfig struct { UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"` } +func (d *EMADynamicSamplerConfig) GetSamplingFields() []string { + return d.FieldList +} + +var _ GetSamplingFielder = (*EMAThroughputSamplerConfig)(nil) + type EMAThroughputSamplerConfig struct { GoalThroughputPerSec int `json:"goalthroughputpersec" yaml:"GoalThroughputPerSec,omitempty"` UseClusterSize bool `json:"useclustersize" yaml:"UseClusterSize,omitempty"` @@ -187,6 +211,12 @@ type EMAThroughputSamplerConfig struct { UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"` } +func (d *EMAThroughputSamplerConfig) GetSamplingFields() []string { + return d.FieldList +} + +var _ GetSamplingFielder = (*WindowedThroughputSamplerConfig)(nil) + type WindowedThroughputSamplerConfig struct { UpdateFrequency Duration `json:"updatefrequency" yaml:"UpdateFrequency,omitempty"` LookbackFrequency Duration `json:"lookbackfrequency" yaml:"LookbackFrequency,omitempty"` @@ -197,6 +227,12 @@ type WindowedThroughputSamplerConfig struct { UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"` } +func (d *WindowedThroughputSamplerConfig) GetSamplingFields() []string { + return d.FieldList +} + +var _ GetSamplingFielder = (*TotalThroughputSamplerConfig)(nil) + type TotalThroughputSamplerConfig struct { GoalThroughputPerSec int `json:"goalthroughputpersec" yaml:"GoalThroughputPerSec,omitempty" validate:"gte=1"` UseClusterSize bool `json:"useclustersize" yaml:"UseClusterSize,omitempty"` @@ -206,12 +242,50 @@ type TotalThroughputSamplerConfig struct { UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"` } +func (d *TotalThroughputSamplerConfig) GetSamplingFields() []string { + return d.FieldList +} + +var _ GetSamplingFielder = (*RulesBasedSamplerConfig)(nil) + type RulesBasedSamplerConfig struct { // Rules has deliberately different names for json and yaml for conversion from old to new format Rules []*RulesBasedSamplerRule `json:"rule" yaml:"Rules,omitempty"` CheckNestedFields bool `json:"checknestedfields" yaml:"CheckNestedFields,omitempty"` } +func (r *RulesBasedSamplerConfig) GetSamplingFields() []string { + fields := make(generics.Set[string], 0) + + for _, rule := range r.Rules { + if rule == nil { + continue + } + + for _, condition := range rule.Conditions { + // Field and Fields are mutually exclusive, so we only need to check one. + if condition.Fields != nil { + fields.Add(condition.Fields...) + continue + } + + if condition.Field != "" { + fields.Add(condition.Field) + } + } + + if rule.Sampler != nil { + fields.Add(rule.Sampler.GetSamplingFields()...) + } + } + + return fields.Members() +} + +var _ GetSamplingFielder = (*RulesBasedDownstreamSampler)(nil) + +// RulesBasedDownstreamSampler is a sampler that can be used as a downstream sampler in a rules-based sampler. +// Only one of the fields should be set. type RulesBasedDownstreamSampler struct { DynamicSampler *DynamicSamplerConfig `json:"dynamicsampler" yaml:"DynamicSampler,omitempty"` EMADynamicSampler *EMADynamicSamplerConfig `json:"emadynamicsampler" yaml:"EMADynamicSampler,omitempty"` @@ -221,6 +295,35 @@ type RulesBasedDownstreamSampler struct { DeterministicSampler *DeterministicSamplerConfig `json:"deterministicsampler" yaml:"DeterministicSampler,omitempty"` } +func (r *RulesBasedDownstreamSampler) GetSamplingFields() []string { + + if r.DeterministicSampler != nil { + return r.DeterministicSampler.GetSamplingFields() + } + + if r.DynamicSampler != nil { + return r.DynamicSampler.GetSamplingFields() + } + + if r.EMADynamicSampler != nil { + return r.EMADynamicSampler.GetSamplingFields() + } + + if r.EMAThroughputSampler != nil { + return r.EMAThroughputSampler.GetSamplingFields() + } + + if r.WindowedThroughputSampler != nil { + return r.WindowedThroughputSampler.GetSamplingFields() + } + + if r.TotalThroughputSampler != nil { + return r.TotalThroughputSampler.GetSamplingFields() + } + + return []string{} +} + type RulesBasedSamplerRule struct { // Conditions has deliberately different names for json and yaml for conversion from old to new format Name string `json:"name" yaml:"Name,omitempty"` diff --git a/sample/deterministic.go b/sample/deterministic.go index 1d2e5a44c4..c51500ddfe 100644 --- a/sample/deterministic.go +++ b/sample/deterministic.go @@ -57,3 +57,7 @@ func (d *DeterministicSampler) GetSampleRate(trace *types.Trace) (rate uint, kee return uint(d.sampleRate), shouldKeep, "deterministic/chance", "" } + +func (d *DeterministicSampler) GetKeyFields() []string { + return d.Config.GetSamplingFields() +} diff --git a/sample/dynamic.go b/sample/dynamic.go index 7367e5865b..a8a77b1c6d 100644 --- a/sample/dynamic.go +++ b/sample/dynamic.go @@ -23,7 +23,8 @@ type DynamicSampler struct { prefix string lastMetrics map[string]int64 - key *traceKey + key *traceKey + keyFields []string dynsampler dynsampler.Sampler } @@ -42,6 +43,7 @@ func (d *DynamicSampler) Start() error { d.maxKeys = 500 } d.prefix = "dynamic_" + d.keyFields = d.Config.GetSamplingFields() // spin up the actual dynamic sampler d.dynsampler = &dynsampler.AvgSampleRate{ @@ -96,3 +98,7 @@ func (d *DynamicSampler) GetSampleRate(trace *types.Trace) (rate uint, keep bool } return rate, shouldKeep, "dynamic", key } + +func (d *DynamicSampler) GetKeyFields() []string { + return d.keyFields +} diff --git a/sample/dynamic_ema.go b/sample/dynamic_ema.go index 7fd04d2780..ce13e6b2f6 100644 --- a/sample/dynamic_ema.go +++ b/sample/dynamic_ema.go @@ -27,7 +27,8 @@ type EMADynamicSampler struct { prefix string lastMetrics map[string]int64 - key *traceKey + key *traceKey + keyFields []string dynsampler dynsampler.Sampler } @@ -47,6 +48,7 @@ func (d *EMADynamicSampler) Start() error { d.maxKeys = 500 } d.prefix = "emadynamic_" + d.keyFields = d.Config.GetSamplingFields() // spin up the actual dynamic sampler d.dynsampler = &dynsampler.EMASampleRate{ @@ -104,3 +106,7 @@ func (d *EMADynamicSampler) GetSampleRate(trace *types.Trace) (rate uint, keep b } return rate, shouldKeep, "emadynamic", key } + +func (d *EMADynamicSampler) GetKeyFields() []string { + return d.keyFields +} diff --git a/sample/ema_throughput.go b/sample/ema_throughput.go index 7d27983c67..2814641851 100644 --- a/sample/ema_throughput.go +++ b/sample/ema_throughput.go @@ -30,7 +30,8 @@ type EMAThroughputSampler struct { prefix string lastMetrics map[string]int64 - key *traceKey + key *traceKey + keyFields []string dynsampler *dynsampler.EMAThroughput } @@ -56,6 +57,7 @@ func (d *EMAThroughputSampler) Start() error { } d.prefix = "emathroughput_" + d.keyFields = d.Config.GetSamplingFields() // spin up the actual dynamic sampler d.dynsampler = &dynsampler.EMAThroughput{ GoalThroughputPerSec: d.goalThroughputPerSec / d.clusterSize, @@ -121,3 +123,7 @@ func (d *EMAThroughputSampler) GetSampleRate(trace *types.Trace) (rate uint, kee } return rate, shouldKeep, "emathroughput", key } + +func (d *EMAThroughputSampler) GetKeyFields() []string { + return d.keyFields +} diff --git a/sample/rules.go b/sample/rules.go index 3c48709f6f..98c2f5df56 100644 --- a/sample/rules.go +++ b/sample/rules.go @@ -15,11 +15,12 @@ import ( var _ ClusterSizer = (*RulesBasedSampler)(nil) type RulesBasedSampler struct { - Config *config.RulesBasedSamplerConfig - Logger logger.Logger - Metrics metrics.Metrics - samplers map[string]Sampler - prefix string + Config *config.RulesBasedSamplerConfig + Logger logger.Logger + Metrics metrics.Metrics + samplers map[string]Sampler + prefix string + keyFields []string } const RootPrefix = "root." @@ -35,6 +36,7 @@ func (s *RulesBasedSampler) Start() error { s.Metrics.Register(s.prefix+"sample_rate", "histogram") s.samplers = make(map[string]Sampler) + s.keyFields = s.Config.GetSamplingFields() for _, rule := range s.Config.Rules { for _, cond := range rule.Conditions { @@ -162,6 +164,10 @@ func (s *RulesBasedSampler) GetSampleRate(trace *types.Trace) (rate uint, keep b return 1, true, "no rule matched", "" } +func (s *RulesBasedSampler) GetKeyFields() []string { + return s.keyFields +} + func ruleMatchesTrace(t *types.Trace, rule *config.RulesBasedSamplerRule, checkNestedFields bool) bool { // We treat a rule with no conditions as a match. if rule.Conditions == nil { diff --git a/sample/rules_test.go b/sample/rules_test.go index d4ec68bd32..b9d70be913 100644 --- a/sample/rules_test.go +++ b/sample/rules_test.go @@ -2,6 +2,7 @@ package sample import ( "fmt" + "slices" "testing" "github.com/honeycombio/refinery/config" @@ -17,9 +18,10 @@ type TestRulesData struct { Spans []*types.Span // Set to the matching rule's sample rate if the rule matches. // Set to the default rate (1) if you expect no rule to match. - ExpectedRate uint - ExpectedKeep bool - ExpectedName string + ExpectedRate uint + ExpectedKeep bool + ExpectedName string + ExpectedKeyFields []string } func TestRules(t *testing.T) { @@ -49,8 +51,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -77,8 +80,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -105,8 +109,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -133,8 +138,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -165,9 +171,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, - ExpectedName: "fallback", + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedName: "fallback", + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -212,8 +219,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test", "test_two"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -240,8 +248,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: false, - ExpectedRate: 0, + ExpectedKeep: false, + ExpectedRate: 0, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -261,8 +270,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: false, - ExpectedRate: 0, + ExpectedKeep: false, + ExpectedRate: 0, + ExpectedKeyFields: []string{}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -303,8 +313,9 @@ func TestRules(t *testing.T) { }, ExpectedKeep: true, // the trace does not match all the rules so we expect the default sample rate - ExpectedRate: 1, - ExpectedName: "no rule matched", + ExpectedRate: 1, + ExpectedName: "no rule matched", + ExpectedKeyFields: []string{"first", "second"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -331,8 +342,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -358,8 +370,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -385,8 +398,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -413,8 +427,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -441,8 +456,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -469,8 +485,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"first"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -497,8 +514,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -538,9 +556,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "Check root span for span count", - ExpectedKeep: true, - ExpectedRate: 1, + ExpectedName: "Check root span for span count", + ExpectedKeep: true, + ExpectedRate: 1, + ExpectedKeyFields: []string{"meta.span_count"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -591,9 +610,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "Check root span for span count", - ExpectedKeep: false, - ExpectedRate: 0, + ExpectedName: "Check root span for span count", + ExpectedKeep: false, + ExpectedRate: 0, + ExpectedKeyFields: []string{"meta.span_count"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -633,9 +653,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "Check that root span is missing", - ExpectedKeep: false, - ExpectedRate: 0, + ExpectedName: "Check that root span is missing", + ExpectedKeep: false, + ExpectedRate: 0, + ExpectedKeyFields: []string{}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -684,9 +705,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "Check that root span is present", - ExpectedKeep: true, - ExpectedRate: 99, + ExpectedName: "Check that root span is present", + ExpectedKeep: true, + ExpectedRate: 99, + ExpectedKeyFields: []string{}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -713,8 +735,9 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test", "test2"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -741,9 +764,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedName: "no rule matched", - ExpectedRate: 1, + ExpectedKeep: true, + ExpectedName: "no rule matched", + ExpectedRate: 1, + ExpectedKeyFields: []string{"test", "test2"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -771,9 +795,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedName: "no rule matched", - ExpectedRate: 1, + ExpectedKeep: true, + ExpectedName: "no rule matched", + ExpectedRate: 1, + ExpectedKeyFields: []string{"test", "test2"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -832,9 +857,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "Check that the number of descendants is greater than 3", - ExpectedKeep: false, - ExpectedRate: 1, + ExpectedName: "Check that the number of descendants is greater than 3", + ExpectedKeep: false, + ExpectedRate: 1, + ExpectedKeyFields: []string{string(config.NUM_DESCENDANTS)}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -891,9 +917,10 @@ func TestRules(t *testing.T) { }, }, }, - ExpectedName: "no rule matched", - ExpectedKeep: true, - ExpectedRate: 1, + ExpectedName: "no rule matched", + ExpectedKeep: true, + ExpectedRate: 1, + ExpectedKeyFields: []string{string(config.NUM_DESCENDANTS)}, }, } @@ -919,6 +946,7 @@ func TestRules(t *testing.T) { } } + sampler.Start() rate, keep, reason, key := sampler.GetSampleRate(trace) assert.Equal(t, d.ExpectedRate, rate, d.Rules) @@ -929,6 +957,10 @@ func TestRules(t *testing.T) { assert.Contains(t, reason, name) assert.Equal(t, "", key) + keyFields := sampler.GetKeyFields() + slices.Sort(keyFields) + assert.Equal(t, d.ExpectedKeyFields, keyFields) + // we can only test when we don't expect to keep the trace if !d.ExpectedKeep { assert.Equal(t, d.ExpectedKeep, keep, d.Rules) @@ -966,8 +998,9 @@ func TestRulesWithNestedFields(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test.test1"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -995,8 +1028,9 @@ func TestRulesWithNestedFields(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test.test1"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -1025,8 +1059,9 @@ func TestRulesWithNestedFields(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 4, + ExpectedKeep: true, + ExpectedRate: 4, + ExpectedKeyFields: []string{"test.test1"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -1055,9 +1090,10 @@ func TestRulesWithNestedFields(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 1, - ExpectedName: "no rule matched", + ExpectedKeep: true, + ExpectedRate: 1, + ExpectedName: "no rule matched", + ExpectedKeyFields: []string{"test.test1"}, }, { Rules: &config.RulesBasedSamplerConfig{ @@ -1087,8 +1123,9 @@ func TestRulesWithNestedFields(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"test.test1", "test.test2"}, }, } @@ -1112,6 +1149,7 @@ func TestRulesWithNestedFields(t *testing.T) { trace.AddSpan(span) } + sampler.Start() rate, keep, reason, key := sampler.GetSampleRate(trace) assert.Equal(t, d.ExpectedRate, rate, d.Rules) @@ -1122,6 +1160,9 @@ func TestRulesWithNestedFields(t *testing.T) { assert.Contains(t, reason, name) assert.Equal(t, "", key) + keyFields := sampler.GetKeyFields() + slices.Sort(keyFields) + assert.Equal(t, d.ExpectedKeyFields, keyFields) // we can only test when we don't expect to keep the trace if !d.ExpectedKeep { assert.Equal(t, d.ExpectedKeep, keep, d.Rules) @@ -1171,8 +1212,9 @@ func TestRulesWithDynamicSampler(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"http.status_code", "rule_test"}, }, } @@ -1206,6 +1248,10 @@ func TestRulesWithDynamicSampler(t *testing.T) { assert.Contains(t, reason, name) assert.Equal(t, "200•,", key) + keyFields := sampler.GetKeyFields() + slices.Sort(keyFields) + assert.Equal(t, d.ExpectedKeyFields, keyFields) + // we can only test when we don't expect to keep the trace if !d.ExpectedKeep { assert.Equal(t, d.ExpectedKeep, keep, d.Rules) @@ -1257,8 +1303,9 @@ func TestRulesWithEMADynamicSampler(t *testing.T) { }, }, }, - ExpectedKeep: true, - ExpectedRate: 10, + ExpectedKeep: true, + ExpectedRate: 10, + ExpectedKeyFields: []string{"http.status_code", "rule_test"}, }, } @@ -1292,6 +1339,9 @@ func TestRulesWithEMADynamicSampler(t *testing.T) { assert.Contains(t, reason, name) assert.Equal(t, "200•,", key) + keyFields := sampler.GetKeyFields() + slices.Sort(keyFields) + assert.Equal(t, d.ExpectedKeyFields, keyFields) // we can only test when we don't expect to keep the trace if !d.ExpectedKeep { assert.Equal(t, d.ExpectedKeep, keep, d.Rules) diff --git a/sample/sample.go b/sample/sample.go index b1b0f44490..0f4a1312e6 100644 --- a/sample/sample.go +++ b/sample/sample.go @@ -14,6 +14,7 @@ import ( type Sampler interface { GetSampleRate(trace *types.Trace) (rate uint, keep bool, reason string, key string) + GetKeyFields() []string Start() error } diff --git a/sample/totalthroughput.go b/sample/totalthroughput.go index 3475d29b5b..b82d5b85c6 100644 --- a/sample/totalthroughput.go +++ b/sample/totalthroughput.go @@ -25,7 +25,8 @@ type TotalThroughputSampler struct { prefix string lastMetrics map[string]int64 - key *traceKey + key *traceKey + keyFields []string dynsampler *dynsampler.TotalThroughput } @@ -52,6 +53,7 @@ func (d *TotalThroughputSampler) Start() error { d.maxKeys = 500 } d.prefix = "totalthroughput_" + d.keyFields = d.Config.GetSamplingFields() // spin up the actual dynamic sampler d.dynsampler = &dynsampler.TotalThroughput{ @@ -113,3 +115,7 @@ func (d *TotalThroughputSampler) GetSampleRate(trace *types.Trace) (rate uint, k } return rate, shouldKeep, "totalthroughput", key } + +func (d *TotalThroughputSampler) GetKeyFields() []string { + return d.keyFields +} diff --git a/sample/windowed_throughput.go b/sample/windowed_throughput.go index 1c1e1cd542..cb8cc8612a 100644 --- a/sample/windowed_throughput.go +++ b/sample/windowed_throughput.go @@ -26,7 +26,8 @@ type WindowedThroughputSampler struct { prefix string lastMetrics map[string]int64 - key *traceKey + key *traceKey + keyFields []string dynsampler *dynsampler.WindowedThroughput } @@ -47,6 +48,7 @@ func (d *WindowedThroughputSampler) Start() error { d.maxKeys = 500 } d.prefix = "windowedthroughput_" + d.keyFields = d.Config.GetSamplingFields() // spin up the actual dynamic sampler d.dynsampler = &dynsampler.WindowedThroughput{ @@ -109,3 +111,6 @@ func (d *WindowedThroughputSampler) GetSampleRate(trace *types.Trace) (rate uint } return rate, shouldKeep, "Windowedthroughput", key } +func (d *WindowedThroughputSampler) GetKeyFields() []string { + return d.keyFields +}