Skip to content

Commit

Permalink
ensure all HEC mapped data can be marshalled to json, handling explic…
Browse files Browse the repository at this point in the history
…itly NaN and Inf values (open-telemetry#5581)
  • Loading branch information
atoulme authored Oct 4, 2021
1 parent 22d742d commit 44660a0
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 3 deletions.
25 changes: 22 additions & 3 deletions exporter/splunkhecexporter/metricdata_to_splunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,27 @@ const (
sumSuffix = "_sum"
// bucketSuffix is the bucket metric value suffix.
bucketSuffix = "_bucket"
// nanValue is the string representation of a NaN value in HEC events
nanValue = "NaN"
// plusInfValue is the string representation of a +Inf value in HEC events
plusInfValue = "+Inf"
// minusInfValue is the string representation of a -Inf value in HEC events
minusInfValue = "-Inf"
)

func sanitizeFloat(value float64) interface{} {
if math.IsNaN(value) {
return nanValue
}
if math.IsInf(value, 1) {
return plusInfValue
}
if math.IsInf(value, -1) {
return minusInfValue
}
return value
}

func metricDataToSplunk(logger *zap.Logger, data pdata.Metrics, config *Config) ([]*splunk.Event, int) {
numDroppedTimeSeries := 0
splunkMetrics := make([]*splunk.Event, 0, data.DataPointCount())
Expand Down Expand Up @@ -90,7 +109,7 @@ func metricDataToSplunk(logger *zap.Logger, data pdata.Metrics, config *Config)
case pdata.MetricValueTypeInt:
fields[metricFieldName] = dataPt.IntVal()
case pdata.MetricValueTypeDouble:
fields[metricFieldName] = dataPt.DoubleVal()
fields[metricFieldName] = sanitizeFloat(dataPt.DoubleVal())
}
fields[splunkMetricTypeKey] = pdata.MetricDataTypeGauge.String()
sm := createEvent(dataPt.Timestamp(), host, source, sourceType, index, fields)
Expand Down Expand Up @@ -157,7 +176,7 @@ func metricDataToSplunk(logger *zap.Logger, data pdata.Metrics, config *Config)
case pdata.MetricValueTypeInt:
fields[metricFieldName] = dataPt.IntVal()
case pdata.MetricValueTypeDouble:
fields[metricFieldName] = dataPt.DoubleVal()
fields[metricFieldName] = sanitizeFloat(dataPt.DoubleVal())
}
fields[splunkMetricTypeKey] = pdata.MetricDataTypeSum.String()
sm := createEvent(dataPt.Timestamp(), host, source, sourceType, index, fields)
Expand Down Expand Up @@ -191,7 +210,7 @@ func metricDataToSplunk(logger *zap.Logger, data pdata.Metrics, config *Config)
populateAttributes(fields, dataPt.Attributes())
dp := dataPt.QuantileValues().At(bi)
fields["qt"] = float64ToDimValue(dp.Quantile())
fields[metricFieldName+"_"+strconv.FormatFloat(dp.Quantile(), 'f', -1, 64)] = dp.Value()
fields[metricFieldName+"_"+strconv.FormatFloat(dp.Quantile(), 'f', -1, 64)] = sanitizeFloat(dp.Value())
fields[splunkMetricTypeKey] = pdata.MetricDataTypeSummary.String()
sm := createEvent(dataPt.Timestamp(), host, source, sourceType, index, fields)
splunkMetrics = append(splunkMetrics, sm)
Expand Down
67 changes: 67 additions & 0 deletions exporter/splunkhecexporter/metricdata_to_splunk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
package splunkhecexporter

import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"testing"
"time"

Expand Down Expand Up @@ -95,6 +98,66 @@ func Test_metricDataToSplunk(t *testing.T) {
return createDefaultConfig().(*Config)
},
},
{
name: "nan_gauge_value",
metricsDataFn: func() pdata.Metrics {
metrics := newMetricsWithResources()
ilm := metrics.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0)
gauge := ilm.Metrics().AppendEmpty()
gauge.SetName("gauge_with_dims")
gauge.SetDataType(pdata.MetricDataTypeGauge)
dp := gauge.Gauge().DataPoints().AppendEmpty()
dp.SetTimestamp(pdata.NewTimestampFromTime(tsUnix))
dp.SetDoubleVal(math.NaN())
return metrics
},
wantSplunkMetrics: []*splunk.Event{
commonSplunkMetric("gauge_with_dims", tsMSecs, []string{"k0", "k1", "metric_type"}, []interface{}{"v0", "v1", "Gauge"}, "NaN", "", "", "", "unknown"),
},
configFn: func() *Config {
return createDefaultConfig().(*Config)
},
},
{
name: "+Inf_gauge_value",
metricsDataFn: func() pdata.Metrics {
metrics := newMetricsWithResources()
ilm := metrics.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0)
gauge := ilm.Metrics().AppendEmpty()
gauge.SetName("gauge_with_dims")
gauge.SetDataType(pdata.MetricDataTypeGauge)
dp := gauge.Gauge().DataPoints().AppendEmpty()
dp.SetTimestamp(pdata.NewTimestampFromTime(tsUnix))
dp.SetDoubleVal(math.Inf(1))
return metrics
},
wantSplunkMetrics: []*splunk.Event{
commonSplunkMetric("gauge_with_dims", tsMSecs, []string{"k0", "k1", "metric_type"}, []interface{}{"v0", "v1", "Gauge"}, "+Inf", "", "", "", "unknown"),
},
configFn: func() *Config {
return createDefaultConfig().(*Config)
},
},
{
name: "-Inf_gauge_value",
metricsDataFn: func() pdata.Metrics {
metrics := newMetricsWithResources()
ilm := metrics.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0)
gauge := ilm.Metrics().AppendEmpty()
gauge.SetName("gauge_with_dims")
gauge.SetDataType(pdata.MetricDataTypeGauge)
dp := gauge.Gauge().DataPoints().AppendEmpty()
dp.SetTimestamp(pdata.NewTimestampFromTime(tsUnix))
dp.SetDoubleVal(math.Inf(-1))
return metrics
},
wantSplunkMetrics: []*splunk.Event{
commonSplunkMetric("gauge_with_dims", tsMSecs, []string{"k0", "k1", "metric_type"}, []interface{}{"v0", "v1", "Gauge"}, "-Inf", "", "", "", "unknown"),
},
configFn: func() *Config {
return createDefaultConfig().(*Config)
},
},
{
name: "nil_histogram_value",
metricsDataFn: func() pdata.Metrics {
Expand Down Expand Up @@ -545,8 +608,12 @@ func Test_metricDataToSplunk(t *testing.T) {
cfg := tt.configFn()
gotMetrics, gotNumDroppedTimeSeries := metricDataToSplunk(logger, md, cfg)
assert.Equal(t, tt.wantNumDroppedTimeseries, gotNumDroppedTimeSeries)
encoder := json.NewEncoder(ioutil.Discard)

for i, want := range tt.wantSplunkMetrics {
assert.Equal(t, want, gotMetrics[i])
err := encoder.Encode(gotMetrics[i])
assert.NoError(t, err)
}
})
}
Expand Down

0 comments on commit 44660a0

Please sign in to comment.