diff --git a/exporter/collector/config.go b/exporter/collector/config.go index c3206b492..ba86f5c6a 100644 --- a/exporter/collector/config.go +++ b/exporter/collector/config.go @@ -49,6 +49,9 @@ type MetricConfig struct { SkipCreateMetricDescriptor bool `mapstructure:"skip_create_descriptor"` // If a metric belongs to one of these domains it does not get a prefix. KnownDomains []string `mapstructure:"known_domains"` + // If true, this will send all timeseries using `CreateServiceTimeSeries`. + // Implicitly, this sets `SkipMetricDescriptor` to true. + CreateServiceTimeSeries bool `mapstructure:"create_service_timeseries"` } // ResourceMapping defines mapping of resources from source (OpenCensus) to target (Google Cloud). diff --git a/exporter/collector/go.mod b/exporter/collector/go.mod index 028bc53a2..41e268b55 100644 --- a/exporter/collector/go.mod +++ b/exporter/collector/go.mod @@ -24,6 +24,7 @@ require ( cloud.google.com/go/monitoring v1.1.0 github.com/aws/aws-sdk-go v1.42.14 // indirect github.com/google/go-cmp v0.5.6 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 ) replace github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace => ../trace diff --git a/exporter/collector/go.sum b/exporter/collector/go.sum index 7f3d33ebd..743e2e1d1 100644 --- a/exporter/collector/go.sum +++ b/exporter/collector/go.sum @@ -618,8 +618,9 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/exporter/collector/internal/integrationtest/testcases.go b/exporter/collector/internal/integrationtest/testcases.go index b8a23008b..aad3697c3 100644 --- a/exporter/collector/internal/integrationtest/testcases.go +++ b/exporter/collector/internal/integrationtest/testcases.go @@ -90,5 +90,13 @@ var ( ExpectFixturePath: "testdata/fixtures/gke_control_plane_metrics_agent_metrics_expect.json", Skip: true, }, + { + Name: "CreateServiceTimeSeries", + OTLPInputFixturePath: "testdata/fixtures/create_service_timeseries_metrics.json", + ExpectFixturePath: "testdata/fixtures/create_service_timeseries_metrics_expect.json", + Configure: func(cfg *collector.Config) { + cfg.MetricConfig.CreateServiceTimeSeries = true + }, + }, } ) diff --git a/exporter/collector/metricsexporter.go b/exporter/collector/metricsexporter.go index f8eef6fa9..e8b20d5f7 100644 --- a/exporter/collector/metricsexporter.go +++ b/exporter/collector/metricsexporter.go @@ -19,6 +19,7 @@ package collector import ( "context" + "errors" "fmt" "log" "math" @@ -31,6 +32,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter/exporterhelper" "go.opentelemetry.io/collector/model/pdata" + "golang.org/x/oauth2/google" "google.golang.org/genproto/googleapis/api/distribution" "google.golang.org/genproto/googleapis/api/label" metricpb "google.golang.org/genproto/googleapis/api/metric" @@ -79,6 +81,19 @@ func newGoogleCloudMetricsExporter( ) (component.MetricsExporter, error) { setVersionInUserAgent(cfg, set.BuildInfo.Version) + // TODO - Share this lookup somewhere + if cfg.ProjectID == "" { + creds, err := google.FindDefaultCredentials(ctx, monitoring.DefaultAuthScopes()...) + // TODO- better error messages, this is copy-pasta from OpenCensus exporter. + if err != nil { + return nil, fmt.Errorf("google_cloud: %v", err) + } + if creds.ProjectID == "" { + return nil, errors.New("google_cloud: no project found with application default credentials") + } + cfg.ProjectID = creds.ProjectID + } + clientOpts, err := generateClientOptions(cfg) if err != nil { return nil, err @@ -128,8 +143,8 @@ func (me *metricsExporter) pushMetrics(ctx context.Context, m pdata.Metrics) err mes := ilm.Metrics() for k := 0; k < mes.Len(); k++ { metric := mes.At(k) - // TODO - check to see if this is a service/system metric and doesn't send descriptors. - if !me.cfg.MetricConfig.SkipCreateMetricDescriptor { + // We only send metric descriptors if we're configured *and* we're not sending service timeseries. + if !(me.cfg.MetricConfig.SkipCreateMetricDescriptor || me.cfg.MetricConfig.CreateServiceTimeSeries) { for _, md := range me.mapper.metricDescriptor(metric) { if md != nil { select { @@ -146,8 +161,7 @@ func (me *metricsExporter) pushMetrics(ctx context.Context, m pdata.Metrics) err } // TODO: self observability - // TODO: Figure out how to configure service time series calls. - if false { + if me.cfg.MetricConfig.CreateServiceTimeSeries { err := me.createServiceTimeSeries(ctx, timeSeries) recordPointCount(ctx, len(timeSeries), m.DataPointCount()-len(timeSeries), err) return err diff --git a/exporter/collector/testdata/fixtures/create_service_timeseries_metrics.json b/exporter/collector/testdata/fixtures/create_service_timeseries_metrics.json new file mode 100644 index 000000000..4415bc65c --- /dev/null +++ b/exporter/collector/testdata/fixtures/create_service_timeseries_metrics.json @@ -0,0 +1,62 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "cloud.platform", + "value": { + "stringValue": "gcp_kubernetes_engine" + } + }, + { + "key": "k8s.node.name", + "value": { + "stringValue": "gke-rabbitmq-test-dev-default-pool-4ffbde79-k6w0" + } + }, + { + "key": "cloud.availability_zone", + "value": { + "stringValue": "us-central1-c" + } + }, + { + "key": "k8s.cluster.name", + "value": { + "stringValue": "rabbitmq-test-dev" + } + } + ] + }, + "instrumentationLibraryMetrics": [ + { + "instrumentationLibrary": {}, + "metrics": [ + { + "name":"kubernetes.io/internal/nodes/clustermetrics/node_network_availability_transition_time", + "description":"Node network unavailable condition last transition time (seconds since epoch)", + "gauge":{ + "dataPoints":[ + { + "attributes":[ + { + "key":"network_unavailable", + "value":{ + "stringValue":"False" + } + } + ], + "timeUnixNano":"1639162382976000000", + "asInt":"1639069893" + } + ] + } + } + ] + } + ] + } + ] + } + \ No newline at end of file diff --git a/exporter/collector/testdata/fixtures/create_service_timeseries_metrics_expect.json b/exporter/collector/testdata/fixtures/create_service_timeseries_metrics_expect.json new file mode 100644 index 000000000..b245fbc26 --- /dev/null +++ b/exporter/collector/testdata/fixtures/create_service_timeseries_metrics_expect.json @@ -0,0 +1,36 @@ +{ + "createServiceTimeSeriesRequests": [ + { + "timeSeries": [ + { + "metric": { + "type": "kubernetes.io/internal/nodes/clustermetrics/node_network_availability_transition_time", + "labels": { + "network_unavailable": "False" + } + }, + "resource": { + "type": "k8s_node", + "labels": { + "cluster_name": "rabbitmq-test-dev", + "location": "us-central1-c", + "node_name": "gke-rabbitmq-test-dev-default-pool-4ffbde79-k6w0" + } + }, + "metricKind": "GAUGE", + "valueType": "INT64", + "points": [ + { + "interval": { + "endTime": "1970-01-01T00:00:00Z" + }, + "value": { + "int64Value": "1639069893" + } + } + ] + } + ] + } + ] +} \ No newline at end of file