mirror of https://github.com/knative/caching.git
221 lines
5.9 KiB
Go
221 lines
5.9 KiB
Go
// 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"
|
|
"github.com/golang/protobuf/proto"
|
|
"go.opencensus.io/trace"
|
|
"google.golang.org/api/support/bundler"
|
|
tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2"
|
|
|
|
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
|
|
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
|
|
)
|
|
|
|
// 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 []*tracepb.Span)
|
|
overflowLogger
|
|
client *tracingclient.Client
|
|
}
|
|
|
|
var _ trace.Exporter = (*traceExporter)(nil)
|
|
|
|
func newTraceExporter(o Options) (*traceExporter, error) {
|
|
ctx := o.Context
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
client, err := tracingclient.NewClient(ctx, o.TraceClientOptions...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("stackdriver: couldn't initialize trace client: %v", err)
|
|
}
|
|
return newTraceExporterWithClient(o, client), nil
|
|
}
|
|
|
|
const defaultBufferedByteLimit = 8 * 1024 * 1024
|
|
|
|
func newTraceExporterWithClient(o Options, c *tracingclient.Client) *traceExporter {
|
|
e := &traceExporter{
|
|
projectID: o.ProjectID,
|
|
client: c,
|
|
o: o,
|
|
}
|
|
b := bundler.NewBundler((*tracepb.Span)(nil), func(bundle interface{}) {
|
|
e.uploadFn(bundle.([]*tracepb.Span))
|
|
})
|
|
if o.BundleDelayThreshold > 0 {
|
|
b.DelayThreshold = o.BundleDelayThreshold
|
|
} else {
|
|
b.DelayThreshold = 2 * time.Second
|
|
}
|
|
if o.BundleCountThreshold > 0 {
|
|
b.BundleCountThreshold = o.BundleCountThreshold
|
|
} else {
|
|
b.BundleCountThreshold = 50
|
|
}
|
|
if o.NumberOfWorkers > 0 {
|
|
b.HandlerLimit = o.NumberOfWorkers
|
|
}
|
|
// The measured "bytes" are not really bytes, see exportReceiver.
|
|
b.BundleByteThreshold = b.BundleCountThreshold * 200
|
|
b.BundleByteLimit = b.BundleCountThreshold * 1000
|
|
if o.TraceSpansBufferMaxBytes > 0 {
|
|
b.BufferedByteLimit = o.TraceSpansBufferMaxBytes
|
|
} else {
|
|
b.BufferedByteLimit = defaultBufferedByteLimit
|
|
}
|
|
|
|
e.bundler = b
|
|
e.uploadFn = e.uploadSpans
|
|
return e
|
|
}
|
|
|
|
// ExportSpan exports a SpanData to Stackdriver Trace.
|
|
func (e *traceExporter) ExportSpan(s *trace.SpanData) {
|
|
protoSpan := protoFromSpanData(s, e.projectID, e.o.Resource, e.o.UserAgent)
|
|
protoSize := proto.Size(protoSpan)
|
|
err := e.bundler.Add(protoSpan, protoSize)
|
|
switch err {
|
|
case nil:
|
|
return
|
|
case bundler.ErrOversizedItem:
|
|
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()
|
|
}
|
|
|
|
func (e *traceExporter) pushTraceSpans(ctx context.Context, node *commonpb.Node, r *resourcepb.Resource, spans []*trace.SpanData) (int, error) {
|
|
ctx, span := trace.StartSpan(
|
|
ctx,
|
|
"contrib.go.opencensus.io/exporter/stackdriver.PushTraceSpans",
|
|
trace.WithSampler(trace.NeverSample()),
|
|
)
|
|
defer span.End()
|
|
span.AddAttributes(trace.Int64Attribute("num_spans", int64(len(spans))))
|
|
|
|
protoSpans := make([]*tracepb.Span, 0, len(spans))
|
|
|
|
res := e.o.Resource
|
|
if r != nil {
|
|
res = e.o.MapResource(resourcepbToResource(r))
|
|
}
|
|
|
|
for _, span := range spans {
|
|
protoSpans = append(protoSpans, protoFromSpanData(span, e.projectID, res, e.o.UserAgent))
|
|
}
|
|
|
|
req := tracepb.BatchWriteSpansRequest{
|
|
Name: "projects/" + e.projectID,
|
|
Spans: protoSpans,
|
|
}
|
|
// Create a never-sampled span to prevent traces associated with exporter.
|
|
ctx, cancel := newContextWithTimeout(ctx, e.o.Timeout)
|
|
defer cancel()
|
|
|
|
err := e.client.BatchWriteSpans(ctx, &req)
|
|
|
|
if err != nil {
|
|
return len(spans), err
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// uploadSpans uploads a set of spans to Stackdriver.
|
|
func (e *traceExporter) uploadSpans(spans []*tracepb.Span) {
|
|
req := tracepb.BatchWriteSpansRequest{
|
|
Name: "projects/" + e.projectID,
|
|
Spans: spans,
|
|
}
|
|
// Create a never-sampled span to prevent traces associated with exporter.
|
|
ctx, cancel := newContextWithTimeout(e.o.Context, e.o.Timeout)
|
|
defer cancel()
|
|
ctx, span := trace.StartSpan(
|
|
ctx,
|
|
"contrib.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: 2, 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++
|
|
}
|
|
}
|