From 0b1a09c65244009ce9778cb8115f4f9dd5a5d4c4 Mon Sep 17 00:00:00 2001 From: kamaev Date: Fri, 9 Jul 2021 21:31:09 +0500 Subject: [PATCH 1/4] add carbon plaintext --- carbon/datapoint.go | 57 ++++++++++++++++++++++ carbon/datapoint_test.go | 100 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 carbon/datapoint.go create mode 100644 carbon/datapoint_test.go diff --git a/carbon/datapoint.go b/carbon/datapoint.go new file mode 100644 index 0000000..c4ce26f --- /dev/null +++ b/carbon/datapoint.go @@ -0,0 +1,57 @@ +package carbon + +import "strconv" + +const ( + nextPart = " " + nextNode = "." + nextTag = ";" + nextTagValue = "=" + nextDatapoint = "\n" +) + +// Datapoint is a value stored at a timestamp bucket. +// If no value is recorded at a particular timestamp bucket in a series, +// the value will be None (null). +// Doc: https://graphite.readthedocs.io/en/latest/terminology.html +type Datapoint struct { + path string + value float64 + timestamp int64 +} + +// NewDatapoint is a datapoint building function +func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { + return &Datapoint{ + path: name, + value: value, + timestamp: timestamp, + } +} + +// WithPrefix adds provided prefix to datapoint +// Every function call extends datapoint path from start +// For example WithPrefix("second").WithPrefix("first") is +// equal to WithPrefix("first.second") +func (d *Datapoint) WithPrefix(prefix string) *Datapoint { + d.path = prefix + nextNode + d.path + return d +} + +// WithTag adds provided tag=value pair to datapoint +// Every function call extends datapoint path +// For example WithTag("tag1","tag1Value").WithTag("tag2","tag2Value") adds ";tag1=tag1Value;tag2=tag2Value" +// While WithTag("tag2","tag2Value").WithTag("tag1","tag1Value") adds ";tag2=tag2Value;tag1=tag1Value" +func (d *Datapoint) WithTag(name, value string) *Datapoint { + d.path += nextTag + name + nextTagValue + value + return d +} + +// Plaintext returns Graphite Plaintext record form of datapoint +func (d *Datapoint) Plaintext() string { + var ( + value = strconv.FormatFloat(d.value, 'f', -1, 64) + timestamp = strconv.FormatInt(d.timestamp, 10) + ) + return d.path + nextPart + value + nextPart + timestamp + nextDatapoint +} diff --git a/carbon/datapoint_test.go b/carbon/datapoint_test.go new file mode 100644 index 0000000..3949535 --- /dev/null +++ b/carbon/datapoint_test.go @@ -0,0 +1,100 @@ +package carbon + +import "testing" + +func Test_NewDatapoint(t *testing.T) { + const ( + testName = "name" + testPrefixParentNode = "parent" + testPrefixChildNode = "child" + testFirstTagName = "firstTag" + testFirstTagValue = "firstTagValue" + testSecondTagName = "secondTag" + testSecondTagValue = "secondTagValue" + testValue = 3.14159265359 + testTimestamp = 1552503600 + ) + + t.Run("simple", func(t *testing.T) { + var ( + actual = NewDatapoint(testName, testValue, testTimestamp).Plaintext() + expected = "name 3.14159265359 1552503600\n" + ) + if actual != expected { + t.Fatalf("expected: %s\nactual: %s", expected, actual) + } + }) + + t.Run("with prefix", func(t *testing.T) { + var ( + actual = NewDatapoint(testName, testValue, testTimestamp). + WithPrefix(testPrefixParentNode). + Plaintext() + expected = "parent.name 3.14159265359 1552503600\n" + ) + if actual != expected { + t.Fatalf("expected: %s\nactual: %s", expected, actual) + } + }) + + t.Run("with nested prefix", func(t *testing.T) { + var ( + actual = NewDatapoint(testName, testValue, testTimestamp). + WithPrefix(testPrefixChildNode). + WithPrefix(testPrefixParentNode). + Plaintext() + expected = "parent.child.name 3.14159265359 1552503600\n" + ) + if actual != expected { + t.Fatalf("expected: %s\nactual: %s", expected, actual) + } + }) + + t.Run("with nested prefix and tag", func(t *testing.T) { + var ( + recordForms = []string{ + NewDatapoint(testName, testValue, testTimestamp). + WithPrefix(testPrefixChildNode). + WithPrefix(testPrefixParentNode). + WithTag(testFirstTagName, testFirstTagValue). + Plaintext(), + NewDatapoint(testName, testValue, testTimestamp). + WithTag(testFirstTagName, testFirstTagValue). + WithPrefix(testPrefixChildNode). + WithPrefix(testPrefixParentNode). + Plaintext(), + } + expected = "parent.child.name;firstTag=firstTagValue 3.14159265359 1552503600\n" + ) + for _, actual := range recordForms { + if actual != expected { + t.Fatalf("expected: %s\nactual: %s", expected, actual) + } + } + }) + + t.Run("with nested prefix and tags", func(t *testing.T) { + var ( + recordForms = []string{ + NewDatapoint(testName, testValue, testTimestamp). + WithPrefix(testPrefixChildNode). + WithPrefix(testPrefixParentNode). + WithTag(testFirstTagName, testFirstTagValue). + WithTag(testSecondTagName, testSecondTagValue). + Plaintext(), + NewDatapoint(testName, testValue, testTimestamp). + WithTag(testFirstTagName, testFirstTagValue). + WithTag(testSecondTagName, testSecondTagValue). + WithPrefix(testPrefixChildNode). + WithPrefix(testPrefixParentNode). + Plaintext(), + } + expected = "parent.child.name;firstTag=firstTagValue;secondTag=secondTagValue 3.14159265359 1552503600\n" + ) + for _, actual := range recordForms { + if actual != expected { + t.Fatalf("expected: %s\nactual: %s", expected, actual) + } + } + }) +} From 422223f56c3d223004ab5ef6239bf0d4ee3570e5 Mon Sep 17 00:00:00 2001 From: kamaev Date: Sun, 11 Jul 2021 13:12:32 +0500 Subject: [PATCH 2/4] use bytes.Buffer instead of string for path, refactor tests, make examples more carbonapi-sh, add benchmarks --- carbon/datapoint.go | 37 ++++++--- carbon/datapoint_test.go | 173 +++++++++++++++++++-------------------- 2 files changed, 110 insertions(+), 100 deletions(-) diff --git a/carbon/datapoint.go b/carbon/datapoint.go index c4ce26f..97ad4e0 100644 --- a/carbon/datapoint.go +++ b/carbon/datapoint.go @@ -1,6 +1,9 @@ package carbon -import "strconv" +import ( + "bytes" + "strconv" +) const ( nextPart = " " @@ -12,21 +15,23 @@ const ( // Datapoint is a value stored at a timestamp bucket. // If no value is recorded at a particular timestamp bucket in a series, -// the value will be None (null). +// the value will be None (null) // Doc: https://graphite.readthedocs.io/en/latest/terminology.html type Datapoint struct { - path string + path *bytes.Buffer value float64 timestamp int64 } // NewDatapoint is a datapoint building function func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { - return &Datapoint{ - path: name, + datapoint := &Datapoint{ + path: &bytes.Buffer{}, value: value, timestamp: timestamp, } + datapoint.path.WriteString(name) + return datapoint } // WithPrefix adds provided prefix to datapoint @@ -34,7 +39,11 @@ func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { // For example WithPrefix("second").WithPrefix("first") is // equal to WithPrefix("first.second") func (d *Datapoint) WithPrefix(prefix string) *Datapoint { - d.path = prefix + nextNode + d.path + path := d.path.String() + d.path.Reset() + d.path.WriteString(prefix) + d.path.WriteString(nextNode) + d.path.WriteString(path) return d } @@ -43,15 +52,23 @@ func (d *Datapoint) WithPrefix(prefix string) *Datapoint { // For example WithTag("tag1","tag1Value").WithTag("tag2","tag2Value") adds ";tag1=tag1Value;tag2=tag2Value" // While WithTag("tag2","tag2Value").WithTag("tag1","tag1Value") adds ";tag2=tag2Value;tag1=tag1Value" func (d *Datapoint) WithTag(name, value string) *Datapoint { - d.path += nextTag + name + nextTagValue + value + d.path.WriteString(nextTag) + d.path.WriteString(name) + d.path.WriteString(nextTagValue) + d.path.WriteString(value) return d } -// Plaintext returns Graphite Plaintext record form of datapoint -func (d *Datapoint) Plaintext() string { +// String returns Graphite Plaintext record form of datapoint +func (d *Datapoint) String() string { var ( value = strconv.FormatFloat(d.value, 'f', -1, 64) timestamp = strconv.FormatInt(d.timestamp, 10) ) - return d.path + nextPart + value + nextPart + timestamp + nextDatapoint + d.path.WriteString(nextPart) + d.path.WriteString(value) + d.path.WriteString(nextPart) + d.path.WriteString(timestamp) + d.path.WriteString(nextDatapoint) + return d.path.String() } diff --git a/carbon/datapoint_test.go b/carbon/datapoint_test.go index 3949535..9361aef 100644 --- a/carbon/datapoint_test.go +++ b/carbon/datapoint_test.go @@ -3,98 +3,91 @@ package carbon import "testing" func Test_NewDatapoint(t *testing.T) { - const ( - testName = "name" - testPrefixParentNode = "parent" - testPrefixChildNode = "child" - testFirstTagName = "firstTag" - testFirstTagValue = "firstTagValue" - testSecondTagName = "secondTag" - testSecondTagValue = "secondTagValue" - testValue = 3.14159265359 - testTimestamp = 1552503600 - ) + testCases := []struct { + actual string + expected string + }{ + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600).String(), + expected: "metric1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithPrefix("foo"). + String(), + expected: "foo.metric1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithPrefix("foo"). + WithPrefix("bar"). + String(), + expected: "bar.foo.metric1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithPrefix("foo"). + WithPrefix("bar"). + WithTag("cpu", "cpu1"). + String(), + expected: "bar.foo.metric1;cpu=cpu1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithTag("cpu", "cpu1"). + WithPrefix("foo"). + WithPrefix("bar"). + String(), + expected: "bar.foo.metric1;cpu=cpu1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithPrefix("foo"). + WithPrefix("bar"). + WithTag("cpu", "cpu1"). + WithTag("dc", "dc1"). + String(), + expected: "bar.foo.metric1;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n", + }, + { + actual: NewDatapoint("metric1", 3.14159265359, 1552503600). + WithTag("cpu", "cpu1"). + WithTag("dc", "dc1"). + WithPrefix("foo"). + WithPrefix("bar"). + String(), + expected: "bar.foo.metric1;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n", + }, + } - t.Run("simple", func(t *testing.T) { - var ( - actual = NewDatapoint(testName, testValue, testTimestamp).Plaintext() - expected = "name 3.14159265359 1552503600\n" - ) - if actual != expected { - t.Fatalf("expected: %s\nactual: %s", expected, actual) + for _, testCase := range testCases { + if testCase.actual != testCase.expected { + t.Fatalf("expected: %s\nactual: %s", testCase.expected, testCase.actual) } - }) - - t.Run("with prefix", func(t *testing.T) { - var ( - actual = NewDatapoint(testName, testValue, testTimestamp). - WithPrefix(testPrefixParentNode). - Plaintext() - expected = "parent.name 3.14159265359 1552503600\n" - ) - if actual != expected { - t.Fatalf("expected: %s\nactual: %s", expected, actual) - } - }) - - t.Run("with nested prefix", func(t *testing.T) { - var ( - actual = NewDatapoint(testName, testValue, testTimestamp). - WithPrefix(testPrefixChildNode). - WithPrefix(testPrefixParentNode). - Plaintext() - expected = "parent.child.name 3.14159265359 1552503600\n" - ) - if actual != expected { - t.Fatalf("expected: %s\nactual: %s", expected, actual) - } - }) + } +} - t.Run("with nested prefix and tag", func(t *testing.T) { - var ( - recordForms = []string{ - NewDatapoint(testName, testValue, testTimestamp). - WithPrefix(testPrefixChildNode). - WithPrefix(testPrefixParentNode). - WithTag(testFirstTagName, testFirstTagValue). - Plaintext(), - NewDatapoint(testName, testValue, testTimestamp). - WithTag(testFirstTagName, testFirstTagValue). - WithPrefix(testPrefixChildNode). - WithPrefix(testPrefixParentNode). - Plaintext(), - } - expected = "parent.child.name;firstTag=firstTagValue 3.14159265359 1552503600\n" - ) - for _, actual := range recordForms { - if actual != expected { - t.Fatalf("expected: %s\nactual: %s", expected, actual) - } - } - }) +func BenchmarkDatapoint_String(b *testing.B) { + for i := 0; i < b.N; i++ { + NewDatapoint("metric1", 3.14159265359, 1552503600). + WithPrefix("foo"). + WithPrefix("bar"). + WithTag("cpu", "cpu1"). + WithTag("dc", "dc1"). + WithTag("env", "stage"). + WithTag("service", "exporter"). + WithTag("version", "v0.0.1"). + WithTag("git-commit", "0b1a09c"). + String() + } +} - t.Run("with nested prefix and tags", func(t *testing.T) { - var ( - recordForms = []string{ - NewDatapoint(testName, testValue, testTimestamp). - WithPrefix(testPrefixChildNode). - WithPrefix(testPrefixParentNode). - WithTag(testFirstTagName, testFirstTagValue). - WithTag(testSecondTagName, testSecondTagValue). - Plaintext(), - NewDatapoint(testName, testValue, testTimestamp). - WithTag(testFirstTagName, testFirstTagValue). - WithTag(testSecondTagName, testSecondTagValue). - WithPrefix(testPrefixChildNode). - WithPrefix(testPrefixParentNode). - Plaintext(), - } - expected = "parent.child.name;firstTag=firstTagValue;secondTag=secondTagValue 3.14159265359 1552503600\n" - ) - for _, actual := range recordForms { - if actual != expected { - t.Fatalf("expected: %s\nactual: %s", expected, actual) - } - } - }) +func BenchmarkDatapoint_StringByName(b *testing.B) { + for i := 0; i < b.N; i++ { + NewDatapoint( + "bar.foo.metric1;cpu=cpu1;dc=dc1;env=stage;service=exporter;version=v0.0.1;git-commit=0b1a09c", + 3.14159265359, + 1552503600). + String() + } } From adeea462e0abc6754b8ff434fda688ada75e7b54 Mon Sep 17 00:00:00 2001 From: kamaev Date: Mon, 12 Jul 2021 17:38:23 +0500 Subject: [PATCH 3/4] rename path to buffer --- carbon/datapoint.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/carbon/datapoint.go b/carbon/datapoint.go index 97ad4e0..18054f3 100644 --- a/carbon/datapoint.go +++ b/carbon/datapoint.go @@ -18,7 +18,7 @@ const ( // the value will be None (null) // Doc: https://graphite.readthedocs.io/en/latest/terminology.html type Datapoint struct { - path *bytes.Buffer + buffer *bytes.Buffer value float64 timestamp int64 } @@ -26,11 +26,11 @@ type Datapoint struct { // NewDatapoint is a datapoint building function func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { datapoint := &Datapoint{ - path: &bytes.Buffer{}, + buffer: &bytes.Buffer{}, value: value, timestamp: timestamp, } - datapoint.path.WriteString(name) + datapoint.buffer.WriteString(name) return datapoint } @@ -39,11 +39,11 @@ func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { // For example WithPrefix("second").WithPrefix("first") is // equal to WithPrefix("first.second") func (d *Datapoint) WithPrefix(prefix string) *Datapoint { - path := d.path.String() - d.path.Reset() - d.path.WriteString(prefix) - d.path.WriteString(nextNode) - d.path.WriteString(path) + path := d.buffer.String() + d.buffer.Reset() + d.buffer.WriteString(prefix) + d.buffer.WriteString(nextNode) + d.buffer.WriteString(path) return d } @@ -52,10 +52,10 @@ func (d *Datapoint) WithPrefix(prefix string) *Datapoint { // For example WithTag("tag1","tag1Value").WithTag("tag2","tag2Value") adds ";tag1=tag1Value;tag2=tag2Value" // While WithTag("tag2","tag2Value").WithTag("tag1","tag1Value") adds ";tag2=tag2Value;tag1=tag1Value" func (d *Datapoint) WithTag(name, value string) *Datapoint { - d.path.WriteString(nextTag) - d.path.WriteString(name) - d.path.WriteString(nextTagValue) - d.path.WriteString(value) + d.buffer.WriteString(nextTag) + d.buffer.WriteString(name) + d.buffer.WriteString(nextTagValue) + d.buffer.WriteString(value) return d } @@ -65,10 +65,10 @@ func (d *Datapoint) String() string { value = strconv.FormatFloat(d.value, 'f', -1, 64) timestamp = strconv.FormatInt(d.timestamp, 10) ) - d.path.WriteString(nextPart) - d.path.WriteString(value) - d.path.WriteString(nextPart) - d.path.WriteString(timestamp) - d.path.WriteString(nextDatapoint) - return d.path.String() + d.buffer.WriteString(nextPart) + d.buffer.WriteString(value) + d.buffer.WriteString(nextPart) + d.buffer.WriteString(timestamp) + d.buffer.WriteString(nextDatapoint) + return d.buffer.String() } From 21cb7ed2d9988b847556099db1c6db5701314d11 Mon Sep 17 00:00:00 2001 From: kamaev Date: Mon, 19 Jul 2021 12:02:57 +0500 Subject: [PATCH 4/4] refactor --- carbon/carbon.go | 88 +++++++++++++++++++++++++++++++ carbon/carbon_test.go | 109 +++++++++++++++++++++++++++++++++++++++ carbon/datapoint.go | 74 -------------------------- carbon/datapoint_test.go | 93 --------------------------------- 4 files changed, 197 insertions(+), 167 deletions(-) create mode 100644 carbon/carbon.go create mode 100644 carbon/carbon_test.go delete mode 100644 carbon/datapoint.go delete mode 100644 carbon/datapoint_test.go diff --git a/carbon/carbon.go b/carbon/carbon.go new file mode 100644 index 0000000..06e9faf --- /dev/null +++ b/carbon/carbon.go @@ -0,0 +1,88 @@ +package carbon + +import ( + "bytes" + "strconv" +) + +var ( + nextPart = []byte(" ") + nextNode = []byte(".") + nextTag = []byte(";") + nextTagValue = []byte("=") + nextDatapoint = []byte("\n") +) + +// Metric is a Graphite metric builder +type Metric struct { + path *bytes.Buffer + tags *bytes.Buffer +} + +// NewMetric returns new Metric +func NewMetric(name string) *Metric { + metric := &Metric{ + path: &bytes.Buffer{}, + tags: &bytes.Buffer{}, + } + metric.path.WriteString(name) + return metric +} + +// AddNode adds a given node to the metric path +// For example NewMetric("first").AddNode("second") +// builds metric with path equal to "first.second" +func (m *Metric) AddNode(node string) *Metric { + m.path.Write(nextNode) + m.path.WriteString(node) + return m +} + +// AddTag puts given tag=value pair into metric tags +// For example NewMetric("first").AddNode("second").AddTag("tag1","tag1Value") +// builds metric with path "first.second" and tags ";tag1=tag1Value" +func (m *Metric) AddTag(name, value string) *Metric { + m.tags.Write(nextTag) + m.tags.WriteString(name) + m.tags.Write(nextTagValue) + m.tags.WriteString(value) + return m +} + +// Bytes returns byte representation of Graphite metric +func (m *Metric) Bytes() []byte { + if m.tags.Len() == 0 { + return m.path.Bytes() + } + return join(m.path.Bytes(), m.tags.Bytes()) +} + +// GetName returns full metric name +func (m *Metric) GetName() string { + return m.path.String() +} + +// NewDatapoint returns Graphite Plaintext record form of datapoint +// Datapoint is a value stored at a timestamp bucket +// If no value is recorded at a particular timestamp bucket in a series, +// the value will be None (null) +// Doc: https://graphite.readthedocs.io/en/latest/terminology.html +func NewDatapoint(metric *Metric, value float64, timestamp int64) []byte { + var ( + valueStr = strconv.FormatFloat(value, 'f', -1, 64) + timestampStr = strconv.FormatInt(timestamp, 10) + ) + return join(metric.Bytes(), nextPart, []byte(valueStr), nextPart, []byte(timestampStr), nextDatapoint) +} + +func join(s ...[]byte) []byte { + n := 0 + for _, v := range s { + n += len(v) + } + b, i := make([]byte, n), 0 + for _, v := range s { + i += copy(b[i:], v) + } + return b +} diff --git a/carbon/carbon_test.go b/carbon/carbon_test.go new file mode 100644 index 0000000..3800265 --- /dev/null +++ b/carbon/carbon_test.go @@ -0,0 +1,109 @@ +package carbon + +import ( + "bytes" + "testing" +) + +func Test_NewMetric(t *testing.T) { + testCases := []struct { + actual *Metric + expectedName string + expectedBytes []byte + expectedDatapoint []byte + }{ + { + actual: NewMetric("metric1"), + expectedName: "metric1", + expectedBytes: []byte("metric1"), + expectedDatapoint: []byte("metric1 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddNode("foo"), + expectedName: "metric1.foo", + expectedBytes: []byte("metric1.foo"), + expectedDatapoint: []byte("metric1.foo 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddNode("foo").AddNode("bar"), + expectedName: "metric1.foo.bar", + expectedBytes: []byte("metric1.foo.bar"), + expectedDatapoint: []byte("metric1.foo.bar 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddNode("foo").AddNode("bar").AddTag("cpu", "cpu1"), + expectedName: "metric1.foo.bar", + expectedBytes: []byte("metric1.foo.bar;cpu=cpu1"), + expectedDatapoint: []byte("metric1.foo.bar;cpu=cpu1 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddTag("cpu", "cpu1").AddNode("foo").AddNode("bar"), + expectedName: "metric1.foo.bar", + expectedBytes: []byte("metric1.foo.bar;cpu=cpu1"), + expectedDatapoint: []byte("metric1.foo.bar;cpu=cpu1 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddNode("foo").AddNode("bar").AddTag("cpu", "cpu1").AddTag("dc", "dc1"), + expectedName: "metric1.foo.bar", + expectedBytes: []byte("metric1.foo.bar;cpu=cpu1;dc=dc1"), + expectedDatapoint: []byte("metric1.foo.bar;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n"), + }, + { + actual: NewMetric("metric1").AddTag("cpu", "cpu1").AddTag("dc", "dc1").AddNode("foo").AddNode("bar"), + expectedName: "metric1.foo.bar", + expectedBytes: []byte("metric1.foo.bar;cpu=cpu1;dc=dc1"), + expectedDatapoint: []byte("metric1.foo.bar;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n"), + }, + } + + for _, testCase := range testCases { + const ( + value = 3.14159265359 + timestamp = 1552503600 + ) + var ( + actualName = testCase.actual.GetName() + actualBytes = testCase.actual.Bytes() + actualDatapoint = NewDatapoint(testCase.actual, value, timestamp) + ) + if actualName != testCase.expectedName { + t.Fatalf("expected name: %s\nactual name: %s", testCase.expectedName, actualName) + } + if bytes.Compare(actualBytes, testCase.expectedBytes) != 0 { + t.Fatalf("expected bytes: %s\nactual bytes: %s", testCase.expectedBytes, actualBytes) + } + if bytes.Compare(actualDatapoint, testCase.expectedDatapoint) != 0 { + t.Fatalf("expected datapoint: %s\nactual datapoint: %s", testCase.expectedDatapoint, actualDatapoint) + } + } +} + +func Benchmark_NewDatapointPreCreated(b *testing.B) { + metric := NewMetric("metric1"). + AddNode("foo"). + AddNode("bar"). + AddTag("cpu", "cpu1"). + AddTag("dc", "dc1"). + AddTag("env", "stage"). + AddTag("service", "exporter"). + AddTag("version", "v0.0.1"). + AddTag("git-commit", "0b1a09c") + for i := 0; i < b.N; i++ { + NewDatapoint(metric, 3.14159265359, 1552503600) + } +} + +func Benchmark_NewDatapointOnTheFly(b *testing.B) { + for i := 0; i < b.N; i++ { + metric := NewMetric("metric1"). + AddNode("foo"). + AddNode("bar"). + AddTag("cpu", "cpu1"). + AddTag("dc", "dc1"). + AddTag("env", "stage"). + AddTag("service", "exporter"). + AddTag("version", "v0.0.1"). + AddTag("git-commit", "0b1a09c") + NewDatapoint(metric, 3.14159265359, 1552503600) + } +} diff --git a/carbon/datapoint.go b/carbon/datapoint.go deleted file mode 100644 index 18054f3..0000000 --- a/carbon/datapoint.go +++ /dev/null @@ -1,74 +0,0 @@ -package carbon - -import ( - "bytes" - "strconv" -) - -const ( - nextPart = " " - nextNode = "." - nextTag = ";" - nextTagValue = "=" - nextDatapoint = "\n" -) - -// Datapoint is a value stored at a timestamp bucket. -// If no value is recorded at a particular timestamp bucket in a series, -// the value will be None (null) -// Doc: https://graphite.readthedocs.io/en/latest/terminology.html -type Datapoint struct { - buffer *bytes.Buffer - value float64 - timestamp int64 -} - -// NewDatapoint is a datapoint building function -func NewDatapoint(name string, value float64, timestamp int64) *Datapoint { - datapoint := &Datapoint{ - buffer: &bytes.Buffer{}, - value: value, - timestamp: timestamp, - } - datapoint.buffer.WriteString(name) - return datapoint -} - -// WithPrefix adds provided prefix to datapoint -// Every function call extends datapoint path from start -// For example WithPrefix("second").WithPrefix("first") is -// equal to WithPrefix("first.second") -func (d *Datapoint) WithPrefix(prefix string) *Datapoint { - path := d.buffer.String() - d.buffer.Reset() - d.buffer.WriteString(prefix) - d.buffer.WriteString(nextNode) - d.buffer.WriteString(path) - return d -} - -// WithTag adds provided tag=value pair to datapoint -// Every function call extends datapoint path -// For example WithTag("tag1","tag1Value").WithTag("tag2","tag2Value") adds ";tag1=tag1Value;tag2=tag2Value" -// While WithTag("tag2","tag2Value").WithTag("tag1","tag1Value") adds ";tag2=tag2Value;tag1=tag1Value" -func (d *Datapoint) WithTag(name, value string) *Datapoint { - d.buffer.WriteString(nextTag) - d.buffer.WriteString(name) - d.buffer.WriteString(nextTagValue) - d.buffer.WriteString(value) - return d -} - -// String returns Graphite Plaintext record form of datapoint -func (d *Datapoint) String() string { - var ( - value = strconv.FormatFloat(d.value, 'f', -1, 64) - timestamp = strconv.FormatInt(d.timestamp, 10) - ) - d.buffer.WriteString(nextPart) - d.buffer.WriteString(value) - d.buffer.WriteString(nextPart) - d.buffer.WriteString(timestamp) - d.buffer.WriteString(nextDatapoint) - return d.buffer.String() -} diff --git a/carbon/datapoint_test.go b/carbon/datapoint_test.go deleted file mode 100644 index 9361aef..0000000 --- a/carbon/datapoint_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package carbon - -import "testing" - -func Test_NewDatapoint(t *testing.T) { - testCases := []struct { - actual string - expected string - }{ - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600).String(), - expected: "metric1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithPrefix("foo"). - String(), - expected: "foo.metric1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithPrefix("foo"). - WithPrefix("bar"). - String(), - expected: "bar.foo.metric1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithPrefix("foo"). - WithPrefix("bar"). - WithTag("cpu", "cpu1"). - String(), - expected: "bar.foo.metric1;cpu=cpu1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithTag("cpu", "cpu1"). - WithPrefix("foo"). - WithPrefix("bar"). - String(), - expected: "bar.foo.metric1;cpu=cpu1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithPrefix("foo"). - WithPrefix("bar"). - WithTag("cpu", "cpu1"). - WithTag("dc", "dc1"). - String(), - expected: "bar.foo.metric1;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n", - }, - { - actual: NewDatapoint("metric1", 3.14159265359, 1552503600). - WithTag("cpu", "cpu1"). - WithTag("dc", "dc1"). - WithPrefix("foo"). - WithPrefix("bar"). - String(), - expected: "bar.foo.metric1;cpu=cpu1;dc=dc1 3.14159265359 1552503600\n", - }, - } - - for _, testCase := range testCases { - if testCase.actual != testCase.expected { - t.Fatalf("expected: %s\nactual: %s", testCase.expected, testCase.actual) - } - } -} - -func BenchmarkDatapoint_String(b *testing.B) { - for i := 0; i < b.N; i++ { - NewDatapoint("metric1", 3.14159265359, 1552503600). - WithPrefix("foo"). - WithPrefix("bar"). - WithTag("cpu", "cpu1"). - WithTag("dc", "dc1"). - WithTag("env", "stage"). - WithTag("service", "exporter"). - WithTag("version", "v0.0.1"). - WithTag("git-commit", "0b1a09c"). - String() - } -} - -func BenchmarkDatapoint_StringByName(b *testing.B) { - for i := 0; i < b.N; i++ { - NewDatapoint( - "bar.foo.metric1;cpu=cpu1;dc=dc1;env=stage;service=exporter;version=v0.0.1;git-commit=0b1a09c", - 3.14159265359, - 1552503600). - String() - } -}