|
| 1 | +package reconciler |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "encoding/json" |
| 6 | + |
| 7 | + "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" |
| 8 | + "github.com/openshift-pipelines/pipelines-as-code/pkg/tracing" |
| 9 | + tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" |
| 10 | + "go.opentelemetry.io/otel" |
| 11 | + "go.opentelemetry.io/otel/attribute" |
| 12 | + "go.opentelemetry.io/otel/propagation" |
| 13 | + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" |
| 14 | + "go.opentelemetry.io/otel/trace" |
| 15 | + corev1 "k8s.io/api/core/v1" |
| 16 | + "knative.dev/pkg/apis" |
| 17 | +) |
| 18 | + |
| 19 | +const ( |
| 20 | + applicationLabel = "appstudio.openshift.io/application" |
| 21 | + componentLabel = "appstudio.openshift.io/component" |
| 22 | + stageBuild = "build" |
| 23 | +) |
| 24 | + |
| 25 | + |
| 26 | +// extractSpanContext extracts the trace context from the pipelinerunSpanContext annotation. |
| 27 | +func extractSpanContext(pr *tektonv1.PipelineRun) (context.Context, bool) { |
| 28 | + raw, ok := pr.GetAnnotations()[keys.SpanContextAnnotation] |
| 29 | + if !ok || raw == "" { |
| 30 | + return nil, false |
| 31 | + } |
| 32 | + var carrierMap map[string]string |
| 33 | + if err := json.Unmarshal([]byte(raw), &carrierMap); err != nil { |
| 34 | + return nil, false |
| 35 | + } |
| 36 | + carrier := propagation.MapCarrier(carrierMap) |
| 37 | + ctx := otel.GetTextMapPropagator().Extract(context.Background(), carrier) |
| 38 | + sc := trace.SpanContextFromContext(ctx) |
| 39 | + if !sc.IsValid() { |
| 40 | + return nil, false |
| 41 | + } |
| 42 | + return ctx, true |
| 43 | +} |
| 44 | + |
| 45 | +// emitTimingSpans emits wait_duration and execute_duration spans for a completed build PipelineRun. |
| 46 | +func emitTimingSpans(pr *tektonv1.PipelineRun) { |
| 47 | + parentCtx, ok := extractSpanContext(pr) |
| 48 | + if !ok { |
| 49 | + return |
| 50 | + } |
| 51 | + |
| 52 | + tracer := otel.Tracer(tracing.TracerName) |
| 53 | + commonAttrs := buildCommonAttributes(pr) |
| 54 | + |
| 55 | + // Emit waitDuration: creationTimestamp -> status.startTime |
| 56 | + if pr.Status.StartTime != nil { |
| 57 | + _, waitSpan := tracer.Start(parentCtx, "waitDuration", |
| 58 | + trace.WithTimestamp(pr.CreationTimestamp.Time), |
| 59 | + trace.WithAttributes(commonAttrs...), |
| 60 | + ) |
| 61 | + waitSpan.End(trace.WithTimestamp(pr.Status.StartTime.Time)) |
| 62 | + } |
| 63 | + |
| 64 | + // Emit executeDuration: status.startTime -> status.completionTime |
| 65 | + if pr.Status.StartTime != nil && pr.Status.CompletionTime != nil { |
| 66 | + execAttrs := append(append([]attribute.KeyValue{}, commonAttrs...), buildExecuteAttributes(pr)...) |
| 67 | + _, execSpan := tracer.Start(parentCtx, "executeDuration", |
| 68 | + trace.WithTimestamp(pr.Status.StartTime.Time), |
| 69 | + trace.WithAttributes(execAttrs...), |
| 70 | + ) |
| 71 | + execSpan.End(trace.WithTimestamp(pr.Status.CompletionTime.Time)) |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +// buildCommonAttributes returns span attributes common to both timing spans. |
| 76 | +func buildCommonAttributes(pr *tektonv1.PipelineRun) []attribute.KeyValue { |
| 77 | + attrs := []attribute.KeyValue{ |
| 78 | + semconv.K8SNamespaceName(pr.GetNamespace()), |
| 79 | + tracing.TektonPipelineRunNameKey.String(pr.GetName()), |
| 80 | + tracing.TektonPipelineRunUIDKey.String(string(pr.GetUID())), |
| 81 | + tracing.DeliveryStageKey.String(stageBuild), |
| 82 | + tracing.DeliveryApplicationKey.String(pr.GetLabels()[applicationLabel]), |
| 83 | + } |
| 84 | + if component := pr.GetLabels()[componentLabel]; component != "" { |
| 85 | + attrs = append(attrs, tracing.DeliveryComponentKey.String(component)) |
| 86 | + } |
| 87 | + return attrs |
| 88 | +} |
| 89 | + |
| 90 | +// buildExecuteAttributes returns span attributes specific to execute_duration. |
| 91 | +func buildExecuteAttributes(pr *tektonv1.PipelineRun) []attribute.KeyValue { |
| 92 | + cond := pr.Status.GetCondition(apis.ConditionSucceeded) |
| 93 | + success := false |
| 94 | + reason := "" |
| 95 | + if cond != nil { |
| 96 | + reason = cond.Reason |
| 97 | + success = cond.Status == corev1.ConditionTrue |
| 98 | + } |
| 99 | + return []attribute.KeyValue{ |
| 100 | + tracing.DeliverySuccessKey.Bool(success), |
| 101 | + tracing.DeliveryReasonKey.String(reason), |
| 102 | + } |
| 103 | +} |
0 commit comments