From aa6eafd746ae5fac5e5a171407f6bb25e1556008 Mon Sep 17 00:00:00 2001 From: Pyry Date: Mon, 30 Oct 2017 20:46:38 +0200 Subject: [PATCH] Add option to map measurement names to field name into prometheus input --- plugins/inputs/prometheus/README.md | 21 ++++++++++++++++ plugins/inputs/prometheus/prometheus.go | 23 ++++++++++++----- plugins/inputs/prometheus/prometheus_test.go | 26 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/plugins/inputs/prometheus/README.md b/plugins/inputs/prometheus/README.md index d2d19520c4f73..6224ef9f9b491 100644 --- a/plugins/inputs/prometheus/README.md +++ b/plugins/inputs/prometheus/README.md @@ -17,6 +17,8 @@ in Prometheus format. ## Use bearer token for authorization # bearer_token = /path/to/bearer/token + # measurement_name_as_field_name = true + ## Specify timeout duration for slower prometheus clients (default is 3s) # response_timeout = "3s" @@ -43,6 +45,25 @@ If set, the file specified by the `bearer_token` parameter will be read on each interval and its contents will be appended to the Bearer string in the Authorization header. + +### Measurement Name as Field Name + +If `measurement_name_as_field_name` set, output metric fields will use measurement as name instead of `gauge`, +`counter` etc. This is useful for example in the case when you want to map all +metrics under single measurement using `name_override` and still be able to separate +metrics. + +#### Example of output using `measurement_name_as_field_name` +**Source** +``` +cpu_usage_user{cpu="cpu3"} 1.5045135406226022 +``` +**Output** +``` +cpu_usage_user,cpu=cpu3,url=http://example.org:9273/metrics cpu_usage_user=1.5228426395944945 1505776751000000000 +``` + + ### Usage for Caddy HTTP server If you want to monitor Caddy, you need to use Caddy with its Prometheus plugin: diff --git a/plugins/inputs/prometheus/prometheus.go b/plugins/inputs/prometheus/prometheus.go index c929a5b2644b6..d330a2089e066 100644 --- a/plugins/inputs/prometheus/prometheus.go +++ b/plugins/inputs/prometheus/prometheus.go @@ -25,6 +25,9 @@ type Prometheus struct { // An array of Kubernetes services to scrape metrics from. KubernetesServices []string + // Use measurment name as field name instead of gauge, counter etc. + MeasurementNameAsFieldName bool `toml:"measurement_name_as_field_name"` + // Bearer Token authorization file path BearerToken string `toml:"bearer_token"` @@ -218,18 +221,26 @@ func (p *Prometheus) gatherURL(url UrlAndAddress, acc telegraf.Accumulator) erro if url.Address != "" { tags["address"] = url.Address } - + fields := metric.Fields() + if p.MeasurementNameAsFieldName { + newFields := make(map[string]interface{}) + for _, v := range fields { + newFields[metric.Name()] = v + break // fields should only contain one metric as per spec + } + fields = newFields + } switch metric.Type() { case telegraf.Counter: - acc.AddCounter(metric.Name(), metric.Fields(), tags, metric.Time()) + acc.AddCounter(metric.Name(), fields, tags, metric.Time()) case telegraf.Gauge: - acc.AddGauge(metric.Name(), metric.Fields(), tags, metric.Time()) + acc.AddGauge(metric.Name(), fields, tags, metric.Time()) case telegraf.Summary: - acc.AddSummary(metric.Name(), metric.Fields(), tags, metric.Time()) + acc.AddSummary(metric.Name(), fields, tags, metric.Time()) case telegraf.Histogram: - acc.AddHistogram(metric.Name(), metric.Fields(), tags, metric.Time()) + acc.AddHistogram(metric.Name(), fields, tags, metric.Time()) default: - acc.AddFields(metric.Name(), metric.Fields(), tags, metric.Time()) + acc.AddFields(metric.Name(), fields, tags, metric.Time()) } } diff --git a/plugins/inputs/prometheus/prometheus_test.go b/plugins/inputs/prometheus/prometheus_test.go index 5bf07eb3f8bb9..8c053651e6d3c 100644 --- a/plugins/inputs/prometheus/prometheus_test.go +++ b/plugins/inputs/prometheus/prometheus_test.go @@ -78,6 +78,32 @@ func TestPrometheusGeneratesMetricsWithHostNameTag(t *testing.T) { assert.True(t, acc.TagValue("test_metric", "url") == ts.URL) } +func TestPrometheusGeneratesMetricsWithHostNameTagAndMeasurementNameAsFieldName(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, sampleTextFormat) + })) + defer ts.Close() + + p := &Prometheus{ + KubernetesServices: []string{ts.URL}, + MeasurementNameAsFieldName: true, + } + u, _ := url.Parse(ts.URL) + tsAddress := u.Hostname() + + var acc testutil.Accumulator + + err := acc.GatherError(p.Gather) + require.NoError(t, err) + + assert.True(t, acc.HasFloatField("go_gc_duration_seconds", "go_gc_duration_seconds")) + assert.True(t, acc.HasFloatField("go_goroutines", "go_goroutines")) + assert.True(t, acc.HasFloatField("test_metric", "test_metric")) + assert.True(t, acc.HasTimestamp("test_metric", time.Unix(1490802350, 0))) + assert.True(t, acc.TagValue("test_metric", "address") == tsAddress) + assert.True(t, acc.TagValue("test_metric", "url") == ts.URL) +} + func TestPrometheusGeneratesMetricsAlthoughFirstDNSFails(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode")