* go-metrics: fork library and introduce ResettingTimer and InfluxDB reporter. * vendor: change nonsense/go-metrics to ethersphere/go-metrics * go-metrics: add tests. move ResettingTimer logic from reporter to type. * all, metrics: pull in metrics package in go-ethereum * metrics/test: make sure metrics are enabled for tests * metrics: apply gosimple rules * metrics/exp, internal/debug: init expvar endpoint when starting pprof server * internal/debug: tiny comment formatting fix
		
			
				
	
	
		
			2338 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			2338 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Package models implements basic objects used throughout the TICK stack.
 | 
						|
package models // import "github.com/influxdata/influxdb/models"
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/binary"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"math"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/influxdata/influxdb/pkg/escape"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	measurementEscapeCodes = map[byte][]byte{
 | 
						|
		',': []byte(`\,`),
 | 
						|
		' ': []byte(`\ `),
 | 
						|
	}
 | 
						|
 | 
						|
	tagEscapeCodes = map[byte][]byte{
 | 
						|
		',': []byte(`\,`),
 | 
						|
		' ': []byte(`\ `),
 | 
						|
		'=': []byte(`\=`),
 | 
						|
	}
 | 
						|
 | 
						|
	// ErrPointMustHaveAField is returned when operating on a point that does not have any fields.
 | 
						|
	ErrPointMustHaveAField = errors.New("point without fields is unsupported")
 | 
						|
 | 
						|
	// ErrInvalidNumber is returned when a number is expected but not provided.
 | 
						|
	ErrInvalidNumber = errors.New("invalid number")
 | 
						|
 | 
						|
	// ErrInvalidPoint is returned when a point cannot be parsed correctly.
 | 
						|
	ErrInvalidPoint = errors.New("point is invalid")
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// MaxKeyLength is the largest allowed size of the combined measurement and tag keys.
 | 
						|
	MaxKeyLength = 65535
 | 
						|
)
 | 
						|
 | 
						|
// enableUint64Support will enable uint64 support if set to true.
 | 
						|
var enableUint64Support = false
 | 
						|
 | 
						|
// EnableUintSupport manually enables uint support for the point parser.
 | 
						|
// This function will be removed in the future and only exists for unit tests during the
 | 
						|
// transition.
 | 
						|
func EnableUintSupport() {
 | 
						|
	enableUint64Support = true
 | 
						|
}
 | 
						|
 | 
						|
// Point defines the values that will be written to the database.
 | 
						|
type Point interface {
 | 
						|
	// Name return the measurement name for the point.
 | 
						|
	Name() []byte
 | 
						|
 | 
						|
	// SetName updates the measurement name for the point.
 | 
						|
	SetName(string)
 | 
						|
 | 
						|
	// Tags returns the tag set for the point.
 | 
						|
	Tags() Tags
 | 
						|
 | 
						|
	// AddTag adds or replaces a tag value for a point.
 | 
						|
	AddTag(key, value string)
 | 
						|
 | 
						|
	// SetTags replaces the tags for the point.
 | 
						|
	SetTags(tags Tags)
 | 
						|
 | 
						|
	// HasTag returns true if the tag exists for the point.
 | 
						|
	HasTag(tag []byte) bool
 | 
						|
 | 
						|
	// Fields returns the fields for the point.
 | 
						|
	Fields() (Fields, error)
 | 
						|
 | 
						|
	// Time return the timestamp for the point.
 | 
						|
	Time() time.Time
 | 
						|
 | 
						|
	// SetTime updates the timestamp for the point.
 | 
						|
	SetTime(t time.Time)
 | 
						|
 | 
						|
	// UnixNano returns the timestamp of the point as nanoseconds since Unix epoch.
 | 
						|
	UnixNano() int64
 | 
						|
 | 
						|
	// HashID returns a non-cryptographic checksum of the point's key.
 | 
						|
	HashID() uint64
 | 
						|
 | 
						|
	// Key returns the key (measurement joined with tags) of the point.
 | 
						|
	Key() []byte
 | 
						|
 | 
						|
	// String returns a string representation of the point. If there is a
 | 
						|
	// timestamp associated with the point then it will be specified with the default
 | 
						|
	// precision of nanoseconds.
 | 
						|
	String() string
 | 
						|
 | 
						|
	// MarshalBinary returns a binary representation of the point.
 | 
						|
	MarshalBinary() ([]byte, error)
 | 
						|
 | 
						|
	// PrecisionString returns a string representation of the point. If there
 | 
						|
	// is a timestamp associated with the point then it will be specified in the
 | 
						|
	// given unit.
 | 
						|
	PrecisionString(precision string) string
 | 
						|
 | 
						|
	// RoundedString returns a string representation of the point. If there
 | 
						|
	// is a timestamp associated with the point, then it will be rounded to the
 | 
						|
	// given duration.
 | 
						|
	RoundedString(d time.Duration) string
 | 
						|
 | 
						|
	// Split will attempt to return multiple points with the same timestamp whose
 | 
						|
	// string representations are no longer than size. Points with a single field or
 | 
						|
	// a point without a timestamp may exceed the requested size.
 | 
						|
	Split(size int) []Point
 | 
						|
 | 
						|
	// Round will round the timestamp of the point to the given duration.
 | 
						|
	Round(d time.Duration)
 | 
						|
 | 
						|
	// StringSize returns the length of the string that would be returned by String().
 | 
						|
	StringSize() int
 | 
						|
 | 
						|
	// AppendString appends the result of String() to the provided buffer and returns
 | 
						|
	// the result, potentially reducing string allocations.
 | 
						|
	AppendString(buf []byte) []byte
 | 
						|
 | 
						|
	// FieldIterator retuns a FieldIterator that can be used to traverse the
 | 
						|
	// fields of a point without constructing the in-memory map.
 | 
						|
	FieldIterator() FieldIterator
 | 
						|
}
 | 
						|
 | 
						|
// FieldType represents the type of a field.
 | 
						|
type FieldType int
 | 
						|
 | 
						|
const (
 | 
						|
	// Integer indicates the field's type is integer.
 | 
						|
	Integer FieldType = iota
 | 
						|
 | 
						|
	// Float indicates the field's type is float.
 | 
						|
	Float
 | 
						|
 | 
						|
	// Boolean indicates the field's type is boolean.
 | 
						|
	Boolean
 | 
						|
 | 
						|
	// String indicates the field's type is string.
 | 
						|
	String
 | 
						|
 | 
						|
	// Empty is used to indicate that there is no field.
 | 
						|
	Empty
 | 
						|
 | 
						|
	// Unsigned indicates the field's type is an unsigned integer.
 | 
						|
	Unsigned
 | 
						|
)
 | 
						|
 | 
						|
// FieldIterator provides a low-allocation interface to iterate through a point's fields.
 | 
						|
type FieldIterator interface {
 | 
						|
	// Next indicates whether there any fields remaining.
 | 
						|
	Next() bool
 | 
						|
 | 
						|
	// FieldKey returns the key of the current field.
 | 
						|
	FieldKey() []byte
 | 
						|
 | 
						|
	// Type returns the FieldType of the current field.
 | 
						|
	Type() FieldType
 | 
						|
 | 
						|
	// StringValue returns the string value of the current field.
 | 
						|
	StringValue() string
 | 
						|
 | 
						|
	// IntegerValue returns the integer value of the current field.
 | 
						|
	IntegerValue() (int64, error)
 | 
						|
 | 
						|
	// UnsignedValue returns the unsigned value of the current field.
 | 
						|
	UnsignedValue() (uint64, error)
 | 
						|
 | 
						|
	// BooleanValue returns the boolean value of the current field.
 | 
						|
	BooleanValue() (bool, error)
 | 
						|
 | 
						|
	// FloatValue returns the float value of the current field.
 | 
						|
	FloatValue() (float64, error)
 | 
						|
 | 
						|
	// Reset resets the iterator to its initial state.
 | 
						|
	Reset()
 | 
						|
}
 | 
						|
 | 
						|
// Points represents a sortable list of points by timestamp.
 | 
						|
type Points []Point
 | 
						|
 | 
						|
// Len implements sort.Interface.
 | 
						|
func (a Points) Len() int { return len(a) }
 | 
						|
 | 
						|
// Less implements sort.Interface.
 | 
						|
func (a Points) Less(i, j int) bool { return a[i].Time().Before(a[j].Time()) }
 | 
						|
 | 
						|
// Swap implements sort.Interface.
 | 
						|
func (a Points) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 | 
						|
 | 
						|
// point is the default implementation of Point.
 | 
						|
type point struct {
 | 
						|
	time time.Time
 | 
						|
 | 
						|
	// text encoding of measurement and tags
 | 
						|
	// key must always be stored sorted by tags, if the original line was not sorted,
 | 
						|
	// we need to resort it
 | 
						|
	key []byte
 | 
						|
 | 
						|
	// text encoding of field data
 | 
						|
	fields []byte
 | 
						|
 | 
						|
	// text encoding of timestamp
 | 
						|
	ts []byte
 | 
						|
 | 
						|
	// cached version of parsed fields from data
 | 
						|
	cachedFields map[string]interface{}
 | 
						|
 | 
						|
	// cached version of parsed name from key
 | 
						|
	cachedName string
 | 
						|
 | 
						|
	// cached version of parsed tags
 | 
						|
	cachedTags Tags
 | 
						|
 | 
						|
	it fieldIterator
 | 
						|
}
 | 
						|
 | 
						|
// type assertions
 | 
						|
var (
 | 
						|
	_ Point         = (*point)(nil)
 | 
						|
	_ FieldIterator = (*point)(nil)
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// the number of characters for the largest possible int64 (9223372036854775807)
 | 
						|
	maxInt64Digits = 19
 | 
						|
 | 
						|
	// the number of characters for the smallest possible int64 (-9223372036854775808)
 | 
						|
	minInt64Digits = 20
 | 
						|
 | 
						|
	// the number of characters for the largest possible uint64 (18446744073709551615)
 | 
						|
	maxUint64Digits = 20
 | 
						|
 | 
						|
	// the number of characters required for the largest float64 before a range check
 | 
						|
	// would occur during parsing
 | 
						|
	maxFloat64Digits = 25
 | 
						|
 | 
						|
	// the number of characters required for smallest float64 before a range check occur
 | 
						|
	// would occur during parsing
 | 
						|
	minFloat64Digits = 27
 | 
						|
)
 | 
						|
 | 
						|
// ParsePoints returns a slice of Points from a text representation of a point
 | 
						|
// with each point separated by newlines.  If any points fail to parse, a non-nil error
 | 
						|
// will be returned in addition to the points that parsed successfully.
 | 
						|
func ParsePoints(buf []byte) ([]Point, error) {
 | 
						|
	return ParsePointsWithPrecision(buf, time.Now().UTC(), "n")
 | 
						|
}
 | 
						|
 | 
						|
// ParsePointsString is identical to ParsePoints but accepts a string.
 | 
						|
func ParsePointsString(buf string) ([]Point, error) {
 | 
						|
	return ParsePoints([]byte(buf))
 | 
						|
}
 | 
						|
 | 
						|
// ParseKey returns the measurement name and tags from a point.
 | 
						|
//
 | 
						|
// NOTE: to minimize heap allocations, the returned Tags will refer to subslices of buf.
 | 
						|
// This can have the unintended effect preventing buf from being garbage collected.
 | 
						|
func ParseKey(buf []byte) (string, Tags) {
 | 
						|
	meas, tags := ParseKeyBytes(buf)
 | 
						|
	return string(meas), tags
 | 
						|
}
 | 
						|
 | 
						|
func ParseKeyBytes(buf []byte) ([]byte, Tags) {
 | 
						|
	// Ignore the error because scanMeasurement returns "missing fields" which we ignore
 | 
						|
	// when just parsing a key
 | 
						|
	state, i, _ := scanMeasurement(buf, 0)
 | 
						|
 | 
						|
	var tags Tags
 | 
						|
	if state == tagKeyState {
 | 
						|
		tags = parseTags(buf)
 | 
						|
		// scanMeasurement returns the location of the comma if there are tags, strip that off
 | 
						|
		return buf[:i-1], tags
 | 
						|
	}
 | 
						|
	return buf[:i], tags
 | 
						|
}
 | 
						|
 | 
						|
func ParseTags(buf []byte) Tags {
 | 
						|
	return parseTags(buf)
 | 
						|
}
 | 
						|
 | 
						|
func ParseName(buf []byte) ([]byte, error) {
 | 
						|
	// Ignore the error because scanMeasurement returns "missing fields" which we ignore
 | 
						|
	// when just parsing a key
 | 
						|
	state, i, _ := scanMeasurement(buf, 0)
 | 
						|
	if state == tagKeyState {
 | 
						|
		return buf[:i-1], nil
 | 
						|
	}
 | 
						|
	return buf[:i], nil
 | 
						|
}
 | 
						|
 | 
						|
// ParsePointsWithPrecision is similar to ParsePoints, but allows the
 | 
						|
// caller to provide a precision for time.
 | 
						|
//
 | 
						|
// NOTE: to minimize heap allocations, the returned Points will refer to subslices of buf.
 | 
						|
// This can have the unintended effect preventing buf from being garbage collected.
 | 
						|
func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision string) ([]Point, error) {
 | 
						|
	points := make([]Point, 0, bytes.Count(buf, []byte{'\n'})+1)
 | 
						|
	var (
 | 
						|
		pos    int
 | 
						|
		block  []byte
 | 
						|
		failed []string
 | 
						|
	)
 | 
						|
	for pos < len(buf) {
 | 
						|
		pos, block = scanLine(buf, pos)
 | 
						|
		pos++
 | 
						|
 | 
						|
		if len(block) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// lines which start with '#' are comments
 | 
						|
		start := skipWhitespace(block, 0)
 | 
						|
 | 
						|
		// If line is all whitespace, just skip it
 | 
						|
		if start >= len(block) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if block[start] == '#' {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// strip the newline if one is present
 | 
						|
		if block[len(block)-1] == '\n' {
 | 
						|
			block = block[:len(block)-1]
 | 
						|
		}
 | 
						|
 | 
						|
		pt, err := parsePoint(block[start:], defaultTime, precision)
 | 
						|
		if err != nil {
 | 
						|
			failed = append(failed, fmt.Sprintf("unable to parse '%s': %v", string(block[start:]), err))
 | 
						|
		} else {
 | 
						|
			points = append(points, pt)
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
	if len(failed) > 0 {
 | 
						|
		return points, fmt.Errorf("%s", strings.Join(failed, "\n"))
 | 
						|
	}
 | 
						|
	return points, nil
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func parsePoint(buf []byte, defaultTime time.Time, precision string) (Point, error) {
 | 
						|
	// scan the first block which is measurement[,tag1=value1,tag2=value=2...]
 | 
						|
	pos, key, err := scanKey(buf, 0)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// measurement name is required
 | 
						|
	if len(key) == 0 {
 | 
						|
		return nil, fmt.Errorf("missing measurement")
 | 
						|
	}
 | 
						|
 | 
						|
	if len(key) > MaxKeyLength {
 | 
						|
		return nil, fmt.Errorf("max key length exceeded: %v > %v", len(key), MaxKeyLength)
 | 
						|
	}
 | 
						|
 | 
						|
	// scan the second block is which is field1=value1[,field2=value2,...]
 | 
						|
	pos, fields, err := scanFields(buf, pos)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// at least one field is required
 | 
						|
	if len(fields) == 0 {
 | 
						|
		return nil, fmt.Errorf("missing fields")
 | 
						|
	}
 | 
						|
 | 
						|
	var maxKeyErr error
 | 
						|
	walkFields(fields, func(k, v []byte) bool {
 | 
						|
		if sz := seriesKeySize(key, k); sz > MaxKeyLength {
 | 
						|
			maxKeyErr = fmt.Errorf("max key length exceeded: %v > %v", sz, MaxKeyLength)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	})
 | 
						|
 | 
						|
	if maxKeyErr != nil {
 | 
						|
		return nil, maxKeyErr
 | 
						|
	}
 | 
						|
 | 
						|
	// scan the last block which is an optional integer timestamp
 | 
						|
	pos, ts, err := scanTime(buf, pos)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	pt := &point{
 | 
						|
		key:    key,
 | 
						|
		fields: fields,
 | 
						|
		ts:     ts,
 | 
						|
	}
 | 
						|
 | 
						|
	if len(ts) == 0 {
 | 
						|
		pt.time = defaultTime
 | 
						|
		pt.SetPrecision(precision)
 | 
						|
	} else {
 | 
						|
		ts, err := parseIntBytes(ts, 10, 64)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		pt.time, err = SafeCalcTime(ts, precision)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// Determine if there are illegal non-whitespace characters after the
 | 
						|
		// timestamp block.
 | 
						|
		for pos < len(buf) {
 | 
						|
			if buf[pos] != ' ' {
 | 
						|
				return nil, ErrInvalidPoint
 | 
						|
			}
 | 
						|
			pos++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return pt, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetPrecisionMultiplier will return a multiplier for the precision specified.
 | 
						|
func GetPrecisionMultiplier(precision string) int64 {
 | 
						|
	d := time.Nanosecond
 | 
						|
	switch precision {
 | 
						|
	case "u":
 | 
						|
		d = time.Microsecond
 | 
						|
	case "ms":
 | 
						|
		d = time.Millisecond
 | 
						|
	case "s":
 | 
						|
		d = time.Second
 | 
						|
	case "m":
 | 
						|
		d = time.Minute
 | 
						|
	case "h":
 | 
						|
		d = time.Hour
 | 
						|
	}
 | 
						|
	return int64(d)
 | 
						|
}
 | 
						|
 | 
						|
// scanKey scans buf starting at i for the measurement and tag portion of the point.
 | 
						|
// It returns the ending position and the byte slice of key within buf.  If there
 | 
						|
// are tags, they will be sorted if they are not already.
 | 
						|
func scanKey(buf []byte, i int) (int, []byte, error) {
 | 
						|
	start := skipWhitespace(buf, i)
 | 
						|
 | 
						|
	i = start
 | 
						|
 | 
						|
	// Determines whether the tags are sort, assume they are
 | 
						|
	sorted := true
 | 
						|
 | 
						|
	// indices holds the indexes within buf of the start of each tag.  For example,
 | 
						|
	// a buf of 'cpu,host=a,region=b,zone=c' would have indices slice of [4,11,20]
 | 
						|
	// which indicates that the first tag starts at buf[4], seconds at buf[11], and
 | 
						|
	// last at buf[20]
 | 
						|
	indices := make([]int, 100)
 | 
						|
 | 
						|
	// tracks how many commas we've seen so we know how many values are indices.
 | 
						|
	// Since indices is an arbitrarily large slice,
 | 
						|
	// we need to know how many values in the buffer are in use.
 | 
						|
	commas := 0
 | 
						|
 | 
						|
	// First scan the Point's measurement.
 | 
						|
	state, i, err := scanMeasurement(buf, i)
 | 
						|
	if err != nil {
 | 
						|
		return i, buf[start:i], err
 | 
						|
	}
 | 
						|
 | 
						|
	// Optionally scan tags if needed.
 | 
						|
	if state == tagKeyState {
 | 
						|
		i, commas, indices, err = scanTags(buf, i, indices)
 | 
						|
		if err != nil {
 | 
						|
			return i, buf[start:i], err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Now we know where the key region is within buf, and the location of tags, we
 | 
						|
	// need to determine if duplicate tags exist and if the tags are sorted. This iterates
 | 
						|
	// over the list comparing each tag in the sequence with each other.
 | 
						|
	for j := 0; j < commas-1; j++ {
 | 
						|
		// get the left and right tags
 | 
						|
		_, left := scanTo(buf[indices[j]:indices[j+1]-1], 0, '=')
 | 
						|
		_, right := scanTo(buf[indices[j+1]:indices[j+2]-1], 0, '=')
 | 
						|
 | 
						|
		// If left is greater than right, the tags are not sorted. We do not have to
 | 
						|
		// continue because the short path no longer works.
 | 
						|
		// If the tags are equal, then there are duplicate tags, and we should abort.
 | 
						|
		// If the tags are not sorted, this pass may not find duplicate tags and we
 | 
						|
		// need to do a more exhaustive search later.
 | 
						|
		if cmp := bytes.Compare(left, right); cmp > 0 {
 | 
						|
			sorted = false
 | 
						|
			break
 | 
						|
		} else if cmp == 0 {
 | 
						|
			return i, buf[start:i], fmt.Errorf("duplicate tags")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If the tags are not sorted, then sort them.  This sort is inline and
 | 
						|
	// uses the tag indices we created earlier.  The actual buffer is not sorted, the
 | 
						|
	// indices are using the buffer for value comparison.  After the indices are sorted,
 | 
						|
	// the buffer is reconstructed from the sorted indices.
 | 
						|
	if !sorted && commas > 0 {
 | 
						|
		// Get the measurement name for later
 | 
						|
		measurement := buf[start : indices[0]-1]
 | 
						|
 | 
						|
		// Sort the indices
 | 
						|
		indices := indices[:commas]
 | 
						|
		insertionSort(0, commas, buf, indices)
 | 
						|
 | 
						|
		// Create a new key using the measurement and sorted indices
 | 
						|
		b := make([]byte, len(buf[start:i]))
 | 
						|
		pos := copy(b, measurement)
 | 
						|
		for _, i := range indices {
 | 
						|
			b[pos] = ','
 | 
						|
			pos++
 | 
						|
			_, v := scanToSpaceOr(buf, i, ',')
 | 
						|
			pos += copy(b[pos:], v)
 | 
						|
		}
 | 
						|
 | 
						|
		// Check again for duplicate tags now that the tags are sorted.
 | 
						|
		for j := 0; j < commas-1; j++ {
 | 
						|
			// get the left and right tags
 | 
						|
			_, left := scanTo(buf[indices[j]:], 0, '=')
 | 
						|
			_, right := scanTo(buf[indices[j+1]:], 0, '=')
 | 
						|
 | 
						|
			// If the tags are equal, then there are duplicate tags, and we should abort.
 | 
						|
			// If the tags are not sorted, this pass may not find duplicate tags and we
 | 
						|
			// need to do a more exhaustive search later.
 | 
						|
			if bytes.Equal(left, right) {
 | 
						|
				return i, b, fmt.Errorf("duplicate tags")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return i, b, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return i, buf[start:i], nil
 | 
						|
}
 | 
						|
 | 
						|
// The following constants allow us to specify which state to move to
 | 
						|
// next, when scanning sections of a Point.
 | 
						|
const (
 | 
						|
	tagKeyState = iota
 | 
						|
	tagValueState
 | 
						|
	fieldsState
 | 
						|
)
 | 
						|
 | 
						|
// scanMeasurement examines the measurement part of a Point, returning
 | 
						|
// the next state to move to, and the current location in the buffer.
 | 
						|
func scanMeasurement(buf []byte, i int) (int, int, error) {
 | 
						|
	// Check first byte of measurement, anything except a comma is fine.
 | 
						|
	// It can't be a space, since whitespace is stripped prior to this
 | 
						|
	// function call.
 | 
						|
	if i >= len(buf) || buf[i] == ',' {
 | 
						|
		return -1, i, fmt.Errorf("missing measurement")
 | 
						|
	}
 | 
						|
 | 
						|
	for {
 | 
						|
		i++
 | 
						|
		if i >= len(buf) {
 | 
						|
			// cpu
 | 
						|
			return -1, i, fmt.Errorf("missing fields")
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i-1] == '\\' {
 | 
						|
			// Skip character (it's escaped).
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Unescaped comma; move onto scanning the tags.
 | 
						|
		if buf[i] == ',' {
 | 
						|
			return tagKeyState, i + 1, nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Unescaped space; move onto scanning the fields.
 | 
						|
		if buf[i] == ' ' {
 | 
						|
			// cpu value=1.0
 | 
						|
			return fieldsState, i, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// scanTags examines all the tags in a Point, keeping track of and
 | 
						|
// returning the updated indices slice, number of commas and location
 | 
						|
// in buf where to start examining the Point fields.
 | 
						|
func scanTags(buf []byte, i int, indices []int) (int, int, []int, error) {
 | 
						|
	var (
 | 
						|
		err    error
 | 
						|
		commas int
 | 
						|
		state  = tagKeyState
 | 
						|
	)
 | 
						|
 | 
						|
	for {
 | 
						|
		switch state {
 | 
						|
		case tagKeyState:
 | 
						|
			// Grow our indices slice if we have too many tags.
 | 
						|
			if commas >= len(indices) {
 | 
						|
				newIndics := make([]int, cap(indices)*2)
 | 
						|
				copy(newIndics, indices)
 | 
						|
				indices = newIndics
 | 
						|
			}
 | 
						|
			indices[commas] = i
 | 
						|
			commas++
 | 
						|
 | 
						|
			i, err = scanTagsKey(buf, i)
 | 
						|
			state = tagValueState // tag value always follows a tag key
 | 
						|
		case tagValueState:
 | 
						|
			state, i, err = scanTagsValue(buf, i)
 | 
						|
		case fieldsState:
 | 
						|
			indices[commas] = i + 1
 | 
						|
			return i, commas, indices, nil
 | 
						|
		}
 | 
						|
 | 
						|
		if err != nil {
 | 
						|
			return i, commas, indices, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// scanTagsKey scans each character in a tag key.
 | 
						|
func scanTagsKey(buf []byte, i int) (int, error) {
 | 
						|
	// First character of the key.
 | 
						|
	if i >= len(buf) || buf[i] == ' ' || buf[i] == ',' || buf[i] == '=' {
 | 
						|
		// cpu,{'', ' ', ',', '='}
 | 
						|
		return i, fmt.Errorf("missing tag key")
 | 
						|
	}
 | 
						|
 | 
						|
	// Examine each character in the tag key until we hit an unescaped
 | 
						|
	// equals (the tag value), or we hit an error (i.e., unescaped
 | 
						|
	// space or comma).
 | 
						|
	for {
 | 
						|
		i++
 | 
						|
 | 
						|
		// Either we reached the end of the buffer or we hit an
 | 
						|
		// unescaped comma or space.
 | 
						|
		if i >= len(buf) ||
 | 
						|
			((buf[i] == ' ' || buf[i] == ',') && buf[i-1] != '\\') {
 | 
						|
			// cpu,tag{'', ' ', ','}
 | 
						|
			return i, fmt.Errorf("missing tag value")
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == '=' && buf[i-1] != '\\' {
 | 
						|
			// cpu,tag=
 | 
						|
			return i + 1, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// scanTagsValue scans each character in a tag value.
 | 
						|
func scanTagsValue(buf []byte, i int) (int, int, error) {
 | 
						|
	// Tag value cannot be empty.
 | 
						|
	if i >= len(buf) || buf[i] == ',' || buf[i] == ' ' {
 | 
						|
		// cpu,tag={',', ' '}
 | 
						|
		return -1, i, fmt.Errorf("missing tag value")
 | 
						|
	}
 | 
						|
 | 
						|
	// Examine each character in the tag value until we hit an unescaped
 | 
						|
	// comma (move onto next tag key), an unescaped space (move onto
 | 
						|
	// fields), or we error out.
 | 
						|
	for {
 | 
						|
		i++
 | 
						|
		if i >= len(buf) {
 | 
						|
			// cpu,tag=value
 | 
						|
			return -1, i, fmt.Errorf("missing fields")
 | 
						|
		}
 | 
						|
 | 
						|
		// An unescaped equals sign is an invalid tag value.
 | 
						|
		if buf[i] == '=' && buf[i-1] != '\\' {
 | 
						|
			// cpu,tag={'=', 'fo=o'}
 | 
						|
			return -1, i, fmt.Errorf("invalid tag format")
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' && buf[i-1] != '\\' {
 | 
						|
			// cpu,tag=foo,
 | 
						|
			return tagKeyState, i + 1, nil
 | 
						|
		}
 | 
						|
 | 
						|
		// cpu,tag=foo value=1.0
 | 
						|
		// cpu, tag=foo\= value=1.0
 | 
						|
		if buf[i] == ' ' && buf[i-1] != '\\' {
 | 
						|
			return fieldsState, i, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func insertionSort(l, r int, buf []byte, indices []int) {
 | 
						|
	for i := l + 1; i < r; i++ {
 | 
						|
		for j := i; j > l && less(buf, indices, j, j-1); j-- {
 | 
						|
			indices[j], indices[j-1] = indices[j-1], indices[j]
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func less(buf []byte, indices []int, i, j int) bool {
 | 
						|
	// This grabs the tag names for i & j, it ignores the values
 | 
						|
	_, a := scanTo(buf, indices[i], '=')
 | 
						|
	_, b := scanTo(buf, indices[j], '=')
 | 
						|
	return bytes.Compare(a, b) < 0
 | 
						|
}
 | 
						|
 | 
						|
// scanFields scans buf, starting at i for the fields section of a point.  It returns
 | 
						|
// the ending position and the byte slice of the fields within buf.
 | 
						|
func scanFields(buf []byte, i int) (int, []byte, error) {
 | 
						|
	start := skipWhitespace(buf, i)
 | 
						|
	i = start
 | 
						|
	quoted := false
 | 
						|
 | 
						|
	// tracks how many '=' we've seen
 | 
						|
	equals := 0
 | 
						|
 | 
						|
	// tracks how many commas we've seen
 | 
						|
	commas := 0
 | 
						|
 | 
						|
	for {
 | 
						|
		// reached the end of buf?
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// escaped characters?
 | 
						|
		if buf[i] == '\\' && i+1 < len(buf) {
 | 
						|
			i += 2
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// If the value is quoted, scan until we get to the end quote
 | 
						|
		// Only quote values in the field value since quotes are not significant
 | 
						|
		// in the field key
 | 
						|
		if buf[i] == '"' && equals > commas {
 | 
						|
			quoted = !quoted
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// If we see an =, ensure that there is at least on char before and after it
 | 
						|
		if buf[i] == '=' && !quoted {
 | 
						|
			equals++
 | 
						|
 | 
						|
			// check for "... =123" but allow "a\ =123"
 | 
						|
			if buf[i-1] == ' ' && buf[i-2] != '\\' {
 | 
						|
				return i, buf[start:i], fmt.Errorf("missing field key")
 | 
						|
			}
 | 
						|
 | 
						|
			// check for "...a=123,=456" but allow "a=123,a\,=456"
 | 
						|
			if buf[i-1] == ',' && buf[i-2] != '\\' {
 | 
						|
				return i, buf[start:i], fmt.Errorf("missing field key")
 | 
						|
			}
 | 
						|
 | 
						|
			// check for "... value="
 | 
						|
			if i+1 >= len(buf) {
 | 
						|
				return i, buf[start:i], fmt.Errorf("missing field value")
 | 
						|
			}
 | 
						|
 | 
						|
			// check for "... value=,value2=..."
 | 
						|
			if buf[i+1] == ',' || buf[i+1] == ' ' {
 | 
						|
				return i, buf[start:i], fmt.Errorf("missing field value")
 | 
						|
			}
 | 
						|
 | 
						|
			if isNumeric(buf[i+1]) || buf[i+1] == '-' || buf[i+1] == 'N' || buf[i+1] == 'n' {
 | 
						|
				var err error
 | 
						|
				i, err = scanNumber(buf, i+1)
 | 
						|
				if err != nil {
 | 
						|
					return i, buf[start:i], err
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// If next byte is not a double-quote, the value must be a boolean
 | 
						|
			if buf[i+1] != '"' {
 | 
						|
				var err error
 | 
						|
				i, _, err = scanBoolean(buf, i+1)
 | 
						|
				if err != nil {
 | 
						|
					return i, buf[start:i], err
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' && !quoted {
 | 
						|
			commas++
 | 
						|
		}
 | 
						|
 | 
						|
		// reached end of block?
 | 
						|
		if buf[i] == ' ' && !quoted {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	if quoted {
 | 
						|
		return i, buf[start:i], fmt.Errorf("unbalanced quotes")
 | 
						|
	}
 | 
						|
 | 
						|
	// check that all field sections had key and values (e.g. prevent "a=1,b"
 | 
						|
	if equals == 0 || commas != equals-1 {
 | 
						|
		return i, buf[start:i], fmt.Errorf("invalid field format")
 | 
						|
	}
 | 
						|
 | 
						|
	return i, buf[start:i], nil
 | 
						|
}
 | 
						|
 | 
						|
// scanTime scans buf, starting at i for the time section of a point. It
 | 
						|
// returns the ending position and the byte slice of the timestamp within buf
 | 
						|
// and and error if the timestamp is not in the correct numeric format.
 | 
						|
func scanTime(buf []byte, i int) (int, []byte, error) {
 | 
						|
	start := skipWhitespace(buf, i)
 | 
						|
	i = start
 | 
						|
 | 
						|
	for {
 | 
						|
		// reached the end of buf?
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Reached end of block or trailing whitespace?
 | 
						|
		if buf[i] == '\n' || buf[i] == ' ' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle negative timestamps
 | 
						|
		if i == start && buf[i] == '-' {
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Timestamps should be integers, make sure they are so we don't need
 | 
						|
		// to actually  parse the timestamp until needed.
 | 
						|
		if buf[i] < '0' || buf[i] > '9' {
 | 
						|
			return i, buf[start:i], fmt.Errorf("bad timestamp")
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	return i, buf[start:i], nil
 | 
						|
}
 | 
						|
 | 
						|
func isNumeric(b byte) bool {
 | 
						|
	return (b >= '0' && b <= '9') || b == '.'
 | 
						|
}
 | 
						|
 | 
						|
// scanNumber returns the end position within buf, start at i after
 | 
						|
// scanning over buf for an integer, or float.  It returns an
 | 
						|
// error if a invalid number is scanned.
 | 
						|
func scanNumber(buf []byte, i int) (int, error) {
 | 
						|
	start := i
 | 
						|
	var isInt, isUnsigned bool
 | 
						|
 | 
						|
	// Is negative number?
 | 
						|
	if i < len(buf) && buf[i] == '-' {
 | 
						|
		i++
 | 
						|
		// There must be more characters now, as just '-' is illegal.
 | 
						|
		if i == len(buf) {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// how many decimal points we've see
 | 
						|
	decimal := false
 | 
						|
 | 
						|
	// indicates the number is float in scientific notation
 | 
						|
	scientific := false
 | 
						|
 | 
						|
	for {
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' || buf[i] == ' ' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == 'i' && i > start && !(isInt || isUnsigned) {
 | 
						|
			isInt = true
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		} else if buf[i] == 'u' && i > start && !(isInt || isUnsigned) {
 | 
						|
			isUnsigned = true
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == '.' {
 | 
						|
			// Can't have more than 1 decimal (e.g. 1.1.1 should fail)
 | 
						|
			if decimal {
 | 
						|
				return i, ErrInvalidNumber
 | 
						|
			}
 | 
						|
			decimal = true
 | 
						|
		}
 | 
						|
 | 
						|
		// `e` is valid for floats but not as the first char
 | 
						|
		if i > start && (buf[i] == 'e' || buf[i] == 'E') {
 | 
						|
			scientific = true
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// + and - are only valid at this point if they follow an e (scientific notation)
 | 
						|
		if (buf[i] == '+' || buf[i] == '-') && (buf[i-1] == 'e' || buf[i-1] == 'E') {
 | 
						|
			i++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// NaN is an unsupported value
 | 
						|
		if i+2 < len(buf) && (buf[i] == 'N' || buf[i] == 'n') {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
 | 
						|
		if !isNumeric(buf[i]) {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	if (isInt || isUnsigned) && (decimal || scientific) {
 | 
						|
		return i, ErrInvalidNumber
 | 
						|
	}
 | 
						|
 | 
						|
	numericDigits := i - start
 | 
						|
	if isInt {
 | 
						|
		numericDigits--
 | 
						|
	}
 | 
						|
	if decimal {
 | 
						|
		numericDigits--
 | 
						|
	}
 | 
						|
	if buf[start] == '-' {
 | 
						|
		numericDigits--
 | 
						|
	}
 | 
						|
 | 
						|
	if numericDigits == 0 {
 | 
						|
		return i, ErrInvalidNumber
 | 
						|
	}
 | 
						|
 | 
						|
	// It's more common that numbers will be within min/max range for their type but we need to prevent
 | 
						|
	// out or range numbers from being parsed successfully.  This uses some simple heuristics to decide
 | 
						|
	// if we should parse the number to the actual type.  It does not do it all the time because it incurs
 | 
						|
	// extra allocations and we end up converting the type again when writing points to disk.
 | 
						|
	if isInt {
 | 
						|
		// Make sure the last char is an 'i' for integers (e.g. 9i10 is not valid)
 | 
						|
		if buf[i-1] != 'i' {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
		// Parse the int to check bounds the number of digits could be larger than the max range
 | 
						|
		// We subtract 1 from the index to remove the `i` from our tests
 | 
						|
		if len(buf[start:i-1]) >= maxInt64Digits || len(buf[start:i-1]) >= minInt64Digits {
 | 
						|
			if _, err := parseIntBytes(buf[start:i-1], 10, 64); err != nil {
 | 
						|
				return i, fmt.Errorf("unable to parse integer %s: %s", buf[start:i-1], err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if isUnsigned {
 | 
						|
		// Return an error if uint64 support has not been enabled.
 | 
						|
		if !enableUint64Support {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
		// Make sure the last char is a 'u' for unsigned
 | 
						|
		if buf[i-1] != 'u' {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
		// Make sure the first char is not a '-' for unsigned
 | 
						|
		if buf[start] == '-' {
 | 
						|
			return i, ErrInvalidNumber
 | 
						|
		}
 | 
						|
		// Parse the uint to check bounds the number of digits could be larger than the max range
 | 
						|
		// We subtract 1 from the index to remove the `u` from our tests
 | 
						|
		if len(buf[start:i-1]) >= maxUint64Digits {
 | 
						|
			if _, err := parseUintBytes(buf[start:i-1], 10, 64); err != nil {
 | 
						|
				return i, fmt.Errorf("unable to parse unsigned %s: %s", buf[start:i-1], err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		// Parse the float to check bounds if it's scientific or the number of digits could be larger than the max range
 | 
						|
		if scientific || len(buf[start:i]) >= maxFloat64Digits || len(buf[start:i]) >= minFloat64Digits {
 | 
						|
			if _, err := parseFloatBytes(buf[start:i], 10); err != nil {
 | 
						|
				return i, fmt.Errorf("invalid float")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return i, nil
 | 
						|
}
 | 
						|
 | 
						|
// scanBoolean returns the end position within buf, start at i after
 | 
						|
// scanning over buf for boolean. Valid values for a boolean are
 | 
						|
// t, T, true, TRUE, f, F, false, FALSE.  It returns an error if a invalid boolean
 | 
						|
// is scanned.
 | 
						|
func scanBoolean(buf []byte, i int) (int, []byte, error) {
 | 
						|
	start := i
 | 
						|
 | 
						|
	if i < len(buf) && (buf[i] != 't' && buf[i] != 'f' && buf[i] != 'T' && buf[i] != 'F') {
 | 
						|
		return i, buf[start:i], fmt.Errorf("invalid boolean")
 | 
						|
	}
 | 
						|
 | 
						|
	i++
 | 
						|
	for {
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' || buf[i] == ' ' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	// Single char bool (t, T, f, F) is ok
 | 
						|
	if i-start == 1 {
 | 
						|
		return i, buf[start:i], nil
 | 
						|
	}
 | 
						|
 | 
						|
	// length must be 4 for true or TRUE
 | 
						|
	if (buf[start] == 't' || buf[start] == 'T') && i-start != 4 {
 | 
						|
		return i, buf[start:i], fmt.Errorf("invalid boolean")
 | 
						|
	}
 | 
						|
 | 
						|
	// length must be 5 for false or FALSE
 | 
						|
	if (buf[start] == 'f' || buf[start] == 'F') && i-start != 5 {
 | 
						|
		return i, buf[start:i], fmt.Errorf("invalid boolean")
 | 
						|
	}
 | 
						|
 | 
						|
	// Otherwise
 | 
						|
	valid := false
 | 
						|
	switch buf[start] {
 | 
						|
	case 't':
 | 
						|
		valid = bytes.Equal(buf[start:i], []byte("true"))
 | 
						|
	case 'f':
 | 
						|
		valid = bytes.Equal(buf[start:i], []byte("false"))
 | 
						|
	case 'T':
 | 
						|
		valid = bytes.Equal(buf[start:i], []byte("TRUE")) || bytes.Equal(buf[start:i], []byte("True"))
 | 
						|
	case 'F':
 | 
						|
		valid = bytes.Equal(buf[start:i], []byte("FALSE")) || bytes.Equal(buf[start:i], []byte("False"))
 | 
						|
	}
 | 
						|
 | 
						|
	if !valid {
 | 
						|
		return i, buf[start:i], fmt.Errorf("invalid boolean")
 | 
						|
	}
 | 
						|
 | 
						|
	return i, buf[start:i], nil
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// skipWhitespace returns the end position within buf, starting at i after
 | 
						|
// scanning over spaces in tags.
 | 
						|
func skipWhitespace(buf []byte, i int) int {
 | 
						|
	for i < len(buf) {
 | 
						|
		if buf[i] != ' ' && buf[i] != '\t' && buf[i] != 0 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	return i
 | 
						|
}
 | 
						|
 | 
						|
// scanLine returns the end position in buf and the next line found within
 | 
						|
// buf.
 | 
						|
func scanLine(buf []byte, i int) (int, []byte) {
 | 
						|
	start := i
 | 
						|
	quoted := false
 | 
						|
	fields := false
 | 
						|
 | 
						|
	// tracks how many '=' and commas we've seen
 | 
						|
	// this duplicates some of the functionality in scanFields
 | 
						|
	equals := 0
 | 
						|
	commas := 0
 | 
						|
	for {
 | 
						|
		// reached the end of buf?
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// skip past escaped characters
 | 
						|
		if buf[i] == '\\' && i+2 < len(buf) {
 | 
						|
			i += 2
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ' ' {
 | 
						|
			fields = true
 | 
						|
		}
 | 
						|
 | 
						|
		// If we see a double quote, makes sure it is not escaped
 | 
						|
		if fields {
 | 
						|
			if !quoted && buf[i] == '=' {
 | 
						|
				i++
 | 
						|
				equals++
 | 
						|
				continue
 | 
						|
			} else if !quoted && buf[i] == ',' {
 | 
						|
				i++
 | 
						|
				commas++
 | 
						|
				continue
 | 
						|
			} else if buf[i] == '"' && equals > commas {
 | 
						|
				i++
 | 
						|
				quoted = !quoted
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == '\n' && !quoted {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	return i, buf[start:i]
 | 
						|
}
 | 
						|
 | 
						|
// scanTo returns the end position in buf and the next consecutive block
 | 
						|
// of bytes, starting from i and ending with stop byte, where stop byte
 | 
						|
// has not been escaped.
 | 
						|
//
 | 
						|
// If there are leading spaces, they are skipped.
 | 
						|
func scanTo(buf []byte, i int, stop byte) (int, []byte) {
 | 
						|
	start := i
 | 
						|
	for {
 | 
						|
		// reached the end of buf?
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Reached unescaped stop value?
 | 
						|
		if buf[i] == stop && (i == 0 || buf[i-1] != '\\') {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	return i, buf[start:i]
 | 
						|
}
 | 
						|
 | 
						|
// scanTo returns the end position in buf and the next consecutive block
 | 
						|
// of bytes, starting from i and ending with stop byte.  If there are leading
 | 
						|
// spaces, they are skipped.
 | 
						|
func scanToSpaceOr(buf []byte, i int, stop byte) (int, []byte) {
 | 
						|
	start := i
 | 
						|
	if buf[i] == stop || buf[i] == ' ' {
 | 
						|
		return i, buf[start:i]
 | 
						|
	}
 | 
						|
 | 
						|
	for {
 | 
						|
		i++
 | 
						|
		if buf[i-1] == '\\' {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// reached the end of buf?
 | 
						|
		if i >= len(buf) {
 | 
						|
			return i, buf[start:i]
 | 
						|
		}
 | 
						|
 | 
						|
		// reached end of block?
 | 
						|
		if buf[i] == stop || buf[i] == ' ' {
 | 
						|
			return i, buf[start:i]
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func scanTagValue(buf []byte, i int) (int, []byte) {
 | 
						|
	start := i
 | 
						|
	for {
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' && buf[i-1] != '\\' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	if i > len(buf) {
 | 
						|
		return i, nil
 | 
						|
	}
 | 
						|
	return i, buf[start:i]
 | 
						|
}
 | 
						|
 | 
						|
func scanFieldValue(buf []byte, i int) (int, []byte) {
 | 
						|
	start := i
 | 
						|
	quoted := false
 | 
						|
	for i < len(buf) {
 | 
						|
		// Only escape char for a field value is a double-quote and backslash
 | 
						|
		if buf[i] == '\\' && i+1 < len(buf) && (buf[i+1] == '"' || buf[i+1] == '\\') {
 | 
						|
			i += 2
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Quoted value? (e.g. string)
 | 
						|
		if buf[i] == '"' {
 | 
						|
			i++
 | 
						|
			quoted = !quoted
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if buf[i] == ',' && !quoted {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	return i, buf[start:i]
 | 
						|
}
 | 
						|
 | 
						|
func EscapeMeasurement(in []byte) []byte {
 | 
						|
	for b, esc := range measurementEscapeCodes {
 | 
						|
		in = bytes.Replace(in, []byte{b}, esc, -1)
 | 
						|
	}
 | 
						|
	return in
 | 
						|
}
 | 
						|
 | 
						|
func unescapeMeasurement(in []byte) []byte {
 | 
						|
	for b, esc := range measurementEscapeCodes {
 | 
						|
		in = bytes.Replace(in, esc, []byte{b}, -1)
 | 
						|
	}
 | 
						|
	return in
 | 
						|
}
 | 
						|
 | 
						|
func escapeTag(in []byte) []byte {
 | 
						|
	for b, esc := range tagEscapeCodes {
 | 
						|
		if bytes.IndexByte(in, b) != -1 {
 | 
						|
			in = bytes.Replace(in, []byte{b}, esc, -1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return in
 | 
						|
}
 | 
						|
 | 
						|
func unescapeTag(in []byte) []byte {
 | 
						|
	if bytes.IndexByte(in, '\\') == -1 {
 | 
						|
		return in
 | 
						|
	}
 | 
						|
 | 
						|
	for b, esc := range tagEscapeCodes {
 | 
						|
		if bytes.IndexByte(in, b) != -1 {
 | 
						|
			in = bytes.Replace(in, esc, []byte{b}, -1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return in
 | 
						|
}
 | 
						|
 | 
						|
// escapeStringFieldReplacer replaces double quotes and backslashes
 | 
						|
// with the same character preceded by a backslash.
 | 
						|
// As of Go 1.7 this benchmarked better in allocations and CPU time
 | 
						|
// compared to iterating through a string byte-by-byte and appending to a new byte slice,
 | 
						|
// calling strings.Replace twice, and better than (*Regex).ReplaceAllString.
 | 
						|
var escapeStringFieldReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`)
 | 
						|
 | 
						|
// EscapeStringField returns a copy of in with any double quotes or
 | 
						|
// backslashes with escaped values.
 | 
						|
func EscapeStringField(in string) string {
 | 
						|
	return escapeStringFieldReplacer.Replace(in)
 | 
						|
}
 | 
						|
 | 
						|
// unescapeStringField returns a copy of in with any escaped double-quotes
 | 
						|
// or backslashes unescaped.
 | 
						|
func unescapeStringField(in string) string {
 | 
						|
	if strings.IndexByte(in, '\\') == -1 {
 | 
						|
		return in
 | 
						|
	}
 | 
						|
 | 
						|
	var out []byte
 | 
						|
	i := 0
 | 
						|
	for {
 | 
						|
		if i >= len(in) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		// unescape backslashes
 | 
						|
		if in[i] == '\\' && i+1 < len(in) && in[i+1] == '\\' {
 | 
						|
			out = append(out, '\\')
 | 
						|
			i += 2
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// unescape double-quotes
 | 
						|
		if in[i] == '\\' && i+1 < len(in) && in[i+1] == '"' {
 | 
						|
			out = append(out, '"')
 | 
						|
			i += 2
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		out = append(out, in[i])
 | 
						|
		i++
 | 
						|
 | 
						|
	}
 | 
						|
	return string(out)
 | 
						|
}
 | 
						|
 | 
						|
// NewPoint returns a new point with the given measurement name, tags, fields and timestamp.  If
 | 
						|
// an unsupported field value (NaN) or out of range time is passed, this function returns an error.
 | 
						|
func NewPoint(name string, tags Tags, fields Fields, t time.Time) (Point, error) {
 | 
						|
	key, err := pointKey(name, tags, fields, t)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &point{
 | 
						|
		key:    key,
 | 
						|
		time:   t,
 | 
						|
		fields: fields.MarshalBinary(),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// pointKey checks some basic requirements for valid points, and returns the
 | 
						|
// key, along with an possible error.
 | 
						|
func pointKey(measurement string, tags Tags, fields Fields, t time.Time) ([]byte, error) {
 | 
						|
	if len(fields) == 0 {
 | 
						|
		return nil, ErrPointMustHaveAField
 | 
						|
	}
 | 
						|
 | 
						|
	if !t.IsZero() {
 | 
						|
		if err := CheckTime(t); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for key, value := range fields {
 | 
						|
		switch value := value.(type) {
 | 
						|
		case float64:
 | 
						|
			// Ensure the caller validates and handles invalid field values
 | 
						|
			if math.IsNaN(value) {
 | 
						|
				return nil, fmt.Errorf("NaN is an unsupported value for field %s", key)
 | 
						|
			}
 | 
						|
		case float32:
 | 
						|
			// Ensure the caller validates and handles invalid field values
 | 
						|
			if math.IsNaN(float64(value)) {
 | 
						|
				return nil, fmt.Errorf("NaN is an unsupported value for field %s", key)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if len(key) == 0 {
 | 
						|
			return nil, fmt.Errorf("all fields must have non-empty names")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	key := MakeKey([]byte(measurement), tags)
 | 
						|
	for field := range fields {
 | 
						|
		sz := seriesKeySize(key, []byte(field))
 | 
						|
		if sz > MaxKeyLength {
 | 
						|
			return nil, fmt.Errorf("max key length exceeded: %v > %v", sz, MaxKeyLength)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return key, nil
 | 
						|
}
 | 
						|
 | 
						|
func seriesKeySize(key, field []byte) int {
 | 
						|
	// 4 is the length of the tsm1.fieldKeySeparator constant.  It's inlined here to avoid a circular
 | 
						|
	// dependency.
 | 
						|
	return len(key) + 4 + len(field)
 | 
						|
}
 | 
						|
 | 
						|
// NewPointFromBytes returns a new Point from a marshalled Point.
 | 
						|
func NewPointFromBytes(b []byte) (Point, error) {
 | 
						|
	p := &point{}
 | 
						|
	if err := p.UnmarshalBinary(b); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// This does some basic validation to ensure there are fields and they
 | 
						|
	// can be unmarshalled as well.
 | 
						|
	iter := p.FieldIterator()
 | 
						|
	var hasField bool
 | 
						|
	for iter.Next() {
 | 
						|
		if len(iter.FieldKey()) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		hasField = true
 | 
						|
		switch iter.Type() {
 | 
						|
		case Float:
 | 
						|
			_, err := iter.FloatValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
		case Integer:
 | 
						|
			_, err := iter.IntegerValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
		case Unsigned:
 | 
						|
			_, err := iter.UnsignedValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
		case String:
 | 
						|
			// Skip since this won't return an error
 | 
						|
		case Boolean:
 | 
						|
			_, err := iter.BooleanValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !hasField {
 | 
						|
		return nil, ErrPointMustHaveAField
 | 
						|
	}
 | 
						|
 | 
						|
	return p, nil
 | 
						|
}
 | 
						|
 | 
						|
// MustNewPoint returns a new point with the given measurement name, tags, fields and timestamp.  If
 | 
						|
// an unsupported field value (NaN) is passed, this function panics.
 | 
						|
func MustNewPoint(name string, tags Tags, fields Fields, time time.Time) Point {
 | 
						|
	pt, err := NewPoint(name, tags, fields, time)
 | 
						|
	if err != nil {
 | 
						|
		panic(err.Error())
 | 
						|
	}
 | 
						|
	return pt
 | 
						|
}
 | 
						|
 | 
						|
// Key returns the key (measurement joined with tags) of the point.
 | 
						|
func (p *point) Key() []byte {
 | 
						|
	return p.key
 | 
						|
}
 | 
						|
 | 
						|
func (p *point) name() []byte {
 | 
						|
	_, name := scanTo(p.key, 0, ',')
 | 
						|
	return name
 | 
						|
}
 | 
						|
 | 
						|
func (p *point) Name() []byte {
 | 
						|
	return escape.Unescape(p.name())
 | 
						|
}
 | 
						|
 | 
						|
// SetName updates the measurement name for the point.
 | 
						|
func (p *point) SetName(name string) {
 | 
						|
	p.cachedName = ""
 | 
						|
	p.key = MakeKey([]byte(name), p.Tags())
 | 
						|
}
 | 
						|
 | 
						|
// Time return the timestamp for the point.
 | 
						|
func (p *point) Time() time.Time {
 | 
						|
	return p.time
 | 
						|
}
 | 
						|
 | 
						|
// SetTime updates the timestamp for the point.
 | 
						|
func (p *point) SetTime(t time.Time) {
 | 
						|
	p.time = t
 | 
						|
}
 | 
						|
 | 
						|
// Round will round the timestamp of the point to the given duration.
 | 
						|
func (p *point) Round(d time.Duration) {
 | 
						|
	p.time = p.time.Round(d)
 | 
						|
}
 | 
						|
 | 
						|
// Tags returns the tag set for the point.
 | 
						|
func (p *point) Tags() Tags {
 | 
						|
	if p.cachedTags != nil {
 | 
						|
		return p.cachedTags
 | 
						|
	}
 | 
						|
	p.cachedTags = parseTags(p.key)
 | 
						|
	return p.cachedTags
 | 
						|
}
 | 
						|
 | 
						|
func (p *point) HasTag(tag []byte) bool {
 | 
						|
	if len(p.key) == 0 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	var exists bool
 | 
						|
	walkTags(p.key, func(key, value []byte) bool {
 | 
						|
		if bytes.Equal(tag, key) {
 | 
						|
			exists = true
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	})
 | 
						|
 | 
						|
	return exists
 | 
						|
}
 | 
						|
 | 
						|
func walkTags(buf []byte, fn func(key, value []byte) bool) {
 | 
						|
	if len(buf) == 0 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	pos, name := scanTo(buf, 0, ',')
 | 
						|
 | 
						|
	// it's an empty key, so there are no tags
 | 
						|
	if len(name) == 0 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	hasEscape := bytes.IndexByte(buf, '\\') != -1
 | 
						|
	i := pos + 1
 | 
						|
	var key, value []byte
 | 
						|
	for {
 | 
						|
		if i >= len(buf) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		i, key = scanTo(buf, i, '=')
 | 
						|
		i, value = scanTagValue(buf, i+1)
 | 
						|
 | 
						|
		if len(value) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if hasEscape {
 | 
						|
			if !fn(unescapeTag(key), unescapeTag(value)) {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if !fn(key, value) {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		i++
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// walkFields walks each field key and value via fn.  If fn returns false, the iteration
 | 
						|
// is stopped.  The values are the raw byte slices and not the converted types.
 | 
						|
func walkFields(buf []byte, fn func(key, value []byte) bool) {
 | 
						|
	var i int
 | 
						|
	var key, val []byte
 | 
						|
	for len(buf) > 0 {
 | 
						|
		i, key = scanTo(buf, 0, '=')
 | 
						|
		buf = buf[i+1:]
 | 
						|
		i, val = scanFieldValue(buf, 0)
 | 
						|
		buf = buf[i:]
 | 
						|
		if !fn(key, val) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// slice off comma
 | 
						|
		if len(buf) > 0 {
 | 
						|
			buf = buf[1:]
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func parseTags(buf []byte) Tags {
 | 
						|
	if len(buf) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	tags := make(Tags, bytes.Count(buf, []byte(",")))
 | 
						|
	p := 0
 | 
						|
	walkTags(buf, func(key, value []byte) bool {
 | 
						|
		tags[p].Key = key
 | 
						|
		tags[p].Value = value
 | 
						|
		p++
 | 
						|
		return true
 | 
						|
	})
 | 
						|
	return tags
 | 
						|
}
 | 
						|
 | 
						|
// MakeKey creates a key for a set of tags.
 | 
						|
func MakeKey(name []byte, tags Tags) []byte {
 | 
						|
	// unescape the name and then re-escape it to avoid double escaping.
 | 
						|
	// The key should always be stored in escaped form.
 | 
						|
	return append(EscapeMeasurement(unescapeMeasurement(name)), tags.HashKey()...)
 | 
						|
}
 | 
						|
 | 
						|
// SetTags replaces the tags for the point.
 | 
						|
func (p *point) SetTags(tags Tags) {
 | 
						|
	p.key = MakeKey(p.Name(), tags)
 | 
						|
	p.cachedTags = tags
 | 
						|
}
 | 
						|
 | 
						|
// AddTag adds or replaces a tag value for a point.
 | 
						|
func (p *point) AddTag(key, value string) {
 | 
						|
	tags := p.Tags()
 | 
						|
	tags = append(tags, Tag{Key: []byte(key), Value: []byte(value)})
 | 
						|
	sort.Sort(tags)
 | 
						|
	p.cachedTags = tags
 | 
						|
	p.key = MakeKey(p.Name(), tags)
 | 
						|
}
 | 
						|
 | 
						|
// Fields returns the fields for the point.
 | 
						|
func (p *point) Fields() (Fields, error) {
 | 
						|
	if p.cachedFields != nil {
 | 
						|
		return p.cachedFields, nil
 | 
						|
	}
 | 
						|
	cf, err := p.unmarshalBinary()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p.cachedFields = cf
 | 
						|
	return p.cachedFields, nil
 | 
						|
}
 | 
						|
 | 
						|
// SetPrecision will round a time to the specified precision.
 | 
						|
func (p *point) SetPrecision(precision string) {
 | 
						|
	switch precision {
 | 
						|
	case "n":
 | 
						|
	case "u":
 | 
						|
		p.SetTime(p.Time().Truncate(time.Microsecond))
 | 
						|
	case "ms":
 | 
						|
		p.SetTime(p.Time().Truncate(time.Millisecond))
 | 
						|
	case "s":
 | 
						|
		p.SetTime(p.Time().Truncate(time.Second))
 | 
						|
	case "m":
 | 
						|
		p.SetTime(p.Time().Truncate(time.Minute))
 | 
						|
	case "h":
 | 
						|
		p.SetTime(p.Time().Truncate(time.Hour))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// String returns the string representation of the point.
 | 
						|
func (p *point) String() string {
 | 
						|
	if p.Time().IsZero() {
 | 
						|
		return string(p.Key()) + " " + string(p.fields)
 | 
						|
	}
 | 
						|
	return string(p.Key()) + " " + string(p.fields) + " " + strconv.FormatInt(p.UnixNano(), 10)
 | 
						|
}
 | 
						|
 | 
						|
// AppendString appends the string representation of the point to buf.
 | 
						|
func (p *point) AppendString(buf []byte) []byte {
 | 
						|
	buf = append(buf, p.key...)
 | 
						|
	buf = append(buf, ' ')
 | 
						|
	buf = append(buf, p.fields...)
 | 
						|
 | 
						|
	if !p.time.IsZero() {
 | 
						|
		buf = append(buf, ' ')
 | 
						|
		buf = strconv.AppendInt(buf, p.UnixNano(), 10)
 | 
						|
	}
 | 
						|
 | 
						|
	return buf
 | 
						|
}
 | 
						|
 | 
						|
// StringSize returns the length of the string that would be returned by String().
 | 
						|
func (p *point) StringSize() int {
 | 
						|
	size := len(p.key) + len(p.fields) + 1
 | 
						|
 | 
						|
	if !p.time.IsZero() {
 | 
						|
		digits := 1 // even "0" has one digit
 | 
						|
		t := p.UnixNano()
 | 
						|
		if t < 0 {
 | 
						|
			// account for negative sign, then negate
 | 
						|
			digits++
 | 
						|
			t = -t
 | 
						|
		}
 | 
						|
		for t > 9 { // already accounted for one digit
 | 
						|
			digits++
 | 
						|
			t /= 10
 | 
						|
		}
 | 
						|
		size += digits + 1 // digits and a space
 | 
						|
	}
 | 
						|
 | 
						|
	return size
 | 
						|
}
 | 
						|
 | 
						|
// MarshalBinary returns a binary representation of the point.
 | 
						|
func (p *point) MarshalBinary() ([]byte, error) {
 | 
						|
	if len(p.fields) == 0 {
 | 
						|
		return nil, ErrPointMustHaveAField
 | 
						|
	}
 | 
						|
 | 
						|
	tb, err := p.time.MarshalBinary()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	b := make([]byte, 8+len(p.key)+len(p.fields)+len(tb))
 | 
						|
	i := 0
 | 
						|
 | 
						|
	binary.BigEndian.PutUint32(b[i:], uint32(len(p.key)))
 | 
						|
	i += 4
 | 
						|
 | 
						|
	i += copy(b[i:], p.key)
 | 
						|
 | 
						|
	binary.BigEndian.PutUint32(b[i:i+4], uint32(len(p.fields)))
 | 
						|
	i += 4
 | 
						|
 | 
						|
	i += copy(b[i:], p.fields)
 | 
						|
 | 
						|
	copy(b[i:], tb)
 | 
						|
	return b, nil
 | 
						|
}
 | 
						|
 | 
						|
// UnmarshalBinary decodes a binary representation of the point into a point struct.
 | 
						|
func (p *point) UnmarshalBinary(b []byte) error {
 | 
						|
	var n int
 | 
						|
 | 
						|
	// Read key length.
 | 
						|
	if len(b) < 4 {
 | 
						|
		return io.ErrShortBuffer
 | 
						|
	}
 | 
						|
	n, b = int(binary.BigEndian.Uint32(b[:4])), b[4:]
 | 
						|
 | 
						|
	// Read key.
 | 
						|
	if len(b) < n {
 | 
						|
		return io.ErrShortBuffer
 | 
						|
	}
 | 
						|
	p.key, b = b[:n], b[n:]
 | 
						|
 | 
						|
	// Read fields length.
 | 
						|
	if len(b) < 4 {
 | 
						|
		return io.ErrShortBuffer
 | 
						|
	}
 | 
						|
	n, b = int(binary.BigEndian.Uint32(b[:4])), b[4:]
 | 
						|
 | 
						|
	// Read fields.
 | 
						|
	if len(b) < n {
 | 
						|
		return io.ErrShortBuffer
 | 
						|
	}
 | 
						|
	p.fields, b = b[:n], b[n:]
 | 
						|
 | 
						|
	// Read timestamp.
 | 
						|
	if err := p.time.UnmarshalBinary(b); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// PrecisionString returns a string representation of the point. If there
 | 
						|
// is a timestamp associated with the point then it will be specified in the
 | 
						|
// given unit.
 | 
						|
func (p *point) PrecisionString(precision string) string {
 | 
						|
	if p.Time().IsZero() {
 | 
						|
		return fmt.Sprintf("%s %s", p.Key(), string(p.fields))
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s %s %d", p.Key(), string(p.fields),
 | 
						|
		p.UnixNano()/GetPrecisionMultiplier(precision))
 | 
						|
}
 | 
						|
 | 
						|
// RoundedString returns a string representation of the point. If there
 | 
						|
// is a timestamp associated with the point, then it will be rounded to the
 | 
						|
// given duration.
 | 
						|
func (p *point) RoundedString(d time.Duration) string {
 | 
						|
	if p.Time().IsZero() {
 | 
						|
		return fmt.Sprintf("%s %s", p.Key(), string(p.fields))
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s %s %d", p.Key(), string(p.fields),
 | 
						|
		p.time.Round(d).UnixNano())
 | 
						|
}
 | 
						|
 | 
						|
func (p *point) unmarshalBinary() (Fields, error) {
 | 
						|
	iter := p.FieldIterator()
 | 
						|
	fields := make(Fields, 8)
 | 
						|
	for iter.Next() {
 | 
						|
		if len(iter.FieldKey()) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		switch iter.Type() {
 | 
						|
		case Float:
 | 
						|
			v, err := iter.FloatValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
			fields[string(iter.FieldKey())] = v
 | 
						|
		case Integer:
 | 
						|
			v, err := iter.IntegerValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
			fields[string(iter.FieldKey())] = v
 | 
						|
		case Unsigned:
 | 
						|
			v, err := iter.UnsignedValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
			fields[string(iter.FieldKey())] = v
 | 
						|
		case String:
 | 
						|
			fields[string(iter.FieldKey())] = iter.StringValue()
 | 
						|
		case Boolean:
 | 
						|
			v, err := iter.BooleanValue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("unable to unmarshal field %s: %s", string(iter.FieldKey()), err)
 | 
						|
			}
 | 
						|
			fields[string(iter.FieldKey())] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fields, nil
 | 
						|
}
 | 
						|
 | 
						|
// HashID returns a non-cryptographic checksum of the point's key.
 | 
						|
func (p *point) HashID() uint64 {
 | 
						|
	h := NewInlineFNV64a()
 | 
						|
	h.Write(p.key)
 | 
						|
	sum := h.Sum64()
 | 
						|
	return sum
 | 
						|
}
 | 
						|
 | 
						|
// UnixNano returns the timestamp of the point as nanoseconds since Unix epoch.
 | 
						|
func (p *point) UnixNano() int64 {
 | 
						|
	return p.Time().UnixNano()
 | 
						|
}
 | 
						|
 | 
						|
// Split will attempt to return multiple points with the same timestamp whose
 | 
						|
// string representations are no longer than size. Points with a single field or
 | 
						|
// a point without a timestamp may exceed the requested size.
 | 
						|
func (p *point) Split(size int) []Point {
 | 
						|
	if p.time.IsZero() || p.StringSize() <= size {
 | 
						|
		return []Point{p}
 | 
						|
	}
 | 
						|
 | 
						|
	// key string, timestamp string, spaces
 | 
						|
	size -= len(p.key) + len(strconv.FormatInt(p.time.UnixNano(), 10)) + 2
 | 
						|
 | 
						|
	var points []Point
 | 
						|
	var start, cur int
 | 
						|
 | 
						|
	for cur < len(p.fields) {
 | 
						|
		end, _ := scanTo(p.fields, cur, '=')
 | 
						|
		end, _ = scanFieldValue(p.fields, end+1)
 | 
						|
 | 
						|
		if cur > start && end-start > size {
 | 
						|
			points = append(points, &point{
 | 
						|
				key:    p.key,
 | 
						|
				time:   p.time,
 | 
						|
				fields: p.fields[start : cur-1],
 | 
						|
			})
 | 
						|
			start = cur
 | 
						|
		}
 | 
						|
 | 
						|
		cur = end + 1
 | 
						|
	}
 | 
						|
 | 
						|
	points = append(points, &point{
 | 
						|
		key:    p.key,
 | 
						|
		time:   p.time,
 | 
						|
		fields: p.fields[start:],
 | 
						|
	})
 | 
						|
 | 
						|
	return points
 | 
						|
}
 | 
						|
 | 
						|
// Tag represents a single key/value tag pair.
 | 
						|
type Tag struct {
 | 
						|
	Key   []byte
 | 
						|
	Value []byte
 | 
						|
}
 | 
						|
 | 
						|
// NewTag returns a new Tag.
 | 
						|
func NewTag(key, value []byte) Tag {
 | 
						|
	return Tag{
 | 
						|
		Key:   key,
 | 
						|
		Value: value,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Size returns the size of the key and value.
 | 
						|
func (t Tag) Size() int { return len(t.Key) + len(t.Value) }
 | 
						|
 | 
						|
// Clone returns a shallow copy of Tag.
 | 
						|
//
 | 
						|
// Tags associated with a Point created by ParsePointsWithPrecision will hold references to the byte slice that was parsed.
 | 
						|
// Use Clone to create a Tag with new byte slices that do not refer to the argument to ParsePointsWithPrecision.
 | 
						|
func (t Tag) Clone() Tag {
 | 
						|
	other := Tag{
 | 
						|
		Key:   make([]byte, len(t.Key)),
 | 
						|
		Value: make([]byte, len(t.Value)),
 | 
						|
	}
 | 
						|
 | 
						|
	copy(other.Key, t.Key)
 | 
						|
	copy(other.Value, t.Value)
 | 
						|
 | 
						|
	return other
 | 
						|
}
 | 
						|
 | 
						|
// String returns the string reprsentation of the tag.
 | 
						|
func (t *Tag) String() string {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	buf.WriteByte('{')
 | 
						|
	buf.WriteString(string(t.Key))
 | 
						|
	buf.WriteByte(' ')
 | 
						|
	buf.WriteString(string(t.Value))
 | 
						|
	buf.WriteByte('}')
 | 
						|
	return buf.String()
 | 
						|
}
 | 
						|
 | 
						|
// Tags represents a sorted list of tags.
 | 
						|
type Tags []Tag
 | 
						|
 | 
						|
// NewTags returns a new Tags from a map.
 | 
						|
func NewTags(m map[string]string) Tags {
 | 
						|
	if len(m) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	a := make(Tags, 0, len(m))
 | 
						|
	for k, v := range m {
 | 
						|
		a = append(a, NewTag([]byte(k), []byte(v)))
 | 
						|
	}
 | 
						|
	sort.Sort(a)
 | 
						|
	return a
 | 
						|
}
 | 
						|
 | 
						|
// Keys returns the list of keys for a tag set.
 | 
						|
func (a Tags) Keys() []string {
 | 
						|
	if len(a) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	keys := make([]string, len(a))
 | 
						|
	for i, tag := range a {
 | 
						|
		keys[i] = string(tag.Key)
 | 
						|
	}
 | 
						|
	return keys
 | 
						|
}
 | 
						|
 | 
						|
// Values returns the list of values for a tag set.
 | 
						|
func (a Tags) Values() []string {
 | 
						|
	if len(a) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	values := make([]string, len(a))
 | 
						|
	for i, tag := range a {
 | 
						|
		values[i] = string(tag.Value)
 | 
						|
	}
 | 
						|
	return values
 | 
						|
}
 | 
						|
 | 
						|
// String returns the string representation of the tags.
 | 
						|
func (a Tags) String() string {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	buf.WriteByte('[')
 | 
						|
	for i := range a {
 | 
						|
		buf.WriteString(a[i].String())
 | 
						|
		if i < len(a)-1 {
 | 
						|
			buf.WriteByte(' ')
 | 
						|
		}
 | 
						|
	}
 | 
						|
	buf.WriteByte(']')
 | 
						|
	return buf.String()
 | 
						|
}
 | 
						|
 | 
						|
// Size returns the number of bytes needed to store all tags. Note, this is
 | 
						|
// the number of bytes needed to store all keys and values and does not account
 | 
						|
// for data structures or delimiters for example.
 | 
						|
func (a Tags) Size() int {
 | 
						|
	var total int
 | 
						|
	for _, t := range a {
 | 
						|
		total += t.Size()
 | 
						|
	}
 | 
						|
	return total
 | 
						|
}
 | 
						|
 | 
						|
// Clone returns a copy of the slice where the elements are a result of calling `Clone` on the original elements
 | 
						|
//
 | 
						|
// Tags associated with a Point created by ParsePointsWithPrecision will hold references to the byte slice that was parsed.
 | 
						|
// Use Clone to create Tags with new byte slices that do not refer to the argument to ParsePointsWithPrecision.
 | 
						|
func (a Tags) Clone() Tags {
 | 
						|
	if len(a) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	others := make(Tags, len(a))
 | 
						|
	for i := range a {
 | 
						|
		others[i] = a[i].Clone()
 | 
						|
	}
 | 
						|
 | 
						|
	return others
 | 
						|
}
 | 
						|
 | 
						|
func (a Tags) Len() int           { return len(a) }
 | 
						|
func (a Tags) Less(i, j int) bool { return bytes.Compare(a[i].Key, a[j].Key) == -1 }
 | 
						|
func (a Tags) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
						|
 | 
						|
// Equal returns true if a equals other.
 | 
						|
func (a Tags) Equal(other Tags) bool {
 | 
						|
	if len(a) != len(other) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	for i := range a {
 | 
						|
		if !bytes.Equal(a[i].Key, other[i].Key) || !bytes.Equal(a[i].Value, other[i].Value) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// CompareTags returns -1 if a < b, 1 if a > b, and 0 if a == b.
 | 
						|
func CompareTags(a, b Tags) int {
 | 
						|
	// Compare each key & value until a mismatch.
 | 
						|
	for i := 0; i < len(a) && i < len(b); i++ {
 | 
						|
		if cmp := bytes.Compare(a[i].Key, b[i].Key); cmp != 0 {
 | 
						|
			return cmp
 | 
						|
		}
 | 
						|
		if cmp := bytes.Compare(a[i].Value, b[i].Value); cmp != 0 {
 | 
						|
			return cmp
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If all tags are equal up to this point then return shorter tagset.
 | 
						|
	if len(a) < len(b) {
 | 
						|
		return -1
 | 
						|
	} else if len(a) > len(b) {
 | 
						|
		return 1
 | 
						|
	}
 | 
						|
 | 
						|
	// All tags are equal.
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
// Get returns the value for a key.
 | 
						|
func (a Tags) Get(key []byte) []byte {
 | 
						|
	// OPTIMIZE: Use sort.Search if tagset is large.
 | 
						|
 | 
						|
	for _, t := range a {
 | 
						|
		if bytes.Equal(t.Key, key) {
 | 
						|
			return t.Value
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GetString returns the string value for a string key.
 | 
						|
func (a Tags) GetString(key string) string {
 | 
						|
	return string(a.Get([]byte(key)))
 | 
						|
}
 | 
						|
 | 
						|
// Set sets the value for a key.
 | 
						|
func (a *Tags) Set(key, value []byte) {
 | 
						|
	for i, t := range *a {
 | 
						|
		if bytes.Equal(t.Key, key) {
 | 
						|
			(*a)[i].Value = value
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*a = append(*a, Tag{Key: key, Value: value})
 | 
						|
	sort.Sort(*a)
 | 
						|
}
 | 
						|
 | 
						|
// SetString sets the string value for a string key.
 | 
						|
func (a *Tags) SetString(key, value string) {
 | 
						|
	a.Set([]byte(key), []byte(value))
 | 
						|
}
 | 
						|
 | 
						|
// Delete removes a tag by key.
 | 
						|
func (a *Tags) Delete(key []byte) {
 | 
						|
	for i, t := range *a {
 | 
						|
		if bytes.Equal(t.Key, key) {
 | 
						|
			copy((*a)[i:], (*a)[i+1:])
 | 
						|
			(*a)[len(*a)-1] = Tag{}
 | 
						|
			*a = (*a)[:len(*a)-1]
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Map returns a map representation of the tags.
 | 
						|
func (a Tags) Map() map[string]string {
 | 
						|
	m := make(map[string]string, len(a))
 | 
						|
	for _, t := range a {
 | 
						|
		m[string(t.Key)] = string(t.Value)
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// Merge merges the tags combining the two. If both define a tag with the
 | 
						|
// same key, the merged value overwrites the old value.
 | 
						|
// A new map is returned.
 | 
						|
func (a Tags) Merge(other map[string]string) Tags {
 | 
						|
	merged := make(map[string]string, len(a)+len(other))
 | 
						|
	for _, t := range a {
 | 
						|
		merged[string(t.Key)] = string(t.Value)
 | 
						|
	}
 | 
						|
	for k, v := range other {
 | 
						|
		merged[k] = v
 | 
						|
	}
 | 
						|
	return NewTags(merged)
 | 
						|
}
 | 
						|
 | 
						|
// HashKey hashes all of a tag's keys.
 | 
						|
func (a Tags) HashKey() []byte {
 | 
						|
	// Empty maps marshal to empty bytes.
 | 
						|
	if len(a) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Type invariant: Tags are sorted
 | 
						|
 | 
						|
	escaped := make(Tags, 0, len(a))
 | 
						|
	sz := 0
 | 
						|
	for _, t := range a {
 | 
						|
		ek := escapeTag(t.Key)
 | 
						|
		ev := escapeTag(t.Value)
 | 
						|
 | 
						|
		if len(ev) > 0 {
 | 
						|
			escaped = append(escaped, Tag{Key: ek, Value: ev})
 | 
						|
			sz += len(ek) + len(ev)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	sz += len(escaped) + (len(escaped) * 2) // separators
 | 
						|
 | 
						|
	// Generate marshaled bytes.
 | 
						|
	b := make([]byte, sz)
 | 
						|
	buf := b
 | 
						|
	idx := 0
 | 
						|
	for _, k := range escaped {
 | 
						|
		buf[idx] = ','
 | 
						|
		idx++
 | 
						|
		copy(buf[idx:idx+len(k.Key)], k.Key)
 | 
						|
		idx += len(k.Key)
 | 
						|
		buf[idx] = '='
 | 
						|
		idx++
 | 
						|
		copy(buf[idx:idx+len(k.Value)], k.Value)
 | 
						|
		idx += len(k.Value)
 | 
						|
	}
 | 
						|
	return b[:idx]
 | 
						|
}
 | 
						|
 | 
						|
// CopyTags returns a shallow copy of tags.
 | 
						|
func CopyTags(a Tags) Tags {
 | 
						|
	other := make(Tags, len(a))
 | 
						|
	copy(other, a)
 | 
						|
	return other
 | 
						|
}
 | 
						|
 | 
						|
// DeepCopyTags returns a deep copy of tags.
 | 
						|
func DeepCopyTags(a Tags) Tags {
 | 
						|
	// Calculate size of keys/values in bytes.
 | 
						|
	var n int
 | 
						|
	for _, t := range a {
 | 
						|
		n += len(t.Key) + len(t.Value)
 | 
						|
	}
 | 
						|
 | 
						|
	// Build single allocation for all key/values.
 | 
						|
	buf := make([]byte, n)
 | 
						|
 | 
						|
	// Copy tags to new set.
 | 
						|
	other := make(Tags, len(a))
 | 
						|
	for i, t := range a {
 | 
						|
		copy(buf, t.Key)
 | 
						|
		other[i].Key, buf = buf[:len(t.Key)], buf[len(t.Key):]
 | 
						|
 | 
						|
		copy(buf, t.Value)
 | 
						|
		other[i].Value, buf = buf[:len(t.Value)], buf[len(t.Value):]
 | 
						|
	}
 | 
						|
 | 
						|
	return other
 | 
						|
}
 | 
						|
 | 
						|
// Fields represents a mapping between a Point's field names and their
 | 
						|
// values.
 | 
						|
type Fields map[string]interface{}
 | 
						|
 | 
						|
// FieldIterator retuns a FieldIterator that can be used to traverse the
 | 
						|
// fields of a point without constructing the in-memory map.
 | 
						|
func (p *point) FieldIterator() FieldIterator {
 | 
						|
	p.Reset()
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
type fieldIterator struct {
 | 
						|
	start, end  int
 | 
						|
	key, keybuf []byte
 | 
						|
	valueBuf    []byte
 | 
						|
	fieldType   FieldType
 | 
						|
}
 | 
						|
 | 
						|
// Next indicates whether there any fields remaining.
 | 
						|
func (p *point) Next() bool {
 | 
						|
	p.it.start = p.it.end
 | 
						|
	if p.it.start >= len(p.fields) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	p.it.end, p.it.key = scanTo(p.fields, p.it.start, '=')
 | 
						|
	if escape.IsEscaped(p.it.key) {
 | 
						|
		p.it.keybuf = escape.AppendUnescaped(p.it.keybuf[:0], p.it.key)
 | 
						|
		p.it.key = p.it.keybuf
 | 
						|
	}
 | 
						|
 | 
						|
	p.it.end, p.it.valueBuf = scanFieldValue(p.fields, p.it.end+1)
 | 
						|
	p.it.end++
 | 
						|
 | 
						|
	if len(p.it.valueBuf) == 0 {
 | 
						|
		p.it.fieldType = Empty
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	c := p.it.valueBuf[0]
 | 
						|
 | 
						|
	if c == '"' {
 | 
						|
		p.it.fieldType = String
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	if strings.IndexByte(`0123456789-.nNiIu`, c) >= 0 {
 | 
						|
		if p.it.valueBuf[len(p.it.valueBuf)-1] == 'i' {
 | 
						|
			p.it.fieldType = Integer
 | 
						|
			p.it.valueBuf = p.it.valueBuf[:len(p.it.valueBuf)-1]
 | 
						|
		} else if p.it.valueBuf[len(p.it.valueBuf)-1] == 'u' {
 | 
						|
			p.it.fieldType = Unsigned
 | 
						|
			p.it.valueBuf = p.it.valueBuf[:len(p.it.valueBuf)-1]
 | 
						|
		} else {
 | 
						|
			p.it.fieldType = Float
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	// to keep the same behavior that currently exists, default to boolean
 | 
						|
	p.it.fieldType = Boolean
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// FieldKey returns the key of the current field.
 | 
						|
func (p *point) FieldKey() []byte {
 | 
						|
	return p.it.key
 | 
						|
}
 | 
						|
 | 
						|
// Type returns the FieldType of the current field.
 | 
						|
func (p *point) Type() FieldType {
 | 
						|
	return p.it.fieldType
 | 
						|
}
 | 
						|
 | 
						|
// StringValue returns the string value of the current field.
 | 
						|
func (p *point) StringValue() string {
 | 
						|
	return unescapeStringField(string(p.it.valueBuf[1 : len(p.it.valueBuf)-1]))
 | 
						|
}
 | 
						|
 | 
						|
// IntegerValue returns the integer value of the current field.
 | 
						|
func (p *point) IntegerValue() (int64, error) {
 | 
						|
	n, err := parseIntBytes(p.it.valueBuf, 10, 64)
 | 
						|
	if err != nil {
 | 
						|
		return 0, fmt.Errorf("unable to parse integer value %q: %v", p.it.valueBuf, err)
 | 
						|
	}
 | 
						|
	return n, nil
 | 
						|
}
 | 
						|
 | 
						|
// UnsignedValue returns the unsigned value of the current field.
 | 
						|
func (p *point) UnsignedValue() (uint64, error) {
 | 
						|
	n, err := parseUintBytes(p.it.valueBuf, 10, 64)
 | 
						|
	if err != nil {
 | 
						|
		return 0, fmt.Errorf("unable to parse unsigned value %q: %v", p.it.valueBuf, err)
 | 
						|
	}
 | 
						|
	return n, nil
 | 
						|
}
 | 
						|
 | 
						|
// BooleanValue returns the boolean value of the current field.
 | 
						|
func (p *point) BooleanValue() (bool, error) {
 | 
						|
	b, err := parseBoolBytes(p.it.valueBuf)
 | 
						|
	if err != nil {
 | 
						|
		return false, fmt.Errorf("unable to parse bool value %q: %v", p.it.valueBuf, err)
 | 
						|
	}
 | 
						|
	return b, nil
 | 
						|
}
 | 
						|
 | 
						|
// FloatValue returns the float value of the current field.
 | 
						|
func (p *point) FloatValue() (float64, error) {
 | 
						|
	f, err := parseFloatBytes(p.it.valueBuf, 64)
 | 
						|
	if err != nil {
 | 
						|
		return 0, fmt.Errorf("unable to parse floating point value %q: %v", p.it.valueBuf, err)
 | 
						|
	}
 | 
						|
	return f, nil
 | 
						|
}
 | 
						|
 | 
						|
// Reset resets the iterator to its initial state.
 | 
						|
func (p *point) Reset() {
 | 
						|
	p.it.fieldType = Empty
 | 
						|
	p.it.key = nil
 | 
						|
	p.it.valueBuf = nil
 | 
						|
	p.it.start = 0
 | 
						|
	p.it.end = 0
 | 
						|
}
 | 
						|
 | 
						|
// MarshalBinary encodes all the fields to their proper type and returns the binary
 | 
						|
// represenation
 | 
						|
// NOTE: uint64 is specifically not supported due to potential overflow when we decode
 | 
						|
// again later to an int64
 | 
						|
// NOTE2: uint is accepted, and may be 64 bits, and is for some reason accepted...
 | 
						|
func (p Fields) MarshalBinary() []byte {
 | 
						|
	var b []byte
 | 
						|
	keys := make([]string, 0, len(p))
 | 
						|
 | 
						|
	for k := range p {
 | 
						|
		keys = append(keys, k)
 | 
						|
	}
 | 
						|
 | 
						|
	// Not really necessary, can probably be removed.
 | 
						|
	sort.Strings(keys)
 | 
						|
 | 
						|
	for i, k := range keys {
 | 
						|
		if i > 0 {
 | 
						|
			b = append(b, ',')
 | 
						|
		}
 | 
						|
		b = appendField(b, k, p[k])
 | 
						|
	}
 | 
						|
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
func appendField(b []byte, k string, v interface{}) []byte {
 | 
						|
	b = append(b, []byte(escape.String(k))...)
 | 
						|
	b = append(b, '=')
 | 
						|
 | 
						|
	// check popular types first
 | 
						|
	switch v := v.(type) {
 | 
						|
	case float64:
 | 
						|
		b = strconv.AppendFloat(b, v, 'f', -1, 64)
 | 
						|
	case int64:
 | 
						|
		b = strconv.AppendInt(b, v, 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case string:
 | 
						|
		b = append(b, '"')
 | 
						|
		b = append(b, []byte(EscapeStringField(v))...)
 | 
						|
		b = append(b, '"')
 | 
						|
	case bool:
 | 
						|
		b = strconv.AppendBool(b, v)
 | 
						|
	case int32:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case int16:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case int8:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case int:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case uint64:
 | 
						|
		b = strconv.AppendUint(b, v, 10)
 | 
						|
		b = append(b, 'u')
 | 
						|
	case uint32:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case uint16:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case uint8:
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case uint:
 | 
						|
		// TODO: 'uint' should be converted to writing as an unsigned integer,
 | 
						|
		// but we cannot since that would break backwards compatibility.
 | 
						|
		b = strconv.AppendInt(b, int64(v), 10)
 | 
						|
		b = append(b, 'i')
 | 
						|
	case float32:
 | 
						|
		b = strconv.AppendFloat(b, float64(v), 'f', -1, 32)
 | 
						|
	case []byte:
 | 
						|
		b = append(b, v...)
 | 
						|
	case nil:
 | 
						|
		// skip
 | 
						|
	default:
 | 
						|
		// Can't determine the type, so convert to string
 | 
						|
		b = append(b, '"')
 | 
						|
		b = append(b, []byte(EscapeStringField(fmt.Sprintf("%v", v)))...)
 | 
						|
		b = append(b, '"')
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
type byteSlices [][]byte
 | 
						|
 | 
						|
func (a byteSlices) Len() int           { return len(a) }
 | 
						|
func (a byteSlices) Less(i, j int) bool { return bytes.Compare(a[i], a[j]) == -1 }
 | 
						|
func (a byteSlices) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 |