diff --git a/command/server.go b/command/server.go index 5558d51422eb..78fc97ee4e12 100644 --- a/command/server.go +++ b/command/server.go @@ -25,6 +25,7 @@ import ( "github.com/armon/go-metrics" "github.com/armon/go-metrics/circonus" + "github.com/armon/go-metrics/datadog" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/vault/audit" @@ -893,6 +894,21 @@ func (c *ServerCommand) setupTelemetry(config *server.Config) error { fanout = append(fanout, sink) } + if telConfig.DogStatsDAddr != "" { + var tags []string + + if telConfig.DogStatsDTags != nil { + tags = telConfig.DogStatsDTags + } + + sink, err := datadog.NewDogStatsdSink(telConfig.DogStatsDAddr, metricsConf.HostName) + if err != nil { + return fmt.Errorf("failed to start DogStatsD sink. Got: %s", err) + } + sink.SetTags(tags) + fanout = append(fanout, sink) + } + // Initialize the global sink if len(fanout) > 0 { fanout = append(fanout, inm) diff --git a/command/server/config.go b/command/server/config.go index dad485928d8e..c27ede919f8f 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -196,6 +196,15 @@ type Telemetry struct { // (e.g. a specific geo location or datacenter, dc:sfo) // Default: none CirconusBrokerSelectTag string `hcl:"circonus_broker_select_tag"` + + // Dogstats: + // DogStatsdAddr is the address of a dogstatsd instance. If provided, + // metrics will be sent to that instance + DogStatsDAddr string `hcl:"dogstatsd_addr"` + + // DogStatsdTags are the global tags that should be sent with each packet to dogstatsd + // It is a list of strings, where each string looks like "my_tag_name:my_tag_value" + DogStatsDTags []string `hcl:"dogstatsd_tags"` } func (s *Telemetry) GoString() string { @@ -734,6 +743,8 @@ func parseTelemetry(result *Config, list *ast.ObjectList) error { "circonus_broker_id", "circonus_broker_select_tag", "disable_hostname", + "dogstatsd_addr", + "dogstatsd_tags", "statsd_address", "statsite_address", } diff --git a/command/server/config_test.go b/command/server/config_test.go index 789be400f4e3..49e462b19239 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -58,6 +58,8 @@ func TestLoadConfigFile(t *testing.T) { StatsdAddr: "bar", StatsiteAddr: "foo", DisableHostname: false, + DogStatsDAddr: "127.0.0.1:7254", + DogStatsDTags: []string{"tag_1:val_1", "tag_2:val_2"}, }, DisableCache: true, diff --git a/command/server/test-fixtures/config.hcl b/command/server/test-fixtures/config.hcl index 731f0a037ebc..e3b34ed0e057 100644 --- a/command/server/test-fixtures/config.hcl +++ b/command/server/test-fixtures/config.hcl @@ -28,6 +28,8 @@ ha_backend "consul" { telemetry { statsd_address = "bar" statsite_address = "foo" + dogstatsd_addr = "127.0.0.1:7254" + dogstatsd_tags = ["tag_1:val_1", "tag_2:val_2"] } max_lease_ttl = "10h" diff --git a/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go b/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go new file mode 100644 index 000000000000..aaba9fe0e224 --- /dev/null +++ b/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go @@ -0,0 +1,125 @@ +package datadog + +import ( + "fmt" + "strings" + + "github.com/DataDog/datadog-go/statsd" +) + +// DogStatsdSink provides a MetricSink that can be used +// with a dogstatsd server. It utilizes the Dogstatsd client at github.com/DataDog/datadog-go/statsd +type DogStatsdSink struct { + client *statsd.Client + hostName string + propagateHostname bool +} + +// NewDogStatsdSink is used to create a new DogStatsdSink with sane defaults +func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) { + client, err := statsd.New(addr) + if err != nil { + return nil, err + } + sink := &DogStatsdSink{ + client: client, + hostName: hostName, + propagateHostname: false, + } + return sink, nil +} + +// SetTags sets common tags on the Dogstatsd Client that will be sent +// along with all dogstatsd packets. +// Ref: http://docs.datadoghq.com/guides/dogstatsd/#tags +func (s *DogStatsdSink) SetTags(tags []string) { + s.client.Tags = tags +} + +// EnableHostnamePropagation forces a Dogstatsd `host` tag with the value specified by `s.HostName` +// Since the go-metrics package has its own mechanism for attaching a hostname to metrics, +// setting the `propagateHostname` flag ensures that `s.HostName` overrides the host tag naively set by the DogStatsd server +func (s *DogStatsdSink) EnableHostNamePropagation() { + s.propagateHostname = true +} + +func (s *DogStatsdSink) flattenKey(parts []string) string { + joined := strings.Join(parts, ".") + return strings.Map(func(r rune) rune { + switch r { + case ':': + fallthrough + case ' ': + return '_' + default: + return r + } + }, joined) +} + +func (s *DogStatsdSink) parseKey(key []string) ([]string, []string) { + // Since DogStatsd supports dimensionality via tags on metric keys, this sink's approach is to splice the hostname out of the key in favor of a `host` tag + // The `host` tag is either forced here, or set downstream by the DogStatsd server + + var tags []string + hostName := s.hostName + + //Splice the hostname out of the key + for i, el := range key { + if el == hostName { + key = append(key[:i], key[i+1:]...) + } + } + + if s.propagateHostname { + tags = append(tags, fmt.Sprintf("host:%s", hostName)) + } + return key, tags +} + +// Implementation of methods in the MetricSink interface + +func (s *DogStatsdSink) SetGauge(key []string, val float32) { + s.SetGaugeWithTags(key, val, []string{}) +} + +func (s *DogStatsdSink) IncrCounter(key []string, val float32) { + s.IncrCounterWithTags(key, val, []string{}) +} + +// EmitKey is not implemented since DogStatsd does not provide a metric type that holds an +// arbitrary number of values +func (s *DogStatsdSink) EmitKey(key []string, val float32) { +} + +func (s *DogStatsdSink) AddSample(key []string, val float32) { + s.AddSampleWithTags(key, val, []string{}) +} + +// The following ...WithTags methods correspond to Datadog's Tag extension to Statsd. +// http://docs.datadoghq.com/guides/dogstatsd/#tags + +func (s *DogStatsdSink) SetGaugeWithTags(key []string, val float32, tags []string) { + flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags) + rate := 1.0 + s.client.Gauge(flatKey, float64(val), tags, rate) +} + +func (s *DogStatsdSink) IncrCounterWithTags(key []string, val float32, tags []string) { + flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags) + rate := 1.0 + s.client.Count(flatKey, int64(val), tags, rate) +} + +func (s *DogStatsdSink) AddSampleWithTags(key []string, val float32, tags []string) { + flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags) + rate := 1.0 + s.client.TimeInMilliseconds(flatKey, float64(val), tags, rate) +} + +func (s *DogStatsdSink) getFlatkeyAndCombinedTags(key []string, tags []string) (flattenedKey string, combinedTags []string) { + key, hostTags := s.parseKey(key) + flatKey := s.flattenKey(key) + tags = append(tags, hostTags...) + return flatKey, tags +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 159193820e89..c342251bc1b1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -156,6 +156,12 @@ "revision": "f036747b9d0e8590f175a5d654a2194a7d9df4b5", "revisionTime": "2017-06-01T21:44:32Z" }, + { + "checksumSHA1": "mAzNU3zeZGEwqjDT4ZkspFvx3TI=", + "path": "github.com/armon/go-metrics/datadog", + "revision": "f036747b9d0e8590f175a5d654a2194a7d9df4b5", + "revisionTime": "2017-06-01T21:44:32Z" + }, { "checksumSHA1": "gNO0JNpLzYOdInGeq7HqMZUzx9M=", "path": "github.com/armon/go-radix", diff --git a/website/source/docs/configuration/telemetry.html.md b/website/source/docs/configuration/telemetry.html.md index df462f8bc319..fc52fe7f2ae8 100644 --- a/website/source/docs/configuration/telemetry.html.md +++ b/website/source/docs/configuration/telemetry.html.md @@ -60,8 +60,7 @@ telemetry { ### `circonus` -These `telemetry` parameters apply to -[Circonus](http://circonus.com/). +These `telemetry` parameters apply to [Circonus](http://circonus.com/). - `circonus_api_token` `(string: "")` - Specifies a valid Circonus API Token used to create/manage check. If provided, metric management is enabled. @@ -120,3 +119,19 @@ These `telemetry` parameters apply to best use of this is to as a hint for which broker should be used based on *where* this particular instance is running (e.g. a specific geo location or datacenter, dc:sfo). + +### `dogstatsd` + +These `telemetry` parameters apply to +[DogStatsD](http://docs.datadoghq.com/guides/dogstatsd/). + +- `dogstatsd_addr` `(string: "")` - This provides the address of a DogStatsD + instance. DogStatsD is a protocol-compatible flavor of statsd, with the added + ability to decorate metrics with tags and event information. If provided, + Consul will send various telemetry information to that instance for + aggregation. This can be used to capture runtime information. + + +- `dogstatsd_tags` `(string array: [])` - This provides a list of global tags + that will be added to all telemetry packets sent to DogStatsD. It is a list + of strings, where each string looks like "my_tag_name:my_tag_value".