From 3f9860a685d19648b55461a5b2b9f95a2c3086c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Edstr=C3=B6m?= <108799+Legogris@users.noreply.github.com> Date: Fri, 8 Feb 2019 21:13:33 +0100 Subject: [PATCH] Add resource type and resource label support to stackdriver output (#5391) --- plugins/outputs/stackdriver/README.md | 13 ++++++ plugins/outputs/stackdriver/stackdriver.go | 40 ++++++++++++++----- .../outputs/stackdriver/stackdriver_test.go | 36 +++++++++++++++++ 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/plugins/outputs/stackdriver/README.md b/plugins/outputs/stackdriver/README.md index a2d13e6e1..7c6e806bd 100644 --- a/plugins/outputs/stackdriver/README.md +++ b/plugins/outputs/stackdriver/README.md @@ -7,6 +7,10 @@ Requires `project` to specify where Stackdriver metrics will be delivered to. Metrics are grouped by the `namespace` variable and metric key - eg: `custom.googleapis.com/telegraf/system/load5` +[Resource type](https://cloud.google.com/monitoring/api/resources) is configured by the `resource_type` variable (default `global`). + +Additional resource labels can be configured by `resource_labels`. By default the required `project_id` label is always set to the `project` variable. + ### Configuration ```toml @@ -16,6 +20,15 @@ Metrics are grouped by the `namespace` variable and metric key - eg: `custom.goo # The namespace for the metric descriptor namespace = "telegraf" + + # Custom resource type + resource_type = "generic_node" + +# Additonal resource labels +[outputs.stackdriver.resource_labels] + node_id = "$HOSTNAME" + namespace = "myapp" + location = "eu-north0" ``` ### Restrictions diff --git a/plugins/outputs/stackdriver/stackdriver.go b/plugins/outputs/stackdriver/stackdriver.go index a1c9e20da..096f77ff3 100644 --- a/plugins/outputs/stackdriver/stackdriver.go +++ b/plugins/outputs/stackdriver/stackdriver.go @@ -19,8 +19,10 @@ import ( // Stackdriver is the Google Stackdriver config info. type Stackdriver struct { - Project string - Namespace string + Project string + Namespace string + ResourceType string `toml:"resource_type"` + ResourceLabels map[string]string `toml:"resource_labels"` client *monitoring.MetricClient } @@ -47,11 +49,21 @@ const ( ) var sampleConfig = ` - # GCP Project - project = "erudite-bloom-151019" + [[outputs.stackdriver]] + # GCP Project + project = "erudite-bloom-151019" - # The namespace for the metric descriptor - namespace = "telegraf" + # The namespace for the metric descriptor + namespace = "telegraf" + + # Custom resource type + resource_type = "generic_node" + + # Additonal resource labels + [outputs.stackdriver.resource_labels] + node_id = "$HOSTNAME" + namespace = "myapp" + location = "eu-north0" ` // Connect initiates the primary connection to the GCP project. @@ -64,6 +76,16 @@ func (s *Stackdriver) Connect() error { return fmt.Errorf("Namespace is a required field for stackdriver output") } + if s.ResourceType == "" { + s.ResourceType = "global" + } + + if s.ResourceLabels == nil { + s.ResourceLabels = make(map[string]string, 1) + } + + s.ResourceLabels["project_id"] = s.Project + if s.client == nil { ctx := context.Background() client, err := monitoring.NewMetricClient(ctx) @@ -137,10 +159,8 @@ func (s *Stackdriver) Write(metrics []telegraf.Metric) error { }, MetricKind: metricKind, Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - Labels: map[string]string{ - "project_id": s.Project, - }, + Type: s.ResourceType, + Labels: s.ResourceLabels, }, Points: []*monitoringpb.Point{ dataPoint, diff --git a/plugins/outputs/stackdriver/stackdriver_test.go b/plugins/outputs/stackdriver/stackdriver_test.go index d9aab38fd..c60d72d36 100644 --- a/plugins/outputs/stackdriver/stackdriver_test.go +++ b/plugins/outputs/stackdriver/stackdriver_test.go @@ -97,6 +97,42 @@ func TestWrite(t *testing.T) { require.NoError(t, err) err = s.Write(testutil.MockMetrics()) require.NoError(t, err) + + request := mockMetric.reqs[0].(*monitoringpb.CreateTimeSeriesRequest) + require.Equal(t, request.TimeSeries[0].Resource.Type, "global") + require.Equal(t, request.TimeSeries[0].Resource.Labels["project_id"], "projects/[PROJECT]") +} + +func TestWriteResourceTypeAndLabels(t *testing.T) { + expectedResponse := &emptypb.Empty{} + mockMetric.err = nil + mockMetric.reqs = nil + mockMetric.resps = append(mockMetric.resps[:0], expectedResponse) + + c, err := monitoring.NewMetricClient(context.Background(), clientOpt) + if err != nil { + t.Fatal(err) + } + + s := &Stackdriver{ + Project: fmt.Sprintf("projects/%s", "[PROJECT]"), + Namespace: "test", + ResourceType: "foo", + ResourceLabels: map[string]string{ + "mylabel": "myvalue", + }, + client: c, + } + + err = s.Connect() + require.NoError(t, err) + err = s.Write(testutil.MockMetrics()) + require.NoError(t, err) + + request := mockMetric.reqs[0].(*monitoringpb.CreateTimeSeriesRequest) + require.Equal(t, request.TimeSeries[0].Resource.Type, "foo") + require.Equal(t, request.TimeSeries[0].Resource.Labels["project_id"], "projects/[PROJECT]") + require.Equal(t, request.TimeSeries[0].Resource.Labels["mylabel"], "myvalue") } func TestWriteAscendingTime(t *testing.T) {