Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactoring of rules configuration and rules converter #681

Merged
merged 30 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2e1222b
A mostly working config converter plus data
kentquirk May 9, 2023
956b880
Lots of cleanup
kentquirk May 9, 2023
b81bbea
Cross-check against original with fixes
kentquirk May 9, 2023
6aaa899
Generate sample config
kentquirk May 10, 2023
9481675
Convert to v3 of yaml package
kentquirk May 10, 2023
7cb0a7f
Revise after feedback from design
kentquirk May 10, 2023
8c867b3
Add conditional
kentquirk May 11, 2023
abb4369
Set defaults for username and pw
kentquirk May 11, 2023
1bf9c29
Add some docs to command line help.
kentquirk May 11, 2023
0a3aa08
Add README.md
kentquirk May 11, 2023
ac410a0
Emit info about removed values
kentquirk May 13, 2023
1eaae19
Convert from test code to subcommands
kentquirk May 13, 2023
e7c3ecb
Embed the templates for standalone use.
kentquirk May 13, 2023
2227298
Clean up name generation
kentquirk May 14, 2023
36f6694
Factor out common code
kentquirk May 14, 2023
7b347d1
Add summaries back and clean up descriptions
kentquirk May 14, 2023
541c954
Generate doc file for config too
kentquirk May 14, 2023
11e3a3b
Rename for clarity in an E&S world
kentquirk May 14, 2023
6f7af9e
Burnt by build tags
kentquirk May 14, 2023
43a8763
Deflake test
kentquirk May 14, 2023
a255f6e
Merge branch 'main' into kent.v2.rename_dataset_to_destname
kentquirk May 15, 2023
cac7321
Really, git, I meant to delete this
kentquirk May 15, 2023
6672d56
Unexport ReloadInto
kentquirk May 15, 2023
b095cc4
Rules converter and some changes to config data
kentquirk May 15, 2023
ec0606c
Normalize case appropriately
kentquirk May 15, 2023
8455297
we like __default__ better
kentquirk May 15, 2023
328e5f0
Fix a linty thing
kentquirk May 15, 2023
472916c
Fix bugs when reading from yaml instead of toml
kentquirk May 15, 2023
5eaa2c2
Refactor rule converter
kentquirk May 17, 2023
01fae0a
Merge from main
kentquirk May 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,25 +306,25 @@ func TestReadRulesConfig(t *testing.T) {
assert.NoError(t, err)
switch r := d.(type) {
case *RulesBasedSamplerConfig:
assert.Len(t, r.Rule, 6)
assert.Len(t, r.Rules, 6)

var rule *RulesBasedSamplerRule

rule = r.Rule[0]
rule = r.Rules[0]
assert.True(t, rule.Drop)
assert.Equal(t, 0, rule.SampleRate)
assert.Len(t, rule.Condition, 1)
assert.Len(t, rule.Conditions, 1)

rule = r.Rule[1]
rule = r.Rules[1]
assert.Equal(t, 1, rule.SampleRate)
assert.Equal(t, "keep slow 500 errors", rule.Name)
assert.Len(t, rule.Condition, 2)
assert.Len(t, rule.Conditions, 2)

rule = r.Rule[4]
rule = r.Rules[4]
assert.Equal(t, 5, rule.SampleRate)
assert.Equal(t, "span", rule.Scope)

rule = r.Rule[5]
rule = r.Rules[5]
assert.Equal(t, 10, rule.SampleRate)
assert.Equal(t, "", rule.Scope)

Expand Down
12 changes: 6 additions & 6 deletions config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ func newFileConfig(opts *CmdEnv) (*fileConfig, error) {

// TODO: this is temporary while we still conform to the old config format;
// once we're fully migrated, we can remove this stuff.
if dryRun, ok := getValueForCaseInsensitiveKey(rulesconf, "dryrun", false); ok {
if dryRun, ok := GetValueForCaseInsensitiveKey(rulesconf, "dryrun", false); ok {
mainconf.DryRun = dryRun
}
if dryRunFieldName, ok := getValueForCaseInsensitiveKey(rulesconf, "dryrunfieldname", ""); ok && dryRunFieldName != "" {
if dryRunFieldName, ok := GetValueForCaseInsensitiveKey(rulesconf, "dryrunfieldname", ""); ok && dryRunFieldName != "" {
mainconf.DryRunFieldName = dryRunFieldName
}

Expand Down Expand Up @@ -443,11 +443,11 @@ func (f *fileConfig) GetAllSamplerRules() (map[string]any, error) {
return f.rulesConfig, nil
}

// getValueForCaseInsensitiveKey is a generic function that returns the value from a map[string]any
// GetValueForCaseInsensitiveKey is a generic function that returns the value from a map[string]any
// for the given key, ignoring case of the key. It returns ok=true only if the key was found
// and could be converted to the required type. Otherwise it returns the default value
// and ok=false.
func getValueForCaseInsensitiveKey[T any](m map[string]any, key string, def T) (T, bool) {
func GetValueForCaseInsensitiveKey[T any](m map[string]any, key string, def T) (T, bool) {
for k, v := range m {
if strings.EqualFold(k, key) {
if t, ok := v.(T); ok {
Expand All @@ -473,13 +473,13 @@ func (f *fileConfig) GetSamplerConfigForDestName(destname string) (any, string,
// both fail will we return not found.

const notfound = "not found"
if v, ok := getValueForCaseInsensitiveKey(config, destname, map[string]any{}); ok {
if v, ok := GetValueForCaseInsensitiveKey(config, destname, map[string]any{}); ok {
// we have a specific sampler, so we extract that sampler's config
config = v
}

// now we need the name of the sampler
samplerName, _ := getValueForCaseInsensitiveKey(config, "sampler", "DeterministicSampler")
samplerName, _ := GetValueForCaseInsensitiveKey(config, "sampler", "DeterministicSampler")

var i any
switch samplerName {
Expand Down
126 changes: 73 additions & 53 deletions config/sampler_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,92 @@ import (
"strings"
)

// The json tags in this file are used for conversion from the old format (see tools/convert for details).
// They are deliberately all lowercase.
// The yaml tags are used for the new format and are PascalCase.

type V2SamplerChoice struct {
Name string `json:"name" yaml:"Name,omitempty"`
DeterministicSampler *DeterministicSamplerConfig `json:"deterministicsampler" yaml:"DeterministicSampler,omitempty"`
RulesBasedSampler *RulesBasedSamplerConfig `json:"rulesbasedsampler" yaml:"RulesBasedSampler,omitempty"`
DynamicSampler *DynamicSamplerConfig `json:"dynamicsampler" yaml:"DynamicSampler,omitempty"`
EMADynamicSampler *EMADynamicSamplerConfig `json:"emadynamicsampler" yaml:"EmaDynamicSampler,omitempty"`
TotalThroughputSampler *TotalThroughputSamplerConfig `json:"totalthroughputsampler" yaml:"TotalThroughputSampler,omitempty"`
}

type V2SamplerConfig struct {
ConfigVersion int `json:"configversion" yaml:"ConfigVersion" validate:"required,ge=2"`
Samplers map[string]*V2SamplerChoice `json:"samplers" yaml:"Samplers,omitempty" validate:"required"`
}

type DeterministicSamplerConfig struct {
SampleRate int `default:"1" validate:"required,gte=1"`
SampleRate int `json:"samplerate" yaml:"SampleRate,omitempty" default:"1" validate:"required,gte=1"`
}

type DynamicSamplerConfig struct {
SampleRate int64 `validate:"required,gte=1"`
ClearFrequencySec int64 ``
FieldList []string `validate:"required"`
UseTraceLength bool ``
AddSampleRateKeyToTrace bool ``
AddSampleRateKeyToTraceField string `validate:"required_with=AddSampleRateKeyToTrace"`
SampleRate int64 `json:"samplerate" yaml:"SampleRate,omitempty" validate:"required,gte=1"`
ClearFrequencySec int64 `json:"clearfrequencysec" yaml:"ClearFrequencySec,omitempty"`
FieldList []string `json:"fieldlist" yaml:"FieldList,omitempty" validate:"required"`
UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"`
AddSampleRateKeyToTrace bool `json:"addsampleratekeytotrace" yaml:"AddSampleRateKeyToTrace,omitempty"`
AddSampleRateKeyToTraceField string `json:"addsampleratekeytotracefield" yaml:"AddSampleRateKeyToTraceField,omitempty" validate:"required_with=AddSampleRateKeyToTrace"`
}

type EMADynamicSamplerConfig struct {
GoalSampleRate int `validate:"gte=1"`
AdjustmentInterval int ``
Weight float64 `validate:"gt=0,lt=1"`
AgeOutValue float64 ``
BurstMultiple float64 ``
BurstDetectionDelay uint ``
MaxKeys int ``
FieldList []string `validate:"required"`
UseTraceLength bool ``
AddSampleRateKeyToTrace bool ``
AddSampleRateKeyToTraceField string `validate:"required_with=AddSampleRateKeyToTrace"`
GoalSampleRate int `json:"goalsamplerate" yaml:"GoalSampleRate,omitempty" validate:"gte=1"`
AdjustmentInterval int `json:"adjustmentinterval" yaml:"AdjustmentInterval,omitempty"`
Weight float64 `json:"weight" yaml:"Weight,omitempty" validate:"gt=0,lt=1"`
AgeOutValue float64 `json:"ageoutvalue" yaml:"AgeOutValue,omitempty"`
BurstMultiple float64 `json:"burstmultiple" yaml:"BurstMultiple,omitempty"`
BurstDetectionDelay uint `json:"burstdetectiondelay" yaml:"BurstDetectionDelay,omitempty"`
MaxKeys int `json:"maxkeys" yaml:"MaxKeys,omitempty"`
FieldList []string `json:"fieldlist" yaml:"FieldList,omitempty" validate:"required"`
UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"`
AddSampleRateKeyToTrace bool `json:"addsampleratekeytotrace" yaml:"AddSampleRateKeyToTrace,omitempty"`
AddSampleRateKeyToTraceField string `json:"addsampleratekeytotracefield" yaml:"AddSampleRateKeyToTraceField,omitempty" validate:"required_with=AddSampleRateKeyToTrace"`
}

type TotalThroughputSamplerConfig struct {
GoalThroughputPerSec int64 `validate:"gte=1"`
ClearFrequencySec int64 ``
FieldList []string `validate:"required"`
UseTraceLength bool ``
AddSampleRateKeyToTrace bool ``
AddSampleRateKeyToTraceField string `validate:"required_with=AddSampleRateKeyToTrace"`
GoalThroughputPerSec int64 `json:"goalthroughputpersec" yaml:"GoalThroughputPerSec,omitempty" validate:"gte=1"`
ClearFrequencySec int64 `json:"clearfrequencysec" yaml:"ClearFrequencySec,omitempty"`
FieldList []string `json:"fieldlist" yaml:"FieldList,omitempty" validate:"required"`
UseTraceLength bool `json:"usetracelength" yaml:"UseTraceLength,omitempty"`
AddSampleRateKeyToTrace bool `json:"addsampleratekeytotrace" yaml:"AddSampleRateKeyToTrace,omitempty"`
AddSampleRateKeyToTraceField string `json:"addsampleratekeytotracefield" yaml:"AddSampleRateKeyToTraceField,omitempty" validate:"required_with=AddSampleRateKeyToTrace"`
}

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"`
}

type RulesBasedDownstreamSampler struct {
DynamicSampler *DynamicSamplerConfig `json:"dynamicsampler" yaml:"DynamicSampler,omitempty"`
EMADynamicSampler *EMADynamicSamplerConfig `json:"emadynamicsampler" yaml:"EmaDynamicSampler,omitempty"`
TotalThroughputSampler *TotalThroughputSamplerConfig `json:"totalthroughputsampler" yaml:"TotalThroughputSampler,omitempty"`
}

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"`
SampleRate int `json:"samplerate" yaml:"SampleRate,omitempty"`
Drop bool `json:"drop" yaml:"Drop,omitempty"`
Scope string `json:"scope" yaml:"Scope,omitempty" validate:"oneof=span trace"`
Conditions []*RulesBasedSamplerCondition `json:"condition" yaml:"Conditions,omitempty"`
Sampler *RulesBasedDownstreamSampler `json:"sampler" yaml:"Sampler,omitempty"`
}

func (r *RulesBasedSamplerRule) String() string {
return fmt.Sprintf("%+v", *r)
}

type RulesBasedSamplerCondition struct {
Field string `validate:"required"`
Operator string `validate:"required"`
Value interface{} ``
Datatype string ``
Matches func(value any, exists bool) bool
Field string `json:"field" yaml:"Field" validate:"required"`
Operator string `json:"operator" yaml:"Operator" validate:"required"`
Value interface{} `json:"value" yaml:"Value" `
Datatype string `json:"datatype" yaml:"Datatype,omitempty"`
Matches func(value any, exists bool) bool `json:"-" yaml:"-"`
}

func (r *RulesBasedSamplerCondition) Init() error {
Expand Down Expand Up @@ -374,30 +418,6 @@ func setMatchStringBasedOperators(r *RulesBasedSamplerCondition, condition strin
return nil
}

type RulesBasedDownstreamSampler struct {
DynamicSampler *DynamicSamplerConfig
EMADynamicSampler *EMADynamicSamplerConfig
TotalThroughputSampler *TotalThroughputSamplerConfig
}

type RulesBasedSamplerRule struct {
Name string ``
SampleRate int ``
Sampler *RulesBasedDownstreamSampler ``
Drop bool ``
Scope string `validate:"oneof=span trace"`
Condition []*RulesBasedSamplerCondition ``
}

func (r *RulesBasedSamplerRule) String() string {
return fmt.Sprintf("%+v", *r)
}

type RulesBasedSamplerConfig struct {
Rule []*RulesBasedSamplerRule ``
CheckNestedFields bool ``
}

func (r *RulesBasedSamplerConfig) String() string {
return fmt.Sprintf("%+v", *r)
}
16 changes: 8 additions & 8 deletions sample/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func (s *RulesBasedSampler) Start() error {
s.samplers = make(map[string]Sampler)

// Check if any rule has a downstream sampler and create it
for _, rule := range s.Config.Rule {
for _, cond := range rule.Condition {
for _, rule := range s.Config.Rules {
for _, cond := range rule.Conditions {
if err := cond.Init(); err != nil {
s.Logger.Debug().WithFields(map[string]interface{}{
"rule_name": rule.Name,
Expand Down Expand Up @@ -73,7 +73,7 @@ func (s *RulesBasedSampler) GetSampleRate(trace *types.Trace) (rate uint, keep b
"trace_id": trace.TraceID,
})

for _, rule := range s.Config.Rule {
for _, rule := range s.Config.Rules {
var matched bool
var reason string

Expand Down Expand Up @@ -135,13 +135,13 @@ func (s *RulesBasedSampler) GetSampleRate(trace *types.Trace) (rate uint, keep b

func ruleMatchesTrace(t *types.Trace, rule *config.RulesBasedSamplerRule, checkNestedFields bool) bool {
// We treat a rule with no conditions as a match.
if rule.Condition == nil {
if rule.Conditions == nil {
return true
}

var matched int

for _, condition := range rule.Condition {
for _, condition := range rule.Conditions {
span:
for _, span := range t.GetSpans() {
value, exists := extractValueFromSpan(span, condition, checkNestedFields)
Expand All @@ -158,18 +158,18 @@ func ruleMatchesTrace(t *types.Trace, rule *config.RulesBasedSamplerRule, checkN

}
}
return matched == len(rule.Condition)
return matched == len(rule.Conditions)
}

func ruleMatchesSpanInTrace(trace *types.Trace, rule *config.RulesBasedSamplerRule, checkNestedFields bool) bool {
// We treat a rule with no conditions as a match.
if rule.Condition == nil {
if rule.Conditions == nil {
return true
}

for _, span := range trace.GetSpans() {
ruleMatched := true
for _, condition := range rule.Condition {
for _, condition := range rule.Conditions {
// whether this condition is matched by this span.
value, exists := extractValueFromSpan(span, condition, checkNestedFields)
if condition.Matches == nil {
Expand Down
Loading