github.com/pachyderm/pachyderm@v1.13.4/src/client/pkg/tracing/extended/extended_trace.go (about)

     1  package extended
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/pachyderm/pachyderm/src/client/pkg/tracing"
     9  	col "github.com/pachyderm/pachyderm/src/server/pkg/collection"
    10  
    11  	etcd "github.com/coreos/etcd/clientv3"
    12  	opentracing "github.com/opentracing/opentracing-go"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    14  	log "github.com/sirupsen/logrus"
    15  	"google.golang.org/grpc/metadata"
    16  )
    17  
    18  const (
    19  	// traceMDKey is the grpc metadata key whose value is a serialized
    20  	// ExtendedTrace proto tied to the current CreatePipeline request. In a grpc
    21  	// quirk, this key must end in '-bin' so that the value (a serialized
    22  	// timestamp) is treated as arbitrary bytes and base-64 encoded before being
    23  	// transmitted (see
    24  	// https://github.com/grpc/grpc-go/blob/b2c5f4a808fd5de543c4e987cd85d356140ed681/Documentation/grpc-metadata.md)
    25  	traceMDKey = "pipeline-trace-duration-bin"
    26  
    27  	// TracesCollectionPrefix is the prefix associated with the 'traces'
    28  	// collection in etcd (which maps pipelines and commits to extended traces)
    29  	tracesCollectionPrefix = "commit_traces"
    30  
    31  	// TraceDurationEnvVar determines whether a traced 'CreatePipeline' RPC is
    32  	// propagated to the PPS master, and whether worker creation and such is
    33  	// traced in addition to the original RPC. This value should be set to a
    34  	// go duration to create an extended trace
    35  	TraceDurationEnvVar = "PACH_TRACE_DURATION"
    36  
    37  	// The default duration over which to conduct an extended trace (used if the
    38  	// RPC's duration can't be parsed)
    39  	defaultDuration = 5 * time.Minute
    40  )
    41  
    42  // TracesCol returns the etcd collection of extended traces
    43  func TracesCol(c *etcd.Client) col.Collection {
    44  	return col.NewCollection(c,
    45  		tracesCollectionPrefix,
    46  		nil, // no indexes
    47  		&TraceProto{},
    48  		nil, // no key check (keys are pipeline names)
    49  		nil) // no val check
    50  }
    51  
    52  // PersistAny copies any extended traces from the incoming RPC context in 'ctx'
    53  // into etcd. Currently, this is only called by CreatePipeline, when it stores a
    54  // trace for future updates by the PPS master and workers.  This function is
    55  // best-effort, and therefore doesn't currently return an error. Any errors are
    56  // logged, and then the given context is returned.
    57  func PersistAny(ctx context.Context, c *etcd.Client, pipeline string) {
    58  	if !tracing.IsActive() {
    59  		return
    60  	}
    61  	span := opentracing.SpanFromContext(ctx)
    62  	if span == nil {
    63  		// No incoming trace, so nothing to propagate
    64  		return
    65  	}
    66  
    67  	md, ok := metadata.FromIncomingContext(ctx)
    68  	if !ok {
    69  		return // no extended trace attached to RPC
    70  	}
    71  
    72  	// Expected len('vals') is 0 or 1
    73  	vals := md.Get(traceMDKey)
    74  	if len(vals) == 0 {
    75  		return // no extended trace attached to RPC
    76  	}
    77  	if len(vals) > 1 {
    78  		log.Warnf("Multiple durations attached to extended trace for %q, using %s", pipeline, vals[0])
    79  	}
    80  
    81  	// Extended trace found, now create a span & persist it to etcd
    82  	duration, err := time.ParseDuration(vals[0])
    83  	if err != nil {
    84  		log.Errorf("could not parse extended span duration %q for pipeline %q: %v",
    85  			vals[0], pipeline, err)
    86  		return // Ignore extended trace attached to RPC
    87  	}
    88  
    89  	// serialize extended trace & write to etcd
    90  	traceProto := &TraceProto{
    91  		SerializedTrace: map[string]string{}, // init map
    92  		Pipeline:        pipeline,
    93  	}
    94  	opentracing.GlobalTracer().Inject(
    95  		span.Context(), opentracing.TextMap,
    96  		opentracing.TextMapCarrier(traceProto.SerializedTrace),
    97  	)
    98  	if _, err := col.NewSTM(ctx, c, func(stm col.STM) error {
    99  		tracesCol := TracesCol(c).ReadWrite(stm)
   100  		return tracesCol.PutTTL(pipeline, traceProto, int64(duration.Seconds()))
   101  	}); err != nil {
   102  		log.Errorf("could not persist extended trace for pipeline %q to etcd: %v. ",
   103  			vals[0], pipeline, err, defaultDuration)
   104  	}
   105  }
   106  
   107  func (t *TraceProto) isValid() bool {
   108  	return len(t.SerializedTrace) > 0
   109  }
   110  
   111  // AddSpanToAnyPipelineTrace finds any extended traces associated with
   112  // 'pipeline', and if any such trace exists, it creates a new span associated
   113  // with that trace and returns it
   114  func AddSpanToAnyPipelineTrace(ctx context.Context, c *etcd.Client,
   115  	pipeline, operation string, kvs ...interface{}) (opentracing.Span, context.Context) {
   116  	if !tracing.IsActive() {
   117  		return nil, ctx // no Jaeger instance to send trace info to
   118  	}
   119  
   120  	traceProto := &TraceProto{}
   121  	tracesCol := TracesCol(c).ReadOnly(ctx)
   122  	if err := tracesCol.Get(pipeline, traceProto); err != nil {
   123  		if !col.IsErrNotFound(err) {
   124  			log.Errorf("error getting trace for pipeline %q: %v", pipeline, err)
   125  		}
   126  		return nil, ctx
   127  	}
   128  	if !traceProto.isValid() {
   129  		return nil, ctx // no trace found
   130  	}
   131  
   132  	// Deserialize opentracing span from 'traceProto'
   133  	spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap,
   134  		opentracing.TextMapCarrier(traceProto.SerializedTrace))
   135  	if err != nil {
   136  		log.Errorf("could not extract span context from ExtendedTrace proto: %v", err)
   137  		return nil, ctx
   138  	}
   139  
   140  	// return new span
   141  	span, ctx := opentracing.StartSpanFromContext(ctx,
   142  		operation, opentracing.FollowsFrom(spanCtx),
   143  		opentracing.Tag{Key: "pipeline", Value: pipeline})
   144  	tracing.TagAnySpan(span, kvs...)
   145  	return span, ctx
   146  }
   147  
   148  // EmbedAnyDuration augments 'ctx' (and returns a new ctx) based on whether
   149  // the environment variable in 'ExtendedTraceEnvVar' is set.  Returns a context
   150  // that may have the new span attached, and 'true' if an an extended trace was
   151  // created, or 'false' otherwise. Currently only called by the CreatePipeline
   152  // cobra command
   153  func EmbedAnyDuration(ctx context.Context) (newCtx context.Context, err error) {
   154  	duration, ok := os.LookupEnv(TraceDurationEnvVar)
   155  	if !ok {
   156  		return ctx, nil // PACH_TRACE_DURATION is not set
   157  	}
   158  	if _, ok := os.LookupEnv(tracing.ShortTraceEnvVar); !ok {
   159  		return ctx, errors.Errorf("cannot set %s without setting %s",
   160  			TraceDurationEnvVar, tracing.ShortTraceEnvVar)
   161  	}
   162  	if _, err := time.ParseDuration(duration); err != nil {
   163  		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(traceMDKey,
   164  			defaultDuration.String()))
   165  		return ctx, errors.Wrapf(err,
   166  			"could not parse duration %q (using default duration %q)", duration, defaultDuration)
   167  	}
   168  	ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(traceMDKey, duration))
   169  	return ctx, nil
   170  }