Add Metrics
This commit is contained in:
parent
4dc448056c
commit
8e97af8dc3
121 changed files with 8364 additions and 3811 deletions
20
vendor/github.com/influxdata/influxdb/LICENSE
generated
vendored
20
vendor/github.com/influxdata/influxdb/LICENSE
generated
vendored
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2016 Errplane Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
vendor/github.com/influxdata/influxdb/LICENSE_OF_DEPENDENCIES.md
generated
vendored
25
vendor/github.com/influxdata/influxdb/LICENSE_OF_DEPENDENCIES.md
generated
vendored
|
@ -1,25 +0,0 @@
|
|||
# List
|
||||
- bootstrap 3.3.5 [MIT LICENSE](https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
- collectd.org [ISC LICENSE](https://github.com/collectd/go-collectd/blob/master/LICENSE)
|
||||
- github.com/BurntSushi/toml [WTFPL LICENSE](https://github.com/BurntSushi/toml/blob/master/COPYING)
|
||||
- github.com/bmizerany/pat [MIT LICENSE](https://github.com/bmizerany/pat#license)
|
||||
- github.com/boltdb/bolt [MIT LICENSE](https://github.com/boltdb/bolt/blob/master/LICENSE)
|
||||
- github.com/cespare/xxhash [MIT LICENSE](https://github.com/cespare/xxhash/blob/master/LICENSE.txt)
|
||||
- github.com/clarkduvall/hyperloglog [MIT LICENSE](https://github.com/clarkduvall/hyperloglog/blob/master/LICENSE)
|
||||
- github.com/davecgh/go-spew/spew [ISC LICENSE](https://github.com/davecgh/go-spew/blob/master/LICENSE)
|
||||
- github.com/dgrijalva/jwt-go [MIT LICENSE](https://github.com/dgrijalva/jwt-go/blob/master/LICENSE)
|
||||
- github.com/dgryski/go-bits [MIT LICENSE](https://github.com/dgryski/go-bits/blob/master/LICENSE)
|
||||
- github.com/dgryski/go-bitstream [MIT LICENSE](https://github.com/dgryski/go-bitstream/blob/master/LICENSE)
|
||||
- github.com/gogo/protobuf/proto [BSD LICENSE](https://github.com/gogo/protobuf/blob/master/LICENSE)
|
||||
- github.com/golang/snappy [BSD LICENSE](https://github.com/golang/snappy/blob/master/LICENSE)
|
||||
- github.com/google/go-cmp [BSD LICENSE](https://github.com/google/go-cmp/blob/master/LICENSE)
|
||||
- github.com/influxdata/usage-client [MIT LICENSE](https://github.com/influxdata/usage-client/blob/master/LICENSE.txt)
|
||||
- github.com/jwilder/encoding [MIT LICENSE](https://github.com/jwilder/encoding/blob/master/LICENSE)
|
||||
- github.com/paulbellamy/ratecounter [MIT LICENSE](https://github.com/paulbellamy/ratecounter/blob/master/LICENSE)
|
||||
- github.com/peterh/liner [MIT LICENSE](https://github.com/peterh/liner/blob/master/COPYING)
|
||||
- github.com/rakyll/statik [APACHE LICENSE](https://github.com/rakyll/statik/blob/master/LICENSE)
|
||||
- github.com/retailnext/hllpp [BSD LICENSE](https://github.com/retailnext/hllpp/blob/master/LICENSE)
|
||||
- github.com/uber-go/atomic [MIT LICENSE](https://github.com/uber-go/atomic/blob/master/LICENSE.txt)
|
||||
- github.com/uber-go/zap [MIT LICENSE](https://github.com/uber-go/zap/blob/master/LICENSE.txt)
|
||||
- golang.org/x/crypto [BSD LICENSE](https://github.com/golang/crypto/blob/master/LICENSE)
|
||||
- jquery 2.1.4 [MIT LICENSE](https://github.com/jquery/jquery/blob/master/LICENSE.txt)
|
48
vendor/github.com/influxdata/influxdb/models/consistency.go
generated
vendored
48
vendor/github.com/influxdata/influxdb/models/consistency.go
generated
vendored
|
@ -1,48 +0,0 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConsistencyLevel represent a required replication criteria before a write can
|
||||
// be returned as successful.
|
||||
//
|
||||
// The consistency level is handled in open-source InfluxDB but only applicable to clusters.
|
||||
type ConsistencyLevel int
|
||||
|
||||
const (
|
||||
// ConsistencyLevelAny allows for hinted handoff, potentially no write happened yet.
|
||||
ConsistencyLevelAny ConsistencyLevel = iota
|
||||
|
||||
// ConsistencyLevelOne requires at least one data node acknowledged a write.
|
||||
ConsistencyLevelOne
|
||||
|
||||
// ConsistencyLevelQuorum requires a quorum of data nodes to acknowledge a write.
|
||||
ConsistencyLevelQuorum
|
||||
|
||||
// ConsistencyLevelAll requires all data nodes to acknowledge a write.
|
||||
ConsistencyLevelAll
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidConsistencyLevel is returned when parsing the string version
|
||||
// of a consistency level.
|
||||
ErrInvalidConsistencyLevel = errors.New("invalid consistency level")
|
||||
)
|
||||
|
||||
// ParseConsistencyLevel converts a consistency level string to the corresponding ConsistencyLevel const.
|
||||
func ParseConsistencyLevel(level string) (ConsistencyLevel, error) {
|
||||
switch strings.ToLower(level) {
|
||||
case "any":
|
||||
return ConsistencyLevelAny, nil
|
||||
case "one":
|
||||
return ConsistencyLevelOne, nil
|
||||
case "quorum":
|
||||
return ConsistencyLevelQuorum, nil
|
||||
case "all":
|
||||
return ConsistencyLevelAll, nil
|
||||
default:
|
||||
return 0, ErrInvalidConsistencyLevel
|
||||
}
|
||||
}
|
21
vendor/github.com/influxdata/influxdb1-client/LICENSE
generated
vendored
Normal file
21
vendor/github.com/influxdata/influxdb1-client/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 InfluxData
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,4 +1,4 @@
|
|||
package models // import "github.com/influxdata/influxdb/models"
|
||||
package models // import "github.com/influxdata/influxdb1-client/models"
|
||||
|
||||
// from stdlib hash/fnv/fnv.go
|
||||
const (
|
|
@ -1,4 +1,4 @@
|
|||
package models // import "github.com/influxdata/influxdb/models"
|
||||
package models // import "github.com/influxdata/influxdb1-client/models"
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
@ -12,6 +12,12 @@ func parseIntBytes(b []byte, base int, bitSize int) (i int64, err error) {
|
|||
return strconv.ParseInt(s, base, bitSize)
|
||||
}
|
||||
|
||||
// parseUintBytes is a zero-alloc wrapper around strconv.ParseUint.
|
||||
func parseUintBytes(b []byte, base int, bitSize int) (i uint64, err error) {
|
||||
s := unsafeBytesToString(b)
|
||||
return strconv.ParseUint(s, base, bitSize)
|
||||
}
|
||||
|
||||
// parseFloatBytes is a zero-alloc wrapper around strconv.ParseFloat.
|
||||
func parseFloatBytes(b []byte, bitSize int) (float64, error) {
|
||||
s := unsafeBytesToString(b)
|
|
@ -1,5 +1,5 @@
|
|||
// Package models implements basic objects used throughout the TICK stack.
|
||||
package models // import "github.com/influxdata/influxdb/models"
|
||||
package models // import "github.com/influxdata/influxdb1-client/models"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -12,20 +12,27 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/influxdata/influxdb/pkg/escape"
|
||||
"github.com/influxdata/influxdb1-client/pkg/escape"
|
||||
)
|
||||
|
||||
type escapeSet struct {
|
||||
k [1]byte
|
||||
esc [2]byte
|
||||
}
|
||||
|
||||
var (
|
||||
measurementEscapeCodes = map[byte][]byte{
|
||||
',': []byte(`\,`),
|
||||
' ': []byte(`\ `),
|
||||
measurementEscapeCodes = [...]escapeSet{
|
||||
{k: [1]byte{','}, esc: [2]byte{'\\', ','}},
|
||||
{k: [1]byte{' '}, esc: [2]byte{'\\', ' '}},
|
||||
}
|
||||
|
||||
tagEscapeCodes = map[byte][]byte{
|
||||
',': []byte(`\,`),
|
||||
' ': []byte(`\ `),
|
||||
'=': []byte(`\=`),
|
||||
tagEscapeCodes = [...]escapeSet{
|
||||
{k: [1]byte{','}, esc: [2]byte{'\\', ','}},
|
||||
{k: [1]byte{' '}, esc: [2]byte{'\\', ' '}},
|
||||
{k: [1]byte{'='}, esc: [2]byte{'\\', '='}},
|
||||
}
|
||||
|
||||
// ErrPointMustHaveAField is returned when operating on a point that does not have any fields.
|
||||
|
@ -43,6 +50,16 @@ const (
|
|||
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.
|
||||
|
@ -54,6 +71,9 @@ type Point interface {
|
|||
// Tags returns the tag set for the point.
|
||||
Tags() Tags
|
||||
|
||||
// ForEachTag iterates over each tag invoking fn. If fn return false, iteration stops.
|
||||
ForEachTag(fn func(k, v []byte) bool)
|
||||
|
||||
// AddTag adds or replaces a tag value for a point.
|
||||
AddTag(key, value string)
|
||||
|
||||
|
@ -137,6 +157,9 @@ const (
|
|||
|
||||
// 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.
|
||||
|
@ -156,6 +179,9 @@ type FieldIterator interface {
|
|||
// 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)
|
||||
|
||||
|
@ -205,6 +231,12 @@ type point struct {
|
|||
it fieldIterator
|
||||
}
|
||||
|
||||
// type assertions
|
||||
var (
|
||||
_ Point = (*point)(nil)
|
||||
_ FieldIterator = (*point)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
// the number of characters for the largest possible int64 (9223372036854775807)
|
||||
maxInt64Digits = 19
|
||||
|
@ -212,6 +244,9 @@ const (
|
|||
// 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
|
||||
|
@ -238,31 +273,46 @@ func ParsePointsString(buf string) ([]Point, error) {
|
|||
// 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) {
|
||||
name, tags := ParseKeyBytes(buf)
|
||||
return string(name), tags
|
||||
}
|
||||
|
||||
func ParseKeyBytes(buf []byte) ([]byte, Tags) {
|
||||
return ParseKeyBytesWithTags(buf, nil)
|
||||
}
|
||||
|
||||
func ParseKeyBytesWithTags(buf []byte, tags Tags) ([]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
|
||||
var name []byte
|
||||
if state == tagKeyState {
|
||||
tags = parseTags(buf)
|
||||
tags = parseTags(buf, tags)
|
||||
// scanMeasurement returns the location of the comma if there are tags, strip that off
|
||||
return string(buf[:i-1]), tags
|
||||
name = buf[:i-1]
|
||||
} else {
|
||||
name = buf[:i]
|
||||
}
|
||||
return string(buf[:i]), tags
|
||||
return unescapeMeasurement(name), tags
|
||||
}
|
||||
|
||||
func ParseTags(buf []byte) (Tags, error) {
|
||||
return parseTags(buf), nil
|
||||
func ParseTags(buf []byte) Tags {
|
||||
return parseTags(buf, nil)
|
||||
}
|
||||
|
||||
func ParseName(buf []byte) ([]byte, error) {
|
||||
func ParseName(buf []byte) []byte {
|
||||
// Ignore the error because scanMeasurement returns "missing fields" which we ignore
|
||||
// when just parsing a key
|
||||
state, i, _ := scanMeasurement(buf, 0)
|
||||
var name []byte
|
||||
if state == tagKeyState {
|
||||
return buf[:i-1], nil
|
||||
name = buf[:i-1]
|
||||
} else {
|
||||
name = buf[:i]
|
||||
}
|
||||
return buf[:i], nil
|
||||
|
||||
return unescapeMeasurement(name)
|
||||
}
|
||||
|
||||
// ParsePointsWithPrecision is similar to ParsePoints, but allows the
|
||||
|
@ -285,7 +335,6 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
|
|||
continue
|
||||
}
|
||||
|
||||
// lines which start with '#' are comments
|
||||
start := skipWhitespace(block, 0)
|
||||
|
||||
// If line is all whitespace, just skip it
|
||||
|
@ -293,6 +342,7 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
|
|||
continue
|
||||
}
|
||||
|
||||
// lines which start with '#' are comments
|
||||
if block[start] == '#' {
|
||||
continue
|
||||
}
|
||||
|
@ -318,7 +368,7 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
|
|||
}
|
||||
|
||||
func parsePoint(buf []byte, defaultTime time.Time, precision string) (Point, error) {
|
||||
// scan the first block which is measurement[,tag1=value1,tag2=value=2...]
|
||||
// scan the first block which is measurement[,tag1=value1,tag2=value2...]
|
||||
pos, key, err := scanKey(buf, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -345,7 +395,7 @@ func parsePoint(buf []byte, defaultTime time.Time, precision string) (Point, err
|
|||
}
|
||||
|
||||
var maxKeyErr error
|
||||
walkFields(fields, func(k, v []byte) bool {
|
||||
err = 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
|
||||
|
@ -353,6 +403,10 @@ func parsePoint(buf []byte, defaultTime time.Time, precision string) (Point, err
|
|||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if maxKeyErr != nil {
|
||||
return nil, maxKeyErr
|
||||
}
|
||||
|
@ -815,7 +869,7 @@ func isNumeric(b byte) bool {
|
|||
// error if a invalid number is scanned.
|
||||
func scanNumber(buf []byte, i int) (int, error) {
|
||||
start := i
|
||||
var isInt bool
|
||||
var isInt, isUnsigned bool
|
||||
|
||||
// Is negative number?
|
||||
if i < len(buf) && buf[i] == '-' {
|
||||
|
@ -841,10 +895,14 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
break
|
||||
}
|
||||
|
||||
if buf[i] == 'i' && i > start && !isInt {
|
||||
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] == '.' {
|
||||
|
@ -879,7 +937,7 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
i++
|
||||
}
|
||||
|
||||
if isInt && (decimal || scientific) {
|
||||
if (isInt || isUnsigned) && (decimal || scientific) {
|
||||
return i, ErrInvalidNumber
|
||||
}
|
||||
|
||||
|
@ -914,6 +972,26 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
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 {
|
||||
|
@ -1015,7 +1093,7 @@ func scanLine(buf []byte, i int) (int, []byte) {
|
|||
}
|
||||
|
||||
// skip past escaped characters
|
||||
if buf[i] == '\\' {
|
||||
if buf[i] == '\\' && i+2 < len(buf) {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
@ -1144,24 +1222,34 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
|
|||
return i, buf[start:i]
|
||||
}
|
||||
|
||||
func escapeMeasurement(in []byte) []byte {
|
||||
for b, esc := range measurementEscapeCodes {
|
||||
in = bytes.Replace(in, []byte{b}, esc, -1)
|
||||
func EscapeMeasurement(in []byte) []byte {
|
||||
for _, c := range measurementEscapeCodes {
|
||||
if bytes.IndexByte(in, c.k[0]) != -1 {
|
||||
in = bytes.Replace(in, c.k[:], c.esc[:], -1)
|
||||
}
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func unescapeMeasurement(in []byte) []byte {
|
||||
for b, esc := range measurementEscapeCodes {
|
||||
in = bytes.Replace(in, esc, []byte{b}, -1)
|
||||
if bytes.IndexByte(in, '\\') == -1 {
|
||||
return in
|
||||
}
|
||||
|
||||
for i := range measurementEscapeCodes {
|
||||
c := &measurementEscapeCodes[i]
|
||||
if bytes.IndexByte(in, c.k[0]) != -1 {
|
||||
in = bytes.Replace(in, c.esc[:], c.k[:], -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)
|
||||
for i := range tagEscapeCodes {
|
||||
c := &tagEscapeCodes[i]
|
||||
if bytes.IndexByte(in, c.k[0]) != -1 {
|
||||
in = bytes.Replace(in, c.k[:], c.esc[:], -1)
|
||||
}
|
||||
}
|
||||
return in
|
||||
|
@ -1172,9 +1260,10 @@ func unescapeTag(in []byte) []byte {
|
|||
return in
|
||||
}
|
||||
|
||||
for b, esc := range tagEscapeCodes {
|
||||
if bytes.IndexByte(in, b) != -1 {
|
||||
in = bytes.Replace(in, esc, []byte{b}, -1)
|
||||
for i := range tagEscapeCodes {
|
||||
c := &tagEscapeCodes[i]
|
||||
if bytes.IndexByte(in, c.k[0]) != -1 {
|
||||
in = bytes.Replace(in, c.esc[:], c.k[:], -1)
|
||||
}
|
||||
}
|
||||
return in
|
||||
|
@ -1226,7 +1315,8 @@ func unescapeStringField(in string) string {
|
|||
}
|
||||
|
||||
// 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.
|
||||
// an unsupported field value (NaN, or +/-Inf) 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 {
|
||||
|
@ -1257,11 +1347,17 @@ func pointKey(measurement string, tags Tags, fields Fields, t time.Time) ([]byte
|
|||
switch value := value.(type) {
|
||||
case float64:
|
||||
// Ensure the caller validates and handles invalid field values
|
||||
if math.IsInf(value, 0) {
|
||||
return nil, fmt.Errorf("+/-Inf is an unsupported value for field %s", key)
|
||||
}
|
||||
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.IsInf(float64(value), 0) {
|
||||
return nil, fmt.Errorf("+/-Inf is an unsupported value for field %s", key)
|
||||
}
|
||||
if math.IsNaN(float64(value)) {
|
||||
return nil, fmt.Errorf("NaN is an unsupported value for field %s", key)
|
||||
}
|
||||
|
@ -1315,6 +1411,11 @@ func NewPointFromBytes(b []byte) (Point, error) {
|
|||
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:
|
||||
|
@ -1382,10 +1483,14 @@ func (p *point) Tags() Tags {
|
|||
if p.cachedTags != nil {
|
||||
return p.cachedTags
|
||||
}
|
||||
p.cachedTags = parseTags(p.key)
|
||||
p.cachedTags = parseTags(p.key, nil)
|
||||
return p.cachedTags
|
||||
}
|
||||
|
||||
func (p *point) ForEachTag(fn func(k, v []byte) bool) {
|
||||
walkTags(p.key, fn)
|
||||
}
|
||||
|
||||
func (p *point) HasTag(tag []byte) bool {
|
||||
if len(p.key) == 0 {
|
||||
return false
|
||||
|
@ -1445,11 +1550,14 @@ func walkTags(buf []byte, fn func(key, value []byte) bool) {
|
|||
|
||||
// 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) {
|
||||
func walkFields(buf []byte, fn func(key, value []byte) bool) error {
|
||||
var i int
|
||||
var key, val []byte
|
||||
for len(buf) > 0 {
|
||||
i, key = scanTo(buf, 0, '=')
|
||||
if i > len(buf)-2 {
|
||||
return fmt.Errorf("invalid value: field-key=%s", key)
|
||||
}
|
||||
buf = buf[i+1:]
|
||||
i, val = scanFieldValue(buf, 0)
|
||||
buf = buf[i:]
|
||||
|
@ -1462,26 +1570,52 @@ func walkFields(buf []byte, fn func(key, value []byte) bool) {
|
|||
buf = buf[1:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTags(buf []byte) Tags {
|
||||
// parseTags parses buf into the provided destination tags, returning destination
|
||||
// Tags, which may have a different length and capacity.
|
||||
func parseTags(buf []byte, dst Tags) Tags {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tags := make(Tags, 0, bytes.Count(buf, []byte(",")))
|
||||
n := bytes.Count(buf, []byte(","))
|
||||
if cap(dst) < n {
|
||||
dst = make(Tags, n)
|
||||
} else {
|
||||
dst = dst[:n]
|
||||
}
|
||||
|
||||
// Ensure existing behaviour when point has no tags and nil slice passed in.
|
||||
if dst == nil {
|
||||
dst = Tags{}
|
||||
}
|
||||
|
||||
// Series keys can contain escaped commas, therefore the number of commas
|
||||
// in a series key only gives an estimation of the upper bound on the number
|
||||
// of tags.
|
||||
var i int
|
||||
walkTags(buf, func(key, value []byte) bool {
|
||||
tags = append(tags, NewTag(key, value))
|
||||
dst[i].Key, dst[i].Value = key, value
|
||||
i++
|
||||
return true
|
||||
})
|
||||
return tags
|
||||
return dst[:i]
|
||||
}
|
||||
|
||||
// MakeKey creates a key for a set of tags.
|
||||
func MakeKey(name []byte, tags Tags) []byte {
|
||||
return AppendMakeKey(nil, name, tags)
|
||||
}
|
||||
|
||||
// AppendMakeKey appends the key derived from name and tags to dst and returns the extended buffer.
|
||||
func AppendMakeKey(dst []byte, 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()...)
|
||||
dst = append(dst, EscapeMeasurement(unescapeMeasurement(name))...)
|
||||
dst = tags.AppendHashKey(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// SetTags replaces the tags for the point.
|
||||
|
@ -1630,10 +1764,7 @@ func (p *point) UnmarshalBinary(b []byte) error {
|
|||
p.fields, b = b[:n], b[n:]
|
||||
|
||||
// Read timestamp.
|
||||
if err := p.time.UnmarshalBinary(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return p.time.UnmarshalBinary(b)
|
||||
}
|
||||
|
||||
// PrecisionString returns a string representation of the point. If there
|
||||
|
@ -1678,6 +1809,12 @@ func (p *point) unmarshalBinary() (Fields, error) {
|
|||
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:
|
||||
|
@ -1708,7 +1845,7 @@ func (p *point) UnixNano() int64 {
|
|||
// 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() || len(p.String()) <= size {
|
||||
if p.time.IsZero() || p.StringSize() <= size {
|
||||
return []Point{p}
|
||||
}
|
||||
|
||||
|
@ -1803,6 +1940,82 @@ func NewTags(m map[string]string) Tags {
|
|||
return a
|
||||
}
|
||||
|
||||
// HashKey hashes all of a tag's keys.
|
||||
func (a Tags) HashKey() []byte {
|
||||
return a.AppendHashKey(nil)
|
||||
}
|
||||
|
||||
func (a Tags) needsEscape() bool {
|
||||
for i := range a {
|
||||
t := &a[i]
|
||||
for j := range tagEscapeCodes {
|
||||
c := &tagEscapeCodes[j]
|
||||
if bytes.IndexByte(t.Key, c.k[0]) != -1 || bytes.IndexByte(t.Value, c.k[0]) != -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AppendHashKey appends the result of hashing all of a tag's keys and values to dst and returns the extended buffer.
|
||||
func (a Tags) AppendHashKey(dst []byte) []byte {
|
||||
// Empty maps marshal to empty bytes.
|
||||
if len(a) == 0 {
|
||||
return dst
|
||||
}
|
||||
|
||||
// Type invariant: Tags are sorted
|
||||
|
||||
sz := 0
|
||||
var escaped Tags
|
||||
if a.needsEscape() {
|
||||
var tmp [20]Tag
|
||||
if len(a) < len(tmp) {
|
||||
escaped = tmp[:len(a)]
|
||||
} else {
|
||||
escaped = make(Tags, len(a))
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
t := &a[i]
|
||||
nt := &escaped[i]
|
||||
nt.Key = escapeTag(t.Key)
|
||||
nt.Value = escapeTag(t.Value)
|
||||
sz += len(nt.Key) + len(nt.Value)
|
||||
}
|
||||
} else {
|
||||
sz = a.Size()
|
||||
escaped = a
|
||||
}
|
||||
|
||||
sz += len(escaped) + (len(escaped) * 2) // separators
|
||||
|
||||
// Generate marshaled bytes.
|
||||
if cap(dst)-len(dst) < sz {
|
||||
nd := make([]byte, len(dst), len(dst)+sz)
|
||||
copy(nd, dst)
|
||||
dst = nd
|
||||
}
|
||||
buf := dst[len(dst) : len(dst)+sz]
|
||||
idx := 0
|
||||
for i := range escaped {
|
||||
k := &escaped[i]
|
||||
if len(k.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
buf[idx] = ','
|
||||
idx++
|
||||
copy(buf[idx:], k.Key)
|
||||
idx += len(k.Key)
|
||||
buf[idx] = '='
|
||||
idx++
|
||||
copy(buf[idx:], k.Value)
|
||||
idx += len(k.Value)
|
||||
}
|
||||
return dst[:len(dst)+idx]
|
||||
}
|
||||
|
||||
// String returns the string representation of the tags.
|
||||
func (a Tags) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
@ -1822,8 +2035,8 @@ func (a Tags) String() string {
|
|||
// for data structures or delimiters for example.
|
||||
func (a Tags) Size() int {
|
||||
var total int
|
||||
for _, t := range a {
|
||||
total += t.Size()
|
||||
for i := range a {
|
||||
total += a[i].Size()
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
@ -1919,18 +2132,6 @@ 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))
|
||||
|
@ -1940,60 +2141,6 @@ func (a Tags) Map() map[string]string {
|
|||
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))
|
||||
|
@ -2071,10 +2218,13 @@ func (p *point) Next() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if strings.IndexByte(`0123456789-.nNiI`, c) >= 0 {
|
||||
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
|
||||
}
|
||||
|
@ -2110,6 +2260,15 @@ func (p *point) IntegerValue() (int64, error) {
|
|||
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)
|
||||
|
@ -2192,6 +2351,9 @@ func appendField(b []byte, k string, v interface{}) []byte {
|
|||
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')
|
||||
|
@ -2201,10 +2363,9 @@ func appendField(b []byte, k string, v interface{}) []byte {
|
|||
case uint8:
|
||||
b = strconv.AppendInt(b, int64(v), 10)
|
||||
b = append(b, 'i')
|
||||
// TODO: 'uint' should be considered just as "dangerous" as a uint64,
|
||||
// perhaps the value should be checked and capped at MaxInt64? We could
|
||||
// then include uint64 as an accepted value
|
||||
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:
|
||||
|
@ -2224,8 +2385,29 @@ func appendField(b []byte, k string, v interface{}) []byte {
|
|||
return b
|
||||
}
|
||||
|
||||
type byteSlices [][]byte
|
||||
// ValidKeyToken returns true if the token used for measurement, tag key, or tag
|
||||
// value is a valid unicode string and only contains printable, non-replacement characters.
|
||||
func ValidKeyToken(s string) bool {
|
||||
if !utf8.ValidString(s) {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if !unicode.IsPrint(r) || r == unicode.ReplacementChar {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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] }
|
||||
// ValidKeyTokens returns true if the measurement name and all tags are valid.
|
||||
func ValidKeyTokens(name string, tags Tags) bool {
|
||||
if !ValidKeyToken(name) {
|
||||
return false
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if !ValidKeyToken(string(tag.Key)) || !ValidKeyToken(string(tag.Value)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
7
vendor/github.com/influxdata/influxdb1-client/models/uint_support.go
generated
vendored
Normal file
7
vendor/github.com/influxdata/influxdb1-client/models/uint_support.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build uint uint64
|
||||
|
||||
package models
|
||||
|
||||
func init() {
|
||||
EnableUintSupport()
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// Package escape contains utilities for escaping parts of InfluxQL
|
||||
// and InfluxDB line protocol.
|
||||
package escape // import "github.com/influxdata/influxdb/pkg/escape"
|
||||
package escape // import "github.com/influxdata/influxdb1-client/pkg/escape"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -78,7 +78,11 @@ func Unescape(in []byte) []byte {
|
|||
|
||||
i := 0
|
||||
inLen := len(in)
|
||||
var out []byte
|
||||
|
||||
// The output size will be no more than inLen. Preallocating the
|
||||
// capacity of the output is faster and uses less memory than
|
||||
// letting append() do its own (over)allocation.
|
||||
out := make([]byte, 0, inLen)
|
||||
|
||||
for {
|
||||
if i >= inLen {
|
|
@ -1,5 +1,5 @@
|
|||
// Package client (v2) is the current official Go client for InfluxDB.
|
||||
package client // import "github.com/influxdata/influxdb/client/v2"
|
||||
package client // import "github.com/influxdata/influxdb1-client/v2"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -9,13 +9,15 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/models"
|
||||
"github.com/influxdata/influxdb1-client/models"
|
||||
)
|
||||
|
||||
// HTTPConfig is the config data needed to create an HTTP Client.
|
||||
|
@ -43,6 +45,9 @@ type HTTPConfig struct {
|
|||
// TLSConfig allows the user to set their own TLS config for the HTTP
|
||||
// Client. If set, this option overrides InsecureSkipVerify.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Proxy configures the Proxy function on the HTTP client.
|
||||
Proxy func(req *http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
// BatchPointsConfig is the config data needed to create an instance of the BatchPoints struct.
|
||||
|
@ -73,6 +78,10 @@ type Client interface {
|
|||
// the UDP client.
|
||||
Query(q Query) (*Response, error)
|
||||
|
||||
// QueryAsChunk makes an InfluxDB Query on the database. This will fail if using
|
||||
// the UDP client.
|
||||
QueryAsChunk(q Query) (*ChunkedResponse, error)
|
||||
|
||||
// Close releases any resources a Client may be using.
|
||||
Close() error
|
||||
}
|
||||
|
@ -97,6 +106,7 @@ func NewHTTPClient(conf HTTPConfig) (Client, error) {
|
|||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: conf.InsecureSkipVerify,
|
||||
},
|
||||
Proxy: conf.Proxy,
|
||||
}
|
||||
if conf.TLSConfig != nil {
|
||||
tr.TLSClientConfig = conf.TLSConfig
|
||||
|
@ -118,8 +128,9 @@ func NewHTTPClient(conf HTTPConfig) (Client, error) {
|
|||
// Ping returns how long the request took, the version of the server it connected to, and an error if one occurred.
|
||||
func (c *client) Ping(timeout time.Duration) (time.Duration, string, error) {
|
||||
now := time.Now()
|
||||
|
||||
u := c.url
|
||||
u.Path = "ping"
|
||||
u.Path = path.Join(u.Path, "ping")
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
|
@ -150,7 +161,7 @@ func (c *client) Ping(timeout time.Duration) (time.Duration, string, error) {
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var err = fmt.Errorf(string(body))
|
||||
var err = errors.New(string(body))
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
|
@ -168,7 +179,7 @@ func (c *client) Close() error {
|
|||
// once the client is instantiated.
|
||||
type client struct {
|
||||
// N.B - if url.UserInfo is accessed in future modifications to the
|
||||
// methods on client, you will need to syncronise access to url.
|
||||
// methods on client, you will need to synchronize access to url.
|
||||
url url.URL
|
||||
username string
|
||||
password string
|
||||
|
@ -318,8 +329,8 @@ func (p *Point) String() string {
|
|||
|
||||
// PrecisionString returns a line-protocol string of the Point,
|
||||
// with the timestamp formatted for the given precision.
|
||||
func (p *Point) PrecisionString(precison string) string {
|
||||
return p.pt.PrecisionString(precison)
|
||||
func (p *Point) PrecisionString(precision string) string {
|
||||
return p.pt.PrecisionString(precision)
|
||||
}
|
||||
|
||||
// Name returns the measurement name of the point.
|
||||
|
@ -356,6 +367,9 @@ func (c *client) Write(bp BatchPoints) error {
|
|||
var b bytes.Buffer
|
||||
|
||||
for _, p := range bp.Points() {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
if _, err := b.WriteString(p.pt.PrecisionString(bp.Precision())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -366,7 +380,8 @@ func (c *client) Write(bp BatchPoints) error {
|
|||
}
|
||||
|
||||
u := c.url
|
||||
u.Path = "write"
|
||||
u.Path = path.Join(u.Path, "write")
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), &b)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -396,7 +411,7 @@ func (c *client) Write(bp BatchPoints) error {
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
||||
var err = fmt.Errorf(string(body))
|
||||
var err = errors.New(string(body))
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -405,12 +420,13 @@ func (c *client) Write(bp BatchPoints) error {
|
|||
|
||||
// Query defines a query to send to the server.
|
||||
type Query struct {
|
||||
Command string
|
||||
Database string
|
||||
Precision string
|
||||
Chunked bool
|
||||
ChunkSize int
|
||||
Parameters map[string]interface{}
|
||||
Command string
|
||||
Database string
|
||||
RetentionPolicy string
|
||||
Precision string
|
||||
Chunked bool
|
||||
ChunkSize int
|
||||
Parameters map[string]interface{}
|
||||
}
|
||||
|
||||
// NewQuery returns a query object.
|
||||
|
@ -424,6 +440,19 @@ func NewQuery(command, database, precision string) Query {
|
|||
}
|
||||
}
|
||||
|
||||
// NewQueryWithRP returns a query object.
|
||||
// The database, retention policy, and precision arguments can be empty strings if they are not needed
|
||||
// for the query. Setting the retention policy only works on InfluxDB versions 1.6 or greater.
|
||||
func NewQueryWithRP(command, database, retentionPolicy, precision string) Query {
|
||||
return Query{
|
||||
Command: command,
|
||||
Database: database,
|
||||
RetentionPolicy: retentionPolicy,
|
||||
Precision: precision,
|
||||
Parameters: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewQueryWithParameters returns a query object.
|
||||
// The database and precision arguments can be empty strings if they are not needed for the query.
|
||||
// parameters is a map of the parameter names used in the command to their values.
|
||||
|
@ -446,11 +475,11 @@ type Response struct {
|
|||
// It returns nil if no errors occurred on any statements.
|
||||
func (r *Response) Error() error {
|
||||
if r.Err != "" {
|
||||
return fmt.Errorf(r.Err)
|
||||
return errors.New(r.Err)
|
||||
}
|
||||
for _, result := range r.Results {
|
||||
if result.Err != "" {
|
||||
return fmt.Errorf(result.Err)
|
||||
return errors.New(result.Err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -471,55 +500,37 @@ type Result struct {
|
|||
|
||||
// Query sends a command to the server and returns the Response.
|
||||
func (c *client) Query(q Query) (*Response, error) {
|
||||
u := c.url
|
||||
u.Path = "query"
|
||||
|
||||
jsonParameters, err := json.Marshal(q.Parameters)
|
||||
|
||||
req, err := c.createDefaultRequest(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "")
|
||||
req.Header.Set("User-Agent", c.useragent)
|
||||
|
||||
if c.username != "" {
|
||||
req.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
|
||||
params := req.URL.Query()
|
||||
params.Set("q", q.Command)
|
||||
params.Set("db", q.Database)
|
||||
params.Set("params", string(jsonParameters))
|
||||
if q.Chunked {
|
||||
params.Set("chunked", "true")
|
||||
if q.ChunkSize > 0 {
|
||||
params.Set("chunk_size", strconv.Itoa(q.ChunkSize))
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
}
|
||||
|
||||
if q.Precision != "" {
|
||||
params.Set("epoch", q.Precision)
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response Response
|
||||
if q.Chunked {
|
||||
cr := NewChunkedResponse(resp.Body)
|
||||
for {
|
||||
r, err := cr.NextResponse()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
// If we got an error while decoding the response, send that back.
|
||||
return nil, err
|
||||
}
|
||||
|
@ -548,19 +559,108 @@ func (c *client) Query(q Query) (*Response, error) {
|
|||
return nil, fmt.Errorf("unable to decode json: received status code %d err: %s", resp.StatusCode, decErr)
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have an error in our json response, and didn't get statusOK
|
||||
// then send back an error
|
||||
if resp.StatusCode != http.StatusOK && response.Error() == nil {
|
||||
return &response, fmt.Errorf("received status code %d from server",
|
||||
resp.StatusCode)
|
||||
return &response, fmt.Errorf("received status code %d from server", resp.StatusCode)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// QueryAsChunk sends a command to the server and returns the Response.
|
||||
func (c *client) QueryAsChunk(q Query) (*ChunkedResponse, error) {
|
||||
req, err := c.createDefaultRequest(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := req.URL.Query()
|
||||
params.Set("chunked", "true")
|
||||
if q.ChunkSize > 0 {
|
||||
params.Set("chunk_size", strconv.Itoa(q.ChunkSize))
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewChunkedResponse(resp.Body), nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
// If we lack a X-Influxdb-Version header, then we didn't get a response from influxdb
|
||||
// but instead some other service. If the error code is also a 500+ code, then some
|
||||
// downstream loadbalancer/proxy/etc had an issue and we should report that.
|
||||
if resp.Header.Get("X-Influxdb-Version") == "" && resp.StatusCode >= http.StatusInternalServerError {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil || len(body) == 0 {
|
||||
return fmt.Errorf("received status code %d from downstream server", resp.StatusCode)
|
||||
}
|
||||
|
||||
return fmt.Errorf("received status code %d from downstream server, with response body: %q", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// If we get an unexpected content type, then it is also not from influx direct and therefore
|
||||
// we want to know what we received and what status code was returned for debugging purposes.
|
||||
if cType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")); cType != "application/json" {
|
||||
// Read up to 1kb of the body to help identify downstream errors and limit the impact of things
|
||||
// like downstream serving a large file
|
||||
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
|
||||
if err != nil || len(body) == 0 {
|
||||
return fmt.Errorf("expected json response, got empty body, with status: %v", resp.StatusCode)
|
||||
}
|
||||
|
||||
return fmt.Errorf("expected json response, got %q, with status: %v and response body: %q", cType, resp.StatusCode, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) createDefaultRequest(q Query) (*http.Request, error) {
|
||||
u := c.url
|
||||
u.Path = path.Join(u.Path, "query")
|
||||
|
||||
jsonParameters, err := json.Marshal(q.Parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "")
|
||||
req.Header.Set("User-Agent", c.useragent)
|
||||
|
||||
if c.username != "" {
|
||||
req.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
|
||||
params := req.URL.Query()
|
||||
params.Set("q", q.Command)
|
||||
params.Set("db", q.Database)
|
||||
if q.RetentionPolicy != "" {
|
||||
params.Set("rp", q.RetentionPolicy)
|
||||
}
|
||||
params.Set("params", string(jsonParameters))
|
||||
|
||||
if q.Precision != "" {
|
||||
params.Set("epoch", q.Precision)
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
return req, nil
|
||||
|
||||
}
|
||||
|
||||
// duplexReader reads responses and writes it to another writer while
|
||||
// satisfying the reader interface.
|
||||
type duplexReader struct {
|
||||
r io.Reader
|
||||
r io.ReadCloser
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
|
@ -572,6 +672,11 @@ func (r *duplexReader) Read(p []byte) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the response.
|
||||
func (r *duplexReader) Close() error {
|
||||
return r.r.Close()
|
||||
}
|
||||
|
||||
// ChunkedResponse represents a response from the server that
|
||||
// uses chunking to stream the output.
|
||||
type ChunkedResponse struct {
|
||||
|
@ -582,8 +687,12 @@ type ChunkedResponse struct {
|
|||
|
||||
// NewChunkedResponse reads a stream and produces responses from the stream.
|
||||
func NewChunkedResponse(r io.Reader) *ChunkedResponse {
|
||||
rc, ok := r.(io.ReadCloser)
|
||||
if !ok {
|
||||
rc = ioutil.NopCloser(r)
|
||||
}
|
||||
resp := &ChunkedResponse{}
|
||||
resp.duplex = &duplexReader{r: r, w: &resp.buf}
|
||||
resp.duplex = &duplexReader{r: rc, w: &resp.buf}
|
||||
resp.dec = json.NewDecoder(resp.duplex)
|
||||
resp.dec.UseNumber()
|
||||
return resp
|
||||
|
@ -592,10 +701,9 @@ func NewChunkedResponse(r io.Reader) *ChunkedResponse {
|
|||
// NextResponse reads the next line of the stream and returns a response.
|
||||
func (r *ChunkedResponse) NextResponse() (*Response, error) {
|
||||
var response Response
|
||||
|
||||
if err := r.dec.Decode(&response); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
// A decoding error happened. This probably means the server crashed
|
||||
// and sent a last-ditch error message to us. Ensure we have read the
|
||||
|
@ -607,3 +715,8 @@ func (r *ChunkedResponse) NextResponse() (*Response, error) {
|
|||
r.buf.Reset()
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// Close closes the response.
|
||||
func (r *ChunkedResponse) Close() error {
|
||||
return r.duplex.Close()
|
||||
}
|
|
@ -107,6 +107,10 @@ func (uc *udpclient) Query(q Query) (*Response, error) {
|
|||
return nil, fmt.Errorf("Querying via UDP is not supported")
|
||||
}
|
||||
|
||||
func (uc *udpclient) QueryAsChunk(q Query) (*ChunkedResponse, error) {
|
||||
return nil, fmt.Errorf("Querying via UDP is not supported")
|
||||
}
|
||||
|
||||
func (uc *udpclient) Ping(timeout time.Duration) (time.Duration, string, error) {
|
||||
return 0, "", nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue