Skip to content

Commit

Permalink
Merge pull request #6 from smira/tag-support
Browse files Browse the repository at this point in the history
Implement support for statsd tags
  • Loading branch information
smira authored Mar 30, 2018
2 parents 408e199 + 7a36500 commit f00e0db
Show file tree
Hide file tree
Showing 7 changed files with 485 additions and 28 deletions.
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Build Status](https://travis-ci.org/smira/go-statsd.svg?branch=master)](https://travis-ci.org/smira/go-statsd)
[![Documentation](https://godoc.org/github.com/smira/go-statsd?status.svg)](http://godoc.org/github.com/smira/go-statsd)
[![Go Report Card](https://goreportcard.com/badge/github.com/smira/go-statsd)](https://goreportcard.com/report/github.com/smira/go-statsd)
[![codecov](https://codecov.io/gh/smira/go-statsd/branch/master/graph/badge.svg)](https://codecov.io/gh/smira/go-statsd)
[![License](https://img.shields.io/github/license/smira/go-statsd.svg?maxAge=2592000)](https://github.com/smira/go-statsd/LICENSE)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd?ref=badge_shield)

Expand Down Expand Up @@ -43,6 +44,55 @@ if that happens it's usually signal of enormous metric volume.
Any statsd-compatible server should work well with `go-statsd`, [statsite](https://github.com/statsite/statsite) works
exceptionally well as it has great performance and low memory footprint even with huge number of metrics.

## Usage

Initialize client instance with options, one client per application is usually enough:

```go
client := statsd.NewClient("localhost:8125",
statsd.MaxPacketSize(1400),
statsd.MetricPrefix("web."))
```

Send metrics as events happen in the application, metrics will be packed together and
delivered to statsd server:

```go
start := time.Now()
client.Incr("requests.http", 1)
// ...
client.PrecisionTiming("requests.route.api.latency", time.Since(start))
```

Shutdown client during application shutdown to flush all the pending metrics:

```go
client.Close()
```

## Tagging

Metrics could be tagged to support aggregation on TSDB side. go-statsd supports
tags in [InfluxDB](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/#datagram-format) formats.
Format and default tags (applied to every metric) are passed as options
to the client initialization:

```go
client := statsd.NewClient("localhost:8125",
statsd.TagStyle(TagFormatDatadog),
statsd.DefaultTags(statsd.StringTag("app", "billing")))
```

For every metric sent, tags could be added as the last argument(s) to the function
call:

```go
client.Incr("request", 1,
statsd.StringTag("procotol", "http"), statsd.IntTag("port", 80))
```


## Benchmark

Benchmark comparing several clients:
Expand Down Expand Up @@ -77,4 +127,4 @@ Ideas were borrowed from the following stastd clients:
License is [MIT License](LICENSE).


[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsmira%2Fgo-statsd?ref=badge_large)
102 changes: 75 additions & 27 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func NewClient(addr string, options ...Option) *Client {
BufPoolCapacity: DefaultBufPoolCapacity,
SendQueueCapacity: DefaultSendQueueCapacity,
SendLoopCount: DefaultSendLoopCount,
TagFormat: TagFormatInfluxDB,
},

shutdown: make(chan struct{}),
Expand Down Expand Up @@ -115,17 +116,24 @@ func (c *Client) GetLostPackets() int64 {

// Incr increments a counter metric
//
// Often used to note a particular event
func (c *Client) Incr(stat string, count int64) {
// Often used to note a particular event, for example incoming web request.
func (c *Client) Incr(stat string, count int64, tags ...Tag) {
if 0 != count {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = strconv.AppendInt(c.buf, count, 10)
c.buf = append(c.buf, []byte("|c\n")...)
c.buf = append(c.buf, []byte("|c")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
Expand All @@ -135,50 +143,74 @@ func (c *Client) Incr(stat string, count int64) {
// Decr decrements a counter metri
//
// Often used to note a particular event
func (c *Client) Decr(stat string, count int64) {
c.Incr(stat, -count)
func (c *Client) Decr(stat string, count int64, tags ...Tag) {
c.Incr(stat, -count, tags...)
}

// Timing tracks a duration event, the time delta must be given in milliseconds
func (c *Client) Timing(stat string, delta int64) {
func (c *Client) Timing(stat string, delta int64, tags ...Tag) {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = strconv.AppendInt(c.buf, delta, 10)
c.buf = append(c.buf, []byte("|ms\n")...)
c.buf = append(c.buf, []byte("|ms")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
}

// PrecisionTiming track a duration event, the time delta has to be a duration
func (c *Client) PrecisionTiming(stat string, delta time.Duration) {
//
// Usually request processing time, time to run database query, etc. are used with
// this metric type.
func (c *Client) PrecisionTiming(stat string, delta time.Duration, tags ...Tag) {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = strconv.AppendFloat(c.buf, float64(delta)/float64(time.Millisecond), 'f', -1, 64)
c.buf = append(c.buf, []byte("|ms\n")...)
c.buf = append(c.buf, []byte("|ms")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
}

func (c *Client) igauge(stat string, sign []byte, value int64) {
func (c *Client) igauge(stat string, sign []byte, value int64, tags ...Tag) {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = append(c.buf, sign...)
c.buf = strconv.AppendInt(c.buf, value, 10)
c.buf = append(c.buf, []byte("|g\n")...)
c.buf = append(c.buf, []byte("|g")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
Expand All @@ -192,67 +224,83 @@ func (c *Client) igauge(stat string, sign []byte, value int64) {
// delta to be true, that specifies that the gauge should be updated, not set. Due to the
// underlying protocol, you can't explicitly set a gauge to a negative number without
// first setting it to zero.
func (c *Client) Gauge(stat string, value int64) {
func (c *Client) Gauge(stat string, value int64, tags ...Tag) {
if value < 0 {
c.igauge(stat, nil, 0)
c.igauge(stat, nil, 0, tags...)
}

c.igauge(stat, nil, value)
c.igauge(stat, nil, value, tags...)
}

// GaugeDelta sends a change for a gauge
func (c *Client) GaugeDelta(stat string, value int64) {
func (c *Client) GaugeDelta(stat string, value int64, tags ...Tag) {
// Gauge Deltas are always sent with a leading '+' or '-'. The '-' takes care of itself but the '+' must added by hand
if value < 0 {
c.igauge(stat, nil, value)
c.igauge(stat, nil, value, tags...)
} else {
c.igauge(stat, []byte{'+'}, value)
c.igauge(stat, []byte{'+'}, value, tags...)
}
}

func (c *Client) fgauge(stat string, sign []byte, value float64) {
func (c *Client) fgauge(stat string, sign []byte, value float64, tags ...Tag) {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = append(c.buf, sign...)
c.buf = strconv.AppendFloat(c.buf, value, 'f', -1, 64)
c.buf = append(c.buf, []byte("|g\n")...)
c.buf = append(c.buf, []byte("|g")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
}

// FGauge sends a floating point value for a gauge
func (c *Client) FGauge(stat string, value float64) {
func (c *Client) FGauge(stat string, value float64, tags ...Tag) {
if value < 0 {
c.igauge(stat, nil, 0)
c.igauge(stat, nil, 0, tags...)
}

c.fgauge(stat, nil, value)
c.fgauge(stat, nil, value, tags...)
}

// FGaugeDelta sends a floating point change for a gauge
func (c *Client) FGaugeDelta(stat string, value float64) {
func (c *Client) FGaugeDelta(stat string, value float64, tags ...Tag) {
if value < 0 {
c.fgauge(stat, nil, value)
c.fgauge(stat, nil, value, tags...)
} else {
c.fgauge(stat, []byte{'+'}, value)
c.fgauge(stat, []byte{'+'}, value, tags...)
}
}

// SetAdd adds unique element to a set
func (c *Client) SetAdd(stat string, value string) {
//
// Statsd server will provide cardinality of the set over aggregation period.
func (c *Client) SetAdd(stat string, value string, tags ...Tag) {
c.bufLock.Lock()
lastLen := len(c.buf)

c.buf = append(c.buf, []byte(c.options.MetricPrefix)...)
c.buf = append(c.buf, []byte(stat)...)
if c.options.TagFormat.Placement == TagPlacementName {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, ':')
c.buf = append(c.buf, []byte(value)...)
c.buf = append(c.buf, []byte("|s\n")...)
c.buf = append(c.buf, []byte("|s")...)
if c.options.TagFormat.Placement == TagPlacementSuffix {
c.buf = c.formatTags(c.buf, tags)
}
c.buf = append(c.buf, '\n')

c.checkBuf(lastLen)
c.bufLock.Unlock()
Expand Down
Loading

0 comments on commit f00e0db

Please sign in to comment.