From 7b558058b7cc960667590e5413ef55157b06652e Mon Sep 17 00:00:00 2001 From: Ramon Nogueira Date: Fri, 3 Aug 2018 08:39:44 -0700 Subject: [PATCH] Remove deprecated Stackdriver exporter (#856) It's moved to contrib.go.opencensus.io/exporter/stackdriver. --- exporter/stackdriver/stackdriver.go | 148 ---- exporter/stackdriver/stackdriver_test.go | 125 ---- exporter/stackdriver/stats.go | 439 ------------ exporter/stackdriver/stats_test.go | 866 ----------------------- exporter/stackdriver/trace.go | 172 ----- exporter/stackdriver/trace_proto.go | 255 ------- exporter/stackdriver/trace_proto_test.go | 389 ---------- exporter/stackdriver/trace_test.go | 62 -- 8 files changed, 2456 deletions(-) delete mode 100644 exporter/stackdriver/stackdriver.go delete mode 100644 exporter/stackdriver/stackdriver_test.go delete mode 100644 exporter/stackdriver/stats.go delete mode 100644 exporter/stackdriver/stats_test.go delete mode 100644 exporter/stackdriver/trace.go delete mode 100644 exporter/stackdriver/trace_proto.go delete mode 100644 exporter/stackdriver/trace_proto_test.go delete mode 100644 exporter/stackdriver/trace_test.go diff --git a/exporter/stackdriver/stackdriver.go b/exporter/stackdriver/stackdriver.go deleted file mode 100644 index b4f152149..000000000 --- a/exporter/stackdriver/stackdriver.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver has moved. -// -// Deprecated: Use contrib.go.opencensus.io/exporter/stackdriver instead. -package stackdriver // import "go.opencensus.io/exporter/stackdriver" - -import ( - "context" - "errors" - "fmt" - "log" - "time" - - traceapi "cloud.google.com/go/trace/apiv2" - "go.opencensus.io/stats/view" - "go.opencensus.io/trace" - "golang.org/x/oauth2/google" - "google.golang.org/api/option" - monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" -) - -// Options contains options for configuring the exporter. -// -// Deprecated: This package has been moved to: contrib.go.opencensus.io/exporter/stackdriver. -type Options struct { - // ProjectID is the identifier of the Stackdriver - // project the user is uploading the stats data to. - // If not set, this will default to your "Application Default Credentials". - // For details see: https://developers.google.com/accounts/docs/application-default-credentials - ProjectID string - - // OnError is the hook to be called when there is - // an error uploading the stats or tracing data. - // If no custom hook is set, errors are logged. - // Optional. - OnError func(err error) - - // MonitoringClientOptions are additional options to be passed - // to the underlying Stackdriver Monitoring API client. - // Optional. - MonitoringClientOptions []option.ClientOption - - // TraceClientOptions are additional options to be passed - // to the underlying Stackdriver Trace API client. - // Optional. - TraceClientOptions []option.ClientOption - - // BundleDelayThreshold determines the max amount of time - // the exporter can wait before uploading view data to - // the backend. - // Optional. - BundleDelayThreshold time.Duration - - // BundleCountThreshold determines how many view data events - // can be buffered before batch uploading them to the backend. - // Optional. - BundleCountThreshold int - - // Resource is an optional field that represents the Stackdriver - // MonitoredResource, a resource that can be used for monitoring. - // If no custom ResourceDescriptor is set, a default MonitoredResource - // with type global and no resource labels will be used. - // Optional. - Resource *monitoredrespb.MonitoredResource - - // MetricPrefix overrides the OpenCensus prefix of a stackdriver metric. - // Optional. - MetricPrefix string -} - -// Exporter is a stats.Exporter and trace.Exporter -// implementation that uploads data to Stackdriver. -// -// Deprecated: This package has been moved to: contrib.go.opencensus.io/exporter/stackdriver. -type Exporter struct { - traceExporter *traceExporter - statsExporter *statsExporter -} - -// NewExporter creates a new Exporter that implements both stats.Exporter and -// trace.Exporter. -// -// Deprecated: This package has been moved to: contrib.go.opencensus.io/exporter/stackdriver. -func NewExporter(o Options) (*Exporter, error) { - if o.ProjectID == "" { - creds, err := google.FindDefaultCredentials(context.Background(), traceapi.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("stackdriver: %v", err) - } - if creds.ProjectID == "" { - return nil, errors.New("stackdriver: no project found with application default credentials") - } - o.ProjectID = creds.ProjectID - } - se, err := newStatsExporter(o) - if err != nil { - return nil, err - } - te, err := newTraceExporter(o) - if err != nil { - return nil, err - } - return &Exporter{ - statsExporter: se, - traceExporter: te, - }, nil -} - -// ExportView exports to the Stackdriver Monitoring if view data -// has one or more rows. -func (e *Exporter) ExportView(vd *view.Data) { - e.statsExporter.ExportView(vd) -} - -// ExportSpan exports a SpanData to Stackdriver Trace. -func (e *Exporter) ExportSpan(sd *trace.SpanData) { - e.traceExporter.ExportSpan(sd) -} - -// Flush waits for exported data to be uploaded. -// -// This is useful if your program is ending and you do not -// want to lose recent stats or spans. -func (e *Exporter) Flush() { - e.statsExporter.Flush() - e.traceExporter.Flush() -} - -func (o Options) handleError(err error) { - if o.OnError != nil { - o.OnError(err) - return - } - log.Printf("Error exporting to Stackdriver: %v", err) -} diff --git a/exporter/stackdriver/stackdriver_test.go b/exporter/stackdriver/stackdriver_test.go deleted file mode 100644 index 55cc81e93..000000000 --- a/exporter/stackdriver/stackdriver_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2018, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "go.opencensus.io/internal/testpb" - "go.opencensus.io/plugin/ochttp" - "go.opencensus.io/stats/view" - "go.opencensus.io/trace" - "golang.org/x/net/context/ctxhttp" -) - -func TestExport(t *testing.T) { - projectID, ok := os.LookupEnv("STACKDRIVER_TEST_PROJECT_ID") - if !ok { - t.Skip("STACKDRIVER_TEST_PROJECT_ID not set") - } - - var exportErrors []error - - exporter, err := NewExporter(Options{ProjectID: projectID, OnError: func(err error) { - exportErrors = append(exportErrors, err) - }}) - if err != nil { - t.Fatal(err) - } - defer exporter.Flush() - - trace.RegisterExporter(exporter) - defer trace.UnregisterExporter(exporter) - view.RegisterExporter(exporter) - defer view.UnregisterExporter(exporter) - - trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) - - _, span := trace.StartSpan(context.Background(), "custom-span") - time.Sleep(10 * time.Millisecond) - span.End() - - // Test HTTP spans - - handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - _, backgroundSpan := trace.StartSpan(context.Background(), "BackgroundWork") - spanContext := backgroundSpan.SpanContext() - time.Sleep(10 * time.Millisecond) - backgroundSpan.End() - - _, span := trace.StartSpan(req.Context(), "Sleep") - span.AddLink(trace.Link{Type: trace.LinkTypeChild, TraceID: spanContext.TraceID, SpanID: spanContext.SpanID}) - time.Sleep(150 * time.Millisecond) // do work - span.End() - rw.Write([]byte("Hello, world!")) - }) - server := httptest.NewServer(&ochttp.Handler{Handler: handler}) - defer server.Close() - - ctx := context.Background() - client := &http.Client{ - Transport: &ochttp.Transport{}, - } - resp, err := ctxhttp.Get(ctx, client, server.URL+"/test/123?abc=xyz") - if err != nil { - t.Fatal(err) - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if want, got := "Hello, world!", string(body); want != got { - t.Fatalf("resp.Body = %q; want %q", want, got) - } - - // Flush twice to expose issue of exporter creating traces internally (#557) - exporter.Flush() - exporter.Flush() - - for _, err := range exportErrors { - t.Error(err) - } -} - -func TestGRPC(t *testing.T) { - projectID, ok := os.LookupEnv("STACKDRIVER_TEST_PROJECT_ID") - if !ok { - t.Skip("STACKDRIVER_TEST_PROJECT_ID not set") - } - - exporter, err := NewExporter(Options{ProjectID: projectID}) - if err != nil { - t.Fatal(err) - } - defer exporter.Flush() - - trace.RegisterExporter(exporter) - defer trace.UnregisterExporter(exporter) - view.RegisterExporter(exporter) - defer view.UnregisterExporter(exporter) - - trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) - - client, done := testpb.NewTestClient(t) - defer done() - - client.Single(context.Background(), &testpb.FooRequest{SleepNanos: int64(42 * time.Millisecond)}) -} diff --git a/exporter/stackdriver/stats.go b/exporter/stackdriver/stats.go deleted file mode 100644 index 93635dac8..000000000 --- a/exporter/stackdriver/stats.go +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "errors" - "fmt" - "os" - "path" - "strconv" - "strings" - "sync" - "time" - - "go.opencensus.io/internal" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" - "go.opencensus.io/trace" - - "cloud.google.com/go/monitoring/apiv3" - "github.com/golang/protobuf/ptypes/timestamp" - "google.golang.org/api/option" - "google.golang.org/api/support/bundler" - distributionpb "google.golang.org/genproto/googleapis/api/distribution" - labelpb "google.golang.org/genproto/googleapis/api/label" - "google.golang.org/genproto/googleapis/api/metric" - metricpb "google.golang.org/genproto/googleapis/api/metric" - monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" - monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" -) - -const maxTimeSeriesPerUpload = 200 -const opencensusTaskKey = "opencensus_task" -const opencensusTaskDescription = "Opencensus task identifier" -const defaultDisplayNamePrefix = "OpenCensus" - -// statsExporter exports stats to the Stackdriver Monitoring. -type statsExporter struct { - bundler *bundler.Bundler - o Options - - createdViewsMu sync.Mutex - createdViews map[string]*metricpb.MetricDescriptor // Views already created remotely - - c *monitoring.MetricClient - taskValue string -} - -// Enforces the singleton on NewExporter per projectID per process -// lest there will be races with Stackdriver. -var ( - seenProjectsMu sync.Mutex - seenProjects = make(map[string]bool) -) - -var ( - errBlankProjectID = errors.New("expecting a non-blank ProjectID") - errSingletonExporter = errors.New("only one exporter can be created per unique ProjectID per process") -) - -// newStatsExporter returns an exporter that uploads stats data to Stackdriver Monitoring. -// Only one Stackdriver exporter should be created per ProjectID per process, any subsequent -// invocations of NewExporter with the same ProjectID will return an error. -func newStatsExporter(o Options) (*statsExporter, error) { - if strings.TrimSpace(o.ProjectID) == "" { - return nil, errBlankProjectID - } - - seenProjectsMu.Lock() - defer seenProjectsMu.Unlock() - _, seen := seenProjects[o.ProjectID] - if seen { - return nil, errSingletonExporter - } - - seenProjects[o.ProjectID] = true - - opts := append(o.MonitoringClientOptions, option.WithUserAgent(internal.UserAgent)) - client, err := monitoring.NewMetricClient(context.Background(), opts...) - if err != nil { - return nil, err - } - e := &statsExporter{ - c: client, - o: o, - createdViews: make(map[string]*metricpb.MetricDescriptor), - taskValue: getTaskValue(), - } - e.bundler = bundler.NewBundler((*view.Data)(nil), func(bundle interface{}) { - vds := bundle.([]*view.Data) - e.handleUpload(vds...) - }) - e.bundler.DelayThreshold = e.o.BundleDelayThreshold - e.bundler.BundleCountThreshold = e.o.BundleCountThreshold - return e, nil -} - -// ExportView exports to the Stackdriver Monitoring if view data -// has one or more rows. -func (e *statsExporter) ExportView(vd *view.Data) { - if len(vd.Rows) == 0 { - return - } - err := e.bundler.Add(vd, 1) - switch err { - case nil: - return - case bundler.ErrOversizedItem: - go e.handleUpload(vd) - case bundler.ErrOverflow: - e.o.handleError(errors.New("failed to upload: buffer full")) - default: - e.o.handleError(err) - } -} - -// getTaskValue returns a task label value in the format of -// "go-@". -func getTaskValue() string { - hostname, err := os.Hostname() - if err != nil { - hostname = "localhost" - } - return "go-" + strconv.Itoa(os.Getpid()) + "@" + hostname -} - -// handleUpload handles uploading a slice -// of Data, as well as error handling. -func (e *statsExporter) handleUpload(vds ...*view.Data) { - if err := e.uploadStats(vds); err != nil { - e.o.handleError(err) - } -} - -// Flush waits for exported view data to be uploaded. -// -// This is useful if your program is ending and you do not -// want to lose recent spans. -func (e *statsExporter) Flush() { - e.bundler.Flush() -} - -func (e *statsExporter) uploadStats(vds []*view.Data) error { - ctx, span := trace.StartSpan( - context.Background(), - "go.opencensus.io/exporter/stackdriver.uploadStats", - trace.WithSampler(trace.NeverSample()), - ) - defer span.End() - - for _, vd := range vds { - if err := e.createMeasure(ctx, vd); err != nil { - span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}) - return err - } - } - for _, req := range e.makeReq(vds, maxTimeSeriesPerUpload) { - if err := e.c.CreateTimeSeries(ctx, req); err != nil { - span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}) - // TODO(jbd): Don't fail fast here, batch errors? - return err - } - } - return nil -} - -func (e *statsExporter) makeReq(vds []*view.Data, limit int) []*monitoringpb.CreateTimeSeriesRequest { - var reqs []*monitoringpb.CreateTimeSeriesRequest - var timeSeries []*monitoringpb.TimeSeries - - resource := e.o.Resource - if resource == nil { - resource = &monitoredrespb.MonitoredResource{ - Type: "global", - } - } - - for _, vd := range vds { - for _, row := range vd.Rows { - ts := &monitoringpb.TimeSeries{ - Metric: &metricpb.Metric{ - Type: namespacedViewName(vd.View.Name), - Labels: newLabels(row.Tags, e.taskValue), - }, - Resource: resource, - Points: []*monitoringpb.Point{newPoint(vd.View, row, vd.Start, vd.End)}, - } - timeSeries = append(timeSeries, ts) - if len(timeSeries) == limit { - reqs = append(reqs, &monitoringpb.CreateTimeSeriesRequest{ - Name: monitoring.MetricProjectPath(e.o.ProjectID), - TimeSeries: timeSeries, - }) - timeSeries = []*monitoringpb.TimeSeries{} - } - } - } - if len(timeSeries) > 0 { - reqs = append(reqs, &monitoringpb.CreateTimeSeriesRequest{ - Name: monitoring.MetricProjectPath(e.o.ProjectID), - TimeSeries: timeSeries, - }) - } - return reqs -} - -// createMeasure creates a MetricDescriptor for the given view data in Stackdriver Monitoring. -// An error will be returned if there is already a metric descriptor created with the same name -// but it has a different aggregation or keys. -func (e *statsExporter) createMeasure(ctx context.Context, vd *view.Data) error { - e.createdViewsMu.Lock() - defer e.createdViewsMu.Unlock() - - m := vd.View.Measure - agg := vd.View.Aggregation - tagKeys := vd.View.TagKeys - viewName := vd.View.Name - - if md, ok := e.createdViews[viewName]; ok { - return equalMeasureAggTagKeys(md, m, agg, tagKeys) - } - - metricType := namespacedViewName(viewName) - var valueType metricpb.MetricDescriptor_ValueType - unit := m.Unit() - - switch agg.Type { - case view.AggTypeCount: - valueType = metricpb.MetricDescriptor_INT64 - // If the aggregation type is count, which counts the number of recorded measurements, the unit must be "1", - // because this view does not apply to the recorded values. - unit = stats.UnitDimensionless - case view.AggTypeSum: - switch m.(type) { - case *stats.Int64Measure: - valueType = metricpb.MetricDescriptor_INT64 - case *stats.Float64Measure: - valueType = metricpb.MetricDescriptor_DOUBLE - } - case view.AggTypeDistribution: - valueType = metricpb.MetricDescriptor_DISTRIBUTION - case view.AggTypeLastValue: - switch m.(type) { - case *stats.Int64Measure: - valueType = metricpb.MetricDescriptor_INT64 - case *stats.Float64Measure: - valueType = metricpb.MetricDescriptor_DOUBLE - } - default: - return fmt.Errorf("unsupported aggregation type: %s", agg.Type.String()) - } - - metricKind := metricpb.MetricDescriptor_CUMULATIVE - displayNamePrefix := defaultDisplayNamePrefix - if e.o.MetricPrefix != "" { - displayNamePrefix = e.o.MetricPrefix - } - - md, err := createMetricDescriptor(ctx, e.c, &monitoringpb.CreateMetricDescriptorRequest{ - Name: fmt.Sprintf("projects/%s", e.o.ProjectID), - MetricDescriptor: &metricpb.MetricDescriptor{ - Name: fmt.Sprintf("projects/%s/metricDescriptors/%s", e.o.ProjectID, metricType), - DisplayName: path.Join(displayNamePrefix, viewName), - Description: vd.View.Description, - Unit: unit, - Type: metricType, - MetricKind: metricKind, - ValueType: valueType, - Labels: newLabelDescriptors(vd.View.TagKeys), - }, - }) - if err != nil { - return err - } - - e.createdViews[viewName] = md - return nil -} - -func newPoint(v *view.View, row *view.Row, start, end time.Time) *monitoringpb.Point { - return &monitoringpb.Point{ - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: newTypedValue(v, row), - } -} - -func newTypedValue(vd *view.View, r *view.Row) *monitoringpb.TypedValue { - switch v := r.Data.(type) { - case *view.CountData: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: v.Value, - }} - case *view.SumData: - switch vd.Measure.(type) { - case *stats.Int64Measure: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: int64(v.Value), - }} - case *stats.Float64Measure: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: v.Value, - }} - } - case *view.DistributionData: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DistributionValue{ - DistributionValue: &distributionpb.Distribution{ - Count: v.Count, - Mean: v.Mean, - SumOfSquaredDeviation: v.SumOfSquaredDev, - // TODO(songya): uncomment this once Stackdriver supports min/max. - // Range: &distributionpb.Distribution_Range{ - // Min: v.Min, - // Max: v.Max, - // }, - BucketOptions: &distributionpb.Distribution_BucketOptions{ - Options: &distributionpb.Distribution_BucketOptions_ExplicitBuckets{ - ExplicitBuckets: &distributionpb.Distribution_BucketOptions_Explicit{ - Bounds: vd.Aggregation.Buckets, - }, - }, - }, - BucketCounts: v.CountPerBucket, - }, - }} - case *view.LastValueData: - switch vd.Measure.(type) { - case *stats.Int64Measure: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: int64(v.Value), - }} - case *stats.Float64Measure: - return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: v.Value, - }} - } - } - return nil -} - -func namespacedViewName(v string) string { - return path.Join("custom.googleapis.com", "opencensus", v) -} - -func newLabels(tags []tag.Tag, taskValue string) map[string]string { - labels := make(map[string]string) - for _, tag := range tags { - labels[internal.Sanitize(tag.Key.Name())] = tag.Value - } - labels[opencensusTaskKey] = taskValue - return labels -} - -func newLabelDescriptors(keys []tag.Key) []*labelpb.LabelDescriptor { - labelDescriptors := make([]*labelpb.LabelDescriptor, len(keys)+1) - for i, key := range keys { - labelDescriptors[i] = &labelpb.LabelDescriptor{ - Key: internal.Sanitize(key.Name()), - ValueType: labelpb.LabelDescriptor_STRING, // We only use string tags - } - } - // Add a specific open census task id label. - labelDescriptors[len(keys)] = &labelpb.LabelDescriptor{ - Key: opencensusTaskKey, - ValueType: labelpb.LabelDescriptor_STRING, - Description: opencensusTaskDescription, - } - return labelDescriptors -} - -func equalMeasureAggTagKeys(md *metricpb.MetricDescriptor, m stats.Measure, agg *view.Aggregation, keys []tag.Key) error { - var aggTypeMatch bool - switch md.ValueType { - case metricpb.MetricDescriptor_INT64: - if _, ok := m.(*stats.Int64Measure); !(ok || agg.Type == view.AggTypeCount) { - return fmt.Errorf("stackdriver metric descriptor was not created as int64") - } - aggTypeMatch = agg.Type == view.AggTypeCount || agg.Type == view.AggTypeSum || agg.Type == view.AggTypeLastValue - case metricpb.MetricDescriptor_DOUBLE: - if _, ok := m.(*stats.Float64Measure); !ok { - return fmt.Errorf("stackdriver metric descriptor was not created as double") - } - aggTypeMatch = agg.Type == view.AggTypeSum || agg.Type == view.AggTypeLastValue - case metricpb.MetricDescriptor_DISTRIBUTION: - aggTypeMatch = agg.Type == view.AggTypeDistribution - } - - if !aggTypeMatch { - return fmt.Errorf("stackdriver metric descriptor was not created with aggregation type %T", agg.Type) - } - - if len(md.Labels) != len(keys)+1 { - return errors.New("stackdriver metric descriptor was not created with the view labels") - } - - labels := make(map[string]struct{}, len(keys)+1) - for _, k := range keys { - labels[internal.Sanitize(k.Name())] = struct{}{} - } - labels[opencensusTaskKey] = struct{}{} - - for _, k := range md.Labels { - if _, ok := labels[k.Key]; !ok { - return fmt.Errorf("stackdriver metric descriptor was not created with label %q", k) - } - } - - return nil -} - -var createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metric.MetricDescriptor, error) { - return c.CreateMetricDescriptor(ctx, mdr) -} - -var getMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.GetMetricDescriptorRequest) (*metric.MetricDescriptor, error) { - return c.GetMetricDescriptor(ctx, mdr) -} diff --git a/exporter/stackdriver/stats_test.go b/exporter/stackdriver/stats_test.go deleted file mode 100644 index d734dc52e..000000000 --- a/exporter/stackdriver/stats_test.go +++ /dev/null @@ -1,866 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "reflect" - "testing" - "time" - - "cloud.google.com/go/monitoring/apiv3" - "github.com/golang/protobuf/ptypes/timestamp" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" - "google.golang.org/api/option" - "google.golang.org/genproto/googleapis/api/label" - "google.golang.org/genproto/googleapis/api/metric" - metricpb "google.golang.org/genproto/googleapis/api/metric" - monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" - monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" - "google.golang.org/grpc" -) - -var authOptions = []option.ClientOption{option.WithGRPCConn(&grpc.ClientConn{})} - -func TestRejectBlankProjectID(t *testing.T) { - ids := []string{"", " ", " "} - for _, projectID := range ids { - opts := Options{ProjectID: projectID, MonitoringClientOptions: authOptions} - exp, err := newStatsExporter(opts) - if err == nil || exp != nil { - t.Errorf("%q ProjectID must be rejected: NewExporter() = %v err = %q", projectID, exp, err) - } - } -} - -// Ensure only one exporter per projectID per process, any -// subsequent invocations of NewExporter should fail. -func TestNewExporterSingletonPerProcess(t *testing.T) { - ids := []string{"open-census.io", "x", "fakeProjectID"} - for _, projectID := range ids { - opts := Options{ProjectID: projectID, MonitoringClientOptions: authOptions} - exp, err := newStatsExporter(opts) - if err != nil { - t.Errorf("NewExporter() projectID = %q err = %q", projectID, err) - continue - } - if exp == nil { - t.Errorf("NewExporter returned a nil Exporter") - continue - } - exp, err = newStatsExporter(opts) - if err == nil || exp != nil { - t.Errorf("NewExporter more than once should fail; exp (%v) err %v", exp, err) - } - } -} - -func TestExporter_makeReq(t *testing.T) { - m := stats.Float64("test-measure", "measure desc", "unit") - - key, err := tag.NewKey("test_key") - if err != nil { - t.Fatal(err) - } - - v := &view.View{ - Name: "testview", - Description: "desc", - TagKeys: []tag.Key{key}, - Measure: m, - Aggregation: view.Count(), - } - distView := &view.View{ - Name: "distview", - Description: "desc", - Measure: m, - Aggregation: view.Distribution(2, 4, 7), - } - - start := time.Now() - end := start.Add(time.Minute) - count1 := &view.CountData{Value: 10} - count2 := &view.CountData{Value: 16} - sum1 := &view.SumData{Value: 5.5} - sum2 := &view.SumData{Value: -11.1} - last1 := view.LastValueData{Value: 100} - last2 := view.LastValueData{Value: 200} - taskValue := getTaskValue() - - tests := []struct { - name string - projID string - vd *view.Data - want []*monitoringpb.CreateTimeSeriesRequest - }{ - { - name: "count agg + timeline", - projID: "proj-id", - vd: newTestViewData(v, start, end, count1, count2), - want: []*monitoringpb.CreateTimeSeriesRequest{{ - Name: monitoring.MetricProjectPath("proj-id"), - TimeSeries: []*monitoringpb.TimeSeries{ - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-1", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: 10, - }}, - }, - }, - }, - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-2", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: 16, - }}, - }, - }, - }, - }, - }}, - }, - { - name: "sum agg + timeline", - projID: "proj-id", - vd: newTestViewData(v, start, end, sum1, sum2), - want: []*monitoringpb.CreateTimeSeriesRequest{{ - Name: monitoring.MetricProjectPath("proj-id"), - TimeSeries: []*monitoringpb.TimeSeries{ - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-1", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: 5.5, - }}, - }, - }, - }, - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-2", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: -11.1, - }}, - }, - }, - }, - }, - }}, - }, - { - name: "last value agg", - projID: "proj-id", - vd: newTestViewData(v, start, end, &last1, &last2), - want: []*monitoringpb.CreateTimeSeriesRequest{{ - Name: monitoring.MetricProjectPath("proj-id"), - TimeSeries: []*monitoringpb.TimeSeries{ - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-1", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: 100, - }}, - }, - }, - }, - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-2", - opencensusTaskKey: taskValue, - }, - }, - Resource: &monitoredrespb.MonitoredResource{ - Type: "global", - }, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ - DoubleValue: 200, - }}, - }, - }, - }, - }, - }}, - }, - { - name: "dist agg + time window", - projID: "proj-id", - vd: newTestDistViewData(distView, start, end), - want: nil, //TODO: add expectation for distribution - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &statsExporter{ - o: Options{ProjectID: tt.projID}, - taskValue: taskValue, - } - resps := e.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload) - if tt.want == nil { - t.Skip("Missing expectation") - } - if got, want := len(resps), len(tt.want); got != want { - t.Fatalf("%v: Exporter.makeReq() returned %d responses; want %d", tt.name, got, want) - } - if len(tt.want) == 0 { - return - } - if !reflect.DeepEqual(resps, tt.want) { - t.Errorf("%v: Exporter.makeReq() = %v, want %v", tt.name, resps, tt.want) - } - }) - } -} - -func TestExporter_makeReq_batching(t *testing.T) { - m := stats.Float64("test-measure/makeReq_batching", "measure desc", "unit") - - key, err := tag.NewKey("test_key") - if err != nil { - t.Fatal(err) - } - - v := &view.View{ - Name: "view", - Description: "desc", - TagKeys: []tag.Key{key}, - Measure: m, - Aggregation: view.Count(), - } - - tests := []struct { - name string - iter int - limit int - wantReqs int - wantTotal int - }{ - { - name: "4 vds; 3 limit", - iter: 2, - limit: 3, - wantReqs: 2, - wantTotal: 4, - }, - { - name: "4 vds; 4 limit", - iter: 2, - limit: 4, - wantReqs: 1, - wantTotal: 4, - }, - { - name: "4 vds; 5 limit", - iter: 2, - limit: 5, - wantReqs: 1, - wantTotal: 4, - }, - } - - count1 := &view.CountData{Value: 10} - count2 := &view.CountData{Value: 16} - - for _, tt := range tests { - var vds []*view.Data - for i := 0; i < tt.iter; i++ { - vds = append(vds, newTestViewData(v, time.Now(), time.Now(), count1, count2)) - } - - e := &statsExporter{} - resps := e.makeReq(vds, tt.limit) - if len(resps) != tt.wantReqs { - t.Errorf("%v: got %v; want %d requests", tt.name, resps, tt.wantReqs) - } - - var total int - for _, resp := range resps { - total += len(resp.TimeSeries) - } - if got, want := total, tt.wantTotal; got != want { - t.Errorf("%v: len(resps[...].TimeSeries) = %d; want %d", tt.name, got, want) - } - } -} - -func TestEqualAggWindowTagKeys(t *testing.T) { - key1, _ := tag.NewKey("test-key-one") - key2, _ := tag.NewKey("test-key-two") - tests := []struct { - name string - md *metricpb.MetricDescriptor - m stats.Measure - agg *view.Aggregation - keys []tag.Key - wantErr bool - }{ - { - name: "count agg with in64 measure", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Int64("name", "", ""), - agg: view.Count(), - wantErr: false, - }, - { - name: "count agg with double measure", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Float64("name", "", ""), - agg: view.Count(), - wantErr: false, - }, - { - name: "sum agg double", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DOUBLE, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Float64("name", "", ""), - agg: view.Sum(), - wantErr: false, - }, - { - name: "sum agg int64", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Int64("name", "", ""), - agg: view.Sum(), - wantErr: false, - }, - { - name: "last value agg double", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DOUBLE, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Float64("name", "", ""), - agg: view.LastValue(), - wantErr: false, - }, - { - name: "last value agg int64", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Int64("name", "", ""), - agg: view.LastValue(), - wantErr: false, - }, - { - name: "distribution - mismatch", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DISTRIBUTION, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Int64("name", "", ""), - agg: view.Count(), - wantErr: true, - }, - { - name: "last value - measure mismatch", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Float64("name", "", ""), - agg: view.LastValue(), - wantErr: true, - }, - { - name: "distribution agg with keys", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DISTRIBUTION, - Labels: []*label.LabelDescriptor{ - {Key: "test_key_one"}, - {Key: "test_key_two"}, - {Key: opencensusTaskKey}, - }, - }, - m: stats.Int64("name", "", ""), - agg: view.Distribution(), - keys: []tag.Key{key1, key2}, - wantErr: false, - }, - { - name: "distribution agg with keys -- mismatch", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DISTRIBUTION, - }, - m: stats.Int64("name", "", ""), - agg: view.Distribution(), - keys: []tag.Key{key1, key2}, - wantErr: true, - }, - { - name: "count agg with pointers", - md: &metricpb.MetricDescriptor{ - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: []*label.LabelDescriptor{{Key: opencensusTaskKey}}, - }, - m: stats.Int64("name", "", ""), - agg: view.Count(), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := equalMeasureAggTagKeys(tt.md, tt.m, tt.agg, tt.keys) - if err != nil && !tt.wantErr { - t.Errorf("equalAggTagKeys() = %q; want no error", err) - } - if err == nil && tt.wantErr { - t.Errorf("equalAggTagKeys() = %q; want error", err) - } - - }) - } -} - -func TestExporter_createMeasure(t *testing.T) { - oldCreateMetricDescriptor := createMetricDescriptor - - defer func() { - createMetricDescriptor = oldCreateMetricDescriptor - }() - - key, _ := tag.NewKey("test-key-one") - m := stats.Float64("test-measure/TestExporter_createMeasure", "measure desc", stats.UnitMilliseconds) - - v := &view.View{ - Name: "test_view_sum", - Description: "view_description", - TagKeys: []tag.Key{key}, - Measure: m, - Aggregation: view.Sum(), - } - - data := &view.CountData{Value: 0} - vd := newTestViewData(v, time.Now(), time.Now(), data, data) - - e := &statsExporter{ - createdViews: make(map[string]*metricpb.MetricDescriptor), - o: Options{ProjectID: "test_project"}, - } - - var createCalls int - createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metric.MetricDescriptor, error) { - createCalls++ - if got, want := mdr.MetricDescriptor.Name, "projects/test_project/metricDescriptors/custom.googleapis.com/opencensus/test_view_sum"; got != want { - t.Errorf("MetricDescriptor.Name = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Type, "custom.googleapis.com/opencensus/test_view_sum"; got != want { - t.Errorf("MetricDescriptor.Type = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.ValueType, metricpb.MetricDescriptor_DOUBLE; got != want { - t.Errorf("MetricDescriptor.ValueType = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.MetricKind, metricpb.MetricDescriptor_CUMULATIVE; got != want { - t.Errorf("MetricDescriptor.MetricKind = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Description, "view_description"; got != want { - t.Errorf("MetricDescriptor.Description = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.DisplayName, "OpenCensus/test_view_sum"; got != want { - t.Errorf("MetricDescriptor.DisplayName = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Unit, stats.UnitMilliseconds; got != want { - t.Errorf("MetricDescriptor.Unit = %q; want %q", got, want) - } - return &metric.MetricDescriptor{ - DisplayName: "OpenCensus/test_view_sum", - Description: "view_description", - Unit: stats.UnitMilliseconds, - Type: "custom.googleapis.com/opencensus/test_view_sum", - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_DOUBLE, - Labels: newLabelDescriptors(vd.View.TagKeys), - }, nil - } - - ctx := context.Background() - if err := e.createMeasure(ctx, vd); err != nil { - t.Errorf("Exporter.createMeasure() error = %v", err) - } - if err := e.createMeasure(ctx, vd); err != nil { - t.Errorf("Exporter.createMeasure() error = %v", err) - } - if count := createCalls; count != 1 { - t.Errorf("createMetricDescriptor needs to be called for once; called %v times", count) - } - if count := len(e.createdViews); count != 1 { - t.Errorf("len(e.createdViews) = %v; want 1", count) - } -} - -func TestExporter_createMeasure_CountAggregation(t *testing.T) { - oldCreateMetricDescriptor := createMetricDescriptor - - defer func() { - createMetricDescriptor = oldCreateMetricDescriptor - }() - - key, _ := tag.NewKey("test-key-one") - m := stats.Float64("test-measure/TestExporter_createMeasure", "measure desc", stats.UnitMilliseconds) - - v := &view.View{ - Name: "test_view_count", - Description: "view_description", - TagKeys: []tag.Key{key}, - Measure: m, - Aggregation: view.Count(), - } - - data := &view.CountData{Value: 0} - vd := newTestViewData(v, time.Now(), time.Now(), data, data) - - e := &statsExporter{ - createdViews: make(map[string]*metricpb.MetricDescriptor), - o: Options{ProjectID: "test_project"}, - } - - createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metric.MetricDescriptor, error) { - if got, want := mdr.MetricDescriptor.Name, "projects/test_project/metricDescriptors/custom.googleapis.com/opencensus/test_view_count"; got != want { - t.Errorf("MetricDescriptor.Name = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Type, "custom.googleapis.com/opencensus/test_view_count"; got != want { - t.Errorf("MetricDescriptor.Type = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.ValueType, metricpb.MetricDescriptor_INT64; got != want { - t.Errorf("MetricDescriptor.ValueType = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.MetricKind, metricpb.MetricDescriptor_CUMULATIVE; got != want { - t.Errorf("MetricDescriptor.MetricKind = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Description, "view_description"; got != want { - t.Errorf("MetricDescriptor.Description = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.DisplayName, "OpenCensus/test_view_count"; got != want { - t.Errorf("MetricDescriptor.DisplayName = %q; want %q", got, want) - } - if got, want := mdr.MetricDescriptor.Unit, stats.UnitDimensionless; got != want { - t.Errorf("MetricDescriptor.Unit = %q; want %q", got, want) - } - return &metric.MetricDescriptor{ - DisplayName: "OpenCensus/test_view_sum", - Description: "view_description", - Unit: stats.UnitDimensionless, - Type: "custom.googleapis.com/opencensus/test_view_count", - MetricKind: metricpb.MetricDescriptor_CUMULATIVE, - ValueType: metricpb.MetricDescriptor_INT64, - Labels: newLabelDescriptors(vd.View.TagKeys), - }, nil - } - ctx := context.Background() - if err := e.createMeasure(ctx, vd); err != nil { - t.Errorf("Exporter.createMeasure() error = %v", err) - } -} - -func TestExporter_makeReq_withCustomMonitoredResource(t *testing.T) { - m := stats.Float64("test-measure/TestExporter_makeReq_withCustomMonitoredResource", "measure desc", "unit") - - key, err := tag.NewKey("test_key") - if err != nil { - t.Fatal(err) - } - - v := &view.View{ - Name: "testview", - Description: "desc", - TagKeys: []tag.Key{key}, - Measure: m, - Aggregation: view.Count(), - } - if err := view.Register(v); err != nil { - t.Fatal(err) - } - defer view.Unregister(v) - - start := time.Now() - end := start.Add(time.Minute) - count1 := &view.CountData{Value: 10} - count2 := &view.CountData{Value: 16} - taskValue := getTaskValue() - - resource := &monitoredrespb.MonitoredResource{ - Type: "gce_instance", - Labels: map[string]string{"instance_id": "instance", "zone": "us-west-1a"}, - } - - tests := []struct { - name string - projID string - vd *view.Data - want []*monitoringpb.CreateTimeSeriesRequest - }{ - { - name: "count agg timeline", - projID: "proj-id", - vd: newTestViewData(v, start, end, count1, count2), - want: []*monitoringpb.CreateTimeSeriesRequest{{ - Name: monitoring.MetricProjectPath("proj-id"), - TimeSeries: []*monitoringpb.TimeSeries{ - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-1", - opencensusTaskKey: taskValue, - }, - }, - Resource: resource, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: 10, - }}, - }, - }, - }, - { - Metric: &metricpb.Metric{ - Type: "custom.googleapis.com/opencensus/testview", - Labels: map[string]string{ - "test_key": "test-value-2", - opencensusTaskKey: taskValue, - }, - }, - Resource: resource, - Points: []*monitoringpb.Point{ - { - Interval: &monitoringpb.TimeInterval{ - StartTime: ×tamp.Timestamp{ - Seconds: start.Unix(), - Nanos: int32(start.Nanosecond()), - }, - EndTime: ×tamp.Timestamp{ - Seconds: end.Unix(), - Nanos: int32(end.Nanosecond()), - }, - }, - Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ - Int64Value: 16, - }}, - }, - }, - }, - }, - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &statsExporter{ - o: Options{ProjectID: tt.projID, Resource: resource}, - taskValue: taskValue, - } - resps := e.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload) - if got, want := len(resps), len(tt.want); got != want { - t.Fatalf("%v: Exporter.makeReq() returned %d responses; want %d", tt.name, got, want) - } - if len(tt.want) == 0 { - return - } - if !reflect.DeepEqual(resps, tt.want) { - t.Errorf("%v: Exporter.makeReq() = %v, want %v", tt.name, resps, tt.want) - } - }) - } -} - -func newTestViewData(v *view.View, start, end time.Time, data1, data2 view.AggregationData) *view.Data { - key, _ := tag.NewKey("test-key") - tag1 := tag.Tag{Key: key, Value: "test-value-1"} - tag2 := tag.Tag{Key: key, Value: "test-value-2"} - return &view.Data{ - View: v, - Rows: []*view.Row{ - { - Tags: []tag.Tag{tag1}, - Data: data1, - }, - { - Tags: []tag.Tag{tag2}, - Data: data2, - }, - }, - Start: start, - End: end, - } -} - -func newTestDistViewData(v *view.View, start, end time.Time) *view.Data { - return &view.Data{ - View: v, - Rows: []*view.Row{ - {Data: &view.DistributionData{ - Count: 5, - Min: 1, - Max: 7, - Mean: 3, - SumOfSquaredDev: 1.5, - CountPerBucket: []int64{2, 2, 1}, - }}, - }, - Start: start, - End: end, - } -} diff --git a/exporter/stackdriver/trace.go b/exporter/stackdriver/trace.go deleted file mode 100644 index e3fd6bab7..000000000 --- a/exporter/stackdriver/trace.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "fmt" - "log" - "sync" - "time" - - tracingclient "cloud.google.com/go/trace/apiv2" - "go.opencensus.io/trace" - "google.golang.org/api/support/bundler" - tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2" -) - -// traceExporter is an implementation of trace.Exporter that uploads spans to -// Stackdriver. -// -type traceExporter struct { - o Options - projectID string - bundler *bundler.Bundler - // uploadFn defaults to uploadSpans; it can be replaced for tests. - uploadFn func(spans []*trace.SpanData) - overflowLogger - client *tracingclient.Client -} - -var _ trace.Exporter = (*traceExporter)(nil) - -func newTraceExporter(o Options) (*traceExporter, error) { - client, err := tracingclient.NewClient(context.Background(), o.TraceClientOptions...) - if err != nil { - return nil, fmt.Errorf("stackdriver: couldn't initialize trace client: %v", err) - } - return newTraceExporterWithClient(o, client), nil -} - -func newTraceExporterWithClient(o Options, c *tracingclient.Client) *traceExporter { - e := &traceExporter{ - projectID: o.ProjectID, - client: c, - o: o, - } - bundler := bundler.NewBundler((*trace.SpanData)(nil), func(bundle interface{}) { - e.uploadFn(bundle.([]*trace.SpanData)) - }) - if o.BundleDelayThreshold > 0 { - bundler.DelayThreshold = o.BundleDelayThreshold - } else { - bundler.DelayThreshold = 2 * time.Second - } - if o.BundleCountThreshold > 0 { - bundler.BundleCountThreshold = o.BundleCountThreshold - } else { - bundler.BundleCountThreshold = 50 - } - // The measured "bytes" are not really bytes, see exportReceiver. - bundler.BundleByteThreshold = bundler.BundleCountThreshold * 200 - bundler.BundleByteLimit = bundler.BundleCountThreshold * 1000 - bundler.BufferedByteLimit = bundler.BundleCountThreshold * 2000 - - e.bundler = bundler - e.uploadFn = e.uploadSpans - return e -} - -// ExportSpan exports a SpanData to Stackdriver Trace. -func (e *traceExporter) ExportSpan(s *trace.SpanData) { - // n is a length heuristic. - n := 1 - n += len(s.Attributes) - n += len(s.Annotations) - n += len(s.MessageEvents) - err := e.bundler.Add(s, n) - switch err { - case nil: - return - case bundler.ErrOversizedItem: - go e.uploadFn([]*trace.SpanData{s}) - case bundler.ErrOverflow: - e.overflowLogger.log() - default: - e.o.handleError(err) - } -} - -// Flush waits for exported trace spans to be uploaded. -// -// This is useful if your program is ending and you do not want to lose recent -// spans. -func (e *traceExporter) Flush() { - e.bundler.Flush() -} - -// uploadSpans uploads a set of spans to Stackdriver. -func (e *traceExporter) uploadSpans(spans []*trace.SpanData) { - req := tracepb.BatchWriteSpansRequest{ - Name: "projects/" + e.projectID, - Spans: make([]*tracepb.Span, 0, len(spans)), - } - for _, span := range spans { - req.Spans = append(req.Spans, protoFromSpanData(span, e.projectID)) - } - // Create a never-sampled span to prevent traces associated with exporter. - ctx, span := trace.StartSpan( // TODO: add timeouts - context.Background(), - "go.opencensus.io/exporter/stackdriver.uploadSpans", - trace.WithSampler(trace.NeverSample()), - ) - defer span.End() - span.AddAttributes(trace.Int64Attribute("num_spans", int64(len(spans)))) - - err := e.client.BatchWriteSpans(ctx, &req) - if err != nil { - span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}) - e.o.handleError(err) - } -} - -// overflowLogger ensures that at most one overflow error log message is -// written every 5 seconds. -type overflowLogger struct { - mu sync.Mutex - pause bool - accum int -} - -func (o *overflowLogger) delay() { - o.pause = true - time.AfterFunc(5*time.Second, func() { - o.mu.Lock() - defer o.mu.Unlock() - switch { - case o.accum == 0: - o.pause = false - case o.accum == 1: - log.Println("OpenCensus Stackdriver exporter: failed to upload span: buffer full") - o.accum = 0 - o.delay() - default: - log.Printf("OpenCensus Stackdriver exporter: failed to upload %d spans: buffer full", o.accum) - o.accum = 0 - o.delay() - } - }) -} - -func (o *overflowLogger) log() { - o.mu.Lock() - defer o.mu.Unlock() - if !o.pause { - log.Println("OpenCensus Stackdriver exporter: failed to upload span: buffer full") - o.delay() - } else { - o.accum++ - } -} diff --git a/exporter/stackdriver/trace_proto.go b/exporter/stackdriver/trace_proto.go deleted file mode 100644 index 5c2dc2d46..000000000 --- a/exporter/stackdriver/trace_proto.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "math" - "time" - "unicode/utf8" - - "go.opencensus.io/internal" - "go.opencensus.io/plugin/ochttp" - - timestamppb "github.com/golang/protobuf/ptypes/timestamp" - wrapperspb "github.com/golang/protobuf/ptypes/wrappers" - "go.opencensus.io/trace" - tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2" - statuspb "google.golang.org/genproto/googleapis/rpc/status" -) - -const ( - maxAnnotationEventsPerSpan = 32 - maxMessageEventsPerSpan = 128 - maxAttributeStringValue = 256 - agentLabel = "g.co/agent" - - labelHTTPHost = `/http/host` - labelHTTPMethod = `/http/method` - labelHTTPStatusCode = `/http/status_code` - labelHTTPPath = `/http/path` - labelHTTPUserAgent = `/http/user_agent` -) - -// proto returns a protocol buffer representation of a SpanData. -func protoFromSpanData(s *trace.SpanData, projectID string) *tracepb.Span { - if s == nil { - return nil - } - - traceIDString := s.SpanContext.TraceID.String() - spanIDString := s.SpanContext.SpanID.String() - - name := s.Name - switch s.SpanKind { - case trace.SpanKindClient: - name = "Sent." + name - case trace.SpanKindServer: - name = "Recv." + name - } - - sp := &tracepb.Span{ - Name: "projects/" + projectID + "/traces/" + traceIDString + "/spans/" + spanIDString, - SpanId: spanIDString, - DisplayName: trunc(name, 128), - StartTime: timestampProto(s.StartTime), - EndTime: timestampProto(s.EndTime), - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: !s.HasRemoteParent}, - } - if p := s.ParentSpanID; p != (trace.SpanID{}) { - sp.ParentSpanId = p.String() - } - if s.Status.Code != 0 || s.Status.Message != "" { - sp.Status = &statuspb.Status{Code: s.Status.Code, Message: s.Status.Message} - } - - var annotations, droppedAnnotationsCount, messageEvents, droppedMessageEventsCount int - copyAttributes(&sp.Attributes, s.Attributes) - - as := s.Annotations - for i, a := range as { - if annotations >= maxAnnotationEventsPerSpan { - droppedAnnotationsCount = len(as) - i - break - } - annotation := &tracepb.Span_TimeEvent_Annotation{Description: trunc(a.Message, maxAttributeStringValue)} - copyAttributes(&annotation.Attributes, a.Attributes) - event := &tracepb.Span_TimeEvent{ - Time: timestampProto(a.Time), - Value: &tracepb.Span_TimeEvent_Annotation_{Annotation: annotation}, - } - annotations++ - if sp.TimeEvents == nil { - sp.TimeEvents = &tracepb.Span_TimeEvents{} - } - sp.TimeEvents.TimeEvent = append(sp.TimeEvents.TimeEvent, event) - } - - if sp.Attributes == nil { - sp.Attributes = &tracepb.Span_Attributes{ - AttributeMap: make(map[string]*tracepb.AttributeValue), - } - } - sp.Attributes.AttributeMap[agentLabel] = &tracepb.AttributeValue{ - Value: &tracepb.AttributeValue_StringValue{ - StringValue: trunc(internal.UserAgent, maxAttributeStringValue), - }, - } - - es := s.MessageEvents - for i, e := range es { - if messageEvents >= maxMessageEventsPerSpan { - droppedMessageEventsCount = len(es) - i - break - } - messageEvents++ - if sp.TimeEvents == nil { - sp.TimeEvents = &tracepb.Span_TimeEvents{} - } - sp.TimeEvents.TimeEvent = append(sp.TimeEvents.TimeEvent, &tracepb.Span_TimeEvent{ - Time: timestampProto(e.Time), - Value: &tracepb.Span_TimeEvent_MessageEvent_{ - MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{ - Type: tracepb.Span_TimeEvent_MessageEvent_Type(e.EventType), - Id: e.MessageID, - UncompressedSizeBytes: e.UncompressedByteSize, - CompressedSizeBytes: e.CompressedByteSize, - }, - }, - }) - } - - if droppedAnnotationsCount != 0 || droppedMessageEventsCount != 0 { - if sp.TimeEvents == nil { - sp.TimeEvents = &tracepb.Span_TimeEvents{} - } - sp.TimeEvents.DroppedAnnotationsCount = clip32(droppedAnnotationsCount) - sp.TimeEvents.DroppedMessageEventsCount = clip32(droppedMessageEventsCount) - } - - if len(s.Links) > 0 { - sp.Links = &tracepb.Span_Links{} - sp.Links.Link = make([]*tracepb.Span_Link, 0, len(s.Links)) - for _, l := range s.Links { - link := &tracepb.Span_Link{ - TraceId: l.TraceID.String(), - SpanId: l.SpanID.String(), - Type: tracepb.Span_Link_Type(l.Type), - } - copyAttributes(&link.Attributes, l.Attributes) - sp.Links.Link = append(sp.Links.Link, link) - } - } - return sp -} - -// timestampProto creates a timestamp proto for a time.Time. -func timestampProto(t time.Time) *timestamppb.Timestamp { - return ×tamppb.Timestamp{ - Seconds: t.Unix(), - Nanos: int32(t.Nanosecond()), - } -} - -// copyAttributes copies a map of attributes to a proto map field. -// It creates the map if it is nil. -func copyAttributes(out **tracepb.Span_Attributes, in map[string]interface{}) { - if len(in) == 0 { - return - } - if *out == nil { - *out = &tracepb.Span_Attributes{} - } - if (*out).AttributeMap == nil { - (*out).AttributeMap = make(map[string]*tracepb.AttributeValue) - } - var dropped int32 - for key, value := range in { - av := attributeValue(value) - if av == nil { - continue - } - switch key { - case ochttp.PathAttribute: - (*out).AttributeMap[labelHTTPPath] = av - case ochttp.HostAttribute: - (*out).AttributeMap[labelHTTPHost] = av - case ochttp.MethodAttribute: - (*out).AttributeMap[labelHTTPMethod] = av - case ochttp.UserAgentAttribute: - (*out).AttributeMap[labelHTTPUserAgent] = av - case ochttp.StatusCodeAttribute: - (*out).AttributeMap[labelHTTPStatusCode] = av - default: - if len(key) > 128 { - dropped++ - continue - } - (*out).AttributeMap[key] = av - } - } - (*out).DroppedAttributesCount = dropped -} - -func attributeValue(v interface{}) *tracepb.AttributeValue { - switch value := v.(type) { - case bool: - return &tracepb.AttributeValue{ - Value: &tracepb.AttributeValue_BoolValue{BoolValue: value}, - } - case int64: - return &tracepb.AttributeValue{ - Value: &tracepb.AttributeValue_IntValue{IntValue: value}, - } - case string: - return &tracepb.AttributeValue{ - Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(value, maxAttributeStringValue)}, - } - } - return nil -} - -// trunc returns a TruncatableString truncated to the given limit. -func trunc(s string, limit int) *tracepb.TruncatableString { - if len(s) > limit { - b := []byte(s[:limit]) - for { - r, size := utf8.DecodeLastRune(b) - if r == utf8.RuneError && size == 1 { - b = b[:len(b)-1] - } else { - break - } - } - return &tracepb.TruncatableString{ - Value: string(b), - TruncatedByteCount: clip32(len(s) - len(b)), - } - } - return &tracepb.TruncatableString{ - Value: s, - TruncatedByteCount: 0, - } -} - -// clip32 clips an int to the range of an int32. -func clip32(x int) int32 { - if x < math.MinInt32 { - return math.MinInt32 - } - if x > math.MaxInt32 { - return math.MaxInt32 - } - return int32(x) -} diff --git a/exporter/stackdriver/trace_proto_test.go b/exporter/stackdriver/trace_proto_test.go deleted file mode 100644 index 2597b08b5..000000000 --- a/exporter/stackdriver/trace_proto_test.go +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "fmt" - "math/big" - "reflect" - "sort" - "strings" - "testing" - "time" - - "go.opencensus.io/internal" - - "github.com/golang/protobuf/proto" - timestamppb "github.com/golang/protobuf/ptypes/timestamp" - wrapperspb "github.com/golang/protobuf/ptypes/wrappers" - "go.opencensus.io/trace" - tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2" - codepb "google.golang.org/genproto/googleapis/rpc/code" - statuspb "google.golang.org/genproto/googleapis/rpc/status" -) - -const projectID = "testproject" - -var ( - traceID = trace.TraceID{0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f} - spanID = trace.SpanID{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1} -) - -type spans []*tracepb.Span - -func (s spans) Len() int { return len(s) } -func (s spans) Less(x, y int) bool { return s[x].DisplayName.Value < s[y].DisplayName.Value } -func (s spans) Swap(x, y int) { s[x], s[y] = s[y], s[x] } - -type testExporter struct { - spans []*trace.SpanData -} - -func (t *testExporter) ExportSpan(s *trace.SpanData) { - t.spans = append(t.spans, s) -} - -func TestExportTrace(t *testing.T) { - ctx := context.Background() - - var te testExporter - trace.RegisterExporter(&te) - defer trace.UnregisterExporter(&te) - - ctx, span0 := trace.StartSpanWithRemoteParent( - ctx, - "span0", - trace.SpanContext{ - TraceID: traceID, - SpanID: spanID, - TraceOptions: 1, - }, - ) - { - ctx1, span1 := trace.StartSpan(ctx, "span1") - { - _, span2 := trace.StartSpan(ctx1, "span2") - span2.AddMessageSendEvent(0x123, 1024, 512) - span2.Annotatef(nil, "in span%d", 2) - span2.Annotate(nil, big.NewRat(2, 4).String()) - span2.AddAttributes( - trace.StringAttribute("key1", "value1"), - trace.StringAttribute("key2", "value2")) - span2.AddAttributes(trace.Int64Attribute("key1", 100)) - span2.End() - } - { - ctx3, span3 := trace.StartSpan(ctx1, "span3") - span3.Annotate(nil, "in span3") - span3.AddMessageReceiveEvent(0x456, 2048, 1536) - span3.SetStatus(trace.Status{Code: int32(codepb.Code_UNAVAILABLE)}) - span3.End() - { - _, span4 := trace.StartSpan(ctx3, "span4") - x := 42 - a1 := []trace.Attribute{trace.StringAttribute("k1", "v1")} - a2 := []trace.Attribute{trace.StringAttribute("k2", "v2")} - a3 := []trace.Attribute{trace.StringAttribute("k3", "v3")} - a4 := map[string]interface{}{"k4": "v4"} - r := big.NewRat(2, 4) - span4.Annotate(a1, r.String()) - span4.Annotatef(a2, "foo %d", x) - span4.Annotate(a3, "in span4") - span4.AddLink(trace.Link{TraceID: trace.TraceID{1, 2}, SpanID: trace.SpanID{3}, Type: trace.LinkTypeParent, Attributes: a4}) - span4.End() - } - } - span1.End() - } - span0.End() - if len(te.spans) != 5 { - t.Errorf("got %d exported spans, want 5", len(te.spans)) - } - - var spbs spans - for _, s := range te.spans { - spbs = append(spbs, protoFromSpanData(s, "testproject")) - } - sort.Sort(spbs) - - for i, want := range []string{ - spanID.String(), - spbs[0].SpanId, - spbs[1].SpanId, - spbs[1].SpanId, - spbs[3].SpanId, - } { - if got := spbs[i].ParentSpanId; got != want { - t.Errorf("span %d: got ParentSpanID %q want %q", i, got, want) - } - } - checkTime := func(ts **timestamppb.Timestamp) { - if *ts == nil { - t.Error("expected timestamp") - } - *ts = nil - } - for _, span := range spbs { - checkTime(&span.StartTime) - checkTime(&span.EndTime) - if span.TimeEvents != nil { - for _, te := range span.TimeEvents.TimeEvent { - checkTime(&te.Time) - } - } - if want := fmt.Sprintf("projects/testproject/traces/%s/spans/%s", traceID, span.SpanId); span.Name != want { - t.Errorf("got span name %q want %q", span.Name, want) - } - span.Name, span.SpanId, span.ParentSpanId = "", "", "" - } - - expectedSpans := spans{ - &tracepb.Span{ - DisplayName: trunc("span0", 128), - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: false}, - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - agentLabel: {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(internal.UserAgent, len(internal.UserAgent))}}, - }, - }, - }, - &tracepb.Span{ - DisplayName: trunc("span1", 128), - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: true}, - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - agentLabel: {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(internal.UserAgent, len(internal.UserAgent))}}, - }, - }, - }, - &tracepb.Span{ - DisplayName: trunc("span2", 128), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - "key2": {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc("value2", 256)}}, - "key1": {Value: &tracepb.AttributeValue_IntValue{IntValue: 100}}, - agentLabel: {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(internal.UserAgent, len(internal.UserAgent))}}, - }, - }, - TimeEvents: &tracepb.Span_TimeEvents{ - TimeEvent: []*tracepb.Span_TimeEvent{ - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("in span2", 256), - }, - }, - }, - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("1/2", 256), - }, - }, - }, - { - Value: &tracepb.Span_TimeEvent_MessageEvent_{ - MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{ - Type: tracepb.Span_TimeEvent_MessageEvent_SENT, - Id: 0x123, - UncompressedSizeBytes: 1024, - CompressedSizeBytes: 512, - }, - }, - }, - }, - }, - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: true}, - }, - &tracepb.Span{ - DisplayName: trunc("span3", 128), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - agentLabel: {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(internal.UserAgent, len(internal.UserAgent))}}, - }, - }, - TimeEvents: &tracepb.Span_TimeEvents{ - TimeEvent: []*tracepb.Span_TimeEvent{ - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("in span3", 256), - }, - }, - }, - { - Value: &tracepb.Span_TimeEvent_MessageEvent_{ - MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{ - Type: tracepb.Span_TimeEvent_MessageEvent_RECEIVED, - Id: 0x456, - UncompressedSizeBytes: 2048, - CompressedSizeBytes: 1536, - }, - }, - }, - }, - }, - Status: &statuspb.Status{ - Code: 14, - }, - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: true}, - }, - &tracepb.Span{ - DisplayName: trunc("span4", 128), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - agentLabel: {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(internal.UserAgent, len(internal.UserAgent))}}, - }, - }, - TimeEvents: &tracepb.Span_TimeEvents{ - TimeEvent: []*tracepb.Span_TimeEvent{ - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("1/2", 256), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - "k1": {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc("v1", 256)}}, - }, - }, - }, - }, - }, - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("foo 42", 256), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - "k2": {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc("v2", 256)}}, - }, - }, - }, - }, - }, - { - Value: &tracepb.Span_TimeEvent_Annotation_{ - Annotation: &tracepb.Span_TimeEvent_Annotation{ - Description: trunc("in span4", 256), - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - "k3": {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc("v3", 256)}}, - }, - }, - }, - }, - }, - }, - }, - Links: &tracepb.Span_Links{ - Link: []*tracepb.Span_Link{ - { - TraceId: "01020000000000000000000000000000", - SpanId: "0300000000000000", - Type: tracepb.Span_Link_PARENT_LINKED_SPAN, - Attributes: &tracepb.Span_Attributes{ - AttributeMap: map[string]*tracepb.AttributeValue{ - "k4": {Value: &tracepb.AttributeValue_StringValue{StringValue: trunc("v4", 256)}}, - }, - }, - }, - }, - }, - SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: true}, - }, - } - - if !reflect.DeepEqual(spbs, expectedSpans) { - var got, want []string - for _, s := range spbs { - got = append(got, proto.MarshalTextString(s)) - } - for _, s := range expectedSpans { - want = append(want, proto.MarshalTextString(s)) - } - t.Errorf("got spans:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n")) - } -} - -func TestEnums(t *testing.T) { - for _, test := range []struct { - x trace.LinkType - y tracepb.Span_Link_Type - }{ - {trace.LinkTypeUnspecified, tracepb.Span_Link_TYPE_UNSPECIFIED}, - {trace.LinkTypeChild, tracepb.Span_Link_CHILD_LINKED_SPAN}, - {trace.LinkTypeParent, tracepb.Span_Link_PARENT_LINKED_SPAN}, - } { - if test.x != trace.LinkType(test.y) { - t.Errorf("got link type values %d and %d, want equal", test.x, test.y) - } - } - - for _, test := range []struct { - x trace.MessageEventType - y tracepb.Span_TimeEvent_MessageEvent_Type - }{ - {trace.MessageEventTypeUnspecified, tracepb.Span_TimeEvent_MessageEvent_TYPE_UNSPECIFIED}, - {trace.MessageEventTypeSent, tracepb.Span_TimeEvent_MessageEvent_SENT}, - {trace.MessageEventTypeRecv, tracepb.Span_TimeEvent_MessageEvent_RECEIVED}, - } { - if test.x != trace.MessageEventType(test.y) { - t.Errorf("got network event type values %d and %d, want equal", test.x, test.y) - } - } -} - -func BenchmarkProto(b *testing.B) { - sd := &trace.SpanData{ - SpanContext: trace.SpanContext{ - TraceID: traceID, - SpanID: spanID, - }, - Name: "foo", - StartTime: time.Now().Add(-time.Second), - EndTime: time.Now(), - Attributes: map[string]interface{}{"foo": "bar"}, - Annotations: []trace.Annotation{ - { - Time: time.Now().Add(-time.Millisecond), - Message: "hello, world", - Attributes: map[string]interface{}{"foo": "bar"}, - }, - }, - MessageEvents: []trace.MessageEvent{ - { - Time: time.Now().Add(-time.Microsecond), - EventType: 1, - MessageID: 2, - UncompressedByteSize: 4, - CompressedByteSize: 3, - }, - }, - Status: trace.Status{ - Code: 42, - Message: "failed", - }, - HasRemoteParent: true, - } - var x int - for i := 0; i < b.N; i++ { - s := protoFromSpanData(sd, `testproject`) - x += len(s.Name) - } - if x == 0 { - fmt.Println(x) - } -} diff --git a/exporter/stackdriver/trace_test.go b/exporter/stackdriver/trace_test.go deleted file mode 100644 index 03a24700d..000000000 --- a/exporter/stackdriver/trace_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2017, OpenCensus Authors -// -// 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 -// -// http://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 stackdriver - -import ( - "context" - "testing" - "time" - - "go.opencensus.io/trace" -) - -func TestBundling(t *testing.T) { - exporter := newTraceExporterWithClient(Options{ - ProjectID: "fakeProjectID", - BundleDelayThreshold: time.Second / 10, - BundleCountThreshold: 10, - }, nil) - - ch := make(chan []*trace.SpanData) - exporter.uploadFn = func(spans []*trace.SpanData) { - ch <- spans - } - trace.RegisterExporter(exporter) - - for i := 0; i < 35; i++ { - _, span := trace.StartSpan(context.Background(), "span", trace.WithSampler(trace.AlwaysSample())) - span.End() - } - - // Read the first three bundles. - <-ch - <-ch - <-ch - - // Test that the fourth bundle isn't sent early. - select { - case <-ch: - t.Errorf("bundle sent too early") - case <-time.After(time.Second / 20): - <-ch - } - - // Test that there aren't extra bundles. - select { - case <-ch: - t.Errorf("too many bundles sent") - case <-time.After(time.Second / 5): - } -}