diff --git a/main.go b/main.go index 306ee2ab..0e7f32a7 100644 --- a/main.go +++ b/main.go @@ -142,7 +142,7 @@ func main() { statsdListenTCP = kingpin.Flag("statsd.listen-tcp", "The TCP address on which to receive statsd metric lines. \"\" disables it.").Default(":9125").String() mappingConfig = kingpin.Flag("statsd.mapping-config", "Metric mapping configuration file name.").String() readBuffer = kingpin.Flag("statsd.read-buffer", "Size (in bytes) of the operating system's transmit read buffer associated with the UDP connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified.").Int() - dumpFSMPath = kingpin.Flag("statsd.dump-fsm", "The path to dump internal FSM generated for glob matching as Dot file.").Default("").String() + dumpFSMPath = kingpin.Flag("debug.dump-fsm", "The path to dump internal FSM generated for glob matching as Dot file.").Default("").String() ) log.AddFlags(kingpin.CommandLine) @@ -203,7 +203,7 @@ func main() { if *dumpFSMPath != "" { err := dumpFSM(mapper, *dumpFSMPath) if err != nil { - log.Fatal("Error dumpping FSM:", err) + log.Fatal("Error dumping FSM:", err) } } go watchConfig(*mappingConfig, mapper) diff --git a/pkg/mapper/fsm/formatter.go b/pkg/mapper/fsm/formatter.go index d6782bad..362c2208 100644 --- a/pkg/mapper/fsm/formatter.go +++ b/pkg/mapper/fsm/formatter.go @@ -15,10 +15,15 @@ package fsm import ( "fmt" + "regexp" "strconv" "strings" ) +var ( + templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`) +) + type templateFormatter struct { captureIndexes []int captureCount int @@ -57,12 +62,11 @@ func (formatter *templateFormatter) format(captures map[int]string) string { if formatter.captureCount == 0 { // no label substitution, keep as it is return formatter.fmtString - } else { - indexes := formatter.captureIndexes - vargs := make([]interface{}, formatter.captureCount) - for i, idx := range indexes { - vargs[i] = captures[idx] - } - return fmt.Sprintf(formatter.fmtString, vargs...) } + indexes := formatter.captureIndexes + vargs := make([]interface{}, formatter.captureCount) + for i, idx := range indexes { + vargs[i] = captures[idx] + } + return fmt.Sprintf(formatter.fmtString, vargs...) } diff --git a/pkg/mapper/fsm/fsm.go b/pkg/mapper/fsm/fsm.go index f3be09c4..0d7dd7f6 100644 --- a/pkg/mapper/fsm/fsm.go +++ b/pkg/mapper/fsm/fsm.go @@ -14,8 +14,6 @@ package fsm import ( - "fmt" - "io" "regexp" "strings" @@ -23,10 +21,6 @@ import ( "github.com/prometheus/common/log" ) -var ( - templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`) -) - type mappingState struct { transitions map[string]*mappingState minRemainingLength int @@ -48,18 +42,18 @@ type fsmBacktrackStackCursor struct { } type FSM struct { - root *mappingState - needsBacktracking bool - metricTypes []string - disableOrdering bool - statesCount int + root *mappingState + metricTypes []string + statesCount int + BacktrackingNeeded bool + OrderingDisabled bool } // NewFSM creates a new FSM instance -func NewFSM(metricTypes []string, maxPossibleTransitions int, disableOrdering bool) *FSM { +func NewFSM(metricTypes []string, maxPossibleTransitions int, orderingDisabled bool) *FSM { fsm := FSM{} root := &mappingState{} - root.transitions = make(map[string]*mappingState, 3) + root.transitions = make(map[string]*mappingState, len(metricTypes)) metricTypes = append(metricTypes, "") for _, field := range metricTypes { @@ -67,7 +61,7 @@ func NewFSM(metricTypes []string, maxPossibleTransitions int, disableOrdering bo (*state).transitions = make(map[string]*mappingState, maxPossibleTransitions) root.transitions[string(field)] = state } - fsm.disableOrdering = disableOrdering + fsm.OrderingDisabled = orderingDisabled fsm.metricTypes = metricTypes fsm.statesCount = 0 fsm.root = root @@ -136,127 +130,6 @@ func (f *FSM) AddState(match string, name string, labels prometheus.Labels, matc } -// DumpFSM accepts a io.writer and write the current FSM into dot file format -func (f *FSM) DumpFSM(w io.Writer) { - idx := 0 - states := make(map[int]*mappingState) - states[idx] = f.root - - w.Write([]byte("digraph g {\n")) - w.Write([]byte("rankdir=LR\n")) // make it vertical - w.Write([]byte("node [ label=\"\",style=filled,fillcolor=white,shape=circle ]\n")) // remove label of node - - for idx < len(states) { - for field, transition := range states[idx].transitions { - states[len(states)] = transition - w.Write([]byte(fmt.Sprintf("%d -> %d [label = \"%s\"];\n", idx, len(states)-1, field))) - if idx == 0 { - // color for metric types - w.Write([]byte(fmt.Sprintf("%d [color=\"#D6B656\",fillcolor=\"#FFF2CC\"];\n", len(states)-1))) - } else if transition.transitions == nil || len(transition.transitions) == 0 { - // color for end state - w.Write([]byte(fmt.Sprintf("%d [color=\"#82B366\",fillcolor=\"#D5E8D4\"];\n", len(states)-1))) - } - } - idx++ - } - // color for start state - w.Write([]byte(fmt.Sprintf("0 [color=\"#a94442\",fillcolor=\"#f2dede\"];\n"))) - w.Write([]byte("}")) -} - -// TestIfNeedBacktracking test if backtrack is needed for current FSM -func (f *FSM) TestIfNeedBacktracking(mappings []string) bool { - needBacktrack := false - // A has * in rules there's other transisitions at the same state - // this makes A the cause of backtracking - ruleByLength := make(map[int][]string) - ruleREByLength := make(map[int][]*regexp.Regexp) - - // first sort rules by length - for _, mapping := range mappings { - l := len(strings.Split(mapping, ".")) - ruleByLength[l] = append(ruleByLength[l], mapping) - - metricRe := strings.Replace(mapping, ".", "\\.", -1) - metricRe = strings.Replace(metricRe, "*", "([^.]*)", -1) - regex, err := regexp.Compile("^" + metricRe + "$") - if err != nil { - log.Warnf("invalid match %s. cannot compile regex in mapping: %v", mapping, err) - } - // put into array no matter there's error or not, we will skip later if regex is nil - ruleREByLength[l] = append(ruleREByLength[l], regex) - } - - for l, rules := range ruleByLength { - if len(rules) == 1 { - continue - } - rulesRE := ruleREByLength[l] - for i1, r1 := range rules { - currentRuleNeedBacktrack := false - re1 := rulesRE[i1] - if re1 == nil || strings.Index(r1, "*") == -1 { - continue - } - // if a rule is A.B.C.*.E.*, is there a rule A.B.C.D.x.x or A.B.C.*.E.F? (x is any string or *) - for index := 0; index < len(r1); index++ { - if r1[index] != '*' { - continue - } - reStr := strings.Replace(r1[:index], ".", "\\.", -1) - reStr = strings.Replace(reStr, "*", "\\*", -1) - re := regexp.MustCompile("^" + reStr) - for i2, r2 := range rules { - if i2 == i1 { - continue - } - if len(re.FindStringSubmatchIndex(r2)) > 0 { - currentRuleNeedBacktrack = true - break - } - } - } - - for i2, r2 := range rules { - if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 { - // log if we care about ordering and the superset occurs before - if !f.disableOrdering && i1 < i2 { - log.Warnf("match \"%s\" is a super set of match \"%s\" but in a lower order, "+ - "the first will never be matched\n", r1, r2) - } - currentRuleNeedBacktrack = false - } - } - for i2, re2 := range rulesRE { - if i2 == i1 || re2 == nil { - continue - } - // if r1 is a subset of other rule, we don't need backtrack - // because either we turned on ordering - // or we disabled ordering and can't match it even with backtrack - if len(re2.FindStringSubmatchIndex(r1)) > 0 { - currentRuleNeedBacktrack = false - } - } - - if currentRuleNeedBacktrack { - log.Warnf("backtracking required because of match \"%s\", "+ - "matching performance may be degraded\n", r1) - needBacktrack = true - } - } - } - - // backtracking will always be needed if ordering of rules is not disabled - // since transistions are stored in (unordered) map - // note: don't move this branch to the beginning of this function - // since we need logs for superset rules - f.needsBacktracking = !f.disableOrdering || needBacktrack - - return f.needsBacktracking -} - // GetMapping implements a mapping algorithm for Glob pattern func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (interface{}, string, prometheus.Labels, bool) { matchFields := strings.Split(statsdMetric, ".") @@ -294,7 +167,7 @@ func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (interfac captures[captureIdx] = field captureIdx++ } - } else if f.needsBacktracking { + } else if f.BacktrackingNeeded { // if backtracking is needed, also check for alternative transition altState, present := currentState.transitions["*"] if !present || fieldsLeft > altState.maxRemainingLength || fieldsLeft < altState.minRemainingLength { @@ -319,7 +192,7 @@ func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (interfac // do we reach a final state? if state.result != nil && i == filedsCount-1 { - if f.disableOrdering { + if f.OrderingDisabled { finalState = state return formatLabels(finalState, captures) } else if finalState == nil || finalState.resultPriority > state.resultPriority { @@ -360,6 +233,97 @@ func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (interfac return formatLabels(finalState, captures) } +// TestIfNeedBacktracking test if backtrack is needed for current mappings +func TestIfNeedBacktracking(mappings []string, orderingDisabled bool) bool { + backtrackingNeeded := false + // A has * in rules there's other transisitions at the same state + // this makes A the cause of backtracking + ruleByLength := make(map[int][]string) + ruleREByLength := make(map[int][]*regexp.Regexp) + + // first sort rules by length + for _, mapping := range mappings { + l := len(strings.Split(mapping, ".")) + ruleByLength[l] = append(ruleByLength[l], mapping) + + metricRe := strings.Replace(mapping, ".", "\\.", -1) + metricRe = strings.Replace(metricRe, "*", "([^.]*)", -1) + regex, err := regexp.Compile("^" + metricRe + "$") + if err != nil { + log.Warnf("invalid match %s. cannot compile regex in mapping: %v", mapping, err) + } + // put into array no matter there's error or not, we will skip later if regex is nil + ruleREByLength[l] = append(ruleREByLength[l], regex) + } + + for l, rules := range ruleByLength { + if len(rules) == 1 { + continue + } + rulesRE := ruleREByLength[l] + for i1, r1 := range rules { + currentRuleNeedBacktrack := false + re1 := rulesRE[i1] + if re1 == nil || strings.Index(r1, "*") == -1 { + continue + } + // if a rule is A.B.C.*.E.*, is there a rule A.B.C.D.x.x or A.B.C.*.E.F? (x is any string or *) + for index := 0; index < len(r1); index++ { + if r1[index] != '*' { + continue + } + reStr := strings.Replace(r1[:index], ".", "\\.", -1) + reStr = strings.Replace(reStr, "*", "\\*", -1) + re := regexp.MustCompile("^" + reStr) + for i2, r2 := range rules { + if i2 == i1 { + continue + } + if len(re.FindStringSubmatchIndex(r2)) > 0 { + currentRuleNeedBacktrack = true + break + } + } + } + + for i2, r2 := range rules { + if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 { + // log if we care about ordering and the superset occurs before + if !orderingDisabled && i1 < i2 { + log.Warnf("match \"%s\" is a super set of match \"%s\" but in a lower order, "+ + "the first will never be matched", r1, r2) + } + currentRuleNeedBacktrack = false + } + } + for i2, re2 := range rulesRE { + if i2 == i1 || re2 == nil { + continue + } + // if r1 is a subset of other rule, we don't need backtrack + // because either we turned on ordering + // or we disabled ordering and can't match it even with backtrack + if len(re2.FindStringSubmatchIndex(r1)) > 0 { + currentRuleNeedBacktrack = false + } + } + + if currentRuleNeedBacktrack { + log.Warnf("backtracking required because of match \"%s\", "+ + "matching performance may be degraded", r1) + backtrackingNeeded = true + } + } + } + + // backtracking will always be needed if ordering of rules is not disabled + // since transistions are stored in (unordered) map + // note: don't move this branch to the beginning of this function + // since we need logs for superset rules + + return !orderingDisabled || backtrackingNeeded +} + func formatLabels(finalState *mappingState, captures map[int]string) (interface{}, string, prometheus.Labels, bool) { // format name and labels if finalState != nil { diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go index 999c55c2..e853abab 100644 --- a/pkg/mapper/mapper.go +++ b/pkg/mapper/mapper.go @@ -31,8 +31,6 @@ var ( metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`) metricNameRE = regexp.MustCompile(`^([a-zA-Z_]|` + templateReplaceRE + `)([a-zA-Z0-9_]|` + templateReplaceRE + `)*$`) labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`) - - templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`) ) type mapperConfigDefaults struct { @@ -81,20 +79,6 @@ var defaultQuantiles = []metricObjective{ {Quantile: 0.99, Error: 0.001}, } -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func max(x, y int) int { - if x > y { - return x - } - return y -} - func (m *MetricMapper) InitFromYAMLString(fileContents string) error { var n MetricMapper @@ -191,7 +175,7 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string) error { mappings = append(mappings, mapping.Match) } } - n.FSM.TestIfNeedBacktracking(mappings) + n.FSM.BacktrackingNeeded = fsm.TestIfNeedBacktracking(mappings, n.FSM.OrderingDisabled) m.FSM = n.FSM m.doRegex = n.doRegex diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go index 58e0ec02..ace47e4b 100644 --- a/pkg/mapper/mapper_test.go +++ b/pkg/mapper/mapper_test.go @@ -16,7 +16,6 @@ package mapper import ( "fmt" "testing" - "time" ) type mappings map[string]struct { @@ -26,6 +25,35 @@ type mappings map[string]struct { notPresent bool } +var ( + ruleTemplateSingleMatchGlob = ` +- match: metric%d.* + name: "metric_single" + labels: + name: "$1" +` + ruleTemplateSingleMatchRegex = ` +- match: metric%d\.([^.]*) + name: "metric_single" + labels: + name: "$1" +` + + ruleTemplateMultipleMatchGlob = ` +- match: metric%d.*.*.*.*.*.*.*.*.*.*.*.* + name: "metric_multi" + labels: + name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" +` + + ruleTemplateMultipleMatchRegex = ` +- match: metric%d\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*) + name: "metric_multi" + labels: + name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" +` +) + func TestMetricMapperYAML(t *testing.T) { scenarios := []struct { config string @@ -615,46 +643,8 @@ func duplicateRules(count int, template string) string { } return rules } - -func TestPerformance(t *testing.T) { - sampleRulesAmount := 100 - - ruleTemplateSingleMatchGlob := ` - - match: metric%d.* - name: "metric_single" - labels: - name: "$1" -` - ruleTemplateSingleMatchRegex := ` - - match: metric%d\.([^.]*) - name: "metric_single" - labels: - name: "$1" -` - - ruleTemplateMultipleMatchGlob := ` - - match: metric%d.*.*.*.*.*.*.*.*.*.*.*.* - name: "metric_multi" - labels: - name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" -` - - ruleTemplateMultipleMatchRegex := ` - - match: metric%d\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*) - name: "metric_multi" - labels: - name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" -` - - scenarios := []struct { - name string - config string - configBad bool - mappings mappings - }{ - { - name: "glob", - config: `--- +func BenchmarkGlob(b *testing.B) { + config := `--- mappings: - match: test.dispatcher.*.*.succeeded name: "dispatch_events" @@ -692,26 +682,40 @@ mappings: second: "$2" third: "$3" job: "-" - `, - mappings: mappings{ - "test.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar.baz": {}, - }, + ` + mappings := mappings{ + "test.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", }, - { - name: "glob no ordering", - config: `--- + "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", + }, + "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar.baz": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobNoOrdering(b *testing.B) { + config := `--- defaults: glob_disable_ordering: true mappings: @@ -751,26 +755,40 @@ mappings: second: "$2" third: "$3" job: "-" - `, - mappings: mappings{ - "test.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar.baz": {}, - }, + ` + mappings := mappings{ + "test.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", }, - { - name: "glob with backtracking (no ordering implicated)", - config: `--- + "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", + }, + "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar.baz": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobNoOrderingWithBacktracking(b *testing.B) { + config := `--- defaults: glob_disable_ordering: true mappings: @@ -817,26 +835,40 @@ mappings: second: "$2" third: "$3" job: "-" - `, - mappings: mappings{ - "test.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar.baz": {}, - }, + ` + mappings := mappings{ + "test.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", }, - { - name: "regex", - config: `--- + "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", + }, + "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar.baz": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegex(b *testing.B) { + config := `--- defaults: match_type: regex mappings: @@ -876,112 +908,207 @@ mappings: second: "$2" third: "$3" job: "-" - `, - mappings: mappings{ - "test.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { - name: "ignored-in-test-dont-set-me", - }, - "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar": { - name: "ignored-in-test-dont-set-me", - }, - "foo.bar.baz": {}, - }, + ` + mappings := mappings{ + "test.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", }, - {}, - { - name: "glob single match ", - config: `--- + "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": { + name: "ignored-in-test-dont-set-me", + }, + "request_time.get/threads/1/posts.200.00000000.nonversioned.discussions.a11bbcdf0ac64ec243658dc64b7100fb.172.20.0.1.12ba97b7eaa1a50001000001.": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar": { + name: "ignored-in-test-dont-set-me", + }, + "foo.bar.baz": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobSingleMatch(b *testing.B) { + config := `--- mappings: - match: metric.* name: "metric_one" labels: name: "$1" - `, - mappings: mappings{ - "metric.aaa": {}, - "metric.bbb": {}, - }, - }, - { - name: "regex single match", - config: `--- + ` + mappings := mappings{ + "metric.aaa": {}, + "metric.bbb": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegexSingleMatch(b *testing.B) { + config := `--- mappings: - match: metric\.([^.]*) name: "metric_one" match_type: regex labels: name: "$1" - `, - mappings: mappings{ - "metric.aaa": {}, - "metric.bbb": {}, - }, - }, - {}, - { - name: "glob multiple captures", - config: `--- + ` + mappings := mappings{ + "metric.aaa": {}, + "metric.bbb": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobMultipleCaptures(b *testing.B) { + config := `--- mappings: - match: metric.*.*.*.*.*.*.*.*.*.*.*.* name: "metric_multi" labels: name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - { - name: "regex multiple captures", - config: `--- + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegexMultipleCaptures(b *testing.B) { + config := `--- mappings: - match: metric\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*) name: "metric_multi" match_type: regex labels: name: "$1-$2-$3.$4-$5-$6.$7-$8-$9.$10-$11-$12" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - { - name: "glob multiple captures no format", - config: `--- + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobMultipleCapturesNoFormat(b *testing.B) { + config := `--- mappings: - match: metric.*.*.*.*.*.*.*.*.*.*.*.* name: "metric_multi" labels: - name: "$1" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - { - name: "regex multiple captures no expansion", - config: `--- + name: "not_relevant" + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegexMultipleCapturesNoFormat(b *testing.B) { + config := `--- mappings: - match: metric\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*) name: "metric_multi" match_type: regex labels: - name: "$1" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - {}, - { - name: "glob multiple captures in different labels", - config: `--- + name: "not_relevant" + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlobMultipleCapturesDifferentLabels(b *testing.B) { + config := `--- mappings: - match: metric.*.*.*.*.*.*.*.*.*.*.*.* name: "metric_multi" @@ -998,14 +1125,28 @@ mappings: label10: "$10" label11: "$11" label12: "$12" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - { - name: "regex multiple captures", - config: `--- + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegexMultipleCapturesDifferentLabels(b *testing.B) { + config := `--- mappings: - match: metric\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*) name: "metric_multi" @@ -1023,120 +1164,255 @@ mappings: label10: "$10" label11: "$11" label12: "$12" - `, - mappings: mappings{ - "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - {}, - { - name: "glob 100 rules", - config: `--- -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchGlob), - mappings: mappings{ - "metric100.a": {}, - }, - }, - { - name: "glob 100 rules, no match", - config: `--- -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchGlob), - mappings: mappings{ - "metricnomatchy.a": {}, - }, - }, - { - name: "glob 100 rules without ordering, no match", - config: `--- -defaults: - glob_disable_ordering: true -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchGlob), - mappings: mappings{ - "metricnomatchy.a": {}, - }, - }, - { - name: "regex 100 rules, average case", - config: `--- + ` + mappings := mappings{ + "metric.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlob10Rules(b *testing.B) { + config := `--- +mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob) + mappings := mappings{ + "metric100.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegex10RulesAverage(b *testing.B) { + config := `--- defaults: match_type: regex -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchRegex), - mappings: mappings{ - fmt.Sprintf("metric%d.a", sampleRulesAmount/2): {}, // average match position - }, - }, - { - name: "regex 100 rules, worst case", - config: `--- +mappings:` + duplicateRules(10, ruleTemplateSingleMatchRegex) + mappings := mappings{ + "metric5.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlob100Rules(b *testing.B) { + config := `--- +mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob) + mappings := mappings{ + "metric100.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlob100RulesNoMatch(b *testing.B) { + config := `--- +mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob) + mappings := mappings{ + "metricnomatchy.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkGlob100RulesNoOrderingNoMatch(b *testing.B) { + config := `--- defaults: - match_type: regex -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchRegex), - mappings: mappings{ - "metric100.a": {}, - }, - }, - {}, - { - name: "glob 100 rules, multiple captures", - config: `--- -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateMultipleMatchGlob), - mappings: mappings{ - "metric50.a.b.c.d.e.f.g.h.i.j.k.l": {}, - }, - }, - { - name: "regex 100 rules, multiple captures, average case", - config: `--- + glob_disable_ordering: true +mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob) + mappings := mappings{ + "metricnomatchy.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegex100RulesAverage(b *testing.B) { + config := `--- defaults: match_type: regex -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateMultipleMatchRegex), - mappings: mappings{ - fmt.Sprintf("metric%d.a.b.c.d.e.f.g.h.i.j.k.l", sampleRulesAmount/2): {}, // average match position - }, - }, - {}, - { - name: "glob 10 rules", - config: `--- -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchGlob), - mappings: mappings{ - "metric50.a": {}, - }, - }, - { - name: "regex 10 rules average case", - config: `--- +mappings:` + duplicateRules(100, ruleTemplateSingleMatchRegex) + mappings := mappings{ + "metric50.a": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) + } + } +} + +func BenchmarkRegex100RulesWorst(b *testing.B) { + config := `--- defaults: match_type: regex -mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchRegex), - mappings: mappings{ - fmt.Sprintf("metric%d.a", sampleRulesAmount/2): {}, // average match position - }, - }, +mappings:` + duplicateRules(100, ruleTemplateSingleMatchRegex) + mappings := mappings{ + "metric100.a": {}, } - for i, scenario := range scenarios { - if len(scenario.config) == 0 { - fmt.Println("--------------------------------") - continue + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) } - mapper := MetricMapper{} - err := mapper.InitFromYAMLString(scenario.config) - if err != nil && !scenario.configBad { - t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) + } +} + +func BenchmarkGlob100RulesMultipleCaptures(b *testing.B) { + config := `--- +mappings:` + duplicateRules(100, ruleTemplateMultipleMatchGlob) + mappings := mappings{ + "metric50.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) } - if err == nil && scenario.configBad { - t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) + } +} + +func BenchmarkRegex100RulesMultipleCapturesAverage(b *testing.B) { + config := `--- +defaults: + match_type: regex +mappings:` + duplicateRules(100, ruleTemplateMultipleMatchRegex) + mappings := mappings{ + "metric50.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) } + } +} - var dummyMetricType MetricType = "" - start := time.Now() - for j := 1; j < 10000; j++ { - for metric, _ := range scenario.mappings { - mapper.GetMapping(metric, dummyMetricType) - } +func BenchmarkRegex100RulesMultipleCapturesWorst(b *testing.B) { + config := `--- +defaults: + match_type: regex +mappings:` + duplicateRules(100, ruleTemplateMultipleMatchRegex) + mappings := mappings{ + "metric100.a.b.c.d.e.f.g.h.i.j.k.l": {}, + } + + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(config) + if err != nil { + b.Fatalf("Config load error: %s %s", config, err) + } + + var dummyMetricType MetricType + b.ResetTimer() + for j := 0; j < b.N; j++ { + for metric := range mappings { + mapper.GetMapping(metric, dummyMetricType) } - fmt.Printf("finished 10000 iterations for \"%s\" in %v\n", scenario.name, time.Now().Sub(start)) } }