From 4a769770fe334345507b9e490d941d1ae50b8e9c Mon Sep 17 00:00:00 2001 From: James Cohen Date: Fri, 5 Dec 2014 17:26:33 +0000 Subject: [PATCH] Relative gauges --- statsdaemon.go | 70 +++++++++++++++++++++++++++++++++++++++++++-- statsdaemon_test.go | 59 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/statsdaemon.go b/statsdaemon.go index 37ec693..941d17e 100644 --- a/statsdaemon.go +++ b/statsdaemon.go @@ -32,6 +32,12 @@ type Packet struct { Sampling float32 } +type GaugeData struct { + Relative bool + Negative bool + Value uint64 +} + type Uint64Slice []uint64 func (s Uint64Slice) Len() int { return len(s) } @@ -80,6 +86,7 @@ var ( In = make(chan *Packet, MAX_UNPROCESSED_PACKETS) counters = make(map[string]int64) gauges = make(map[string]uint64) + trackedGauges = make(map[string]uint64) timers = make(map[string]Uint64Slice) countInactivity = make(map[string]int64) ) @@ -122,7 +129,39 @@ func packetHandler(s *Packet) { } timers[s.Bucket] = append(timers[s.Bucket], s.Value.(uint64)) } else if s.Modifier == "g" { - gauges[s.Bucket] = s.Value.(uint64) + var gaugeValue uint64 = 0 + _, ok := gauges[s.Bucket] + + // initialise gaugeValue as either 0 or existing value + if ok { + gaugeValue = gauges[s.Bucket] + } else { + gaugeValue = 0 + } + + gaugeData := s.Value.(GaugeData) + if gaugeData.Relative { + if gaugeData.Negative { + // subtract checking for -ve numbers + if gaugeData.Value > gaugeValue { + gaugeValue = 0 + } else { + gaugeValue -= gaugeData.Value + } + } else { + // watch out for overflows + if gaugeData.Value > (math.MaxUint64 - gaugeValue) { + gaugeValue = math.MaxUint64 + } else { + gaugeValue += gaugeData.Value + } + } + } else { + gaugeValue = gaugeData.Value + } + + gauges[s.Bucket] = gaugeValue + } else if s.Modifier == "c" { _, ok := counters[s.Bucket] if !ok { @@ -212,12 +251,15 @@ func processCounters(buffer *bytes.Buffer, now int64) int64 { func processGauges(buffer *bytes.Buffer, now int64) int64 { var num int64 + for g, c := range gauges { - if c == math.MaxUint64 { + lastValue, ok := trackedGauges[g] + + if ok && c == lastValue { continue } fmt.Fprintf(buffer, "%s %d %d\n", g, c, now) - gauges[g] = math.MaxUint64 + trackedGauges[g] = c num++ } return num @@ -349,6 +391,28 @@ func parseMessage(data []byte) []*Packet { log.Printf("ERROR: failed to ParseInt %s - %s", string(val), err) continue } + } else if mtypeStr[0] == 'g' { + var relative, negative bool + var stringToParse string + + switch val[0] { + case '+', '-': + relative = true + negative = val[0] == '-' + stringToParse = string(val[1:]) + default: + relative = false + negative = false + stringToParse = string(val) + } + + gaugeValue, err := strconv.ParseUint(stringToParse, 10, 64) + if err != nil { + log.Printf("ERROR: failed to ParseUint %s - %s", string(val), err) + continue + } + + value = GaugeData{relative, negative, gaugeValue} } else { value, err = strconv.ParseUint(string(val), 10, 64) if err != nil { diff --git a/statsdaemon_test.go b/statsdaemon_test.go index d211a96..4095287 100644 --- a/statsdaemon_test.go +++ b/statsdaemon_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "github.com/bmizerany/assert" + "math" "math/rand" "strconv" "testing" @@ -22,7 +23,35 @@ func TestPacketParse(t *testing.T) { assert.Equal(t, len(packets), 1) packet := packets[0] assert.Equal(t, "gaugor", packet.Bucket) - assert.Equal(t, uint64(333), packet.Value.(uint64)) + assert.Equal(t, GaugeData{false, false, 333}, packet.Value) + assert.Equal(t, "g", packet.Modifier) + assert.Equal(t, float32(1), packet.Sampling) + + d = []byte("gaugor:-10|g") + packets = parseMessage(d) + assert.Equal(t, len(packets), 1) + packet = packets[0] + assert.Equal(t, "gaugor", packet.Bucket) + assert.Equal(t, GaugeData{true, true, 10}, packet.Value) + assert.Equal(t, "g", packet.Modifier) + assert.Equal(t, float32(1), packet.Sampling) + + d = []byte("gaugor:+4|g") + packets = parseMessage(d) + assert.Equal(t, len(packets), 1) + packet = packets[0] + assert.Equal(t, "gaugor", packet.Bucket) + assert.Equal(t, GaugeData{true, false, 4}, packet.Value) + assert.Equal(t, "g", packet.Modifier) + assert.Equal(t, float32(1), packet.Sampling) + + // >max(int64) && 2^64 overflow + p.Value = GaugeData{false, false, uint64(math.MaxUint64 - 10)} + packetHandler(p) + p.Value = GaugeData{true, false, 20} + packetHandler(p) + assert.Equal(t, gauges["gaugor"], uint64(math.MaxUint64)) } func TestTimerPacketHandling(t *testing.T) {