Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gauge float metric #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/NeowayLabs/statsdig

go 1.19
28 changes: 26 additions & 2 deletions memsampler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
}
}

Expand All @@ -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)
Expand All @@ -60,13 +73,18 @@ 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)
}

func (s *MemSampler) add(serialized string) {
s.Lock()
s.storage[serialized] += 1
s.storageFloat[serialized] += 1
s.Unlock()
}

Expand All @@ -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]
}
24 changes: 24 additions & 0 deletions memsampler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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() {
Expand All @@ -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()
}()
}
Expand All @@ -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")
})
Expand All @@ -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)
})
Expand Down
21 changes: 18 additions & 3 deletions sampler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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,
Expand Down
64 changes: 64 additions & 0 deletions sampler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down