diff --git a/bridge_test.go b/bridge_test.go index 312869e..c99b750 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -33,6 +33,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 2, + valueStr: "2", labels: map[string]string{}, }, }, @@ -43,6 +44,29 @@ func TestHandlePacket(t *testing.T) { &GaugeEvent{ metricName: "foo", value: 3, + valueStr: "3", + labels: map[string]string{}, + }, + }, + }, { + name: "simple gauge with sampling", + in: "foo:100|g|@0.1", + out: Events{ + &GaugeEvent{ + metricName: "foo", + value: 1000, + valueStr: "1000", + labels: map[string]string{}, + }, + }, + }, { + name: "gauge decrement", + in: "foo:-10|g", + out: Events{ + &GaugeEvent{ + metricName: "foo", + value: -10, + valueStr: "-10", labels: map[string]string{}, }, }, @@ -53,6 +77,7 @@ func TestHandlePacket(t *testing.T) { &TimerEvent{ metricName: "foo", value: 200, + valueStr: "200", labels: map[string]string{}, }, }, @@ -63,6 +88,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{"tag1": "bar", "tag2": "baz"}, }, }, @@ -73,6 +99,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{"tag1": "bar", "tag2": "baz"}, }, }, @@ -83,6 +110,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{"_09digits": "0", "tag_with_dots": "1"}, }, }, @@ -93,6 +121,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{}, }, }, @@ -103,6 +132,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{"tag": "value"}, }, }, @@ -113,6 +143,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 100, + valueStr: "100", labels: map[string]string{"tag": "value"}, }, }, @@ -123,6 +154,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 1000, + valueStr: "1000", labels: map[string]string{"tag1": "bar", "tag2": "baz"}, }, }, @@ -133,6 +165,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 1000, + valueStr: "1000", labels: map[string]string{"tag1": "foo:bar"}, }, }, @@ -149,6 +182,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 200, + valueStr: "200", labels: map[string]string{"tag": "value"}, }, }, @@ -159,31 +193,37 @@ func TestHandlePacket(t *testing.T) { &TimerEvent{ metricName: "foo", value: 200, + valueStr: "200", labels: map[string]string{}, }, &TimerEvent{ metricName: "foo", value: 300, + valueStr: "300", labels: map[string]string{}, }, &CounterEvent{ metricName: "foo", value: 50, + valueStr: "50", labels: map[string]string{}, }, &GaugeEvent{ metricName: "foo", value: 6, + valueStr: "6", labels: map[string]string{}, }, &CounterEvent{ metricName: "bar", value: 1, + valueStr: "1", labels: map[string]string{}, }, &TimerEvent{ metricName: "bar", value: 5, + valueStr: "5", labels: map[string]string{}, }, }, @@ -203,6 +243,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 1, + valueStr: "1", labels: map[string]string{}, }, }, @@ -213,6 +254,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "foo", value: 2, + valueStr: "2", labels: map[string]string{}, }, }, @@ -239,6 +281,7 @@ func TestHandlePacket(t *testing.T) { &CounterEvent{ metricName: "valid_utf8", value: 1, + valueStr: "1", labels: map[string]string{}, }, }, diff --git a/exporter.go b/exporter.go index 02a9a0e..02279f3 100644 --- a/exporter.go +++ b/exporter.go @@ -23,6 +23,7 @@ import ( "strconv" "strings" "unicode/utf8" + "math" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" @@ -144,37 +145,44 @@ func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels) prom type Event interface { MetricName() string Value() float64 + ValueStr() string Labels() map[string]string } type CounterEvent struct { metricName string value float64 + valueStr string labels map[string]string } func (c *CounterEvent) MetricName() string { return c.metricName } func (c *CounterEvent) Value() float64 { return c.value } +func (c *CounterEvent) ValueStr() string { return c.valueStr } func (c *CounterEvent) Labels() map[string]string { return c.labels } type GaugeEvent struct { metricName string value float64 + valueStr string labels map[string]string } func (g *GaugeEvent) MetricName() string { return g.metricName } func (g *GaugeEvent) Value() float64 { return g.value } +func (g *GaugeEvent) ValueStr() string { return g.valueStr } func (c *GaugeEvent) Labels() map[string]string { return c.labels } type TimerEvent struct { metricName string value float64 + valueStr string labels map[string]string } func (t *TimerEvent) MetricName() string { return t.metricName } func (t *TimerEvent) Value() float64 { return t.value } +func (t *TimerEvent) ValueStr() string { return t.valueStr } func (c *TimerEvent) Labels() map[string]string { return c.labels } type Events []Event @@ -252,7 +260,15 @@ func (b *Exporter) Listen(e <-chan Events) { b.suffix(metricName, "gauge"), prometheusLabels, ) - gauge.Set(event.Value()) + + var value = event.ValueStr() + if strings.Index(value, "+") == 0 { + gauge.Add(event.Value()) + } else if strings.Index(value, "-") == 0 { + gauge.Sub(math.Abs(event.Value())) + } else { + gauge.Set(event.Value()) + } eventStats.WithLabelValues("gauge").Inc() @@ -287,24 +303,27 @@ type StatsDListener struct { conn *net.UDPConn } -func buildEvent(statType, metric string, value float64, labels map[string]string) (Event, error) { +func buildEvent(statType, metric string, value float64, valueStr string, labels map[string]string) (Event, error) { switch statType { case "c": return &CounterEvent{ metricName: metric, value: float64(value), + valueStr: valueStr, labels: labels, }, nil case "g": return &GaugeEvent{ metricName: metric, value: float64(value), + valueStr: valueStr, labels: labels, }, nil case "ms", "h": return &TimerEvent{ metricName: metric, value: float64(value), + valueStr: valueStr, labels: labels, }, nil case "s": @@ -375,6 +394,14 @@ func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) { continue } valueStr, statType := components[0], components[1] + + var prefix = "" + if strings.Index(valueStr, "+") == 0 { + prefix = "+" + } else if strings.Index(valueStr, "-") == 0 { + prefix = "-" + } + value, err := strconv.ParseFloat(valueStr, 64) if err != nil { log.Errorf("Bad value %s on line: %s", valueStr, line) @@ -418,7 +445,9 @@ func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) { } } - event, err := buildEvent(statType, metric, value, labels) + // convert the (possibly) sampled value to a string and add the operation prefix back on + valueStr = prefix + strconv.FormatFloat(math.Abs(value), 'f', -1, 64) + event, err := buildEvent(statType, metric, value, valueStr, labels) if err != nil { log.Errorf("Error building event on line %s: %s", line, err) networkStats.WithLabelValues("illegal_event").Inc()