2020-03-25 23:47:17 +02:00
|
|
|
// Copyright The OpenTelemetry Authors
|
2020-03-07 08:01:02 +02:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
// code in this package is mostly copied from contrib.go.opencensus.io/exporter/ocagent/connection.go
|
|
|
|
package otlp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
|
2020-08-11 04:55:52 +02:00
|
|
|
colmetricpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/metrics/v1"
|
|
|
|
coltracepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/trace/v1"
|
2020-03-07 08:01:02 +02:00
|
|
|
|
2020-06-23 07:59:51 +02:00
|
|
|
"go.opentelemetry.io/otel/api/metric"
|
2020-03-13 20:42:20 +02:00
|
|
|
"go.opentelemetry.io/otel/exporters/otlp/internal/transform"
|
|
|
|
metricsdk "go.opentelemetry.io/otel/sdk/export/metric"
|
2020-06-23 07:59:51 +02:00
|
|
|
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
|
2020-03-13 20:42:20 +02:00
|
|
|
tracesdk "go.opentelemetry.io/otel/sdk/export/trace"
|
2020-03-07 08:01:02 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Exporter struct {
|
|
|
|
// mu protects the non-atomic and non-channel variables
|
|
|
|
mu sync.RWMutex
|
2020-03-13 20:42:20 +02:00
|
|
|
// senderMu protects the concurrent unsafe sends on the shared gRPC client connection.
|
2020-03-07 08:01:02 +02:00
|
|
|
senderMu sync.Mutex
|
|
|
|
started bool
|
|
|
|
traceExporter coltracepb.TraceServiceClient
|
2020-03-13 20:42:20 +02:00
|
|
|
metricExporter colmetricpb.MetricsServiceClient
|
2020-03-07 08:01:02 +02:00
|
|
|
grpcClientConn *grpc.ClientConn
|
|
|
|
lastConnectErrPtr unsafe.Pointer
|
|
|
|
|
|
|
|
startOnce sync.Once
|
|
|
|
stopCh chan bool
|
|
|
|
disconnectedCh chan bool
|
|
|
|
|
|
|
|
backgroundConnectionDoneCh chan bool
|
|
|
|
|
2020-09-11 23:14:25 +02:00
|
|
|
c config
|
2020-06-09 21:30:40 +02:00
|
|
|
metadata metadata.MD
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
|
|
|
|
2020-09-09 19:19:03 +02:00
|
|
|
var _ tracesdk.SpanExporter = (*Exporter)(nil)
|
2020-03-13 20:42:20 +02:00
|
|
|
var _ metricsdk.Exporter = (*Exporter)(nil)
|
2020-03-07 08:01:02 +02:00
|
|
|
|
2020-09-11 23:14:25 +02:00
|
|
|
// newConfig initializes a config struct with default values and applies
|
|
|
|
// any ExporterOptions provided.
|
|
|
|
func newConfig(opts ...ExporterOption) config {
|
|
|
|
cfg := config{
|
|
|
|
numWorkers: DefaultNumWorkers,
|
|
|
|
grpcServiceConfig: DefaultGRPCServiceConfig,
|
|
|
|
}
|
2020-03-07 08:01:02 +02:00
|
|
|
for _, opt := range opts {
|
2020-09-11 23:14:25 +02:00
|
|
|
opt(&cfg)
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
2020-09-11 23:14:25 +02:00
|
|
|
return cfg
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
|
|
|
|
2020-09-11 23:14:25 +02:00
|
|
|
// NewExporter constructs a new Exporter and starts it.
|
2020-03-07 08:01:02 +02:00
|
|
|
func NewExporter(opts ...ExporterOption) (*Exporter, error) {
|
|
|
|
exp := NewUnstartedExporter(opts...)
|
|
|
|
if err := exp.Start(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return exp, nil
|
|
|
|
}
|
|
|
|
|
2020-09-11 23:14:25 +02:00
|
|
|
// NewUnstartedExporter constructs a new Exporter and does not start it.
|
2020-03-07 08:01:02 +02:00
|
|
|
func NewUnstartedExporter(opts ...ExporterOption) *Exporter {
|
|
|
|
e := new(Exporter)
|
2020-09-11 23:14:25 +02:00
|
|
|
e.c = newConfig(opts...)
|
2020-06-09 21:30:40 +02:00
|
|
|
if len(e.c.headers) > 0 {
|
|
|
|
e.metadata = metadata.New(e.c.headers)
|
|
|
|
}
|
2020-03-07 08:01:02 +02:00
|
|
|
|
|
|
|
// TODO (rghetia): add resources
|
|
|
|
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2020-04-15 21:04:44 +02:00
|
|
|
errAlreadyStarted = errors.New("already started")
|
|
|
|
errNotStarted = errors.New("not started")
|
|
|
|
errDisconnected = errors.New("exporter disconnected")
|
|
|
|
errStopped = errors.New("exporter stopped")
|
|
|
|
errContextCanceled = errors.New("context canceled")
|
2020-03-07 08:01:02 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Start dials to the collector, establishing a connection to it. It also
|
|
|
|
// initiates the Config and Trace services by sending over the initial
|
|
|
|
// messages that consist of the node identifier. Start invokes a background
|
|
|
|
// connector that will reattempt connections to the collector periodically
|
|
|
|
// if the connection dies.
|
|
|
|
func (e *Exporter) Start() error {
|
|
|
|
var err = errAlreadyStarted
|
|
|
|
e.startOnce.Do(func() {
|
|
|
|
e.mu.Lock()
|
|
|
|
e.started = true
|
|
|
|
e.disconnectedCh = make(chan bool, 1)
|
|
|
|
e.stopCh = make(chan bool)
|
|
|
|
e.backgroundConnectionDoneCh = make(chan bool)
|
|
|
|
e.mu.Unlock()
|
|
|
|
|
|
|
|
// An optimistic first connection attempt to ensure that
|
|
|
|
// applications under heavy load can immediately process
|
|
|
|
// data. See https://github.com/census-ecosystem/opencensus-go-exporter-ocagent/pull/63
|
|
|
|
if err := e.connect(); err == nil {
|
|
|
|
e.setStateConnected()
|
|
|
|
} else {
|
|
|
|
e.setStateDisconnected(err)
|
|
|
|
}
|
|
|
|
go e.indefiniteBackgroundConnection()
|
|
|
|
|
|
|
|
err = nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) prepareCollectorAddress() string {
|
|
|
|
if e.c.collectorAddr != "" {
|
|
|
|
return e.c.collectorAddr
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) enableConnections(cc *grpc.ClientConn) error {
|
|
|
|
e.mu.RLock()
|
|
|
|
started := e.started
|
|
|
|
e.mu.RUnlock()
|
|
|
|
|
|
|
|
if !started {
|
|
|
|
return errNotStarted
|
|
|
|
}
|
|
|
|
|
|
|
|
e.mu.Lock()
|
|
|
|
// If previous clientConn is same as the current then just return.
|
|
|
|
// This doesn't happen right now as this func is only called with new ClientConn.
|
|
|
|
// It is more about future-proofing.
|
|
|
|
if e.grpcClientConn == cc {
|
|
|
|
e.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// If the previous clientConn was non-nil, close it
|
|
|
|
if e.grpcClientConn != nil {
|
|
|
|
_ = e.grpcClientConn.Close()
|
|
|
|
}
|
|
|
|
e.grpcClientConn = cc
|
|
|
|
e.traceExporter = coltracepb.NewTraceServiceClient(cc)
|
2020-03-13 20:42:20 +02:00
|
|
|
e.metricExporter = colmetricpb.NewMetricsServiceClient(cc)
|
2020-03-07 08:01:02 +02:00
|
|
|
e.mu.Unlock()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-09 22:59:22 +02:00
|
|
|
func (e *Exporter) contextWithMetadata(ctx context.Context) context.Context {
|
|
|
|
if e.metadata.Len() > 0 {
|
|
|
|
return metadata.NewOutgoingContext(ctx, e.metadata)
|
|
|
|
}
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2020-03-07 08:01:02 +02:00
|
|
|
func (e *Exporter) dialToCollector() (*grpc.ClientConn, error) {
|
|
|
|
addr := e.prepareCollectorAddress()
|
2020-05-21 18:45:24 +02:00
|
|
|
|
|
|
|
dialOpts := []grpc.DialOption{}
|
|
|
|
if e.c.grpcServiceConfig != "" {
|
|
|
|
dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(e.c.grpcServiceConfig))
|
|
|
|
}
|
2020-03-07 08:01:02 +02:00
|
|
|
if e.c.clientCredentials != nil {
|
|
|
|
dialOpts = append(dialOpts, grpc.WithTransportCredentials(e.c.clientCredentials))
|
|
|
|
} else if e.c.canDialInsecure {
|
|
|
|
dialOpts = append(dialOpts, grpc.WithInsecure())
|
|
|
|
}
|
|
|
|
if e.c.compressor != "" {
|
|
|
|
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(e.c.compressor)))
|
|
|
|
}
|
|
|
|
if len(e.c.grpcDialOptions) != 0 {
|
|
|
|
dialOpts = append(dialOpts, e.c.grpcDialOptions...)
|
|
|
|
}
|
|
|
|
|
2020-06-09 22:59:22 +02:00
|
|
|
ctx := e.contextWithMetadata(context.Background())
|
2020-03-07 08:01:02 +02:00
|
|
|
return grpc.DialContext(ctx, addr, dialOpts...)
|
|
|
|
}
|
|
|
|
|
2020-09-09 19:19:03 +02:00
|
|
|
// closeStopCh is used to wrap the exporters stopCh channel closing for testing.
|
|
|
|
var closeStopCh = func(stopCh chan bool) {
|
|
|
|
close(stopCh)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown closes all connections and releases resources currently being used
|
|
|
|
// by the exporter. If the exporter is not started this does nothing.
|
|
|
|
func (e *Exporter) Shutdown(ctx context.Context) error {
|
2020-03-07 08:01:02 +02:00
|
|
|
e.mu.RLock()
|
|
|
|
cc := e.grpcClientConn
|
|
|
|
started := e.started
|
|
|
|
e.mu.RUnlock()
|
|
|
|
|
|
|
|
if !started {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
if cc != nil {
|
2020-09-09 19:19:03 +02:00
|
|
|
// Clean things up before checking this error.
|
2020-03-07 08:01:02 +02:00
|
|
|
err = cc.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point we can change the state variable started
|
|
|
|
e.mu.Lock()
|
|
|
|
e.started = false
|
|
|
|
e.mu.Unlock()
|
2020-09-09 19:19:03 +02:00
|
|
|
closeStopCh(e.stopCh)
|
2020-03-07 08:01:02 +02:00
|
|
|
|
|
|
|
// Ensure that the backgroundConnector returns
|
2020-09-09 19:19:03 +02:00
|
|
|
select {
|
|
|
|
case <-e.backgroundConnectionDoneCh:
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
2020-03-07 08:01:02 +02:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-13 20:42:20 +02:00
|
|
|
// Export implements the "go.opentelemetry.io/otel/sdk/export/metric".Exporter
|
2020-04-15 21:04:44 +02:00
|
|
|
// interface. It transforms and batches metric Records into OTLP Metrics and
|
|
|
|
// transmits them to the configured collector.
|
2020-05-19 02:44:28 +02:00
|
|
|
func (e *Exporter) Export(parent context.Context, cps metricsdk.CheckpointSet) error {
|
2020-04-15 21:04:44 +02:00
|
|
|
// Unify the parent context Done signal with the exporter stopCh.
|
|
|
|
ctx, cancel := context.WithCancel(parent)
|
|
|
|
defer cancel()
|
|
|
|
go func(ctx context.Context, cancel context.CancelFunc) {
|
2020-03-13 20:42:20 +02:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2020-04-15 21:04:44 +02:00
|
|
|
case <-e.stopCh:
|
|
|
|
cancel()
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
2020-04-15 21:04:44 +02:00
|
|
|
}(ctx, cancel)
|
2020-03-13 20:42:20 +02:00
|
|
|
|
2020-06-23 07:59:51 +02:00
|
|
|
rms, err := transform.CheckpointSet(ctx, e, cps, e.c.numWorkers)
|
2020-04-15 21:04:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !e.connected() {
|
2020-04-15 21:04:44 +02:00
|
|
|
return errDisconnected
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-e.stopCh:
|
2020-04-15 21:04:44 +02:00
|
|
|
return errStopped
|
2020-03-13 20:42:20 +02:00
|
|
|
case <-ctx.Done():
|
2020-04-15 21:04:44 +02:00
|
|
|
return errContextCanceled
|
2020-03-13 20:42:20 +02:00
|
|
|
default:
|
|
|
|
e.senderMu.Lock()
|
2020-06-09 22:59:22 +02:00
|
|
|
_, err := e.metricExporter.Export(e.contextWithMetadata(ctx), &colmetricpb.ExportMetricsServiceRequest{
|
2020-04-15 21:04:44 +02:00
|
|
|
ResourceMetrics: rms,
|
2020-03-13 20:42:20 +02:00
|
|
|
})
|
|
|
|
e.senderMu.Unlock()
|
|
|
|
if err != nil {
|
2020-04-15 21:04:44 +02:00
|
|
|
return err
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-15 21:04:44 +02:00
|
|
|
return nil
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 07:59:51 +02:00
|
|
|
func (e *Exporter) ExportKindFor(*metric.Descriptor, aggregation.Kind) metricsdk.ExportKind {
|
|
|
|
return metricsdk.PassThroughExporter
|
|
|
|
}
|
|
|
|
|
2020-09-09 19:19:03 +02:00
|
|
|
func (e *Exporter) ExportSpans(ctx context.Context, sds []*tracesdk.SpanData) error {
|
|
|
|
return e.uploadTraces(ctx, sds)
|
2020-03-13 20:42:20 +02:00
|
|
|
}
|
|
|
|
|
2020-09-09 19:19:03 +02:00
|
|
|
func (e *Exporter) uploadTraces(ctx context.Context, sdl []*tracesdk.SpanData) error {
|
2020-03-07 08:01:02 +02:00
|
|
|
select {
|
|
|
|
case <-e.stopCh:
|
2020-09-09 19:19:03 +02:00
|
|
|
return nil
|
2020-03-07 08:01:02 +02:00
|
|
|
default:
|
|
|
|
if !e.connected() {
|
2020-09-09 19:19:03 +02:00
|
|
|
return nil
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 18:04:31 +02:00
|
|
|
protoSpans := transform.SpanData(sdl)
|
2020-03-07 08:01:02 +02:00
|
|
|
if len(protoSpans) == 0 {
|
2020-09-09 19:19:03 +02:00
|
|
|
return nil
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
e.senderMu.Lock()
|
2020-06-09 22:59:22 +02:00
|
|
|
_, err := e.traceExporter.Export(e.contextWithMetadata(ctx), &coltracepb.ExportTraceServiceRequest{
|
2020-03-07 08:01:02 +02:00
|
|
|
ResourceSpans: protoSpans,
|
|
|
|
})
|
|
|
|
e.senderMu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
e.setStateDisconnected(err)
|
2020-09-09 19:19:03 +02:00
|
|
|
return err
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-09 19:19:03 +02:00
|
|
|
return nil
|
2020-03-07 08:01:02 +02:00
|
|
|
}
|