vendor: update buildkit to opentelemetry support

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2021-06-15 21:02:39 -07:00
parent 6ba080d337
commit 334c93fbbe
829 changed files with 89541 additions and 24438 deletions

View File

@ -0,0 +1,75 @@
package jaeger
import (
"context"
"sync"
"github.com/moby/buildkit/util/tracing/detect"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
const maxBuffer = 256
var exp = &Exporter{}
func init() {
detect.Register("delegated", func() (sdktrace.SpanExporter, error) {
return exp, nil
}, 100)
}
type Exporter struct {
mu sync.Mutex
exporters []sdktrace.SpanExporter
buffer []sdktrace.ReadOnlySpan
}
var _ sdktrace.SpanExporter = &Exporter{}
func (e *Exporter) ExportSpans(ctx context.Context, ss []sdktrace.ReadOnlySpan) error {
e.mu.Lock()
defer e.mu.Unlock()
var err error
for _, e := range e.exporters {
if err1 := e.ExportSpans(ctx, ss); err1 != nil {
err = err1
}
}
if err != nil {
return err
}
if len(e.buffer) > maxBuffer {
return nil
}
e.buffer = append(e.buffer, ss...)
return nil
}
func (e *Exporter) Shutdown(ctx context.Context) error {
e.mu.Lock()
defer e.mu.Unlock()
var err error
for _, e := range e.exporters {
if err1 := e.Shutdown(ctx); err1 != nil {
err = err1
}
}
return err
}
func (e *Exporter) SetSpanExporter(ctx context.Context, exp sdktrace.SpanExporter) error {
e.mu.Lock()
defer e.mu.Unlock()
e.exporters = append(e.exporters, exp)
if len(e.buffer) > 0 {
return exp.ExportSpans(ctx, e.buffer)
}
return nil
}

View File

@ -0,0 +1,150 @@
package detect
import (
"context"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
type ExporterDetector func() (sdktrace.SpanExporter, error)
type detector struct {
f ExporterDetector
priority int
}
var ServiceName string
var detectors map[string]detector
var once sync.Once
var tp trace.TracerProvider
var exporter sdktrace.SpanExporter
var closers []func(context.Context) error
var err error
func Register(name string, exp ExporterDetector, priority int) {
if detectors == nil {
detectors = map[string]detector{}
}
detectors[name] = detector{
f: exp,
priority: priority,
}
}
func detectExporter() (sdktrace.SpanExporter, error) {
if n := os.Getenv("OTEL_TRACES_EXPORTER"); n != "" {
d, ok := detectors[n]
if !ok {
if n == "none" {
return nil, nil
}
return nil, errors.Errorf("unsupported opentelemetry tracer %v", n)
}
return d.f()
}
arr := make([]detector, 0, len(detectors))
for _, d := range detectors {
arr = append(arr, d)
}
sort.Slice(arr, func(i, j int) bool {
return arr[i].priority < arr[j].priority
})
for _, d := range arr {
exp, err := d.f()
if err != nil {
return nil, err
}
if exp != nil {
return exp, nil
}
}
return nil, nil
}
func detect() error {
tp = trace.NewNoopTracerProvider()
exp, err := detectExporter()
if err != nil {
return err
}
if exp == nil {
return nil
}
res, err := resource.Detect(context.Background(), serviceNameDetector{})
if err != nil {
return err
}
res, err = resource.Merge(resource.Default(), res)
if err != nil {
return err
}
sp := sdktrace.NewBatchSpanProcessor(exp)
sdktp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sp), sdktrace.WithResource(res))
closers = append(closers, sdktp.Shutdown)
exporter = exp
tp = sdktp
return nil
}
func TracerProvider() (trace.TracerProvider, error) {
once.Do(func() {
if err1 := detect(); err1 != nil {
err = err1
}
})
b, _ := strconv.ParseBool(os.Getenv("OTEL_INGORE_ERROR"))
if err != nil && !b {
return nil, err
}
return tp, nil
}
func Exporter() (sdktrace.SpanExporter, error) {
_, err := TracerProvider()
if err != nil {
return nil, err
}
return exporter, nil
}
func Shutdown(ctx context.Context) error {
for _, c := range closers {
if err := c(ctx); err != nil {
return err
}
}
return nil
}
type serviceNameDetector struct{}
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.StringDetector(
semconv.SchemaURL,
semconv.ServiceNameKey,
func() (string, error) {
if n := os.Getenv("OTEL_SERVICE_NAME"); n != "" {
return n, nil
}
if ServiceName != "" {
return ServiceName, nil
}
return filepath.Base(os.Args[0]), nil
},
).Detect(ctx)
}

View File

@ -0,0 +1,45 @@
package detect
import (
"context"
"os"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func init() {
Register("otlp", otlpExporter, 10)
}
func otlpExporter() (sdktrace.SpanExporter, error) {
set := os.Getenv("OTEL_TRACES_EXPORTER") == "otpl" || os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" || os.Getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") != ""
if !set {
return nil, nil
}
proto := os.Getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")
if proto == "" {
proto = os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL")
}
if proto == "" {
proto = "grpc"
}
var c otlptrace.Client
switch proto {
case "grpc":
c = otlptracegrpc.NewClient()
case "http/protobuf":
c = otlptracehttp.NewClient()
// case "http/json": // unsupported by library
default:
return nil, errors.Errorf("unsupported otlp protocol %v", proto)
}
return otlptrace.New(context.Background(), c)
}

View File

@ -0,0 +1,54 @@
package detect
import (
"context"
"os"
"github.com/moby/buildkit/util/appcontext"
"go.opentelemetry.io/otel/propagation"
)
const (
traceparentHeader = "traceparent"
tracestateHeader = "tracestate"
)
func init() {
appcontext.Register(initContext)
}
func initContext(ctx context.Context) context.Context {
// previously defined in https://github.com/open-telemetry/opentelemetry-swift/blob/4ea467ed4b881d7329bf2254ca7ed7f2d9d6e1eb/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift#L14-L15
parent := os.Getenv("OTEL_TRACE_PARENT")
state := os.Getenv("OTEL_TRACE_STATE")
if parent == "" {
return ctx
}
tc := propagation.TraceContext{}
return tc.Extract(ctx, &textMap{parent: parent, state: state})
}
type textMap struct {
parent string
state string
}
func (tm *textMap) Get(key string) string {
switch key {
case traceparentHeader:
return tm.parent
case tracestateHeader:
return tm.state
default:
return ""
}
}
func (tm *textMap) Set(key string, value string) {
}
func (tm *textMap) Keys() []string {
return []string{traceparentHeader, tracestateHeader}
}

View File

@ -0,0 +1,22 @@
package tracing
import (
"go.opentelemetry.io/otel/trace"
)
// MultiSpan allows shared tracing to multiple spans.
// TODO: This is a temporary solution and doesn't really support shared tracing yet. Instead the first always wins.
type MultiSpan struct {
trace.Span
}
func NewMultiSpan() *MultiSpan {
return &MultiSpan{}
}
func (ms *MultiSpan) Add(s trace.Span) {
if ms.Span == nil {
ms.Span = s
}
}

View File

@ -0,0 +1,101 @@
// Copyright The OpenTelemetry 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 otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
import (
"context"
"errors"
"fmt"
"sync"
"time"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"google.golang.org/grpc"
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
)
type client struct {
connection *Connection
lock sync.Mutex
tracesClient coltracepb.TraceServiceClient
}
var _ otlptrace.Client = (*client)(nil)
var (
errNoClient = errors.New("no client")
)
// NewClient creates a new gRPC trace client.
func NewClient(cc *grpc.ClientConn) otlptrace.Client {
c := &client{}
c.connection = NewConnection(cc, c.handleNewConnection)
return c
}
func (c *client) handleNewConnection(cc *grpc.ClientConn) {
c.lock.Lock()
defer c.lock.Unlock()
if cc != nil {
c.tracesClient = coltracepb.NewTraceServiceClient(cc)
} else {
c.tracesClient = nil
}
}
// Start establishes a connection to the collector.
func (c *client) Start(ctx context.Context) error {
return c.connection.StartConnection(ctx)
}
// Stop shuts down the connection to the collector.
func (c *client) Stop(ctx context.Context) error {
return c.connection.Shutdown(ctx)
}
// UploadTraces sends a batch of spans to the collector.
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error {
if !c.connection.Connected() {
return fmt.Errorf("traces exporter is disconnected from the server: %w", c.connection.LastConnectError())
}
ctx, cancel := c.connection.ContextWithStop(ctx)
defer cancel()
ctx, tCancel := context.WithTimeout(ctx, 30*time.Second)
defer tCancel()
ctx = c.connection.ContextWithMetadata(ctx)
err := func() error {
c.lock.Lock()
defer c.lock.Unlock()
if c.tracesClient == nil {
return errNoClient
}
_, err := c.tracesClient.Export(ctx, &coltracepb.ExportTraceServiceRequest{
ResourceSpans: protoSpans,
})
return err
}()
if err != nil {
c.connection.SetStateDisconnected(err)
}
return err
}

View File

@ -0,0 +1,219 @@
// Copyright The OpenTelemetry 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 otlptracegrpc
import (
"context"
"math/rand"
"sync"
"sync/atomic"
"time"
"unsafe"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type Connection struct {
// Ensure pointer is 64-bit aligned for atomic operations on both 32 and 64 bit machines.
lastConnectErrPtr unsafe.Pointer
// mu protects the Connection as it is accessed by the
// exporter goroutines and background Connection goroutine
mu sync.Mutex
cc *grpc.ClientConn
// these fields are read-only after constructor is finished
metadata metadata.MD
newConnectionHandler func(cc *grpc.ClientConn)
// these channels are created once
disconnectedCh chan bool
backgroundConnectionDoneCh chan struct{}
stopCh chan struct{}
// this is for tests, so they can replace the closing
// routine without a worry of modifying some global variable
// or changing it back to original after the test is done
closeBackgroundConnectionDoneCh func(ch chan struct{})
}
func NewConnection(cc *grpc.ClientConn, handler func(cc *grpc.ClientConn)) *Connection {
c := new(Connection)
c.newConnectionHandler = handler
c.cc = cc
c.closeBackgroundConnectionDoneCh = func(ch chan struct{}) {
close(ch)
}
return c
}
func (c *Connection) StartConnection(ctx context.Context) error {
c.stopCh = make(chan struct{})
c.disconnectedCh = make(chan bool, 1)
c.backgroundConnectionDoneCh = make(chan struct{})
if err := c.connect(ctx); err == nil {
c.setStateConnected()
} else {
c.SetStateDisconnected(err)
}
go c.indefiniteBackgroundConnection()
// TODO: proper error handling when initializing connections.
// We can report permanent errors, e.g., invalid settings.
return nil
}
func (c *Connection) LastConnectError() error {
errPtr := (*error)(atomic.LoadPointer(&c.lastConnectErrPtr))
if errPtr == nil {
return nil
}
return *errPtr
}
func (c *Connection) saveLastConnectError(err error) {
var errPtr *error
if err != nil {
errPtr = &err
}
atomic.StorePointer(&c.lastConnectErrPtr, unsafe.Pointer(errPtr))
}
func (c *Connection) SetStateDisconnected(err error) {
c.saveLastConnectError(err)
select {
case c.disconnectedCh <- true:
default:
}
c.newConnectionHandler(nil)
}
func (c *Connection) setStateConnected() {
c.saveLastConnectError(nil)
}
func (c *Connection) Connected() bool {
return c.LastConnectError() == nil
}
const defaultConnReattemptPeriod = 10 * time.Second
func (c *Connection) indefiniteBackgroundConnection() {
defer func() {
c.closeBackgroundConnectionDoneCh(c.backgroundConnectionDoneCh)
}()
connReattemptPeriod := defaultConnReattemptPeriod
// No strong seeding required, nano time can
// already help with pseudo uniqueness.
rng := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1024)))
// maxJitterNanos: 70% of the connectionReattemptPeriod
maxJitterNanos := int64(0.7 * float64(connReattemptPeriod))
for {
// Otherwise these will be the normal scenarios to enable
// reconnection if we trip out.
// 1. If we've stopped, return entirely
// 2. Otherwise block until we are disconnected, and
// then retry connecting
select {
case <-c.stopCh:
return
case <-c.disconnectedCh:
// Quickly check if we haven't stopped at the
// same time.
select {
case <-c.stopCh:
return
default:
}
// Normal scenario that we'll wait for
}
if err := c.connect(context.Background()); err == nil {
c.setStateConnected()
} else {
// this code is unreachable in most cases
// c.connect does not establish Connection
c.SetStateDisconnected(err)
}
// Apply some jitter to avoid lockstep retrials of other
// collector-exporters. Lockstep retrials could result in an
// innocent DDOS, by clogging the machine's resources and network.
jitter := time.Duration(rng.Int63n(maxJitterNanos))
select {
case <-c.stopCh:
return
case <-time.After(connReattemptPeriod + jitter):
}
}
}
func (c *Connection) connect(ctx context.Context) error {
c.newConnectionHandler(c.cc)
return nil
}
func (c *Connection) ContextWithMetadata(ctx context.Context) context.Context {
if c.metadata.Len() > 0 {
return metadata.NewOutgoingContext(ctx, c.metadata)
}
return ctx
}
func (c *Connection) Shutdown(ctx context.Context) error {
close(c.stopCh)
// Ensure that the backgroundConnector returns
select {
case <-c.backgroundConnectionDoneCh:
case <-ctx.Done():
return ctx.Err()
}
c.mu.Lock()
cc := c.cc
c.cc = nil
c.mu.Unlock()
if cc != nil {
return cc.Close()
}
return nil
}
func (c *Connection) ContextWithStop(ctx context.Context) (context.Context, context.CancelFunc) {
// Unify the parent context Done signal with the Connection's
// stop channel.
ctx, cancel := context.WithCancel(ctx)
go func(ctx context.Context, cancel context.CancelFunc) {
select {
case <-ctx.Done():
// Nothing to do, either cancelled or deadline
// happened.
case <-c.stopCh:
cancel()
}
}(ctx, cancel)
return ctx, cancel
}

133
vendor/github.com/moby/buildkit/util/tracing/tracing.go generated vendored Normal file
View File

@ -0,0 +1,133 @@
package tracing
import (
"context"
"fmt"
"io"
"net/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
// StartSpan starts a new span as a child of the span in context.
// If there is no span in context then this is a no-op.
func StartSpan(ctx context.Context, operationName string, opts ...trace.SpanStartOption) (trace.Span, context.Context) {
parent := trace.SpanFromContext(ctx)
tracer := trace.NewNoopTracerProvider().Tracer("")
if parent != nil && parent.SpanContext().IsValid() {
tracer = parent.TracerProvider().Tracer("")
}
ctx, span := tracer.Start(ctx, operationName, opts...)
return span, ctx
}
// FinishWithError finalizes the span and sets the error if one is passed
func FinishWithError(span trace.Span, err error) {
if err != nil {
span.RecordError(err)
if _, ok := err.(interface {
Cause() error
}); ok {
span.SetAttributes(attribute.String(string(semconv.ExceptionStacktraceKey), fmt.Sprintf("%+v", err)))
}
span.SetStatus(codes.Error, err.Error())
}
span.End()
}
// ContextWithSpanFromContext sets the tracing span of a context from other
// context if one is not already set. Alternative would be
// context.WithoutCancel() that would copy the context but reset ctx.Done
func ContextWithSpanFromContext(ctx, ctx2 context.Context) context.Context {
// if already is a span then noop
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
return ctx
}
if span := trace.SpanFromContext(ctx2); span != nil && span.SpanContext().IsValid() {
return trace.ContextWithSpan(ctx, span)
}
return ctx
}
var DefaultTransport http.RoundTripper = &Transport{
RoundTripper: NewTransport(http.DefaultTransport),
}
var DefaultClient = &http.Client{
Transport: DefaultTransport,
}
var propagators = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
type Transport struct {
http.RoundTripper
}
func NewTransport(rt http.RoundTripper) http.RoundTripper {
// TODO: switch to otelhttp. needs upstream updates to avoid transport-global tracer
return &Transport{
RoundTripper: rt,
}
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
span := trace.SpanFromContext(req.Context())
if !span.SpanContext().IsValid() { // no tracer connected with either request or transport
return t.RoundTripper.RoundTrip(req)
}
ctx, span := span.TracerProvider().Tracer("").Start(req.Context(), req.Method)
req = req.WithContext(ctx)
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(req)...)
propagators.Inject(ctx, propagation.HeaderCarrier(req.Header))
resp, err := t.RoundTripper.RoundTrip(req)
if err != nil {
span.RecordError(err)
span.End()
return resp, err
}
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...)
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode))
if req.Method == "HEAD" {
span.End()
} else {
resp.Body = &wrappedBody{ctx: ctx, span: span, body: resp.Body}
}
return resp, err
}
type wrappedBody struct {
ctx context.Context
span trace.Span
body io.ReadCloser
}
var _ io.ReadCloser = &wrappedBody{}
func (wb *wrappedBody) Read(b []byte) (int, error) {
n, err := wb.body.Read(b)
switch err {
case nil:
// nothing to do here but fall through to the return
case io.EOF:
wb.span.End()
default:
wb.span.RecordError(err)
}
return n, err
}
func (wb *wrappedBody) Close() error {
wb.span.End()
return wb.body.Close()
}