forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add carbon2 serializer (influxdata#5345)
- Loading branch information
1 parent
1c69b7a
commit cf500d1
Showing
6 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Carbon2 | ||
|
||
The `carbon2` serializer translates the Telegraf metric format to the [Carbon2 format](http://metrics20.org/implementations/). | ||
|
||
### Configuration | ||
|
||
```toml | ||
[[outputs.file]] | ||
## Files to write to, "stdout" is a specially handled file. | ||
files = ["stdout", "/tmp/metrics.out"] | ||
|
||
## Data format to output. | ||
## Each data format has its own unique set of configuration options, read | ||
## more about them here: | ||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md | ||
data_format = "carbon2" | ||
``` | ||
|
||
Standard form: | ||
``` | ||
metric=name field=field_1 host=foo 30 1234567890 | ||
metric=name field=field_2 host=foo 4 1234567890 | ||
metric=name field=field_N host=foo 59 1234567890 | ||
``` | ||
|
||
### Metrics | ||
|
||
The serializer converts the metrics by creating `intrinsic_tags` using the combination of metric name and fields. So, if one Telegraf metric has 4 fields, the `carbon2` output will be 4 separate metrics. There will be a `metric` tag that represents the name of the metric and a `field` tag to represent the field. | ||
|
||
### Example | ||
|
||
If we take the following InfluxDB Line Protocol: | ||
|
||
``` | ||
weather,location=us-midwest,season=summer temperature=82,wind=100 1234567890 | ||
``` | ||
|
||
after serializing in Carbon2, the result would be: | ||
|
||
``` | ||
metric=weather field=temperature location=us-midwest season=summer 82 1234567890 | ||
metric=weather field=wind location=us-midwest season=summer 100 1234567890 | ||
``` | ||
|
||
### Fields and Tags with spaces | ||
When a field key or tag key/value have spaces, spaces will be replaced with `_`. | ||
|
||
### Tags with empty values | ||
When a tag's value is empty, it will be replaced with `null` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package carbon2 | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/influxdata/telegraf" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type serializer struct { | ||
} | ||
|
||
func NewSerializer() (*serializer, error) { | ||
s := &serializer{} | ||
return s, nil | ||
} | ||
|
||
func (s *serializer) Serialize(metric telegraf.Metric) ([]byte, error) { | ||
return s.createObject(metric), nil | ||
} | ||
|
||
func (s *serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) { | ||
var batch bytes.Buffer | ||
for _, metric := range metrics { | ||
batch.Write(s.createObject(metric)) | ||
} | ||
return batch.Bytes(), nil | ||
} | ||
|
||
func (s *serializer) createObject(metric telegraf.Metric) []byte { | ||
var m bytes.Buffer | ||
for fieldName, fieldValue := range metric.Fields() { | ||
if isNumeric(fieldValue) { | ||
m.WriteString("metric=") | ||
m.WriteString(strings.Replace(metric.Name(), " ", "_", -1)) | ||
m.WriteString(" field=") | ||
m.WriteString(strings.Replace(fieldName, " ", "_", -1)) | ||
m.WriteString(" ") | ||
for _, tag := range metric.TagList() { | ||
m.WriteString(strings.Replace(tag.Key, " ", "_", -1)) | ||
m.WriteString("=") | ||
value := tag.Value | ||
if len(value) == 0 { | ||
value = "null" | ||
} | ||
m.WriteString(strings.Replace(value, " ", "_", -1)) | ||
m.WriteString(" ") | ||
} | ||
m.WriteString(" ") | ||
m.WriteString(fmt.Sprintf("%v", fieldValue)) | ||
m.WriteString(" ") | ||
m.WriteString(strconv.FormatInt(metric.Time().Unix(), 10)) | ||
m.WriteString("\n") | ||
} | ||
} | ||
return m.Bytes() | ||
} | ||
|
||
func isNumeric(v interface{}) bool { | ||
switch v.(type) { | ||
case string: | ||
return false | ||
default: | ||
return true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package carbon2 | ||
|
||
import ( | ||
"fmt" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
"time" | ||
|
||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/metric" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func MustMetric(v telegraf.Metric, err error) telegraf.Metric { | ||
if err != nil { | ||
panic(err) | ||
} | ||
return v | ||
} | ||
|
||
func TestSerializeMetricFloat(t *testing.T) { | ||
now := time.Now() | ||
tags := map[string]string{ | ||
"cpu": "cpu0", | ||
} | ||
fields := map[string]interface{}{ | ||
"usage_idle": float64(91.5), | ||
} | ||
m, err := metric.New("cpu", tags, fields, now) | ||
assert.NoError(t, err) | ||
|
||
s, _ := NewSerializer() | ||
var buf []byte | ||
buf, err = s.Serialize(m) | ||
assert.NoError(t, err) | ||
expS := []byte(fmt.Sprintf(`metric=cpu field=usage_idle cpu=cpu0 91.5 %d`, now.Unix()) + "\n") | ||
assert.Equal(t, string(expS), string(buf)) | ||
} | ||
|
||
func TestSerializeMetricWithEmptyStringTag(t *testing.T) { | ||
now := time.Now() | ||
tags := map[string]string{ | ||
"cpu": "", | ||
} | ||
fields := map[string]interface{}{ | ||
"usage_idle": float64(91.5), | ||
} | ||
m, err := metric.New("cpu", tags, fields, now) | ||
assert.NoError(t, err) | ||
|
||
s, _ := NewSerializer() | ||
var buf []byte | ||
buf, err = s.Serialize(m) | ||
assert.NoError(t, err) | ||
expS := []byte(fmt.Sprintf(`metric=cpu field=usage_idle cpu=null 91.5 %d`, now.Unix()) + "\n") | ||
assert.Equal(t, string(expS), string(buf)) | ||
} | ||
|
||
func TestSerializeWithSpaces(t *testing.T) { | ||
now := time.Now() | ||
tags := map[string]string{ | ||
"cpu 0": "cpu 0", | ||
} | ||
fields := map[string]interface{}{ | ||
"usage_idle 1": float64(91.5), | ||
} | ||
m, err := metric.New("cpu metric", tags, fields, now) | ||
assert.NoError(t, err) | ||
|
||
s, _ := NewSerializer() | ||
var buf []byte | ||
buf, err = s.Serialize(m) | ||
assert.NoError(t, err) | ||
expS := []byte(fmt.Sprintf(`metric=cpu_metric field=usage_idle_1 cpu_0=cpu_0 91.5 %d`, now.Unix()) + "\n") | ||
assert.Equal(t, string(expS), string(buf)) | ||
} | ||
|
||
func TestSerializeMetricInt(t *testing.T) { | ||
now := time.Now() | ||
tags := map[string]string{ | ||
"cpu": "cpu0", | ||
} | ||
fields := map[string]interface{}{ | ||
"usage_idle": int64(90), | ||
} | ||
m, err := metric.New("cpu", tags, fields, now) | ||
assert.NoError(t, err) | ||
|
||
s, _ := NewSerializer() | ||
var buf []byte | ||
buf, err = s.Serialize(m) | ||
assert.NoError(t, err) | ||
|
||
expS := []byte(fmt.Sprintf(`metric=cpu field=usage_idle cpu=cpu0 90 %d`, now.Unix()) + "\n") | ||
assert.Equal(t, string(expS), string(buf)) | ||
} | ||
|
||
func TestSerializeMetricString(t *testing.T) { | ||
now := time.Now() | ||
tags := map[string]string{ | ||
"cpu": "cpu0", | ||
} | ||
fields := map[string]interface{}{ | ||
"usage_idle": "foobar", | ||
} | ||
m, err := metric.New("cpu", tags, fields, now) | ||
assert.NoError(t, err) | ||
|
||
s, _ := NewSerializer() | ||
var buf []byte | ||
buf, err = s.Serialize(m) | ||
assert.NoError(t, err) | ||
|
||
expS := []byte("") | ||
assert.Equal(t, string(expS), string(buf)) | ||
} | ||
|
||
func TestSerializeBatch(t *testing.T) { | ||
m := MustMetric( | ||
metric.New( | ||
"cpu", | ||
map[string]string{}, | ||
map[string]interface{}{ | ||
"value": 42, | ||
}, | ||
time.Unix(0, 0), | ||
), | ||
) | ||
|
||
metrics := []telegraf.Metric{m, m} | ||
s, _ := NewSerializer() | ||
buf, err := s.SerializeBatch(metrics) | ||
require.NoError(t, err) | ||
expS := []byte(`metric=cpu field=value 42 0 | ||
metric=cpu field=value 42 0 | ||
`) | ||
assert.Equal(t, string(expS), string(buf)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters