Skip to content

Commit

Permalink
support disabling unit and metric suffixes and move config into GMP m…
Browse files Browse the repository at this point in the history
…odule.
  • Loading branch information
dashpole committed Aug 21, 2023
1 parent 54cb66c commit cc57eff
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 133 deletions.
6 changes: 3 additions & 3 deletions exporter/collector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ type MetricConfig struct {
// exporter. It allows overriding the function used to map otel resource to
// monitored resource.
MapMonitoredResource func(pcommon.Resource) *monitoredrespb.MonitoredResource
// ExtraMetrics is an extension point for exporters to add to the set
// of ResourceMetrics during a call to PushMetrics.
ExtraMetrics func(pmetric.Metrics) pmetric.ResourceMetricsSlice
// ExtraMetrics is an extension point for exporters to modify the metrics
// before they are sent by the exporter.
ExtraMetrics func(pmetric.Metrics)
// GetMetricName is not settable in config files, but can be used by other
// exporters which extend the functionality of this exporter. It allows
// customizing the naming of metrics. baseName already includes type
Expand Down
47 changes: 47 additions & 0 deletions exporter/collector/googlemanagedprometheus/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package googlemanagedprometheus

// Config provides configuration options specific to the GMP translation.
// It is meant to be embedded in the googlemanagedprometheus configuration.
type Config struct {
// AddMetricSuffixes controls whether suffixes are added to metric names. Defaults to true.
AddMetricSuffixes bool `mapstructure:"add_metric_suffixes"`
// ExtraMetricsConfig configures the target_info and otel_scope_info metrics.
ExtraMetricsConfig ExtraMetricsConfig `mapstructure:"extra_metrics_config"`
}

// ExtraMetricsConfig controls the inclusion of additional metrics.
type ExtraMetricsConfig struct {
// Add `target_info` metric based on the resource. On by default.
EnableTargetInfo bool `mapstructure:"enable_target_info"`
// Add `otel_scope_info` metric and `scope_name`/`scope_version` attributes to all other metrics. On by default.
EnableScopeInfo bool `mapstructure:"enable_scope_info"`
}

func DefaultConfig() Config {
return Config{
AddMetricSuffixes: true,
ExtraMetricsConfig: ExtraMetricsConfig{
EnableTargetInfo: true,
EnableScopeInfo: true,
},
}
}

// Validate checks if the exporter configuration is valid.
func (cfg *Config) Validate() error {
return nil

Check warning on line 46 in exporter/collector/googlemanagedprometheus/config.go

View check run for this annotation

Codecov / codecov/patch

exporter/collector/googlemanagedprometheus/config.go#L45-L46

Added lines #L45 - L46 were not covered by tests
}
24 changes: 18 additions & 6 deletions exporter/collector/googlemanagedprometheus/extra_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ var untypedDoubleExportFeatureGate = featuregate.GlobalRegistry().MustRegister(
featuregate.WithRegisterDescription("Enable automatically exporting untyped Prometheus metrics as both gauge and cumulative to GCP."),
featuregate.WithRegisterReferenceURL("https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/pull/668"))

// AddUntypedMetrics looks for any Gauge data point with the special Ops Agent untyped metric
func (c Config) ExtraMetrics(m pmetric.Metrics) {
addUntypedMetrics(m)
c.addTargetInfoMetric(m)
c.addScopeInfoMetric(m)
}

// addUntypedMetrics looks for any Gauge data point with the special Ops Agent untyped metric
// attribute and duplicates that data point to a matching Sum.
func AddUntypedMetrics(m pmetric.Metrics) {
func addUntypedMetrics(m pmetric.Metrics) {
if !untypedDoubleExportFeatureGate.IsEnabled() {
return
}
Expand Down Expand Up @@ -98,11 +104,14 @@ func AddUntypedMetrics(m pmetric.Metrics) {
}
}

// AddTargetInfoMetric inserts target_info for each resource.
// addTargetInfoMetric inserts target_info for each resource.
// First, it extracts the target_info metric from each ResourceMetric associated with the input pmetric.Metrics
// and inserts it into a new ScopeMetric for that resource, as specified in
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.16.0/specification/compatibility/prometheus_and_openmetrics.md#resource-attributes-1
func AddTargetInfoMetric(m pmetric.Metrics) {
func (c Config) addTargetInfoMetric(m pmetric.Metrics) {
if !c.ExtraMetricsConfig.EnableTargetInfo {
return
}
rms := m.ResourceMetrics()
// loop over input (original) resource metrics
for i := 0; i < rms.Len(); i++ {
Expand Down Expand Up @@ -178,10 +187,13 @@ func AddTargetInfoMetric(m pmetric.Metrics) {
}
}

// AddScopeInfoMetric adds the otel_scope_info metric to a Metrics slice as specified in
// addScopeInfoMetric adds the otel_scope_info metric to a Metrics slice as specified in
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.16.0/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope-1
// It also updates all other metrics with the corresponding scope_name and scope_version attributes, if they are present.
func AddScopeInfoMetric(m pmetric.Metrics) {
func (c Config) addScopeInfoMetric(m pmetric.Metrics) {
if !c.ExtraMetricsConfig.EnableScopeInfo {
return
}
rms := m.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
sms := rms.At(i).ScopeMetrics()
Expand Down
149 changes: 42 additions & 107 deletions exporter/collector/googlemanagedprometheus/extra_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,16 @@ func testMetric(timestamp time.Time) pmetric.Metrics {
func TestAddExtraMetrics(t *testing.T) {
timestamp := time.Now()
for _, tc := range []struct {
testFunc func(pmetric.Metrics) pmetric.ResourceMetricsSlice
input pmetric.Metrics
expected pmetric.ResourceMetricsSlice
name string
enableUntypedFeatureGate bool
config Config
input pmetric.Metrics
expected pmetric.ResourceMetricsSlice
name string
}{
{
name: "add target info from resource metric",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddTargetInfoMetric(m)
return m.ResourceMetrics()
},
input: testMetric(timestamp),
name: "add target info from resource metric",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableTargetInfo: true}},
input: testMetric(timestamp),
expected: func() pmetric.ResourceMetricsSlice {
metrics := testMetric(timestamp).ResourceMetrics()

Expand All @@ -74,12 +72,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "add scope info from scope metrics",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddScopeInfoMetric(m)
return m.ResourceMetrics()
},
input: testMetric(timestamp),
name: "add scope info from scope metrics",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}},
input: testMetric(timestamp),
expected: func() pmetric.ResourceMetricsSlice {
metrics := testMetric(timestamp).ResourceMetrics()

Expand All @@ -100,11 +95,8 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "add scope info with attributes",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddScopeInfoMetric(m)
return m.ResourceMetrics()
},
name: "add scope info with attributes",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "bar")
Expand Down Expand Up @@ -133,50 +125,10 @@ func TestAddExtraMetrics(t *testing.T) {
},
{
name: "add both scope info and target info",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddScopeInfoMetric(m)
AddTargetInfoMetric(m)
return m.ResourceMetrics()
},
input: testMetric(timestamp),
expected: func() pmetric.ResourceMetricsSlice {
metrics := testMetric(timestamp).ResourceMetrics()
scopeMetrics := metrics.At(0).ScopeMetrics()

// Insert a new, empty ScopeMetricsSlice for this resource that will hold target_info
sm := scopeMetrics.AppendEmpty()
metric := sm.Metrics().AppendEmpty()
metric.SetName("target_info")
metric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1)
metric.Gauge().DataPoints().At(0).Attributes().PutStr("foo-label", "bar")
metric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp))

// Insert the scope_info metric into the existing ScopeMetricsSlice
sm = scopeMetrics.At(0)
scopeInfoMetric := sm.Metrics().AppendEmpty()
scopeInfoMetric.SetName("otel_scope_info")
scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1)

// add otel_scope_* attributes to all metrics in all scopes
// this includes otel_scope_info for the existing (input) ScopeMetrics,
// and target_info (which will have an empty scope)
for i := 0; i < sm.Metrics().Len(); i++ {
metric := sm.Metrics().At(i)
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_name", "myscope")
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_version", "v0.0.1")
metric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
}

return metrics
}(),
},
{
name: "ordering of scope/target should not matter",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddTargetInfoMetric(m)
AddScopeInfoMetric(m)
return m.ResourceMetrics()
},
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{
EnableScopeInfo: true,
EnableTargetInfo: true,
}},
input: testMetric(timestamp),
expected: func() pmetric.ResourceMetricsSlice {
metrics := testMetric(timestamp).ResourceMetrics()
Expand Down Expand Up @@ -210,11 +162,8 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "scope info for other metric types",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
AddScopeInfoMetric(m)
return m.ResourceMetrics()
},
name: "scope info for other metric types",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
sum := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().AppendEmpty()
Expand Down Expand Up @@ -293,13 +242,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "add untyped Sum metric from Gauge",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
//nolint:errcheck
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, true)
AddUntypedMetrics(m)
return m.ResourceMetrics()
},
name: "add untyped Sum metric from Gauge",
enableUntypedFeatureGate: true,
config: Config{},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).
Expand Down Expand Up @@ -330,13 +275,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "add untyped Sum metric from Gauge (double value)",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
//nolint:errcheck
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, true)
AddUntypedMetrics(m)
return m.ResourceMetrics()
},
name: "add untyped Sum metric from Gauge (double value)",
enableUntypedFeatureGate: true,
config: Config{},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).
Expand Down Expand Up @@ -373,13 +314,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "untyped Gauge does nothing if feature gate is disabled",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
//nolint:errcheck
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, false)
AddUntypedMetrics(m)
return m.ResourceMetrics()
},
name: "untyped Gauge does nothing if feature gate is disabled",
enableUntypedFeatureGate: false,
config: Config{},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).
Expand All @@ -400,13 +337,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "untyped Gauge does nothing if feature gate is enabled and key!=true",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
//nolint:errcheck
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, true)
AddUntypedMetrics(m)
return m.ResourceMetrics()
},
name: "untyped Gauge does nothing if feature gate is enabled and key!=true",
enableUntypedFeatureGate: true,
config: Config{},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).
Expand All @@ -427,13 +360,9 @@ func TestAddExtraMetrics(t *testing.T) {
}(),
},
{
name: "untyped non-Gauge does nothing if feature gate is enabled",
testFunc: func(m pmetric.Metrics) pmetric.ResourceMetricsSlice {
//nolint:errcheck
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, true)
AddUntypedMetrics(m)
return m.ResourceMetrics()
},
name: "untyped non-Gauge does nothing if feature gate is enabled",
enableUntypedFeatureGate: true,
config: Config{},
input: func() pmetric.Metrics {
metrics := testMetric(timestamp)
metrics.ResourceMetrics().At(0).
Expand All @@ -453,8 +382,14 @@ func TestAddExtraMetrics(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
rms := tc.testFunc(tc.input)
assert.EqualValues(t, tc.expected, rms)
if tc.enableUntypedFeatureGate {
featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, true)
// disable it after the test is over
defer featuregate.GlobalRegistry().Set(gcpUntypedDoubleExportGateKey, false)
}
m := tc.input
tc.config.ExtraMetrics(m)
assert.EqualValues(t, tc.expected, m.ResourceMetrics())
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var promTargetKeys = map[string][]string{
instanceLabel: {instanceLabel, semconv.AttributeFaaSInstance, semconv.AttributeServiceInstanceID},
}

func MapToPrometheusTarget(res pcommon.Resource) *monitoredrespb.MonitoredResource {
func (c Config) MapToPrometheusTarget(res pcommon.Resource) *monitoredrespb.MonitoredResource {
attrs := res.Attributes()
// Prepend namespace if it exists to match what is specified in
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#resource-attributes-1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func TestMapToPrometheusTarget(t *testing.T) {
for k, v := range tc.resourceLabels {
r.Attributes().PutStr(k, v)
}
got := MapToPrometheusTarget(r)
got := DefaultConfig().MapToPrometheusTarget(r)
assert.Equal(t, tc.expected, got)
})
}
Expand Down
5 changes: 3 additions & 2 deletions exporter/collector/googlemanagedprometheus/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ import (
"go.opentelemetry.io/collector/pdata/pmetric"
)

func GetMetricName(baseName string, metric pmetric.Metric) (string, error) {
// GetMetricName returns the metric name with GMP-specific suffixes. The.
func (c Config) GetMetricName(baseName string, metric pmetric.Metric) (string, error) {
// First, build a name that is compliant with prometheus conventions
compliantName := prometheus.BuildPromCompliantName(metric, "")
compliantName := prometheus.BuildCompliantName(metric, "", c.AddMetricSuffixes)
// Second, ad the GMP-specific suffix
switch metric.Type() {
case pmetric.MetricTypeSum:
Expand Down
2 changes: 1 addition & 1 deletion exporter/collector/googlemanagedprometheus/naming_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func TestGetMetricName(t *testing.T) {
assert.True(t, gate.IsEnabled())
metric := pmetric.NewMetric()
tc.metric(metric)
got, err := GetMetricName(tc.baseName, metric)
got, err := DefaultConfig().GetMetricName(tc.baseName, metric)
if tc.expectErr == (err == nil) {
t.Errorf("MetricName(%v, %+v)=err(%v); want err: %v", tc.baseName, metric, err, tc.expectErr)
}
Expand Down
Loading

0 comments on commit cc57eff

Please sign in to comment.