tidy up and use go benchmark

Signed-off-by: Wangchong Zhou <fffonion@gmail.com>
This commit is contained in:
Wangchong Zhou 2018-09-28 12:55:53 -07:00
parent 6d709d52c1
commit 5262b2904c
No known key found for this signature in database
GPG key ID: B607274584E8D5E5
6 changed files with 793 additions and 517 deletions

View file

@ -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() 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() 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() 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) log.AddFlags(kingpin.CommandLine)
@ -203,7 +203,7 @@ func main() {
if *dumpFSMPath != "" { if *dumpFSMPath != "" {
err := dumpFSM(mapper, *dumpFSMPath) err := dumpFSM(mapper, *dumpFSMPath)
if err != nil { if err != nil {
log.Fatal("Error dumpping FSM:", err) log.Fatal("Error dumping FSM:", err)
} }
} }
go watchConfig(*mappingConfig, mapper) go watchConfig(*mappingConfig, mapper)

48
pkg/mapper/fsm/dump.go Normal file
View file

@ -0,0 +1,48 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fsm
import (
"fmt"
"io"
)
// 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("}"))
}

View file

@ -15,10 +15,15 @@ package fsm
import ( import (
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
var (
templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`)
)
type templateFormatter struct { type templateFormatter struct {
captureIndexes []int captureIndexes []int
captureCount int captureCount int
@ -57,7 +62,7 @@ func (formatter *templateFormatter) format(captures map[int]string) string {
if formatter.captureCount == 0 { if formatter.captureCount == 0 {
// no label substitution, keep as it is // no label substitution, keep as it is
return formatter.fmtString return formatter.fmtString
} else { }
indexes := formatter.captureIndexes indexes := formatter.captureIndexes
vargs := make([]interface{}, formatter.captureCount) vargs := make([]interface{}, formatter.captureCount)
for i, idx := range indexes { for i, idx := range indexes {
@ -65,4 +70,3 @@ func (formatter *templateFormatter) format(captures map[int]string) string {
} }
return fmt.Sprintf(formatter.fmtString, vargs...) return fmt.Sprintf(formatter.fmtString, vargs...)
} }
}

View file

@ -14,8 +14,6 @@
package fsm package fsm
import ( import (
"fmt"
"io"
"regexp" "regexp"
"strings" "strings"
@ -23,10 +21,6 @@ import (
"github.com/prometheus/common/log" "github.com/prometheus/common/log"
) )
var (
templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`)
)
type mappingState struct { type mappingState struct {
transitions map[string]*mappingState transitions map[string]*mappingState
minRemainingLength int minRemainingLength int
@ -49,17 +43,17 @@ type fsmBacktrackStackCursor struct {
type FSM struct { type FSM struct {
root *mappingState root *mappingState
needsBacktracking bool
metricTypes []string metricTypes []string
disableOrdering bool
statesCount int statesCount int
BacktrackingNeeded bool
OrderingDisabled bool
} }
// NewFSM creates a new FSM instance // 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{} fsm := FSM{}
root := &mappingState{} root := &mappingState{}
root.transitions = make(map[string]*mappingState, 3) root.transitions = make(map[string]*mappingState, len(metricTypes))
metricTypes = append(metricTypes, "") metricTypes = append(metricTypes, "")
for _, field := range metricTypes { for _, field := range metricTypes {
@ -67,7 +61,7 @@ func NewFSM(metricTypes []string, maxPossibleTransitions int, disableOrdering bo
(*state).transitions = make(map[string]*mappingState, maxPossibleTransitions) (*state).transitions = make(map[string]*mappingState, maxPossibleTransitions)
root.transitions[string(field)] = state root.transitions[string(field)] = state
} }
fsm.disableOrdering = disableOrdering fsm.OrderingDisabled = orderingDisabled
fsm.metricTypes = metricTypes fsm.metricTypes = metricTypes
fsm.statesCount = 0 fsm.statesCount = 0
fsm.root = root fsm.root = root
@ -136,38 +130,112 @@ 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 // GetMapping implements a mapping algorithm for Glob pattern
func (f *FSM) DumpFSM(w io.Writer) { func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (interface{}, string, prometheus.Labels, bool) {
idx := 0 matchFields := strings.Split(statsdMetric, ".")
states := make(map[int]*mappingState) currentState := f.root.transitions[statsdMetricType]
states[idx] = f.root
w.Write([]byte("digraph g {\n")) // the cursor/pointer in the backtrack stack implemented as a double-linked list
w.Write([]byte("rankdir=LR\n")) // make it vertical var backtrackCursor *fsmBacktrackStackCursor
w.Write([]byte("node [ label=\"\",style=filled,fillcolor=white,shape=circle ]\n")) // remove label of node resumeFromBacktrack := false
for idx < len(states) { // the return variable
for field, transition := range states[idx].transitions { var finalState *mappingState
states[len(states)] = transition
w.Write([]byte(fmt.Sprintf("%d -> %d [label = \"%s\"];\n", idx, len(states)-1, field))) captures := make(map[int]string, len(matchFields))
if idx == 0 { // keep track of captured group so we don't need to do append() on captures
// color for metric types captureIdx := 0
w.Write([]byte(fmt.Sprintf("%d [color=\"#D6B656\",fillcolor=\"#FFF2CC\"];\n", len(states)-1))) filedsCount := len(matchFields)
} else if transition.transitions == nil || len(transition.transitions) == 0 { i := 0
// color for end state var state *mappingState
w.Write([]byte(fmt.Sprintf("%d [color=\"#82B366\",fillcolor=\"#D5E8D4\"];\n", len(states)-1))) for { // the loop for backtracking
for { // the loop for a single "depth only" search
var present bool
// if we resume from backtrack, we should skip this branch in this case
// since the state that were saved at the end of this branch
if !resumeFromBacktrack {
if len(currentState.transitions) > 0 {
field := matchFields[i]
state, present = currentState.transitions[field]
fieldsLeft := filedsCount - i - 1
// also compare length upfront to avoid unnecessary loop or backtrack
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
state, present = currentState.transitions["*"]
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
break
} else {
captures[captureIdx] = field
captureIdx++
}
} 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 {
} else {
// push to backtracking stack
newCursor := fsmBacktrackStackCursor{prev: backtrackCursor, state: altState,
fieldIndex: i,
captureIndex: captureIdx, currentCapture: field,
}
// if this is not the first time, connect to the previous cursor
if backtrackCursor != nil {
backtrackCursor.next = &newCursor
}
backtrackCursor = &newCursor
} }
} }
idx++ } else {
// no more transitions for this state
break
} }
// color for start state } // backtrack will resume from here
w.Write([]byte(fmt.Sprintf("0 [color=\"#a94442\",fillcolor=\"#f2dede\"];\n")))
w.Write([]byte("}")) // do we reach a final state?
if state.result != nil && i == filedsCount-1 {
if f.OrderingDisabled {
finalState = state
return formatLabels(finalState, captures)
} else if finalState == nil || finalState.resultPriority > state.resultPriority {
// if we care about ordering, try to find a result with highest prioity
finalState = state
}
break
} }
// TestIfNeedBacktracking test if backtrack is needed for current FSM i++
func (f *FSM) TestIfNeedBacktracking(mappings []string) bool { if i >= filedsCount {
needBacktrack := false break
}
resumeFromBacktrack = false
currentState = state
}
if backtrackCursor == nil {
// if we are not doing backtracking or all path has been travesaled
break
} else {
// pop one from stack
state = backtrackCursor.state
currentState = state
i = backtrackCursor.fieldIndex
captureIdx = backtrackCursor.captureIndex + 1
// put the * capture back
captures[captureIdx-1] = backtrackCursor.currentCapture
backtrackCursor = backtrackCursor.prev
if backtrackCursor != nil {
// deref for GC
backtrackCursor.next = nil
}
resumeFromBacktrack = true
}
}
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 // A has * in rules there's other transisitions at the same state
// this makes A the cause of backtracking // this makes A the cause of backtracking
ruleByLength := make(map[int][]string) ruleByLength := make(map[int][]string)
@ -221,9 +289,9 @@ func (f *FSM) TestIfNeedBacktracking(mappings []string) bool {
for i2, r2 := range rules { for i2, r2 := range rules {
if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 { if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 {
// log if we care about ordering and the superset occurs before // log if we care about ordering and the superset occurs before
if !f.disableOrdering && i1 < i2 { if !orderingDisabled && i1 < i2 {
log.Warnf("match \"%s\" is a super set of match \"%s\" but in a lower order, "+ 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) "the first will never be matched", r1, r2)
} }
currentRuleNeedBacktrack = false currentRuleNeedBacktrack = false
} }
@ -242,8 +310,8 @@ func (f *FSM) TestIfNeedBacktracking(mappings []string) bool {
if currentRuleNeedBacktrack { if currentRuleNeedBacktrack {
log.Warnf("backtracking required because of match \"%s\", "+ log.Warnf("backtracking required because of match \"%s\", "+
"matching performance may be degraded\n", r1) "matching performance may be degraded", r1)
needBacktrack = true backtrackingNeeded = true
} }
} }
} }
@ -252,112 +320,8 @@ func (f *FSM) TestIfNeedBacktracking(mappings []string) bool {
// since transistions are stored in (unordered) map // since transistions are stored in (unordered) map
// note: don't move this branch to the beginning of this function // note: don't move this branch to the beginning of this function
// since we need logs for superset rules // since we need logs for superset rules
f.needsBacktracking = !f.disableOrdering || needBacktrack
return f.needsBacktracking return !orderingDisabled || backtrackingNeeded
}
// 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, ".")
currentState := f.root.transitions[statsdMetricType]
// the cursor/pointer in the backtrack stack implemented as a double-linked list
var backtrackCursor *fsmBacktrackStackCursor
resumeFromBacktrack := false
// the return variable
var finalState *mappingState
captures := make(map[int]string, len(matchFields))
// keep track of captured group so we don't need to do append() on captures
captureIdx := 0
filedsCount := len(matchFields)
i := 0
var state *mappingState
for { // the loop for backtracking
for { // the loop for a single "depth only" search
var present bool
// if we resume from backtrack, we should skip this branch in this case
// since the state that were saved at the end of this branch
if !resumeFromBacktrack {
if len(currentState.transitions) > 0 {
field := matchFields[i]
state, present = currentState.transitions[field]
fieldsLeft := filedsCount - i - 1
// also compare length upfront to avoid unnecessary loop or backtrack
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
state, present = currentState.transitions["*"]
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
break
} else {
captures[captureIdx] = field
captureIdx++
}
} else if f.needsBacktracking {
// if backtracking is needed, also check for alternative transition
altState, present := currentState.transitions["*"]
if !present || fieldsLeft > altState.maxRemainingLength || fieldsLeft < altState.minRemainingLength {
} else {
// push to backtracking stack
newCursor := fsmBacktrackStackCursor{prev: backtrackCursor, state: altState,
fieldIndex: i,
captureIndex: captureIdx, currentCapture: field,
}
// if this is not the first time, connect to the previous cursor
if backtrackCursor != nil {
backtrackCursor.next = &newCursor
}
backtrackCursor = &newCursor
}
}
} else {
// no more transitions for this state
break
}
} // backtrack will resume from here
// do we reach a final state?
if state.result != nil && i == filedsCount-1 {
if f.disableOrdering {
finalState = state
return formatLabels(finalState, captures)
} else if finalState == nil || finalState.resultPriority > state.resultPriority {
// if we care about ordering, try to find a result with highest prioity
finalState = state
}
break
}
i++
if i >= filedsCount {
break
}
resumeFromBacktrack = false
currentState = state
}
if backtrackCursor == nil {
// if we are not doing backtracking or all path has been travesaled
break
} else {
// pop one from stack
state = backtrackCursor.state
currentState = state
i = backtrackCursor.fieldIndex
captureIdx = backtrackCursor.captureIndex + 1
// put the * capture back
captures[captureIdx-1] = backtrackCursor.currentCapture
backtrackCursor = backtrackCursor.prev
if backtrackCursor != nil {
// deref for GC
backtrackCursor.next = nil
}
resumeFromBacktrack = true
}
}
return formatLabels(finalState, captures)
} }
func formatLabels(finalState *mappingState, captures map[int]string) (interface{}, string, prometheus.Labels, bool) { func formatLabels(finalState *mappingState, captures map[int]string) (interface{}, string, prometheus.Labels, bool) {

View file

@ -31,8 +31,6 @@ var (
metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`) metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`)
metricNameRE = regexp.MustCompile(`^([a-zA-Z_]|` + templateReplaceRE + `)([a-zA-Z0-9_]|` + templateReplaceRE + `)*$`) metricNameRE = regexp.MustCompile(`^([a-zA-Z_]|` + templateReplaceRE + `)([a-zA-Z0-9_]|` + templateReplaceRE + `)*$`)
labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`) labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`)
templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`)
) )
type mapperConfigDefaults struct { type mapperConfigDefaults struct {
@ -81,20 +79,6 @@ var defaultQuantiles = []metricObjective{
{Quantile: 0.99, Error: 0.001}, {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 { func (m *MetricMapper) InitFromYAMLString(fileContents string) error {
var n MetricMapper var n MetricMapper
@ -191,7 +175,7 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string) error {
mappings = append(mappings, mapping.Match) mappings = append(mappings, mapping.Match)
} }
} }
n.FSM.TestIfNeedBacktracking(mappings) n.FSM.BacktrackingNeeded = fsm.TestIfNeedBacktracking(mappings, n.FSM.OrderingDisabled)
m.FSM = n.FSM m.FSM = n.FSM
m.doRegex = n.doRegex m.doRegex = n.doRegex

View file

@ -16,7 +16,6 @@ package mapper
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
) )
type mappings map[string]struct { type mappings map[string]struct {
@ -26,6 +25,35 @@ type mappings map[string]struct {
notPresent bool 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) { func TestMetricMapperYAML(t *testing.T) {
scenarios := []struct { scenarios := []struct {
config string config string
@ -615,46 +643,8 @@ func duplicateRules(count int, template string) string {
} }
return rules return rules
} }
func BenchmarkGlob(b *testing.B) {
func TestPerformance(t *testing.T) { config := `---
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: `---
mappings: mappings:
- match: test.dispatcher.*.*.succeeded - match: test.dispatcher.*.*.succeeded
name: "dispatch_events" name: "dispatch_events"
@ -692,8 +682,8 @@ mappings:
second: "$2" second: "$2"
third: "$3" third: "$3"
job: "-" job: "-"
`, `
mappings: mappings{ mappings := mappings{
"test.dispatcher.FooProcessor.send.succeeded": { "test.dispatcher.FooProcessor.send.succeeded": {
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
@ -707,11 +697,25 @@ mappings:
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
"foo.bar.baz": {}, "foo.bar.baz": {},
}, }
},
{ mapper := MetricMapper{}
name: "glob no ordering", err := mapper.InitFromYAMLString(config)
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: defaults:
glob_disable_ordering: true glob_disable_ordering: true
mappings: mappings:
@ -751,8 +755,8 @@ mappings:
second: "$2" second: "$2"
third: "$3" third: "$3"
job: "-" job: "-"
`, `
mappings: mappings{ mappings := mappings{
"test.dispatcher.FooProcessor.send.succeeded": { "test.dispatcher.FooProcessor.send.succeeded": {
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
@ -766,11 +770,25 @@ mappings:
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
"foo.bar.baz": {}, "foo.bar.baz": {},
}, }
},
{ mapper := MetricMapper{}
name: "glob with backtracking (no ordering implicated)", err := mapper.InitFromYAMLString(config)
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: defaults:
glob_disable_ordering: true glob_disable_ordering: true
mappings: mappings:
@ -817,8 +835,8 @@ mappings:
second: "$2" second: "$2"
third: "$3" third: "$3"
job: "-" job: "-"
`, `
mappings: mappings{ mappings := mappings{
"test.dispatcher.FooProcessor.send.succeeded": { "test.dispatcher.FooProcessor.send.succeeded": {
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
@ -832,11 +850,25 @@ mappings:
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
"foo.bar.baz": {}, "foo.bar.baz": {},
}, }
},
{ mapper := MetricMapper{}
name: "regex", err := mapper.InitFromYAMLString(config)
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: defaults:
match_type: regex match_type: regex
mappings: mappings:
@ -876,8 +908,8 @@ mappings:
second: "$2" second: "$2"
third: "$3" third: "$3"
job: "-" job: "-"
`, `
mappings: mappings{ mappings := mappings{
"test.dispatcher.FooProcessor.send.succeeded": { "test.dispatcher.FooProcessor.send.succeeded": {
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
@ -891,252 +923,496 @@ mappings:
name: "ignored-in-test-dont-set-me", name: "ignored-in-test-dont-set-me",
}, },
"foo.bar.baz": {}, "foo.bar.baz": {},
}, }
},
{}, mapper := MetricMapper{}
{ err := mapper.InitFromYAMLString(config)
name: "glob single match ", if err != nil {
config: `--- 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: mappings:
- match: metric.* - match: metric.*
name: "metric_one" name: "metric_one"
labels: labels:
name: "$1" name: "$1"
`, `
mappings: mappings{ mappings := mappings{
"metric.aaa": {}, "metric.aaa": {},
"metric.bbb": {}, "metric.bbb": {},
}, }
},
{ mapper := MetricMapper{}
name: "regex single match", err := mapper.InitFromYAMLString(config)
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: mappings:
- match: metric\.([^.]*) - match: metric\.([^.]*)
name: "metric_one" name: "metric_one"
match_type: regex match_type: regex
labels: labels:
name: "$1" name: "$1"
`, `
mappings: mappings{ mappings := mappings{
"metric.aaa": {}, "metric.aaa": {},
"metric.bbb": {}, "metric.bbb": {},
},
},
{},
{
name: "glob multiple captures",
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:
- 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:
- 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: `---
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: `---
mappings:
- match: metric.*.*.*.*.*.*.*.*.*.*.*.*
name: "metric_multi"
labels:
label1: "$1"
label2: "$2"
label3: "$3"
label4: "$4"
label5: "$5"
label6: "$6"
label7: "$7"
label8: "$8"
label9: "$9"
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:
- match: metric\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)\.([^.]*)
name: "metric_multi"
match_type: regex
labels:
label1: "$1"
label2: "$2"
label3: "$3"
label4: "$4"
label5: "$5"
label6: "$6"
label7: "$7"
label8: "$8"
label9: "$9"
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: `---
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: `---
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: `---
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: `---
defaults:
match_type: regex
mappings:` + duplicateRules(sampleRulesAmount, ruleTemplateSingleMatchRegex),
mappings: mappings{
fmt.Sprintf("metric%d.a", sampleRulesAmount/2): {}, // average match position
},
},
} }
for i, scenario := range scenarios {
if len(scenario.config) == 0 {
fmt.Println("--------------------------------")
continue
}
mapper := MetricMapper{} mapper := MetricMapper{}
err := mapper.InitFromYAMLString(scenario.config) err := mapper.InitFromYAMLString(config)
if err != nil && !scenario.configBad { if err != nil {
t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) b.Fatalf("Config load error: %s %s", config, err)
}
if err == nil && scenario.configBad {
t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config)
} }
var dummyMetricType MetricType = "" var dummyMetricType MetricType
start := time.Now() b.ResetTimer()
for j := 1; j < 10000; j++ { for j := 0; j < b.N; j++ {
for metric, _ := range scenario.mappings { for metric := range mappings {
mapper.GetMapping(metric, dummyMetricType) mapper.GetMapping(metric, dummyMetricType)
} }
} }
fmt.Printf("finished 10000 iterations for \"%s\" in %v\n", scenario.name, time.Now().Sub(start)) }
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": {},
}
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": {},
}
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: "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: "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"
labels:
label1: "$1"
label2: "$2"
label3: "$3"
label4: "$4"
label5: "$5"
label6: "$6"
label7: "$7"
label8: "$8"
label9: "$9"
label10: "$10"
label11: "$11"
label12: "$12"
`
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"
match_type: regex
labels:
label1: "$1"
label2: "$2"
label3: "$3"
label4: "$4"
label5: "$5"
label6: "$6"
label7: "$7"
label8: "$8"
label9: "$9"
label10: "$10"
label11: "$11"
label12: "$12"
`
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(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:
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(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(100, ruleTemplateSingleMatchRegex)
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 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)
}
}
}
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)
}
}
}
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)
}
} }
} }