1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,88 @@
package templating
import (
"sort"
"strings"
)
const (
// DefaultSeparator is the default separation character to use when separating template parts.
DefaultSeparator = "."
)
// Engine uses a Matcher to retrieve the appropriate template and applies the template
// to the input string
type Engine struct {
joiner string
matcher *matcher
}
// Apply extracts the template fields from the given line and returns the measurement
// name, tags and field name
//
//nolint:revive //function-result-limit conditionally 4 return results allowed
func (e *Engine) Apply(line string) (measurementName string, tags map[string]string, field string, err error) {
return e.matcher.match(line).Apply(line, e.joiner)
}
// NewEngine creates a new templating engine
func NewEngine(joiner string, defaultTemplate *Template, templates []string) (*Engine, error) {
engine := Engine{
joiner: joiner,
matcher: newMatcher(defaultTemplate),
}
templateSpecs := parseTemplateSpecs(templates)
for _, templateSpec := range templateSpecs {
if err := engine.matcher.addSpec(templateSpec); err != nil {
return nil, err
}
}
return &engine, nil
}
func parseTemplateSpecs(templates []string) templateSpecs {
tmplts := templateSpecs{}
for _, pattern := range templates {
tmplt := templateSpec{
separator: DefaultSeparator,
}
// Format is [separator] [filter] <template> [tag1=value1,tag2=value2]
parts := strings.Fields(pattern)
partsLength := len(parts)
if partsLength < 1 {
// ignore
continue
}
if partsLength == 1 {
tmplt.template = pattern
} else if partsLength == 4 {
tmplt.separator = parts[0]
tmplt.filter = parts[1]
tmplt.template = parts[2]
tmplt.tagstring = parts[3]
} else {
hasTagstring := strings.Contains(parts[partsLength-1], "=")
if hasTagstring {
tmplt.tagstring = parts[partsLength-1]
tmplt.template = parts[partsLength-2]
if partsLength == 3 {
tmplt.filter = parts[0]
}
} else {
tmplt.template = parts[partsLength-1]
if partsLength == 2 {
tmplt.filter = parts[0]
} else { // length == 3
tmplt.separator = parts[0]
tmplt.filter = parts[1]
}
}
}
tmplts = append(tmplts, tmplt)
}
sort.Sort(tmplts)
return tmplts
}

View file

@ -0,0 +1,77 @@
package templating
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestEngineAlternateSeparator(t *testing.T) {
defaultTemplate, err := NewDefaultTemplateWithPattern("measurement*")
require.NoError(t, err)
engine, err := NewEngine("_", defaultTemplate, []string{
"/ /*/*/* /measurement/origin/measurement*",
})
require.NoError(t, err)
name, tags, field, err := engine.Apply("/telegraf/host01/cpu")
require.NoError(t, err)
require.Equal(t, "telegraf_cpu", name)
require.Equal(t, map[string]string{
"origin": "host01",
}, tags)
require.Empty(t, field)
}
func TestEngineWithWildcardTemplate(t *testing.T) {
var (
defaultTmpl, err = NewDefaultTemplateWithPattern("measurement*")
templates = []string{
"taskmanagerTask.alarm-detector.Assign.alarmDefinitionId metricsType.process.nodeId.x.alarmDefinitionId.measurement.field rule=1",
"taskmanagerTask.*.*.*.* metricsType.process.nodeId.measurement rule=2",
}
)
require.NoError(t, err)
engine, err := NewEngine(".", defaultTmpl, templates)
require.NoError(t, err)
for _, testCase := range []struct {
line string
measurement string
field string
tags map[string]string
}{
{
line: "taskmanagerTask.alarm-detector.Assign.alarmDefinitionId.timeout_errors.duration.p75",
measurement: "duration",
field: "p75",
tags: map[string]string{
"metricsType": "taskmanagerTask",
"process": "alarm-detector",
"nodeId": "Assign",
"x": "alarmDefinitionId",
"alarmDefinitionId": "timeout_errors",
"rule": "1",
},
},
{
line: "taskmanagerTask.alarm-detector.Assign.numRecordsInPerSecond.m5_rate",
measurement: "numRecordsInPerSecond",
tags: map[string]string{
"metricsType": "taskmanagerTask",
"process": "alarm-detector",
"nodeId": "Assign",
"rule": "2",
},
},
} {
t.Run(testCase.line, func(t *testing.T) {
measurement, tags, field, err := engine.Apply(testCase.line)
require.NoError(t, err)
require.Equal(t, testCase.measurement, measurement)
require.Equal(t, testCase.field, field)
require.Equal(t, testCase.tags, tags)
})
}
}

View file

@ -0,0 +1,58 @@
package templating
import (
"strings"
)
// matcher determines which template should be applied to a given metric
// based on a filter tree.
type matcher struct {
root *node
defaultTemplate *Template
}
// newMatcher creates a new matcher.
func newMatcher(defaultTemplate *Template) *matcher {
return &matcher{
root: &node{},
defaultTemplate: defaultTemplate,
}
}
func (m *matcher) addSpec(tmplt templateSpec) error {
// Parse out the default tags specific to this template
tags := make(map[string]string)
if tmplt.tagstring != "" {
for _, kv := range strings.Split(tmplt.tagstring, ",") {
parts := strings.Split(kv, "=")
tags[parts[0]] = parts[1]
}
}
tmpl, err := NewTemplate(tmplt.separator, tmplt.template, tags)
if err != nil {
return err
}
m.add(tmplt.filter, tmpl)
return nil
}
// add inserts the template in the filter tree based the given filter
func (m *matcher) add(filter string, template *Template) {
if filter == "" {
m.defaultTemplate = template
m.root.separator = template.separator
return
}
m.root.insert(filter, template)
}
// match returns the template that matches the given measurement line.
// If no template matches, the default template is returned.
func (m *matcher) match(line string) *Template {
tmpl := m.root.search(line)
if tmpl != nil {
return tmpl
}
return m.defaultTemplate
}

136
internal/templating/node.go Normal file
View file

@ -0,0 +1,136 @@
package templating
import (
"sort"
"strings"
)
// node is an item in a sorted k-ary tree of filter parts. Each child is sorted by its part value.
// The special value of "*", is always sorted last.
type node struct {
separator string
value string
children nodes
template *Template
}
// insert inserts the given string template into the tree. The filter string is separated
// on the template separator and each part is used as the path in the tree.
func (n *node) insert(filter string, template *Template) {
n.separator = template.separator
n.recursiveInsert(strings.Split(filter, n.separator), template)
}
// recursiveInsert does the actual recursive insertion
func (n *node) recursiveInsert(values []string, template *Template) {
// Add the end, set the template
if len(values) == 0 {
n.template = template
return
}
// See if the current element already exists in the tree. If so, insert the
// into that sub-tree
for _, v := range n.children {
if v.value == values[0] {
v.recursiveInsert(values[1:], template)
return
}
}
// New element, add it to the tree and sort the children
newNode := &node{value: values[0]}
n.children = append(n.children, newNode)
sort.Sort(&n.children)
// Now insert the rest of the tree into the new element
newNode.recursiveInsert(values[1:], template)
}
// search searches for a template matching the input string
func (n *node) search(line string) *Template {
separator := n.separator
return n.recursiveSearch(strings.Split(line, separator))
}
// recursiveSearch performs the actual recursive search
func (n *node) recursiveSearch(lineParts []string) *Template {
// nothing to search
if len(lineParts) == 0 || len(n.children) == 0 {
return n.template
}
var (
hasWildcard bool
length = len(n.children)
)
// exclude last child from search if it is a wildcard. sort.Search expects
// a lexicographically sorted set of children and we have artificially sorted
// wildcards to the end of the child set
// wildcards will be searched separately if no exact match is found
if hasWildcard = n.children[length-1].value == "*"; hasWildcard {
length--
}
i := sort.Search(length, func(i int) bool {
return n.children[i].value >= lineParts[0]
})
// given an exact match is found within children set
if i < length && n.children[i].value == lineParts[0] {
// descend into the matching node
if tmpl := n.children[i].recursiveSearch(lineParts[1:]); tmpl != nil {
// given a template is found return it
return tmpl
}
}
// given no template is found and the last child is a wildcard
if hasWildcard {
// also search the wildcard child node
return n.children[length].recursiveSearch(lineParts[1:])
}
// fallback to returning template at this node
return n.template
}
// nodes is simply an array of nodes implementing the sorting interface.
type nodes []*node
// Less returns a boolean indicating whether the filter at position j
// is less than the filter at position k. Filters are order by string
// comparison of each component parts. A wildcard value "*" is never
// less than a non-wildcard value.
//
// For example, the filters:
//
// "*.*"
// "servers.*"
// "servers.localhost"
// "*.localhost"
//
// Would be sorted as:
//
// "servers.localhost"
// "servers.*"
// "*.localhost"
// "*.*"
func (n *nodes) Less(j, k int) bool {
if (*n)[j].value == "*" && (*n)[k].value != "*" {
return false
}
if (*n)[j].value != "*" && (*n)[k].value == "*" {
return true
}
return (*n)[j].value < (*n)[k].value
}
// Swap swaps two elements of the array
func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] }
// Len returns the length of the array
func (n *nodes) Len() int { return len(*n) }

View file

@ -0,0 +1,142 @@
package templating
import (
"fmt"
"strings"
)
// Template represents a pattern and tags to map a metric string to an influxdb Point
type Template struct {
separator string
parts []string
defaultTags map[string]string
greedyField bool
greedyMeasurement bool
}
// Apply extracts the template fields from the given line and returns the measurement
// name, tags and field name
//
//nolint:revive //function-result-limit conditionally 4 return results allowed
func (t *Template) Apply(line string, joiner string) (measurementName string, tags map[string]string, field string, err error) {
allFields := strings.Split(line, t.separator)
var (
measurements []string
tagsMap = make(map[string][]string)
fields []string
)
// Set any default tags
for k, v := range t.defaultTags {
tagsMap[k] = append(tagsMap[k], v)
}
// See if an invalid combination has been specified in the template:
for _, tag := range t.parts {
if tag == "measurement*" {
t.greedyMeasurement = true
} else if tag == "field*" {
t.greedyField = true
}
}
if t.greedyField && t.greedyMeasurement {
return "", nil, "",
fmt.Errorf("either 'field*' or 'measurement*' can be used in each "+
"template (but not both together): %q",
strings.Join(t.parts, joiner))
}
for i, tag := range t.parts {
if i >= len(allFields) {
continue
}
if tag == "" {
continue
}
switch tag {
case "measurement":
measurements = append(measurements, allFields[i])
case "field":
fields = append(fields, allFields[i])
case "field*":
fields = append(fields, allFields[i:]...)
case "measurement*":
measurements = append(measurements, allFields[i:]...)
default:
tagsMap[tag] = append(tagsMap[tag], allFields[i])
}
}
// Convert to map of strings.
tags = make(map[string]string)
for k, values := range tagsMap {
tags[k] = strings.Join(values, joiner)
}
return strings.Join(measurements, joiner), tags, strings.Join(fields, joiner), nil
}
func NewDefaultTemplateWithPattern(pattern string) (*Template, error) {
return NewTemplate(DefaultSeparator, pattern, nil)
}
// NewTemplate returns a new template ensuring it has a measurement specified.
func NewTemplate(separator, pattern string, defaultTags map[string]string) (*Template, error) {
parts := strings.Split(pattern, separator)
hasMeasurement := false
template := &Template{
separator: separator,
parts: parts,
defaultTags: defaultTags,
}
for _, part := range parts {
if strings.HasPrefix(part, "measurement") {
hasMeasurement = true
}
if part == "measurement*" {
template.greedyMeasurement = true
} else if part == "field*" {
template.greedyField = true
}
}
if !hasMeasurement {
return nil, fmt.Errorf("no measurement specified for template. %q", pattern)
}
return template, nil
}
// templateSpec is a template string split in its constituent parts
type templateSpec struct {
separator string
filter string
template string
tagstring string
}
// templateSpecs is simply an array of template specs implementing the sorting interface
type templateSpecs []templateSpec
// Less reports whether the element with
// index j should sort before the element with index k.
func (e templateSpecs) Less(j, k int) bool {
jlen := len(e[j].filter)
klen := len(e[k].filter)
if jlen == 0 && klen != 0 {
return true
}
if klen == 0 && jlen != 0 {
return false
}
return strings.Count(e[j].template, e[j].separator) <
strings.Count(e[k].template, e[k].separator)
}
// Swap swaps the elements with indexes i and j.
func (e templateSpecs) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
// Len is the number of elements in the collection.
func (e templateSpecs) Len() int { return len(e) }

View file

@ -0,0 +1,14 @@
package templating
import "testing"
func BenchmarkTemplateLess(b *testing.B) {
a := templateSpec{
template: "aa|bb|cc|dd|ee|ff",
separator: "|",
}
specs := templateSpecs{a, a}
for i := 0; i < b.N; i++ {
specs.Less(0, 1)
}
}