From c48b3708ef8e4940ee2c318999a7dedc5b0d1b7c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 4 Mar 2018 03:47:49 -0800 Subject: [PATCH] Add Datadog support --- README.md | 7 ++++ data/datadog.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ data/http.go | 2 +- main.go | 10 ++++- 4 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 data/datadog.go diff --git a/README.md b/README.md index cc4c2fb..a145de1 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,11 @@ jplot --url http://:8080/debug/vars \ ![](doc/memstats.png) +### Datadog + +To graph metrics from Datadob, you have to pass a Datadog API and Application key to jplot as follow: + +``` +jplot --url datadog:/// metrics… +``` diff --git a/data/datadog.go b/data/datadog.go new file mode 100644 index 0000000..e9f2096 --- /dev/null +++ b/data/datadog.go @@ -0,0 +1,97 @@ +package data + +import ( + "fmt" + "time" + + "github.com/elgs/gojq" + "gopkg.in/zorkian/go-datadog-api.v2" +) + +type datadogSource struct { + client *datadog.Client + specs []Spec + c chan res + done chan struct{} + + // state + lastQueryTime int64 +} + +// FromDatadog fetches data from Datadog service (http://datadog.com). +func FromDatadog(apiKey, appKey string, specs []Spec, interval time.Duration, size int) *Points { + client := datadog.NewClient(apiKey, appKey) + s := &datadogSource{ + client: client, + specs: specs, + c: make(chan res), + done: make(chan struct{}), + lastQueryTime: time.Now().Unix(), + } + go s.run(interval) + return &Points{ + Size: size, + Source: s, + } +} + +func (s *datadogSource) run(interval time.Duration) { + t := time.NewTicker(interval) + defer t.Stop() + for { + select { + case <-t.C: + s.fetch() + case <-s.done: + close(s.c) + return + } + } +} + +func (s *datadogSource) fetch() { + maxUpdateTimestamp := int64(s.lastQueryTime) + dataPoints := make(map[string]datadog.DataPoint, len(s.specs)) + var err error + for _, spec := range s.specs { + for _, field := range spec.Fields { + query := s.formatQuery(field) + series, err := s.client.QueryMetrics(s.lastQueryTime, time.Now().Unix(), query) + if err != nil { + s.c <- res{err: err} + return + } + if len(series) == 0 { + s.c <- res{err: fmt.Errorf("no data for %s", field.Name)} + return + } + endTs := int64(series[0].GetEnd() / 1000) + if endTs > maxUpdateTimestamp { + maxUpdateTimestamp = endTs + } + // assume the last data point is the latest + dataPoints[field.ID] = series[0].Points[len(series[0].Points)-1] + } + } + + jq := gojq.NewQuery(dataPoints) + s.c <- res{jq: jq, err: err} +} + +func (s *datadogSource) formatQuery(field Field) string { + querySuffix := "" + if field.IsCounter { + querySuffix = ".as_count()" + } + return fmt.Sprintf("%s%s", field.Name, querySuffix) +} + +func (s *datadogSource) Get() (*gojq.JQ, error) { + res := <-s.c + return res.jq, res.err +} + +func (s *datadogSource) Close() error { + close(s.done) + return nil +} diff --git a/data/http.go b/data/http.go index dca35c9..c373d82 100644 --- a/data/http.go +++ b/data/http.go @@ -18,7 +18,7 @@ type res struct { err error } -// FromHTTP fetch data points from url every interval and keep size points. +// FromHTTP fetches data points from url every interval and keep size points. func FromHTTP(url string, interval time.Duration, size int) *Points { h := httpSource{ c: make(chan res), diff --git a/main.go b/main.go index f50359a..2fe25a0 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,15 @@ func main() { } var dp *data.Points if *url != "" { - dp = data.FromHTTP(*url, *interval, *steps) + if strings.HasPrefix(*url, "datadog://") { + keys := strings.Split((*url)[10:], "/") + if len(keys) != 2 { + fatal("invalid datadog url, format is datadog://apiKey/appKey") + } + dp = data.FromDatadog(keys[0], keys[1], specs, *interval, *steps) + } else { + dp = data.FromHTTP(*url, *interval, *steps) + } } else { dp = data.FromStdin(*steps) }