From ee47364474bd7d1ab376a213b67fd1ae07115d26 Mon Sep 17 00:00:00 2001 From: Lou Knauer Date: Fri, 20 Aug 2021 09:39:05 +0200 Subject: [PATCH] Implement lineprotocol parser --- lineprotocol/lineprotocol.go | 73 +++++++++++++++++++++++++++++++ lineprotocol/lineprotocol_test.go | 64 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 lineprotocol/lineprotocol.go create mode 100644 lineprotocol/lineprotocol_test.go diff --git a/lineprotocol/lineprotocol.go b/lineprotocol/lineprotocol.go new file mode 100644 index 0000000..6987ffb --- /dev/null +++ b/lineprotocol/lineprotocol.go @@ -0,0 +1,73 @@ +package lineprotocol + +import ( + "errors" + "strconv" + "strings" + "time" +) + +type Metric struct { + Name string + Value float64 +} + +// measurement: node or cpu +// tags: host, cluster, cpu (cpu only if measurement is cpu) +// fields: metrics... +// t: timestamp (accuracy: seconds) +type Line struct { + measurement string + tags map[string]string + fields []Metric + t time.Time +} + +// Parse a single line as string. +// +// There is performance to be gained by implementing a parser +// that directly reads from a bufio.Scanner. +func Parse(rawline string) (*Line, error) { + line := &Line{} + parts := strings.Fields(rawline) + if len(parts) != 3 { + return nil, errors.New("line format error") + } + + tagsAndMeasurement := strings.Split(parts[0], ",") + line.measurement = tagsAndMeasurement[0] + line.tags = map[string]string{} + for i := 1; i < len(tagsAndMeasurement); i++ { + pair := strings.Split(tagsAndMeasurement[i], "=") + if len(pair) != 2 { + return nil, errors.New("line format error") + } + line.tags[pair[0]] = pair[1] + } + + rawfields := strings.Split(parts[1], ",") + line.fields = []Metric{} + for i := 0; i < len(rawfields); i++ { + pair := strings.Split(rawfields[i], "=") + if len(pair) != 2 { + return nil, errors.New("line format error") + } + field, err := strconv.ParseFloat(pair[1], 64) + if err != nil { + return nil, err + } + + line.fields = append(line.fields, Metric{ + Name: pair[0], + Value: field, + }) + } + + unixTimestamp, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return nil, err + } + + line.t = time.Unix(unixTimestamp, 0) + return line, nil +} diff --git a/lineprotocol/lineprotocol_test.go b/lineprotocol/lineprotocol_test.go new file mode 100644 index 0000000..1f17cb6 --- /dev/null +++ b/lineprotocol/lineprotocol_test.go @@ -0,0 +1,64 @@ +package lineprotocol + +import ( + "reflect" + "testing" +) + +func TestParse(t *testing.T) { + raw := `node,host=lousxps,cluster=test mem_used=4692.252,proc_total=1083,load_five=0.91,cpu_user=1.424336e+06,cpu_guest_nice=0,cpu_guest=0,mem_available=9829.848,mem_slab=514.796,mem_free=4537.956,proc_run=2,cpu_idle=2.1589764e+07,swap_total=0,mem_cached=6368.5,swap_free=0,load_fifteen=0.93,cpu_nice=196,cpu_softirq=41456,mem_buffers=489.992,mem_total=16088.7,load_one=0.84,cpu_system=517223,cpu_iowait=8994,cpu_steal=0,cpu_irq=113265,mem_sreclaimable=362.452 1629356936` + expectedMeasurement := `node` + expectedTags := map[string]string{ + "host": "lousxps", + "cluster": "test", + } + expectedFields := []Metric{ + {"mem_used", 4692.252}, + {"proc_total", 1083}, + {"load_five", 0.91}, + {"cpu_user", 1.424336e+06}, + {"cpu_guest_nice", 0}, + {"cpu_guest", 0}, + {"mem_available", 9829.848}, + {"mem_slab", 514.796}, + {"mem_free", 4537.956}, + {"proc_run", 2}, + {"cpu_idle", 2.1589764e+07}, + {"swap_total", 0}, + {"mem_cached", 6368.5}, + {"swap_free", 0}, + {"load_fifteen", 0.93}, + {"cpu_nice", 196}, + {"cpu_softirq", 41456}, + {"mem_buffers", 489.992}, + {"mem_total", 16088.7}, + {"load_one", 0.84}, + {"cpu_system", 517223}, + {"cpu_iowait", 8994}, + {"cpu_steal", 0}, + {"cpu_irq", 113265}, + {"mem_sreclaimable", 362.452}, + } + expectedTimestamp := 1629356936 + + line, err := Parse(raw) + if err != nil { + t.Error(err) + } + + if line.measurement != expectedMeasurement { + t.Error("measurement not as expected") + } + + if line.t.Unix() != int64(expectedTimestamp) { + t.Error("timestamp not as expected") + } + + if !reflect.DeepEqual(line.tags, expectedTags) { + t.Error("tags not as expected") + } + + if !reflect.DeepEqual(line.fields, expectedFields) { + t.Error("fields not as expected") + } +}