2019-05-22 21:16:55 +00:00
|
|
|
package graphite
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/valyala/fastjson/fastfloat"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Rows contains parsed graphite rows.
|
|
|
|
type Rows struct {
|
|
|
|
Rows []Row
|
|
|
|
|
|
|
|
tagsPool []Tag
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets rs.
|
|
|
|
func (rs *Rows) Reset() {
|
|
|
|
// Reset items, so they can be GC'ed
|
|
|
|
|
|
|
|
for i := range rs.Rows {
|
|
|
|
rs.Rows[i].reset()
|
|
|
|
}
|
|
|
|
rs.Rows = rs.Rows[:0]
|
|
|
|
|
|
|
|
for i := range rs.tagsPool {
|
|
|
|
rs.tagsPool[i].reset()
|
|
|
|
}
|
|
|
|
rs.tagsPool = rs.tagsPool[:0]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal unmarshals grahite plaintext protocol rows from s.
|
|
|
|
//
|
|
|
|
// See https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol
|
|
|
|
//
|
|
|
|
// s must be unchanged until rs is in use.
|
|
|
|
func (rs *Rows) Unmarshal(s string) error {
|
|
|
|
var err error
|
|
|
|
rs.Rows, rs.tagsPool, err = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Row is a single graphite row.
|
|
|
|
type Row struct {
|
|
|
|
Metric string
|
|
|
|
Tags []Tag
|
|
|
|
Value float64
|
|
|
|
Timestamp int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Row) reset() {
|
|
|
|
r.Metric = ""
|
|
|
|
r.Tags = nil
|
|
|
|
r.Value = 0
|
|
|
|
r.Timestamp = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
|
|
|
|
r.reset()
|
|
|
|
n := strings.IndexByte(s, ' ')
|
|
|
|
if n < 0 {
|
|
|
|
return tagsPool, fmt.Errorf("cannot find whitespace between metric and value in %q", s)
|
|
|
|
}
|
|
|
|
metricAndTags := s[:n]
|
|
|
|
tail := s[n+1:]
|
|
|
|
|
|
|
|
n = strings.IndexByte(metricAndTags, ';')
|
|
|
|
if n < 0 {
|
|
|
|
// No tags
|
|
|
|
r.Metric = metricAndTags
|
|
|
|
} else {
|
|
|
|
// Tags found
|
|
|
|
r.Metric = metricAndTags[:n]
|
|
|
|
tagsStart := len(tagsPool)
|
|
|
|
var err error
|
|
|
|
tagsPool, err = unmarshalTags(tagsPool, metricAndTags[n+1:])
|
|
|
|
if err != nil {
|
|
|
|
return tagsPool, fmt.Errorf("cannot umarshal tags: %s", err)
|
|
|
|
}
|
|
|
|
tags := tagsPool[tagsStart:]
|
|
|
|
r.Tags = tags[:len(tags):len(tags)]
|
|
|
|
}
|
|
|
|
|
|
|
|
n = strings.IndexByte(tail, ' ')
|
|
|
|
if n < 0 {
|
2019-06-18 16:04:02 +00:00
|
|
|
// There is no timestamp. Use default timestamp instead.
|
|
|
|
r.Value = fastfloat.ParseBestEffort(tail)
|
|
|
|
return tagsPool, nil
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
r.Value = fastfloat.ParseBestEffort(tail[:n])
|
|
|
|
r.Timestamp = fastfloat.ParseInt64BestEffort(tail[n+1:])
|
|
|
|
return tagsPool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag, error) {
|
|
|
|
for len(s) > 0 {
|
|
|
|
n := strings.IndexByte(s, '\n')
|
|
|
|
if n == 0 {
|
|
|
|
// Skip empty line
|
|
|
|
s = s[1:]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if cap(dst) > len(dst) {
|
|
|
|
dst = dst[:len(dst)+1]
|
|
|
|
} else {
|
|
|
|
dst = append(dst, Row{})
|
|
|
|
}
|
|
|
|
r := &dst[len(dst)-1]
|
|
|
|
if n < 0 {
|
|
|
|
// The last line.
|
|
|
|
var err error
|
|
|
|
tagsPool, err = r.unmarshal(s, tagsPool)
|
|
|
|
if err != nil {
|
|
|
|
return dst, tagsPool, err
|
|
|
|
}
|
|
|
|
return dst, tagsPool, nil
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
tagsPool, err = r.unmarshal(s[:n], tagsPool)
|
|
|
|
if err != nil {
|
|
|
|
return dst, tagsPool, err
|
|
|
|
}
|
|
|
|
s = s[n+1:]
|
|
|
|
}
|
|
|
|
return dst, tagsPool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
|
|
|
|
for {
|
|
|
|
if cap(dst) > len(dst) {
|
|
|
|
dst = dst[:len(dst)+1]
|
|
|
|
} else {
|
|
|
|
dst = append(dst, Tag{})
|
|
|
|
}
|
|
|
|
tag := &dst[len(dst)-1]
|
|
|
|
|
|
|
|
n := strings.IndexByte(s, ';')
|
|
|
|
if n < 0 {
|
|
|
|
// The last tag found
|
|
|
|
if err := tag.unmarshal(s); err != nil {
|
|
|
|
return dst[:len(dst)-1], err
|
|
|
|
}
|
|
|
|
return dst, nil
|
|
|
|
}
|
|
|
|
if err := tag.unmarshal(s[:n]); err != nil {
|
|
|
|
return dst[:len(dst)-1], err
|
|
|
|
}
|
|
|
|
s = s[n+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tag is a graphite tag.
|
|
|
|
type Tag struct {
|
|
|
|
Key string
|
|
|
|
Value string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tag) reset() {
|
|
|
|
t.Key = ""
|
|
|
|
t.Value = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tag) unmarshal(s string) error {
|
|
|
|
t.reset()
|
|
|
|
n := strings.IndexByte(s, '=')
|
|
|
|
if n < 0 {
|
|
|
|
return fmt.Errorf("missing tag value for %q", s)
|
|
|
|
}
|
|
|
|
t.Key = s[:n]
|
|
|
|
if len(t.Key) == 0 {
|
|
|
|
return fmt.Errorf("tag key cannot be empty for %q", s)
|
|
|
|
}
|
|
|
|
t.Value = s[n+1:]
|
|
|
|
return nil
|
|
|
|
}
|