251 lines
6.0 KiB
Go
Executable File
251 lines
6.0 KiB
Go
Executable File
package statsd
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type config struct {
|
|
Conn connConfig
|
|
Client clientConfig
|
|
}
|
|
|
|
type clientConfig struct {
|
|
Muted bool
|
|
Rate float32
|
|
Prefix string
|
|
Tags []tag
|
|
}
|
|
|
|
type connConfig struct {
|
|
Addr string
|
|
ErrorHandler func(error)
|
|
FlushPeriod time.Duration
|
|
MaxPacketSize int
|
|
Network string
|
|
TagFormat TagFormat
|
|
}
|
|
|
|
// An Option represents an option for a Client. It must be used as an
|
|
// argument to New() or Client.Clone().
|
|
type Option func(*config)
|
|
|
|
// Address sets the address of the StatsD daemon.
|
|
//
|
|
// By default, ":8125" is used. This option is ignored in Client.Clone().
|
|
func Address(addr string) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.Addr = addr
|
|
})
|
|
}
|
|
|
|
// ErrorHandler sets the function called when an error happens when sending
|
|
// metrics (e.g. the StatsD daemon is not listening anymore).
|
|
//
|
|
// By default, these errors are ignored. This option is ignored in
|
|
// Client.Clone().
|
|
func ErrorHandler(h func(error)) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.ErrorHandler = h
|
|
})
|
|
}
|
|
|
|
// FlushPeriod sets how often the Client's buffer is flushed. If p is 0, the
|
|
// goroutine that periodically flush the buffer is not lauched and the buffer
|
|
// is only flushed when it is full.
|
|
//
|
|
// By default, the flush period is 100 ms. This option is ignored in
|
|
// Client.Clone().
|
|
func FlushPeriod(p time.Duration) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.FlushPeriod = p
|
|
})
|
|
}
|
|
|
|
// MaxPacketSize sets the maximum packet size in bytes sent by the Client.
|
|
//
|
|
// By default, it is 1440 to avoid IP fragmentation. This option is ignored in
|
|
// Client.Clone().
|
|
func MaxPacketSize(n int) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.MaxPacketSize = n
|
|
})
|
|
}
|
|
|
|
// Network sets the network (udp, tcp, etc) used by the client. See the
|
|
// net.Dial documentation (https://golang.org/pkg/net/#Dial) for the available
|
|
// network options.
|
|
//
|
|
// By default, network is udp. This option is ignored in Client.Clone().
|
|
func Network(network string) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.Network = network
|
|
})
|
|
}
|
|
|
|
// Mute sets whether the Client is muted. All methods of a muted Client do
|
|
// nothing and return immedialtly.
|
|
//
|
|
// This option can be used in Client.Clone() only if the parent Client is not
|
|
// muted. The clones of a muted Client are always muted.
|
|
func Mute(b bool) Option {
|
|
return Option(func(c *config) {
|
|
c.Client.Muted = b
|
|
})
|
|
}
|
|
|
|
// SampleRate sets the sample rate of the Client. It allows sending the metrics
|
|
// less often which can be useful for performance intensive code paths.
|
|
func SampleRate(rate float32) Option {
|
|
return Option(func(c *config) {
|
|
c.Client.Rate = rate
|
|
})
|
|
}
|
|
|
|
// Prefix appends the prefix that will be used in every bucket name.
|
|
//
|
|
// Note that when used in cloned, the prefix of the parent Client is not
|
|
// replaced but is prepended to the given prefix.
|
|
func Prefix(p string) Option {
|
|
return Option(func(c *config) {
|
|
c.Client.Prefix += strings.TrimSuffix(p, ".") + "."
|
|
})
|
|
}
|
|
|
|
// TagFormat represents the format of tags sent by a Client.
|
|
type TagFormat uint8
|
|
|
|
// TagsFormat sets the format of tags.
|
|
func TagsFormat(tf TagFormat) Option {
|
|
return Option(func(c *config) {
|
|
c.Conn.TagFormat = tf
|
|
})
|
|
}
|
|
|
|
// Tags appends the given tags to the tags sent with every metrics. If a tag
|
|
// already exists, it is replaced.
|
|
//
|
|
// The tags must be set as key-value pairs. If the number of tags is not even,
|
|
// Tags panics.
|
|
//
|
|
// If the format of tags have not been set using the TagsFormat option, the tags
|
|
// will be ignored.
|
|
func Tags(tags ...string) Option {
|
|
if len(tags)%2 != 0 {
|
|
panic("statsd: Tags only accepts an even number of arguments")
|
|
}
|
|
|
|
return Option(func(c *config) {
|
|
if len(tags) == 0 {
|
|
return
|
|
}
|
|
|
|
newTags := make([]tag, len(tags)/2)
|
|
for i := 0; i < len(tags)/2; i++ {
|
|
newTags[i] = tag{K: tags[2*i], V: tags[2*i+1]}
|
|
}
|
|
|
|
for _, newTag := range newTags {
|
|
exists := false
|
|
for _, oldTag := range c.Client.Tags {
|
|
if newTag.K == oldTag.K {
|
|
exists = true
|
|
oldTag.V = newTag.V
|
|
}
|
|
}
|
|
if !exists {
|
|
c.Client.Tags = append(c.Client.Tags, tag{
|
|
K: newTag.K,
|
|
V: newTag.V,
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
type tag struct {
|
|
K, V string
|
|
}
|
|
|
|
func joinTags(tf TagFormat, tags []tag) string {
|
|
if len(tags) == 0 || tf == 0 {
|
|
return ""
|
|
}
|
|
join := joinFuncs[tf]
|
|
return join(tags)
|
|
}
|
|
|
|
func splitTags(tf TagFormat, tags string) []tag {
|
|
if len(tags) == 0 || tf == 0 {
|
|
return nil
|
|
}
|
|
split := splitFuncs[tf]
|
|
return split(tags)
|
|
}
|
|
|
|
const (
|
|
// InfluxDB tag format.
|
|
// See https://influxdb.com/blog/2015/11/03/getting_started_with_influx_statsd.html
|
|
InfluxDB TagFormat = iota + 1
|
|
// Datadog tag format.
|
|
// See http://docs.datadoghq.com/guides/metrics/#tags
|
|
Datadog
|
|
)
|
|
|
|
var (
|
|
joinFuncs = map[TagFormat]func([]tag) string{
|
|
// InfluxDB tag format: ,tag1=payroll,region=us-west
|
|
// https://influxdb.com/blog/2015/11/03/getting_started_with_influx_statsd.html
|
|
InfluxDB: func(tags []tag) string {
|
|
var buf bytes.Buffer
|
|
for _, tag := range tags {
|
|
_ = buf.WriteByte(',')
|
|
_, _ = buf.WriteString(tag.K)
|
|
_ = buf.WriteByte('=')
|
|
_, _ = buf.WriteString(tag.V)
|
|
}
|
|
return buf.String()
|
|
},
|
|
// Datadog tag format: |#tag1:value1,tag2:value2
|
|
// http://docs.datadoghq.com/guides/dogstatsd/#datagram-format
|
|
Datadog: func(tags []tag) string {
|
|
buf := bytes.NewBufferString("|#")
|
|
first := true
|
|
for _, tag := range tags {
|
|
if first {
|
|
first = false
|
|
} else {
|
|
_ = buf.WriteByte(',')
|
|
}
|
|
_, _ = buf.WriteString(tag.K)
|
|
_ = buf.WriteByte(':')
|
|
_, _ = buf.WriteString(tag.V)
|
|
}
|
|
return buf.String()
|
|
},
|
|
}
|
|
splitFuncs = map[TagFormat]func(string) []tag{
|
|
InfluxDB: func(s string) []tag {
|
|
s = s[1:]
|
|
pairs := strings.Split(s, ",")
|
|
tags := make([]tag, len(pairs))
|
|
for i, pair := range pairs {
|
|
kv := strings.Split(pair, "=")
|
|
tags[i] = tag{K: kv[0], V: kv[1]}
|
|
}
|
|
return tags
|
|
},
|
|
Datadog: func(s string) []tag {
|
|
s = s[2:]
|
|
pairs := strings.Split(s, ",")
|
|
tags := make([]tag, len(pairs))
|
|
for i, pair := range pairs {
|
|
kv := strings.Split(pair, ":")
|
|
tags[i] = tag{K: kv[0], V: kv[1]}
|
|
}
|
|
return tags
|
|
},
|
|
}
|
|
)
|