exporter/collector/integrationtest/testcases/testcases_metrics.go (441 lines of code) (raw):
// Copyright 2022 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 testcases
import (
"os"
"strings"
"time"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"google.golang.org/api/option"
"google.golang.org/grpc"
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector"
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/googlemanagedprometheus"
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric"
)
var MetricsTestCases = []TestCase{
// Tests for the basic exporter
{
Name: "Sum becomes a GCM Cumulative",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.InstrumentationLibraryLabels = true
// Disable service resource labels here and below to keep output examples simpler.
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithFilteredResourceAttributes(metric.NoAttributes)},
},
{
Name: "Delta Sum becomes a GCM cumulative",
OTLPInputFixturePath: "testdata/fixtures/metrics/delta_counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/delta_counter_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithFilteredResourceAttributes(metric.NoAttributes)},
},
{
Name: "Non-monotonic Sum becomes a GCM Gauge",
OTLPInputFixturePath: "testdata/fixtures/metrics/nonmonotonic_counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/nonmonotonic_counter_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithFilteredResourceAttributes(metric.NoAttributes)},
},
{
Name: "Summary becomes a GCM Cumulative for sum/count, Gauges for quantiles",
OTLPInputFixturePath: "testdata/fixtures/metrics/summary.json",
ExpectFixturePath: "testdata/fixtures/metrics/summary_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
// Summary metrics are not possible with the SDK.
SkipForSDK: true,
},
{
Name: "Gauge becomes a GCM Gauge",
OTLPInputFixturePath: "testdata/fixtures/metrics/gauge.json",
ExpectFixturePath: "testdata/fixtures/metrics/gauge_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.InstrumentationLibraryLabels = true
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithFilteredResourceAttributes(metric.NoAttributes)},
},
{
Name: "Boolean-valued Gauge metric becomes an Int Gauge",
OTLPInputFixturePath: "testdata/fixtures/metrics/boolean_gauge.json",
ExpectFixturePath: "testdata/fixtures/metrics/boolean_gauge_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true, // Boolean valued metrics not implemented in SDK
},
{
Name: "Gauge with Untyped label is a standard GCM Gauge without GMP",
OTLPInputFixturePath: "testdata/fixtures/metrics/untyped_gauge.json",
ExpectFixturePath: "testdata/fixtures/metrics/untyped_gauge_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true,
},
{
Name: "Histogram becomes a GCM Distribution",
OTLPInputFixturePath: "testdata/fixtures/metrics/histogram.json",
ExpectFixturePath: "testdata/fixtures/metrics/histogram_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
cfg.MetricConfig.InstrumentationLibraryLabels = true
cfg.MetricConfig.EnableSumOfSquaredDeviation = true
},
MetricSDKExporterOptions: []metric.Option{
metric.WithFilteredResourceAttributes(metric.NoAttributes),
metric.WithSumOfSquaredDeviation(),
},
},
{
Name: "Exponential Histogram becomes a GCM Distribution with exponential bucketOptions",
OTLPInputFixturePath: "testdata/fixtures/metrics/exponential_histogram.json",
ExpectFixturePath: "testdata/fixtures/metrics/exponential_histogram_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ServiceResourceLabels = false
},
// Blocked on upstream support for exponential histograms:
// https://github.com/open-telemetry/opentelemetry-go/issues/2966
SkipForSDK: true,
},
{
Name: "Metrics from the Prometheus receiver can be successfully delivered",
OTLPInputFixturePath: "testdata/fixtures/metrics/prometheus.json",
ExpectFixturePath: "testdata/fixtures/metrics/prometheus_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.EnableSumOfSquaredDeviation = true
},
MetricSDKExporterOptions: []metric.Option{
metric.WithSumOfSquaredDeviation(),
},
},
{
Name: "Prometheus stale data point is dropped",
OTLPInputFixturePath: "testdata/fixtures/metrics/prometheus_stale.json",
ExpectFixturePath: "testdata/fixtures/metrics/prometheus_stale_expect.json",
CompareFixturePath: "testdata/fixtures/metrics/prometheus_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.EnableSumOfSquaredDeviation = true
},
SkipForSDK: true, // Stale point handling is not required for the SDK.
},
// Tests with special configuration options
{
Name: "Project not found return code",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_notfound_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.ProjectID = "notfoundproject"
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithProjectID("notfoundproject"), metric.WithFilteredResourceAttributes(metric.NoAttributes)},
ExpectErr: true,
},
{
Name: "Modified prefix unknown domain",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_unknown_domain_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.Prefix = "custom.googleapis.com/foobar.org"
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{
metric.WithMetricDescriptorTypeFormatter(func(m metricdata.Metrics) string {
return "custom.googleapis.com/foobar.org/" + m.Name
}),
metric.WithFilteredResourceAttributes(metric.NoAttributes),
},
},
{
Name: "Modified prefix workload.googleapis.com",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_workloadgoogleapis_prefix_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.Prefix = "workload.googleapis.com"
cfg.MetricConfig.ServiceResourceLabels = false
},
MetricSDKExporterOptions: []metric.Option{metric.WithFilteredResourceAttributes(metric.NoAttributes)},
},
{
Name: "Batching only sends 200 timeseries per-batch",
OTLPInputFixturePath: "testdata/fixtures/metrics/batching.json",
ExpectFixturePath: "testdata/fixtures/metrics/batching_expect.json",
// Summary metrics are not possible with the SDK.
SkipForSDK: true,
},
{
Name: "WithResourceFilter adds the appropriate resource attributes",
OTLPInputFixturePath: "testdata/fixtures/metrics/with_resource_filter.json",
ExpectFixturePath: "testdata/fixtures/metrics/with_resource_filter_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ResourceFilters = []collector.ResourceFilter{
{Prefix: "telemetry.sdk."},
}
},
MetricSDKExporterOptions: []metric.Option{
metric.WithFilteredResourceAttributes(func(kv attribute.KeyValue) bool {
// Include the default set of promoted resource attributes
if metric.DefaultResourceAttributesFilter(kv) {
return true
}
return strings.HasPrefix(string(kv.Key), "telemetry.sdk.")
}),
},
},
{
Name: "Multi-project metrics splits into multiple requests to different projects",
OTLPInputFixturePath: "testdata/fixtures/metrics/multi_project.json",
ExpectFixturePath: "testdata/fixtures/metrics/multi_project_expected.json",
// Multi-project exporting is not supported in the SDK exporter
SkipForSDK: true,
},
{
// see https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/issues/525
Name: "Metrics with only one +inf bucket can be sent",
OTLPInputFixturePath: "testdata/fixtures/metrics/prometheus_empty_buckets.json",
ExpectFixturePath: "testdata/fixtures/metrics/prometheus_empty_buckets_expected.json",
SkipForSDK: true,
},
{
Name: "Gzip compression enabled",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_compressed_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.ClientConfig.Compression = "gzip"
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true,
},
{
Name: "CreateServiceTimeSeries option enabled makes CreateServiceTimeSeries calls",
OTLPInputFixturePath: "testdata/fixtures/metrics/create_service_timeseries.json",
ExpectFixturePath: "testdata/fixtures/metrics/create_service_timeseries_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.CreateServiceTimeSeries = true
},
// SDK exporter does not support CreateServiceTimeSeries
SkipForSDK: true,
},
{
Name: "Write ahead log enabled",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_wal_expect.json",
CompareFixturePath: "testdata/fixtures/metrics/counter_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
dir, _ := os.MkdirTemp("", "test-wal-")
cfg.MetricConfig.WALConfig = &collector.WALConfig{
Directory: dir,
MaxBackoff: time.Duration(1 * time.Second),
}
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true,
},
{
Name: "Write ahead log enabled, basic prometheus metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/prometheus.json",
ExpectFixturePath: "testdata/fixtures/metrics/prometheus_wal_expect.json",
CompareFixturePath: "testdata/fixtures/metrics/prometheus_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
dir, _ := os.MkdirTemp("", "test-wal-")
cfg.MetricConfig.WALConfig = &collector.WALConfig{
Directory: dir,
MaxBackoff: time.Duration(1 * time.Second),
}
cfg.MetricConfig.EnableSumOfSquaredDeviation = true
},
SkipForSDK: true,
},
{
Name: "Write ahead log enabled, basic Counter with unavailable return code",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_wal_unavailable_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.ProjectID = "unavailableproject"
dir, _ := os.MkdirTemp("", "test-wal-")
cfg.MetricConfig.WALConfig = &collector.WALConfig{
Directory: dir,
MaxBackoff: time.Duration(2 * time.Second),
}
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true,
ExpectRetries: true,
},
{
Name: "Write ahead log enabled, basic Counter with deadline_exceeded return code",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_wal_deadline_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.ProjectID = "deadline_exceededproject"
dir, _ := os.MkdirTemp("", "test-wal-")
cfg.MetricConfig.WALConfig = &collector.WALConfig{
Directory: dir,
MaxBackoff: time.Duration(2 * time.Second),
}
cfg.MetricConfig.ServiceResourceLabels = false
},
SkipForSDK: true,
ExpectRetries: true,
},
{
Name: "Write ahead log enabled, CreateServiceTimeSeries",
OTLPInputFixturePath: "testdata/fixtures/metrics/create_service_timeseries.json",
ExpectFixturePath: "testdata/fixtures/metrics/create_service_timeseries_wal_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.CreateServiceTimeSeries = true
dir, _ := os.MkdirTemp("", "test-wal-")
cfg.MetricConfig.WALConfig = &collector.WALConfig{
Directory: dir,
MaxBackoff: time.Duration(1 * time.Second),
}
},
// SDK exporter does not support CreateServiceTimeSeries
SkipForSDK: true,
},
{
Name: "Custom User Agent",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_user_agent_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.UserAgent = "custom-user-agent"
},
MetricSDKExporterOptions: []metric.Option{
metric.WithMonitoringClientOptions(option.WithGRPCDialOption(grpc.WithUserAgent("custom-user-agent"))),
},
},
// Tests for the GMP exporter
{
Name: "[GMP] prometheus receiver metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/google_managed_prometheus.json",
ExpectFixturePath: "testdata/fixtures/metrics/google_managed_prometheus_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Gauge becomes a GCM Gauge with /gauge suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/gauge.json",
ExpectFixturePath: "testdata/fixtures/metrics/gauge_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Untyped Gauge becomes a GCM Gauge and a Cumulative with /unknown and /unknown:counter suffixes",
OTLPInputFixturePath: "testdata/fixtures/metrics/untyped_gauge.json",
ExpectFixturePath: "testdata/fixtures/metrics/untyped_gauge_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Sum becomes a GCM Cumulative with /counter suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/counter_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Delta Sum becomes a GCM Cumulative with a /counter suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/delta_counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/delta_counter_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Non-Monotonic Sum becomes a GCM Gauge with a /gauge suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/nonmonotonic_counter.json",
ExpectFixturePath: "testdata/fixtures/metrics/nonmonotonic_counter_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Histogram becomes a GCM Histogram with a /histogram suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/histogram.json",
ExpectFixturePath: "testdata/fixtures/metrics/histogram_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Exponential Histogram becomes a GCM Distribution with exponential bucketOptions and a /histogram suffix",
OTLPInputFixturePath: "testdata/fixtures/metrics/exponential_histogram.json",
ExpectFixturePath: "testdata/fixtures/metrics/exponential_histogram_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
{
Name: "[GMP] Summary becomes a GCM Cumulative for sum/count, Gauges for quantiles",
OTLPInputFixturePath: "testdata/fixtures/metrics/summary.json",
ExpectFixturePath: "testdata/fixtures/metrics/summary_gmp_expect.json",
ConfigureCollector: configureGMPCollector,
// prometheus_target is not supported by the SDK
SkipForSDK: true,
},
// Tests for specific distributions of the collector
{
Name: "Ops Agent Self-Reported metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/ops_agent_self_metrics.json",
ExpectFixturePath: "testdata/fixtures/metrics/ops_agent_self_metrics_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
// Metric descriptors should not be created under agent.googleapis.com
cfg.MetricConfig.SkipCreateMetricDescriptor = true
cfg.MetricConfig.ServiceResourceLabels = false
},
// We don't support disabling metric descriptor creation for the SDK exporter
SkipForSDK: true,
},
{
Name: "Ops Agent Host Metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/ops_agent_host_metrics.json",
ExpectFixturePath: "testdata/fixtures/metrics/ops_agent_host_metrics_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
// Metric descriptors should not be created under agent.googleapis.com
cfg.MetricConfig.SkipCreateMetricDescriptor = true
},
// We don't support disabling metric descriptor creation for the SDK exporter
SkipForSDK: true,
},
{
Name: "GKE Workload Metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/workload_metrics.json",
ExpectFixturePath: "testdata/fixtures/metrics/workload_metrics_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.Prefix = "workload.googleapis.com/"
cfg.MetricConfig.SkipCreateMetricDescriptor = true
cfg.MetricConfig.ServiceResourceLabels = false
},
// We don't support disabling metric descriptor creation for the SDK exporter
SkipForSDK: true,
},
{
Name: "GKE Metrics Agent",
OTLPInputFixturePath: "testdata/fixtures/metrics/gke_metrics_agent.json",
ExpectFixturePath: "testdata/fixtures/metrics/gke_metrics_agent_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.CreateServiceTimeSeries = true
},
// SDK exporter does not support CreateServiceTimeSeries
SkipForSDK: true,
},
{
Name: "GKE Control Plane Metrics Agent",
OTLPInputFixturePath: "testdata/fixtures/metrics/gke_control_plane.json",
ExpectFixturePath: "testdata/fixtures/metrics/gke_control_plane_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
cfg.MetricConfig.CreateServiceTimeSeries = true
cfg.MetricConfig.ServiceResourceLabels = false
},
// SDK exporter does not support CreateServiceTimeSeries
SkipForSDK: true,
},
{
Name: "BMS Ops Agent Host Metrics",
OTLPInputFixturePath: "testdata/fixtures/metrics/bms_ops_agent_host_metrics.json",
ExpectFixturePath: "testdata/fixtures/metrics/bms_ops_agent_host_metrics_expect.json",
ConfigureCollector: func(cfg *collector.Config) {
// Metric descriptors should not be created under agent.googleapis.com
cfg.MetricConfig.SkipCreateMetricDescriptor = true
},
// We don't support disabling metric descriptor creation for the SDK exporter
SkipForSDK: true,
},
// TODO: Add integration tests for workload.googleapis.com metrics from the ops agent
}
func configureGMPCollector(cfg *collector.Config) {
//nolint:errcheck
featuregate.GlobalRegistry().Set("pkg.translator.prometheus.NormalizeName", true)
cfg.MetricConfig.Prefix = "prometheus.googleapis.com/"
cfg.MetricConfig.SkipCreateMetricDescriptor = true
gmpConfig := googlemanagedprometheus.DefaultConfig()
cfg.MetricConfig.GetMetricName = func(baseName string, metric pmetric.Metric) (string, error) {
compliantName := prometheus.BuildCompliantName(metric, "", gmpConfig.AddMetricSuffixes)
return googlemanagedprometheus.GetMetricName(baseName, compliantName, metric)
}
cfg.MetricConfig.MapMonitoredResource = gmpConfig.MapToPrometheusTarget
cfg.MetricConfig.ExtraMetrics = gmpConfig.ExtraMetrics
cfg.MetricConfig.InstrumentationLibraryLabels = false
cfg.MetricConfig.ServiceResourceLabels = false
cfg.MetricConfig.EnableSumOfSquaredDeviation = true
}