diff --git a/spectator/meter/age_gauge.go b/spectator/meter/age_gauge.go new file mode 100644 index 0000000..e6fdc55 --- /dev/null +++ b/spectator/meter/age_gauge.go @@ -0,0 +1,43 @@ +package meter + +import ( + "fmt" + "github.com/Netflix/spectator-go/spectator/writer" +) + +// AgeGauge represents a value that is the time in seconds since the epoch at which an event +// has successfully occurred, or 0 to use the current time in epoch seconds. After an Age Gauge +// has been set, it will continue reporting the number of seconds since the last time recorded, +// for as long as the spectatord process runs. The purpose of this metric type is to enable users +// to more easily implement the Time Since Last Success alerting pattern. +// +// To set `now()` as the last success, set a value of 0. +type AgeGauge struct { + id *Id + writer writer.Writer + meterTypeSymbol string +} + +// NewAgeGauge generates a new gauge, using the provided meter identifier. +func NewAgeGauge(id *Id, writer writer.Writer) *AgeGauge { + return &AgeGauge{id, writer, "A"} +} + +// MeterId returns the meter identifier. +func (g *AgeGauge) MeterId() *Id { + return g.id +} + +// Set records the current value. +func (g *AgeGauge) Set(value int64) { + if value >= 0 { + var line = fmt.Sprintf("%s:%s:%d", g.meterTypeSymbol, g.id.spectatordId, value) + g.writer.Write(line) + } +} + +// Now records the current time in epoch seconds in spectatord. +func (g *AgeGauge) Now() { + var line = fmt.Sprintf("%s:%s:0", g.meterTypeSymbol, g.id.spectatordId) + g.writer.Write(line) +} diff --git a/spectator/meter/age_gauge_test.go b/spectator/meter/age_gauge_test.go new file mode 100644 index 0000000..aa29fbf --- /dev/null +++ b/spectator/meter/age_gauge_test.go @@ -0,0 +1,69 @@ +package meter + +import ( + "github.com/Netflix/spectator-go/spectator/writer" + "testing" +) + +func TestAgeGauge_Set(t *testing.T) { + id := NewId("set", nil) + w := writer.MemoryWriter{} + g := NewAgeGauge(id, &w) + g.Set(100) + + expected := "A:set:100" + if w.Lines[0] != expected { + t.Errorf("Expected line to be %s, got %s", expected, w.Lines[0]) + } +} + +func TestAgeGauge_SetZero(t *testing.T) { + id := NewId("setZero", nil) + w := writer.MemoryWriter{} + g := NewAgeGauge(id, &w) + g.Set(0) + + expected := "A:setZero:0" + if w.Lines[0] != expected { + t.Errorf("Expected line to be %s, got %s", expected, w.Lines[0]) + } +} + +func TestAgeGauge_Now(t *testing.T) { + id := NewId("now", nil) + w := writer.MemoryWriter{} + g := NewAgeGauge(id, &w) + g.Now() + + expected := "A:now:0" + if w.Lines[0] != expected { + t.Errorf("Expected line to be %s, got %s", expected, w.Lines[0]) + } +} + +func TestAgeGauge_SetNegative(t *testing.T) { + id := NewId("setNegative", nil) + w := writer.MemoryWriter{} + g := NewAgeGauge(id, &w) + g.Set(-100) + + if len(w.Lines) != 0 { + t.Error("Negative values should be ignored") + } +} + +func TestAgeGauge_SetMultipleValues(t *testing.T) { + id := NewId("setMultiple", nil) + w := writer.MemoryWriter{} + g := NewAgeGauge(id, &w) + g.Set(100) + g.Set(200) + g.Set(300) + + expectedLines := []string{"A:setMultiple:100", "A:setMultiple:200", "A:setMultiple:300"} + for i, line := range w.Lines { + if line != expectedLines[i] { + t.Errorf("Expected line to be %s, got %s", expectedLines[i], line) + } + } +} diff --git a/spectator/meter/counter.go b/spectator/meter/counter.go index cbc9253..a5124b3 100644 --- a/spectator/meter/counter.go +++ b/spectator/meter/counter.go @@ -15,12 +15,12 @@ import ( type Counter struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } // NewCounter generates a new counter, using the provided meter identifier. func NewCounter(id *Id, writer writer.Writer) *Counter { - return &Counter{id, writer, 'c'} + return &Counter{id, writer, "c"} } // MeterId returns the meter identifier. @@ -30,14 +30,14 @@ func (c *Counter) MeterId() *Id { // Increment increments the counter. func (c *Counter) Increment() { - var line = fmt.Sprintf("%c:%s:%d", c.meterTypeSymbol, c.id.spectatordId, 1) + var line = fmt.Sprintf("%s:%s:%d", c.meterTypeSymbol, c.id.spectatordId, 1) c.writer.Write(line) } // AddFloat adds a specific float64 delta to the current measurement. func (c *Counter) AddFloat(delta float64) { if delta > 0.0 { - var line = fmt.Sprintf("%c:%s:%f", c.meterTypeSymbol, c.id.spectatordId, delta) + var line = fmt.Sprintf("%s:%s:%f", c.meterTypeSymbol, c.id.spectatordId, delta) c.writer.Write(line) } } @@ -45,7 +45,7 @@ func (c *Counter) AddFloat(delta float64) { // Add is to add a specific int64 delta to the current measurement. func (c *Counter) Add(delta int64) { if delta > 0 { - var line = fmt.Sprintf("%c:%s:%d", c.meterTypeSymbol, c.id.spectatordId, delta) + var line = fmt.Sprintf("%s:%s:%d", c.meterTypeSymbol, c.id.spectatordId, delta) c.writer.Write(line) } } diff --git a/spectator/meter/dist_summary.go b/spectator/meter/dist_summary.go index 9aa256a..414dc32 100644 --- a/spectator/meter/dist_summary.go +++ b/spectator/meter/dist_summary.go @@ -15,13 +15,13 @@ import ( type DistributionSummary struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } // NewDistributionSummary generates a new distribution summary, using the // provided meter identifier. func NewDistributionSummary(id *Id, writer writer.Writer) *DistributionSummary { - return &DistributionSummary{id, writer, 'd'} + return &DistributionSummary{id, writer, "d"} } // MeterId returns the meter identifier. @@ -32,7 +32,7 @@ func (d *DistributionSummary) MeterId() *Id { // Record records a new value to track within the distribution. func (d *DistributionSummary) Record(amount int64) { if amount >= 0 { - var line = fmt.Sprintf("%c:%s:%d", d.meterTypeSymbol, d.id.spectatordId, amount) + var line = fmt.Sprintf("%s:%s:%d", d.meterTypeSymbol, d.id.spectatordId, amount) d.writer.Write(line) } } diff --git a/spectator/meter/max_gauge.go b/spectator/meter/max_gauge.go index 8b548db..587d13d 100644 --- a/spectator/meter/max_gauge.go +++ b/spectator/meter/max_gauge.go @@ -16,12 +16,12 @@ import ( type MaxGauge struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } // NewMaxGauge generates a new gauge, using the provided meter identifier. func NewMaxGauge(id *Id, writer writer.Writer) *MaxGauge { - return &MaxGauge{id, writer, 'm'} + return &MaxGauge{id, writer, "m"} } // MeterId returns the meter identifier. @@ -31,6 +31,6 @@ func (g *MaxGauge) MeterId() *Id { // Set records the current value. func (g *MaxGauge) Set(value float64) { - var line = fmt.Sprintf("%c:%s:%f", g.meterTypeSymbol, g.id.spectatordId, value) + var line = fmt.Sprintf("%s:%s:%f", g.meterTypeSymbol, g.id.spectatordId, value) g.writer.Write(line) } diff --git a/spectator/meter/monotonic_counter.go b/spectator/meter/monotonic_counter.go new file mode 100644 index 0000000..1c9a03a --- /dev/null +++ b/spectator/meter/monotonic_counter.go @@ -0,0 +1,38 @@ +package meter + +import ( + "fmt" + "github.com/Netflix/spectator-go/spectator/writer" +) + +// MonotonicCounter is used to measure the rate at which some event is occurring. This +// type is safe for concurrent use. +// +// The value is a monotonically increasing number. A minimum of two samples must be received +// in order for spectatord to calculate a delta value and report it to the backend. +// +// A variety of networking metrics may be reported monotonically and this metric type provides a +// convenient means of recording these values, at the expense of a slower time-to-first metric. +type MonotonicCounter struct { + id *Id + writer writer.Writer + meterTypeSymbol string +} + +// NewMonotonicCounter generates a new counter, using the provided meter identifier. +func NewMonotonicCounter(id *Id, writer writer.Writer) *MonotonicCounter { + return &MonotonicCounter{id, writer, "C"} +} + +// MeterId returns the meter identifier. +func (c *MonotonicCounter) MeterId() *Id { + return c.id +} + +// Set is to set a specific int64 delta as the current measurement. +func (c *MonotonicCounter) Set(delta int64) { + if delta > 0 { + var line = fmt.Sprintf("%s:%s:%d", c.meterTypeSymbol, c.id.spectatordId, delta) + c.writer.Write(line) + } +} diff --git a/spectator/meter/monotonic_counter_test.go b/spectator/meter/monotonic_counter_test.go new file mode 100644 index 0000000..e4ddb7f --- /dev/null +++ b/spectator/meter/monotonic_counter_test.go @@ -0,0 +1,24 @@ +package meter + +import ( + "github.com/Netflix/spectator-go/spectator/writer" + "testing" +) + +func TestMonotonicCounter_Set(t *testing.T) { + w := writer.MemoryWriter{} + id := NewId("add", nil) + c := NewMonotonicCounter(id, &w) + + c.Set(4) + + expected := "C:add:4" + if w.Lines[0] != expected { + t.Error("Expected ", expected, " got ", w.Lines[0]) + } + + c.Set(-1) + if len(w.Lines) != 1 { + t.Error("Negative deltas should be ignored") + } +} diff --git a/spectator/meter/percentile_distsummary.go b/spectator/meter/percentile_distsummary.go index fbf0609..ef99659 100644 --- a/spectator/meter/percentile_distsummary.go +++ b/spectator/meter/percentile_distsummary.go @@ -10,7 +10,7 @@ import ( type PercentileDistributionSummary struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } func (p *PercentileDistributionSummary) MeterId() *Id { @@ -19,13 +19,13 @@ func (p *PercentileDistributionSummary) MeterId() *Id { // NewPercentileDistributionSummary creates a new *PercentileDistributionSummary using the meter identifier. func NewPercentileDistributionSummary(id *Id, writer writer.Writer) *PercentileDistributionSummary { - return &PercentileDistributionSummary{id, writer, 'D'} + return &PercentileDistributionSummary{id, writer, "D"} } // Record records a new value to track within the distribution. func (p *PercentileDistributionSummary) Record(amount int64) { if amount >= 0 { - var line = fmt.Sprintf("%c:%s:%d", p.meterTypeSymbol, p.id.spectatordId, amount) + var line = fmt.Sprintf("%s:%s:%d", p.meterTypeSymbol, p.id.spectatordId, amount) p.writer.Write(line) } } diff --git a/spectator/meter/percentile_timer.go b/spectator/meter/percentile_timer.go index dd83a78..da290ed 100644 --- a/spectator/meter/percentile_timer.go +++ b/spectator/meter/percentile_timer.go @@ -11,14 +11,14 @@ import ( type PercentileTimer struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } func NewPercentileTimer( id *Id, writer writer.Writer, ) *PercentileTimer { - return &PercentileTimer{id, writer, 'T'} + return &PercentileTimer{id, writer, "T"} } func (t *PercentileTimer) MeterId() *Id { @@ -28,7 +28,7 @@ func (t *PercentileTimer) MeterId() *Id { // Record records the value for a single event. func (t *PercentileTimer) Record(amount time.Duration) { if amount >= 0 { - var line = fmt.Sprintf("%c:%s:%f", t.meterTypeSymbol, t.id.spectatordId, amount.Seconds()) + var line = fmt.Sprintf("%s:%s:%f", t.meterTypeSymbol, t.id.spectatordId, amount.Seconds()) t.writer.Write(line) } } diff --git a/spectator/meter/timer.go b/spectator/meter/timer.go index 70cddbe..52caac9 100644 --- a/spectator/meter/timer.go +++ b/spectator/meter/timer.go @@ -11,12 +11,12 @@ import ( type Timer struct { id *Id writer writer.Writer - meterTypeSymbol rune + meterTypeSymbol string } // NewTimer generates a new timer, using the provided meter identifier. func NewTimer(id *Id, writer writer.Writer) *Timer { - return &Timer{id, writer, 't'} + return &Timer{id, writer, "t"} } // MeterId returns the meter identifier. @@ -27,7 +27,7 @@ func (t *Timer) MeterId() *Id { // Record records the duration this specific event took. func (t *Timer) Record(amount time.Duration) { if amount >= 0 { - var line = fmt.Sprintf("%c:%s:%f", t.meterTypeSymbol, t.id.spectatordId, amount.Seconds()) + var line = fmt.Sprintf("%s:%s:%f", t.meterTypeSymbol, t.id.spectatordId, amount.Seconds()) t.writer.Write(line) } }