Skip to content

Commit

Permalink
fix(outputs.stackdriver): Options to use official path and types (inf…
Browse files Browse the repository at this point in the history
  • Loading branch information
powersj committed Jul 5, 2023
1 parent 9f2a8f6 commit 445a4eb
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 7 deletions.
22 changes: 20 additions & 2 deletions plugins/outputs/stackdriver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ costs.

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`
By default, Metrics are grouped by the `namespace` variable and metric key -
eg: `custom.googleapis.com/telegraf/system/load5`. However, this is not the
best practice. Setting `metric_name_format = "official"` will produce a more
easily queried format of: `metric_type_prefix/[namespace_]name_key/kind`. If
the global namespace is not set, it is omitted as well.

[Resource type](https://cloud.google.com/monitoring/api/resources) is configured
by the `resource_type` variable (default `global`).
Expand All @@ -36,12 +39,27 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
project = "erudite-bloom-151019"

## The namespace for the metric descriptor
## This is optional and users are encouraged to set the namespace as a
## resource label instead. If omitted it is not included in the metric name.
namespace = "telegraf"

## Metric Type Prefix
## The DNS name used with the metric type as a prefix.
# metric_type_prefix = "custom.googleapis.com"

## Metric Name Format
## Specifies the layout of the metric name, choose from:
## * path: 'metric_type_prefix_namespace_name_key'
## * official: 'metric_type_prefix/namespace_name_key/kind'
# metric_name_format = "path"

## Metric Data Type
## By default, telegraf will use whatever type the metric comes in as.
## However, for some use cases, forcing int64, may be preferred for values:
## * source: use whatever was passed in
## * double: preferred datatype to allow queries by PromQL.
# metric_data_type = "source"

## Custom resource type
# resource_type = "generic_node"

Expand Down
15 changes: 15 additions & 0 deletions plugins/outputs/stackdriver/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@
project = "erudite-bloom-151019"

## The namespace for the metric descriptor
## This is optional and users are encouraged to set the namespace as a
## resource label instead. If omitted it is not included in the metric name.
namespace = "telegraf"

## Metric Type Prefix
## The DNS name used with the metric type as a prefix.
# metric_type_prefix = "custom.googleapis.com"

## Metric Name Format
## Specifies the layout of the metric name, choose from:
## * path: 'metric_type_prefix_namespace_name_key'
## * official: 'metric_type_prefix/namespace_name_key/kind'
# metric_name_format = "path"

## Metric Data Type
## By default, telegraf will use whatever type the metric comes in as.
## However, for some use cases, forcing int64, may be preferred for values:
## * source: use whatever was passed in
## * double: preferred datatype to allow queries by PromQL.
# metric_data_type = "source"

## Custom resource type
# resource_type = "generic_node"

Expand Down
88 changes: 84 additions & 4 deletions plugins/outputs/stackdriver/stackdriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type Stackdriver struct {
ResourceType string `toml:"resource_type"`
ResourceLabels map[string]string `toml:"resource_labels"`
MetricTypePrefix string `toml:"metric_type_prefix"`
MetricNameFormat string `toml:"metric_name_format"`
MetricDataType string `toml:"metric_data_type"`
Log telegraf.Logger `toml:"-"`

client *monitoring.MetricClient
Expand Down Expand Up @@ -62,6 +64,22 @@ func (s *Stackdriver) Init() error {
s.MetricTypePrefix = "custom.googleapis.com"
}

switch s.MetricNameFormat {
case "":
s.MetricNameFormat = "path"
case "path", "official":
default:
return fmt.Errorf("unrecognized metric name format: %s", s.MetricNameFormat)
}

switch s.MetricDataType {
case "":
s.MetricDataType = "source"
case "source", "double":
default:
return fmt.Errorf("unrecognized metric data type: %s", s.MetricDataType)
}

return nil
}

Expand All @@ -76,7 +94,7 @@ func (s *Stackdriver) Connect() error {
}

if s.Namespace == "" {
return fmt.Errorf("namespace is a required field for stackdriver output")
s.Log.Warn("plugin-level namespace is empty")
}

if s.ResourceType == "" {
Expand Down Expand Up @@ -175,7 +193,7 @@ func (s *Stackdriver) sendBatch(batch []telegraf.Metric) error {
buckets := make(timeSeriesBuckets)
for _, m := range batch {
for _, f := range m.FieldList() {
value, err := getStackdriverTypedValue(f.Value)
value, err := s.getStackdriverTypedValue(f.Value)
if err != nil {
s.Log.Errorf("Get type failed: %q", err)
continue
Expand Down Expand Up @@ -208,7 +226,7 @@ func (s *Stackdriver) sendBatch(batch []telegraf.Metric) error {
// Prepare time series.
timeSeries := &monitoringpb.TimeSeries{
Metric: &metricpb.Metric{
Type: path.Join(s.MetricTypePrefix, s.Namespace, m.Name(), f.Key),
Type: s.generateMetricName(m, f.Key),
Labels: s.getStackdriverLabels(m.TagList()),
},
MetricKind: metricKind,
Expand All @@ -222,6 +240,28 @@ func (s *Stackdriver) sendBatch(batch []telegraf.Metric) error {
}

buckets.Add(m, f, timeSeries)

// If the metric is untyped, it will end with unknown. We will also
// send another metric with the unknown:counter suffix. Google will
// do some heuristics to know which one to use for queries. This
// only occurs when using the official name format.
if s.MetricNameFormat == "official" && strings.HasSuffix(timeSeries.Metric.Type, "unknown") {
counterTimeSeries := &monitoringpb.TimeSeries{
Metric: &metricpb.Metric{
Type: s.generateMetricName(m, f.Key) + ":counter",
Labels: s.getStackdriverLabels(m.TagList()),
},
MetricKind: metricpb.MetricDescriptor_CUMULATIVE,
Resource: &monitoredrespb.MonitoredResource{
Type: s.ResourceType,
Labels: s.ResourceLabels,
},
Points: []*monitoringpb.Point{
dataPoint,
},
}
buckets.Add(m, f, counterTimeSeries)
}
}
}

Expand Down Expand Up @@ -273,6 +313,33 @@ func (s *Stackdriver) sendBatch(batch []telegraf.Metric) error {
return nil
}

func (s *Stackdriver) generateMetricName(m telegraf.Metric, key string) string {
if s.MetricNameFormat == "path" {
return path.Join(s.MetricTypePrefix, s.Namespace, m.Name(), key)
}

name := m.Name() + "_" + key
if s.Namespace != "" {
name = s.Namespace + "_" + m.Name() + "_" + key
}

var kind string
switch m.Type() {
case telegraf.Gauge:
kind = "gauge"
case telegraf.Untyped:
kind = "unknown"
case telegraf.Counter:
kind = "counter"
case telegraf.Histogram:
kind = "histogram"
default:
kind = ""
}

return path.Join(s.MetricTypePrefix, name, kind)
}

func getStackdriverIntervalEndpoints(
kind metricpb.MetricDescriptor_MetricKind,
value *monitoringpb.TypedValue,
Expand Down Expand Up @@ -328,7 +395,20 @@ func getStackdriverMetricKind(vt telegraf.ValueType) (metricpb.MetricDescriptor_
}
}

func getStackdriverTypedValue(value interface{}) (*monitoringpb.TypedValue, error) {
func (s *Stackdriver) getStackdriverTypedValue(value interface{}) (*monitoringpb.TypedValue, error) {
if s.MetricDataType == "double" {
v, err := internal.ToFloat64(value)
if err != nil {
return nil, err
}

return &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_DoubleValue{
DoubleValue: v,
},
}, nil
}

switch v := value.(type) {
case uint64:
if v <= uint64(MaxInt) {
Expand Down
Loading

0 comments on commit 445a4eb

Please sign in to comment.