2015-06-22 20:53:51 +00:00
|
|
|
package graphite
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math"
|
2015-06-23 16:07:47 +00:00
|
|
|
"sort"
|
2015-06-22 20:53:51 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdb/influxdb/tsdb"
|
|
|
|
)
|
|
|
|
|
2015-06-23 05:28:52 +00:00
|
|
|
var defaultTemplate *template
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
var err error
|
2015-06-23 19:49:46 +00:00
|
|
|
defaultTemplate, err = NewTemplate("measurement*", nil)
|
2015-06-23 05:28:52 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-22 20:53:51 +00:00
|
|
|
// Parser encapulates a Graphite Parser.
|
|
|
|
type Parser struct {
|
2015-06-23 05:28:52 +00:00
|
|
|
matcher *matcher
|
2015-06-23 19:35:39 +00:00
|
|
|
tags tsdb.Tags
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewParser returns a GraphiteParser instance.
|
2015-06-23 19:35:39 +00:00
|
|
|
func NewParser(templates []string, defaultTags tsdb.Tags) (*Parser, error) {
|
2015-06-23 16:07:47 +00:00
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
matcher := newMatcher()
|
2015-06-23 16:07:47 +00:00
|
|
|
matcher.AddDefaultTemplate(defaultTemplate)
|
2015-06-22 22:47:03 +00:00
|
|
|
|
2015-06-23 05:28:52 +00:00
|
|
|
for _, pattern := range templates {
|
2015-06-23 16:07:47 +00:00
|
|
|
|
|
|
|
template := pattern
|
|
|
|
filter := ""
|
2015-06-24 03:29:11 +00:00
|
|
|
// Format is [filter] <template> [tag1=value1,tag2=value2]
|
2015-06-23 21:27:05 +00:00
|
|
|
parts := strings.Fields(pattern)
|
2015-06-23 16:07:47 +00:00
|
|
|
if len(parts) >= 2 {
|
|
|
|
filter = parts[0]
|
|
|
|
template = parts[1]
|
|
|
|
}
|
|
|
|
|
2015-06-23 19:49:46 +00:00
|
|
|
tags := tsdb.Tags{}
|
|
|
|
if strings.Contains(parts[len(parts)-1], "=") {
|
|
|
|
tagStrs := strings.Split(parts[len(parts)-1], ",")
|
|
|
|
for _, kv := range tagStrs {
|
|
|
|
parts := strings.Split(kv, "=")
|
|
|
|
tags[parts[0]] = parts[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpl, err := NewTemplate(template, tags)
|
2015-06-23 05:28:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-06-23 16:07:47 +00:00
|
|
|
matcher.Add(filter, tmpl)
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
2015-06-23 19:35:39 +00:00
|
|
|
return &Parser{matcher: matcher, tags: defaultTags}, nil
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse performs Graphite parsing of a single line.
|
|
|
|
func (p *Parser) Parse(line string) (tsdb.Point, error) {
|
|
|
|
// Break into 3 fields (name, value, timestamp).
|
|
|
|
fields := strings.Fields(line)
|
|
|
|
if len(fields) != 3 {
|
|
|
|
return nil, fmt.Errorf("received %q which doesn't have three fields", line)
|
|
|
|
}
|
|
|
|
|
|
|
|
// decode the name and tags
|
2015-06-23 06:19:41 +00:00
|
|
|
matcher := p.matcher.Match(fields[0])
|
|
|
|
name, tags := matcher.Apply(fields[0])
|
2015-06-22 22:47:03 +00:00
|
|
|
if name == "" {
|
|
|
|
return nil, fmt.Errorf("unable to parse measurement name from %s", line)
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse value.
|
|
|
|
v, err := strconv.ParseFloat(fields[1], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("field \"%s\" value: %s", fields[0], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldValues := make(map[string]interface{})
|
|
|
|
fieldValues["value"] = v
|
|
|
|
|
|
|
|
// Parse timestamp.
|
|
|
|
unixTime, err := strconv.ParseFloat(fields[2], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("field \"%s\" time: %s", fields[0], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we have fractional seconds
|
|
|
|
timestamp := time.Unix(int64(unixTime), int64((unixTime-math.Floor(unixTime))*float64(time.Second)))
|
|
|
|
|
2015-06-23 19:35:39 +00:00
|
|
|
for k, v := range p.tags {
|
|
|
|
if _, ok := tags[k]; !ok {
|
|
|
|
tags[k] = v
|
|
|
|
}
|
|
|
|
}
|
2015-06-22 20:53:51 +00:00
|
|
|
point := tsdb.NewPoint(name, tags, fieldValues, timestamp)
|
|
|
|
|
|
|
|
return point, nil
|
|
|
|
}
|
|
|
|
|
2015-06-23 05:12:02 +00:00
|
|
|
type template struct {
|
2015-06-22 22:47:03 +00:00
|
|
|
tags []string
|
2015-06-23 19:49:46 +00:00
|
|
|
defaultTags tsdb.Tags
|
2015-06-22 22:47:03 +00:00
|
|
|
measurementPos int
|
|
|
|
greedyMeasurement bool
|
|
|
|
}
|
|
|
|
|
2015-06-23 19:49:46 +00:00
|
|
|
func NewTemplate(pattern string, defaultTags tsdb.Tags) (*template, error) {
|
2015-06-23 05:12:02 +00:00
|
|
|
tags := strings.Split(pattern, ".")
|
2015-06-23 19:49:46 +00:00
|
|
|
template := &template{tags: tags, measurementPos: -1, defaultTags: defaultTags}
|
2015-06-22 22:47:03 +00:00
|
|
|
|
|
|
|
for i, tag := range tags {
|
|
|
|
if strings.HasPrefix(tag, "measurement") {
|
2015-06-23 05:12:02 +00:00
|
|
|
template.measurementPos = i
|
2015-06-22 22:47:03 +00:00
|
|
|
}
|
|
|
|
if tag == "measurement*" {
|
2015-06-23 05:12:02 +00:00
|
|
|
template.greedyMeasurement = true
|
2015-06-22 22:47:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-23 05:12:02 +00:00
|
|
|
if template.measurementPos == -1 {
|
|
|
|
return nil, fmt.Errorf("no measurement specified for template. %q", pattern)
|
2015-06-22 22:47:03 +00:00
|
|
|
}
|
|
|
|
|
2015-06-23 05:12:02 +00:00
|
|
|
return template, nil
|
2015-06-22 22:47:03 +00:00
|
|
|
}
|
|
|
|
|
2015-06-23 05:28:52 +00:00
|
|
|
func (t *template) Apply(line string) (string, map[string]string) {
|
2015-06-22 22:47:03 +00:00
|
|
|
fields := strings.Split(line, ".")
|
2015-06-22 20:53:51 +00:00
|
|
|
var (
|
|
|
|
measurement string
|
|
|
|
tags = make(map[string]string)
|
|
|
|
)
|
|
|
|
|
2015-06-23 19:49:46 +00:00
|
|
|
// Set any default tags
|
|
|
|
for k, v := range t.defaultTags {
|
|
|
|
tags[k] = v
|
|
|
|
}
|
|
|
|
|
2015-06-23 05:12:02 +00:00
|
|
|
for i, tag := range t.tags {
|
2015-06-22 22:47:03 +00:00
|
|
|
if i >= len(fields) {
|
|
|
|
continue
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2015-06-23 05:12:02 +00:00
|
|
|
if i == t.measurementPos {
|
2015-06-22 22:47:03 +00:00
|
|
|
measurement = fields[i]
|
2015-06-23 05:12:02 +00:00
|
|
|
if t.greedyMeasurement {
|
2015-06-22 22:47:03 +00:00
|
|
|
measurement = strings.Join(fields[i:len(fields)], ".")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tag == "measurement" {
|
2015-06-22 20:53:51 +00:00
|
|
|
measurement = fields[i]
|
2015-06-22 22:47:03 +00:00
|
|
|
} else if tag == "measurement*" {
|
2015-06-22 21:39:28 +00:00
|
|
|
measurement = strings.Join(fields[i:len(fields)], ".")
|
|
|
|
break
|
2015-06-22 23:11:27 +00:00
|
|
|
} else if tag != "" {
|
2015-06-22 22:47:03 +00:00
|
|
|
tags[tag] = fields[i]
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:47:03 +00:00
|
|
|
return measurement, tags
|
2015-06-22 20:53:51 +00:00
|
|
|
}
|
2015-06-23 05:28:52 +00:00
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
// matcher
|
2015-06-23 05:28:52 +00:00
|
|
|
type matcher struct {
|
2015-06-24 04:33:11 +00:00
|
|
|
root *node
|
2015-06-23 16:07:47 +00:00
|
|
|
defaultTemplate *template
|
|
|
|
}
|
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
func newMatcher() *matcher {
|
|
|
|
return &matcher{
|
|
|
|
root: &node{},
|
|
|
|
}
|
|
|
|
}
|
2015-06-23 16:07:47 +00:00
|
|
|
func (m *matcher) Add(match string, template *template) {
|
|
|
|
if match == "" {
|
|
|
|
m.AddDefaultTemplate(template)
|
|
|
|
return
|
|
|
|
}
|
2015-06-24 04:33:11 +00:00
|
|
|
m.root.Insert(match, template)
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *matcher) AddDefaultTemplate(template *template) {
|
|
|
|
m.defaultTemplate = template
|
2015-06-23 05:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *matcher) Match(line string) *template {
|
2015-06-24 04:33:11 +00:00
|
|
|
tmpl := m.root.Search(line)
|
|
|
|
if tmpl != nil {
|
|
|
|
return tmpl
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.defaultTemplate
|
|
|
|
}
|
2015-06-23 16:07:47 +00:00
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
type node struct {
|
|
|
|
value string
|
|
|
|
children nodes
|
|
|
|
template *template
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *node) insert(values []string, template *template) {
|
|
|
|
if len(values) == 0 {
|
|
|
|
return
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
for _, v := range n.children {
|
|
|
|
if v.value == values[0] {
|
|
|
|
v.insert(values[1:], template)
|
|
|
|
return
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-24 04:33:11 +00:00
|
|
|
newNode := &node{value: values[0], template: template}
|
|
|
|
n.children = append(n.children, newNode)
|
|
|
|
sort.Sort(&n.children)
|
|
|
|
newNode.insert(values[1:], template)
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
func (n *node) Insert(match string, template *template) {
|
|
|
|
n.insert(strings.Split(match, "."), template)
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
func (n *node) search(lineParts []string) *template {
|
|
|
|
if len(lineParts) == 0 || len(n.children) == 0 {
|
|
|
|
return n.template
|
|
|
|
}
|
2015-06-23 16:07:47 +00:00
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
// If last element is a wildcard, don't include in this search since it's sorted
|
|
|
|
// to the end but lexigraphically it would not alwasy be and sort.Search assumes
|
|
|
|
// the slice is sorted.
|
|
|
|
length := len(n.children)
|
|
|
|
if n.children[length-1].value == "*" {
|
|
|
|
length -= 1
|
|
|
|
}
|
|
|
|
|
|
|
|
i := sort.Search(length, func(i int) bool {
|
|
|
|
return n.children[i].value == lineParts[0]
|
|
|
|
})
|
2015-06-23 16:07:47 +00:00
|
|
|
|
2015-06-24 04:33:11 +00:00
|
|
|
if i < len(n.children) && n.children[i].value == lineParts[0] {
|
|
|
|
return n.children[i].search(lineParts[1:])
|
|
|
|
} else {
|
|
|
|
if n.children[len(n.children)-1].value == "*" {
|
|
|
|
return n.children[len(n.children)-1].search(lineParts[1:])
|
2015-06-23 16:07:47 +00:00
|
|
|
}
|
2015-06-23 05:28:52 +00:00
|
|
|
}
|
2015-06-24 04:33:11 +00:00
|
|
|
return n.template
|
2015-06-23 05:28:52 +00:00
|
|
|
}
|
2015-06-24 04:33:11 +00:00
|
|
|
|
|
|
|
func (n *node) Search(line string) *template {
|
|
|
|
return n.search(strings.Split(line, "."))
|
|
|
|
}
|
|
|
|
|
|
|
|
type nodes []*node
|
|
|
|
|
|
|
|
// Less returns a boolean indicating whether the filter at position j
|
|
|
|
// is less than the filter at postion 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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] }
|
|
|
|
func (n *nodes) Len() int { return len(*n) }
|