Skip to content

Commit be3a064

Browse files
committed
[Metricbeat] gcp: use ServiceMetrixPrefix in field extractor too (#27286)
1 parent 2c325c2 commit be3a064

4 files changed

Lines changed: 136 additions & 20 deletions

File tree

x-pack/metricbeat/module/gcp/metrics/metricset.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package metrics
77
import (
88
"context"
99
"fmt"
10+
"strings"
1011
"time"
1112

1213
monitoring "cloud.google.com/go/monitoring/apiv3"
@@ -51,9 +52,45 @@ type MetricSet struct {
5152

5253
//metricsConfig holds a configuration specific for metrics metricset.
5354
type metricsConfig struct {
54-
ServiceName string `config:"service" validate:"required"`
55-
MetricTypes []string `config:"metric_types" validate:"required"`
56-
Aligner string `config:"aligner"`
55+
ServiceName string `config:"service" validate:"required"`
56+
// ServiceMetricPrefix allows to specify the prefix string for MetricTypes
57+
// Stackdriver requires metrics to be prefixed with a common prefix.
58+
// This prefix changes based on the services the metrics belongs to.
59+
ServiceMetricPrefix string `config:"service_metric_prefix"`
60+
MetricTypes []string `config:"metric_types" validate:"required"`
61+
Aligner string `config:"aligner"`
62+
}
63+
64+
// prefix returns the service metric prefix, falling back to the Google Cloud
65+
// monitoring service prefix when not specified.
66+
// The prefix is normalized to always end with '/'.
67+
func (mc metricsConfig) prefix() string {
68+
prefix := mc.ServiceMetricPrefix
69+
70+
// NOTE: fallback to Google Cloud prefix for backward compatibility
71+
// Prefix <service>.googleapis.com/ works only for Google Cloud metrics
72+
// List: https://cloud.google.com/monitoring/api/metrics_gcp
73+
if prefix == "" {
74+
prefix = mc.ServiceName + ".googleapis.com/"
75+
}
76+
77+
// Final slash is part of prefix. Creating a prefix with final slash
78+
// normalize the prefix for other use cases
79+
if !strings.HasSuffix(prefix, "/") {
80+
prefix = prefix + "/"
81+
}
82+
83+
return prefix
84+
}
85+
86+
// AddPrefixTo adds the required service metric prefix to the given metric
87+
func (mc metricsConfig) AddPrefixTo(metric string) string {
88+
return mc.prefix() + metric
89+
}
90+
91+
// RemovePrefixFrom removes service metric prefix from the given metric
92+
func (mc metricsConfig) RemovePrefixFrom(metric string) string {
93+
return strings.TrimPrefix(metric, mc.prefix())
5794
}
5895

5996
type metricMeta struct {
@@ -135,7 +172,7 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) (err erro
135172
return err
136173
}
137174

138-
events, err := m.eventMapping(ctx, responses, sdc.ServiceName)
175+
events, err := m.eventMapping(ctx, responses, sdc)
139176
if err != nil {
140177
err = errors.Wrap(err, "eventMapping failed")
141178
m.Logger().Error(err)
@@ -150,14 +187,14 @@ func (m *MetricSet) Fetch(ctx context.Context, reporter mb.ReporterV2) (err erro
150187
return nil
151188
}
152189

153-
func (m *MetricSet) eventMapping(ctx context.Context, tss []timeSeriesWithAligner, serviceName string) ([]mb.Event, error) {
154-
e := newIncomingFieldExtractor(m.Logger())
190+
func (m *MetricSet) eventMapping(ctx context.Context, tss []timeSeriesWithAligner, sdc metricsConfig) ([]mb.Event, error) {
191+
e := newIncomingFieldExtractor(m.Logger(), sdc)
155192

156193
var gcpService = gcp.NewStackdriverMetadataServiceForTimeSeries(nil)
157194
var err error
158195

159196
if !m.config.ExcludeLabels {
160-
if gcpService, err = NewMetadataServiceForConfig(m.config, serviceName); err != nil {
197+
if gcpService, err = NewMetadataServiceForConfig(m.config, sdc.ServiceName); err != nil {
161198
return nil, errors.Wrap(err, "error trying to create metadata service")
162199
}
163200
}
@@ -182,7 +219,7 @@ func (m *MetricSet) eventMapping(ctx context.Context, tss []timeSeriesWithAligne
182219
event.MetricSetFields.Put(singleEvent.Key, singleEvent.Value)
183220
}
184221

185-
if serviceName == "compute" {
222+
if sdc.ServiceName == "compute" {
186223
event.RootFields = addHostFields(groupedEvents)
187224
} else {
188225
event.RootFields = groupedEvents[0].ECS
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package metrics
6+
7+
import "testing"
8+
9+
var fakeMetricsConfig = []metricsConfig{
10+
{"billing", "", []string{}, ""},
11+
{"billing", "foobar/", []string{}, ""},
12+
{"billing", "foobar", []string{}, ""},
13+
}
14+
15+
func Test_metricsConfig_AddPrefixTo(t *testing.T) {
16+
metric := "awesome/metric"
17+
tests := []struct {
18+
name string
19+
fields metricsConfig
20+
want string
21+
}{
22+
{
23+
name: "only service name",
24+
fields: fakeMetricsConfig[0],
25+
want: "billing.googleapis.com/" + metric,
26+
},
27+
{
28+
name: "service metric prefix override",
29+
fields: fakeMetricsConfig[1],
30+
want: "foobar/" + metric,
31+
},
32+
{
33+
name: "service metric prefix override (without trailing /)",
34+
fields: fakeMetricsConfig[2],
35+
want: "foobar/" + metric,
36+
},
37+
}
38+
for _, tt := range tests {
39+
t.Run(tt.name, func(t *testing.T) {
40+
if got := tt.fields.AddPrefixTo(metric); got != tt.want {
41+
t.Errorf("metricsConfig.AddPrefixTo(%s) = %v, want %v", metric, got, tt.want)
42+
}
43+
})
44+
}
45+
}
46+
47+
func Test_metricsConfig_RemovePrefixFrom(t *testing.T) {
48+
metric := "awesome/metric"
49+
tests := []struct {
50+
name string
51+
fields metricsConfig
52+
metric string
53+
want string
54+
}{
55+
{
56+
name: "only service name",
57+
fields: fakeMetricsConfig[0],
58+
metric: "billing.googleapis.com/" + metric,
59+
want: metric,
60+
},
61+
{
62+
name: "service metric prefix override",
63+
fields: fakeMetricsConfig[1],
64+
metric: "foobar/" + metric,
65+
want: metric,
66+
},
67+
{
68+
name: "service metric prefix override (without trailing /)",
69+
fields: fakeMetricsConfig[2],
70+
metric: "foobar/" + metric,
71+
want: metric,
72+
},
73+
}
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
if got := tt.fields.RemovePrefixFrom(metric); got != tt.want {
77+
t.Errorf("metricsConfig.RemovePrefixFrom(%s) = %v, want %v", metric, got, tt.want)
78+
}
79+
})
80+
}
81+
}

x-pack/metricbeat/module/gcp/metrics/response_parser.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package metrics
66

77
import (
8-
"regexp"
98
"strings"
109
"time"
1110

@@ -18,12 +17,13 @@ import (
1817
"github.com/elastic/beats/v7/x-pack/metricbeat/module/gcp"
1918
)
2019

21-
func newIncomingFieldExtractor(l *logp.Logger) *incomingFieldExtractor {
22-
return &incomingFieldExtractor{logger: l}
20+
func newIncomingFieldExtractor(l *logp.Logger, mc metricsConfig) *incomingFieldExtractor {
21+
return &incomingFieldExtractor{logger: l, mc: mc}
2322
}
2423

2524
type incomingFieldExtractor struct {
2625
logger *logp.Logger
26+
mc metricsConfig
2727
}
2828

2929
// KeyValuePoint is a struct to capture the information parsed in an instant of a single metric
@@ -48,7 +48,7 @@ func (e *incomingFieldExtractor) extractTimeSeriesMetricValues(resp *monitoring.
4848
}
4949

5050
p := KeyValuePoint{
51-
Key: cleanMetricNameString(resp.Metric.Type, aligner),
51+
Key: cleanMetricNameString(resp.Metric.Type, aligner, e.mc),
5252
Value: getValueFromPoint(point),
5353
Timestamp: ts,
5454
}
@@ -71,17 +71,13 @@ func (e *incomingFieldExtractor) getTimestamp(p *monitoring.Point) (ts time.Time
7171
return time.Time{}, errors.New("error trying to extract the timestamp from the point data")
7272
}
7373

74-
var rx = regexp.MustCompile(`^[a-z_-]+\.googleapis.com\/`)
75-
76-
func cleanMetricNameString(s string, aligner string) string {
74+
func cleanMetricNameString(s string, aligner string, mc metricsConfig) string {
7775
if s == "" {
7876
return "unknown"
7977
}
8078

81-
prefix := rx.FindString(s)
82-
83-
removedPrefix := strings.TrimPrefix(s, prefix)
84-
replacedChars := strings.Replace(removedPrefix, "/", ".", -1)
79+
unprefixedMetric := mc.RemovePrefixFrom(s)
80+
replacedChars := strings.Replace(unprefixedMetric, "/", ".", -1)
8581

8682
metricName := replacedChars + gcp.AlignersMapToSuffix[aligner]
8783
return metricName

x-pack/metricbeat/module/gcp/metrics/response_parser_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
)
1212

1313
func TestCleanMetricNameString(t *testing.T) {
14+
computeMC := metricsConfig{"compute", "", []string{}, ""}
15+
1416
cases := []struct {
1517
title string
1618
metricType string
@@ -33,7 +35,7 @@ func TestCleanMetricNameString(t *testing.T) {
3335

3436
for _, c := range cases {
3537
t.Run(c.title, func(t *testing.T) {
36-
metricName := cleanMetricNameString(c.metricType, c.aligner)
38+
metricName := cleanMetricNameString(c.metricType, c.aligner, computeMC)
3739
assert.Equal(t, c.expectedMetricName, metricName)
3840
})
3941
}

0 commit comments

Comments
 (0)