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 }