Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StatsD support #24

Merged
merged 6 commits into from
Dec 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Buildkite Metrics

A command-line tool for collecting [Buildkite](https://buildkite.com/) build/job statistics for external metrics systems. Currently [AWS Cloudwatch](http://aws.amazon.com/cloudwatch/) is supported.
A command-line tool for collecting [Buildkite](https://buildkite.com/) build/job statistics for external metrics systems. Currently [AWS Cloudwatch](http://aws.amazon.com/cloudwatch/) and [StatsD](https://github.com/etsy/statsd) are supported.

[![Build status](https://badge.buildkite.com/80d04fcde3a306bef44e77aadb1f1ffdc20ebb3c8f1f585a60.svg)](https://buildkite.com/buildkite/buildkite-metrics)

Expand All @@ -12,6 +12,13 @@ Either download the latest binary from [buildkite-metrics/buildkite-metrics-Linu
go get github.com/buildkite/buildkite-metrics
```

### Backends

By default metrics will be submitted to CloudWatch but the backend can be switched to StatsD using the command-line argument `-backend statsd`. The StatsD backend supports the following arguments

* `-statsd-host HOST`: The StatsD host and port (defaults to `127.0.0.1:8125`).
* `-statsd-tags`: Some StatsD servers like the agent provided by DataDog support tags. If specified, metrics will be tagged by `queue` and `pipeline` otherwise metrics will include the queue/pipeline name in the metric. Only enable this option if you know your StatsD server supports tags.

## Development

You can build and run the binary tool locally with golang installed:
Expand Down
8 changes: 8 additions & 0 deletions backend/backends.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package backend

import "github.com/buildkite/buildkite-metrics/collector"

// Backend is a receiver of metrics
type Backend interface {
Collect(r *collector.Result) error
}
12 changes: 10 additions & 2 deletions cloudwatch.go → backend/cloudwatch.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package backend

import (
"log"
Expand All @@ -9,7 +9,15 @@ import (
"github.com/buildkite/buildkite-metrics/collector"
)

func cloudwatchSend(r *collector.Result) error {
// CloudWatchBackend sends metrics to AWS CloudWatch
type CloudWatchBackend struct {
}

func NewCloudWatchBackend() *CloudWatchBackend {
return &CloudWatchBackend{}
}

func (cb *CloudWatchBackend) Collect(r *collector.Result) error {
svc := cloudwatch.New(session.New())

metrics := []*cloudwatch.MetricDatum{}
Expand Down
67 changes: 67 additions & 0 deletions backend/statsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package backend

import (
"github.com/DataDog/datadog-go/statsd"
"github.com/buildkite/buildkite-metrics/collector"
)

// StatsD sends metrics to StatsD (Datadog spec)
type StatsD struct {
client *statsd.Client
tagsSupported bool
}

func NewStatsDBackend(host string, tagsSupported bool) (*StatsD, error) {
c, err := statsd.NewBuffered(host, 100)
if err != nil {
return nil, err
}
// prefix every metric with the app name
c.Namespace = "buildkite."
return &StatsD{
client: c,
tagsSupported: tagsSupported,
}, nil
}

func (cb *StatsD) Collect(r *collector.Result) error {
for name, value := range r.Totals {
if err := cb.client.Gauge(name, float64(value), []string{}, 1.0); err != nil {
return err
}
}

for queue, counts := range r.Queues {
for name, value := range counts {
var finalName string
tags := []string{}
if cb.tagsSupported {
finalName = "queues." + name
tags = []string{"queue:" + queue}
} else {
finalName = "queues." + queue + "." + name
}
if err := cb.client.Gauge(finalName, float64(value), tags, 1.0); err != nil {
return err
}
}
}

for pipeline, counts := range r.Pipelines {
for name, value := range counts {
var finalName string
tags := []string{}
if cb.tagsSupported {
finalName = "pipeline." + name
tags = []string{"pipeline:" + pipeline}
} else {
finalName = "pipeline." + pipeline + "." + name
}
if err := cb.client.Gauge(finalName, float64(value), tags, 1.0); err != nil {
return err
}
}
}

return nil
}
1 change: 1 addition & 0 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func New(c *bk.Client, opts Opts) *Collector {
return &Collector{
Opts: opts,
buildService: c.Builds,
agentService: c.Agents,
}
}

Expand Down
15 changes: 14 additions & 1 deletion lambda.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"encoding/json"
"log"
"os"
"strings"
"time"

"github.com/buildkite/buildkite-metrics/backend"
"github.com/buildkite/buildkite-metrics/collector"
"github.com/eawsy/aws-lambda-go/service/lambda/runtime"
"gopkg.in/buildkite/go-buildkite.v2/buildkite"
Expand All @@ -18,6 +20,7 @@ func handle(evt json.RawMessage, ctx *runtime.Context) (interface{}, error) {

org := os.Getenv("BUILDKITE_ORG")
token := os.Getenv("BUILDKITE_TOKEN")
backendOpt := os.Getenv("BUILDKITE_BACKEND")

config, err := buildkite.NewTokenConfig(token, false)
if err != nil {
Expand All @@ -32,14 +35,24 @@ func handle(evt json.RawMessage, ctx *runtime.Context) (interface{}, error) {
Historical: time.Hour * 24,
})

var bk backend.Backend
if backendOpt == "statsd" {
bk, err = backend.NewStatsDBackend(os.Getenv("STATSD_HOST"), strings.ToLower(os.Getenv("STATSD_TAGS")) == "true")
if err != nil {
return nil, err
}
} else {
bk = &backend.CloudWatchBackend{}
}

res, err := col.Collect()
if err != nil {
return nil, err
}

res.Dump()

err = cloudwatchSend(res)
err = bk.Collect(res)
if err != nil {
return nil, err
}
Expand Down
26 changes: 25 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import (
"io/ioutil"
"log"
"os"
"strings"
"time"

"github.com/buildkite/buildkite-metrics/backend"
"github.com/buildkite/buildkite-metrics/collector"
"gopkg.in/buildkite/go-buildkite.v2/buildkite"
)

// Version is passed in via ldflags
var Version string

var bk backend.Backend

func main() {
var (
accessToken = flag.String("token", "", "A Buildkite API Access Token")
Expand All @@ -26,6 +30,11 @@ func main() {
quiet = flag.Bool("quiet", false, "Only print errors")
dryRun = flag.Bool("dry-run", false, "Whether to only print metrics")

// backend config
backendOpt = flag.String("backend", "cloudwatch", "Specify the backend to send metrics to: cloudwatch, statsd")
statsdHost = flag.String("statsd-host", "127.0.0.1:8125", "Specify the StatsD server")
statsdTags = flag.Bool("statsd-tags", false, "Whether your StatsD server supports tagging like Datadog")

// filters
queue = flag.String("queue", "", "Only include a specific queue")
)
Expand All @@ -47,6 +56,21 @@ func main() {
os.Exit(1)
}

lowerBackendOpt := strings.ToLower(*backendOpt)
if lowerBackendOpt == "cloudwatch" {
bk = backend.NewCloudWatchBackend()
} else if lowerBackendOpt == "statsd" {
var err error
bk, err = backend.NewStatsDBackend(*statsdHost, *statsdTags)
if err != nil {
fmt.Printf("Error starting StatsD, err: %v\n", err)
os.Exit(1)
}
} else {
fmt.Println("Must provide a supported backend: cloudwatch, statsd")
os.Exit(1)
}

if *quiet {
log.SetOutput(ioutil.Discard)
}
Expand Down Expand Up @@ -82,7 +106,7 @@ func main() {
}

if !*dryRun {
err = cloudwatchSend(res)
err = bk.Collect(res)
if err != nil {
return err
}
Expand Down
34 changes: 34 additions & 0 deletions vendor/github.com/DataDog/datadog-go/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vendor/github.com/DataDog/datadog-go/LICENSE.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions vendor/github.com/DataDog/datadog-go/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions vendor/github.com/DataDog/datadog-go/statsd/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading