Allow NaN as a valid value on the graphite service

The canonical graphite implementation will read and discard NaN values
instead of throwing an error when reading on the line receiver protocol.
Since this is the default behavior for graphite, InfluxDB should have
the same behavior for compatibility.

Previously, a NaN value would result in an error printed to the console.
When you have a large number of NaN values being sent every minute, this
results in the log file filling with useless messages.
pull/4846/head
Jonathan A. Sternberg 2015-11-19 14:50:10 -05:00
parent 1e9e7c2d94
commit 5fa36639db
6 changed files with 35 additions and 1 deletions

View File

@ -58,6 +58,7 @@ There are breaking changes in this release:
- [#4681](https://github.com/influxdb/influxdb/pull/4681): Increase default buffer size for collectd and graphite listeners
- [#4659](https://github.com/influxdb/influxdb/pull/4659): Support IF EXISTS for DROP DATABASE
- [#4685](https://github.com/influxdb/influxdb/pull/4685): Automatically promote node to raft peer if drop server results in removing a raft peer.
- [#4846](https://github.com/influxdb/influxdb/pull/4846): Allow NaN as a valid value on the graphite service; discard these points silently (graphite compatibility). Thanks @jsternberg!
### Bugfixes
- [#4193](https://github.com/influxdb/influxdb/issues/4193): Less than or equal to inequality is not inclusive for time in where clause

View File

@ -0,0 +1,12 @@
package graphite
import "fmt"
type ErrUnsupportedValue struct {
Field string
Value float64
}
func (err *ErrUnsupportedValue) Error() string {
return fmt.Sprintf(`field "%s" value: "%v" is unsupported`, err.Field, err.Value)
}

View File

@ -117,7 +117,7 @@ func (p *Parser) Parse(line string) (models.Point, error) {
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return nil, fmt.Errorf(`field "%s" value: '%v" is unsupported`, fields[0], v)
return nil, &ErrUnsupportedValue{Field: fields[0], Value: v}
}
fieldValues := map[string]interface{}{}

View File

@ -1,6 +1,7 @@
package graphite_test
import (
"reflect"
"strconv"
"testing"
"time"
@ -227,6 +228,10 @@ func TestParseNaN(t *testing.T) {
if err == nil {
t.Fatalf("expected error. got nil")
}
if _, ok := err.(*graphite.ErrUnsupportedValue); !ok {
t.Fatalf("expected *graphite.ErrUnsupportedValue, got %v", reflect.TypeOf(err))
}
}
func TestFilterMatchDefault(t *testing.T) {

View File

@ -5,6 +5,7 @@ import (
"expvar"
"fmt"
"log"
"math"
"net"
"os"
"strings"
@ -28,6 +29,7 @@ const (
statPointsReceived = "pointsRx"
statBytesReceived = "bytesRx"
statPointsParseFail = "pointsParseFail"
statPointsNaNFail = "pointsNaNFail"
statPointsUnsupported = "pointsUnsupportedFail"
statBatchesTrasmitted = "batchesTx"
statPointsTransmitted = "pointsTx"
@ -334,6 +336,14 @@ func (s *Service) handleLine(line string) {
// Parse it.
point, err := s.parser.Parse(line)
if err != nil {
switch err := err.(type) {
case *ErrUnsupportedValue:
// Graphite ignores NaN values with no error.
if math.IsNaN(err.Value) {
s.statMap.Add(statPointsNaNFail, 1)
return
}
}
s.logger.Printf("unable to parse line: %s: %s", line, err)
s.statMap.Add(statPointsParseFail, 1)
return

View File

@ -48,7 +48,10 @@ func Test_ServerGraphiteTCP(t *testing.T) {
t.Fatalf("unexpected database: %s", req.Database)
} else if req.RetentionPolicy != "" {
t.Fatalf("unexpected retention policy: %s", req.RetentionPolicy)
} else if len(req.Points) != 1 {
t.Fatalf("expected 1 point, got %d", len(req.Points))
} else if req.Points[0].String() != pt.String() {
t.Fatalf("expected point %v, got %v", pt.String(), req.Points[0].String())
}
return nil
},
@ -74,6 +77,9 @@ func Test_ServerGraphiteTCP(t *testing.T) {
data := []byte(`cpu 23.456 `)
data = append(data, []byte(fmt.Sprintf("%d", now.Unix()))...)
data = append(data, '\n')
data = append(data, []byte(`memory NaN `)...)
data = append(data, []byte(fmt.Sprintf("%d", now.Unix()))...)
data = append(data, '\n')
_, err = conn.Write(data)
conn.Close()
if err != nil {