149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
|
package templating
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Template represents a pattern and tags to map a metric string to a 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
|
||
|
func (t *Template) Apply(line string, joiner string) (string, map[string]string, string, error) {
|
||
|
fields := strings.Split(line, t.separator)
|
||
|
var (
|
||
|
measurement []string
|
||
|
tags = make(map[string][]string)
|
||
|
field []string
|
||
|
)
|
||
|
|
||
|
// Set any default tags
|
||
|
for k, v := range t.defaultTags {
|
||
|
tags[k] = append(tags[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(fields) {
|
||
|
continue
|
||
|
}
|
||
|
if tag == "" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
switch tag {
|
||
|
case "measurement":
|
||
|
measurement = append(measurement, fields[i])
|
||
|
case "field":
|
||
|
field = append(field, fields[i])
|
||
|
case "field*":
|
||
|
field = append(field, fields[i:]...)
|
||
|
break
|
||
|
case "measurement*":
|
||
|
measurement = append(measurement, fields[i:]...)
|
||
|
break
|
||
|
default:
|
||
|
tags[tag] = append(tags[tag], fields[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert to map of strings.
|
||
|
outtags := make(map[string]string)
|
||
|
for k, values := range tags {
|
||
|
outtags[k] = strings.Join(values, joiner)
|
||
|
}
|
||
|
|
||
|
return strings.Join(measurement, joiner), outtags, strings.Join(field, 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 string, 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 {
|
||
|
if len(e[j].filter) == 0 && len(e[k].filter) == 0 {
|
||
|
jlength := len(strings.Split(e[j].template, e[j].separator))
|
||
|
klength := len(strings.Split(e[k].template, e[k].separator))
|
||
|
return jlength < klength
|
||
|
}
|
||
|
if len(e[j].filter) == 0 {
|
||
|
return true
|
||
|
}
|
||
|
if len(e[k].filter) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
jlength := len(strings.Split(e[j].template, e[j].separator))
|
||
|
klength := len(strings.Split(e[k].template, e[k].separator))
|
||
|
return jlength < klength
|
||
|
}
|
||
|
|
||
|
// 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) }
|