From 7f220a898b24824344c44020933c2acdf7001c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artus=20Crusu=C3=A9=20Andermann?= Date: Mon, 5 Dec 2022 10:06:49 -0300 Subject: [PATCH] First commit to add Gauge float metric --- go.mod | 3 +++ memsampler.go | 28 ++++++++++++++++++-- memsampler_test.go | 24 +++++++++++++++++ sampler.go | 21 ++++++++++++--- sampler_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c79f18d --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/NeowayLabs/statsdig + +go 1.19 diff --git a/memsampler.go b/memsampler.go index 1c72273..04fc2a8 100644 --- a/memsampler.go +++ b/memsampler.go @@ -11,12 +11,14 @@ import ( // It's only usage is to test metric sampling on your application. type MemSampler struct { sync.Mutex - storage map[string]int + storage map[string]int + storageFloat map[string]float64 } func NewMemSampler() *MemSampler { return &MemSampler{ - storage: map[string]int{}, + storage: map[string]int{}, + storageFloat: map[string]float64{}, } } @@ -42,6 +44,17 @@ func (s *MemSampler) GetGauge(name string, value int, tags ...Tag) int { return s.get(serialized) } +func (s *MemSampler) GaugeFloat(name string, value float64, tags ...Tag) error { + serialized := serializeGaugeFloat(name, value, tags) + s.add(serialized) + return nil +} + +func (s *MemSampler) GetGaugeFloat(name string, value float64, tags ...Tag) float64 { + serialized := serializeGaugeFloat(name, value, tags) + return s.getFloat(serialized) +} + func (s *MemSampler) Time(name string, value time.Duration, tags ...Tag) error { serialized := serializeTime(name, value, tags) s.add(serialized) @@ -60,6 +73,10 @@ func serializeGauge(name string, value int, tags []Tag) string { return fmt.Sprintf("gauge:%s:%d:%v", name, value, tags) } +func serializeGaugeFloat(name string, value float64, tags []Tag) string { + return fmt.Sprintf("gaugefloat:%s:%v:%v", name, value, tags) +} + func serializeTime(name string, value time.Duration, tags []Tag) string { return fmt.Sprintf("time:%s:%d:%v", name, value, tags) } @@ -67,6 +84,7 @@ func serializeTime(name string, value time.Duration, tags []Tag) string { func (s *MemSampler) add(serialized string) { s.Lock() s.storage[serialized] += 1 + s.storageFloat[serialized] += 1 s.Unlock() } @@ -75,3 +93,9 @@ func (s *MemSampler) get(serialized string) int { defer s.Unlock() return s.storage[serialized] } + +func (s *MemSampler) getFloat(serialized string) float64 { + s.Lock() + defer s.Unlock() + return s.storageFloat[serialized] +} diff --git a/memsampler_test.go b/memsampler_test.go index 75dd56a..6566f60 100644 --- a/memsampler_test.go +++ b/memsampler_test.go @@ -10,6 +10,7 @@ import ( func TestMemSampler(t *testing.T) { count := 100 + countFloat := 100.00 var wg sync.WaitGroup s := statsdig.NewMemSampler() tags := []statsdig.Tag{ @@ -20,6 +21,7 @@ func TestMemSampler(t *testing.T) { } wg.Add(count) + wg.Add(int(countFloat)) expectedTime := time.Duration(1000 * time.Millisecond) for i := 0; i < count; i++ { go func() { @@ -29,6 +31,9 @@ func TestMemSampler(t *testing.T) { s.Gauge("gauge", 777, tags...) s.Time("time", expectedTime) s.Time("time", expectedTime, tags...) + s.GaugeFloat("gaugefloat", 666.99) + s.GaugeFloat("gaugefloat", 777.41, tags...) + wg.Done() wg.Done() }() } @@ -45,6 +50,17 @@ func TestMemSampler(t *testing.T) { } } + checkMetricFloat := func(name string, counterFloat func() float64) { + if counterFloat() != countFloat { + t.Fatalf( + "%s: expected %v but got %v", + name, + counterFloat(), + countFloat, + ) + } + } + checkMetric("count", func() int { return s.GetCount("count") }) @@ -61,6 +77,14 @@ func TestMemSampler(t *testing.T) { return s.GetGauge("gauge", 777, tags...) }) + checkMetricFloat("gaugefloat", func() float64 { + return s.GetGaugeFloat("gaugefloat", 666.99) + }) + + checkMetricFloat("gaugeFloatWithTags", func() float64 { + return s.GetGaugeFloat("gaugefloat", 777.41, tags...) + }) + checkMetric("time", func() int { return s.GetTime("time", expectedTime) }) diff --git a/sampler.go b/sampler.go index f5fa64d..426c414 100644 --- a/sampler.go +++ b/sampler.go @@ -18,6 +18,10 @@ type Tag struct { Value string } +type valueType interface { + int | float64 +} + // Sampler abstraction, makes it easy to have multiple implementations // of a sampler, which can be useful to testing type Sampler interface { @@ -28,6 +32,9 @@ type Sampler interface { // Gauge sets the gauge with the given name to the given value Gauge(name string, value int, tags ...Tag) error + // GaugeFloat sets the gauge with the given name to the given float64 value + GaugeFloat(name string, value float64, tags ...Tag) error + // Time sets duration in milliseconds with the given name to the given value Time(name string, value time.Duration, tags ...Tag) error } @@ -84,6 +91,14 @@ func (sampler *UDPSampler) Gauge(name string, value int, tags ...Tag) error { return sampler.send(message) } +// Gauge sends a gauge metric as specified here: +// https://github.com/b/statsd_spec#gauges +func (sampler *UDPSampler) GaugeFloat(name string, value float64, tags ...Tag) error { + countType := "gf" + message := serialize(name, value, countType, tags...) + return sampler.send(message) +} + // Time sends a time metric as specified here: // https://github.com/b/statsd_spec#timers func (sampler *UDPSampler) Time(name string, value time.Duration, tags ...Tag) error { @@ -107,9 +122,9 @@ func (sampler *UDPSampler) send(message []byte) error { return nil } -func serialize( +func serialize[VT valueType]( name string, - value int, + value VT, metricType string, tags ...Tag, ) []byte { @@ -122,7 +137,7 @@ func serialize( fulltags += "#" + strings.Join(strtags, ",") } return []byte(fmt.Sprintf( - "%s%s:%d|%s", + "%s%s:%v|%s", name, fulltags, value, diff --git a/sampler_test.go b/sampler_test.go index a2431e4..b0f4fb9 100644 --- a/sampler_test.go +++ b/sampler_test.go @@ -164,6 +164,14 @@ type gaugecase struct { Gauge int } +type gaugefloatcase struct { + Name string + Metric string + Tags []statsdig.Tag + Result string + GaugeFloat float64 +} + func ExampleUDPSampler_Count() { // Creating a Sysdig specific sampler sampler, err := statsdig.NewSysdigSampler() @@ -281,6 +289,62 @@ func TestGauge(t *testing.T) { } } +func TestGaugeFloat(t *testing.T) { + + cases := []gaugefloatcase{ + gaugefloatcase{ + Name: "testGaugeFloat", + Metric: "testGaugeFloat", + GaugeFloat: 500.012, + Result: "testGaugeFloat:500.012|gf", + }, + gaugefloatcase{ + Name: "testGaugeFloatWithTag", + Metric: "TestGaugeFloatTag", + GaugeFloat: 666.12, + Tags: []statsdig.Tag{ + statsdig.Tag{ + Name: "tag", + Value: "gaugingfloat", + }, + }, + Result: "TestGaugeFloatTag#tag=gaugingfloat:666.12|gf", + }, + gaugefloatcase{ + Name: "testGaugeFloatWithTags", + Metric: "TestGaugeFloatTags", + GaugeFloat: 10.155, + Tags: []statsdig.Tag{ + statsdig.Tag{ + Name: "tag", + Value: "hi", + }, + statsdig.Tag{ + Name: "tag2", + Value: "1", + }, + }, + Result: "TestGaugeFloatTags#tag=hi,tag2=1:10.155|gf", + }, + } + + for _, tcase := range cases { + t.Run(tcase.Name, func(t *testing.T) { + testMetric( + t, + func(t *testing.T, sampler statsdig.Sampler) error { + return sampler.GaugeFloat( + tcase.Metric, + tcase.GaugeFloat, + tcase.Tags..., + ) + }, + tcase.Result, + ) + }) + } +} + func TestTime(t *testing.T) { type testcase struct {