From cdcb114bdad95003ccfd4efb0733971970ef9568 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 24 Jun 2021 17:33:04 +0100 Subject: [PATCH 01/34] Initial attempt to support distributed traces for the cloud lifecycle --- .../computer/MonitoringCloudListener.java | 109 +++++-- .../computer/OtelTraceService.java | 271 ++++++++++++++++++ ...wareAbstractCloudProvisioningListener.java | 113 ++++++++ .../context/FlowNodeContextKey.java | 21 ++ .../context/PlannedNodeContextKey.java | 21 ++ .../JenkinsOtelSemanticAttributes.java | 5 + 6 files changed, 518 insertions(+), 22 deletions(-) create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/OtelContextAwareAbstractCloudProvisioningListener.java create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/FlowNodeContextKey.java create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/PlannedNodeContextKey.java diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index ea876bb08..bd453e327 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -5,67 +5,132 @@ package io.jenkins.plugins.opentelemetry.computer; +import com.google.errorprone.annotations.MustBeClosed; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.model.Label; import hudson.model.Node; -import hudson.slaves.CloudProvisioningListener; +import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.OtelUtils; +import io.jenkins.plugins.opentelemetry.computer.opentelemetry.OtelContextAwareAbstractCloudProvisioningListener; +import io.jenkins.plugins.opentelemetry.computer.opentelemetry.context.PlannedNodeContextKey; +import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; -import javax.inject.Inject; +import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; +import static com.google.common.base.Verify.verifyNotNull; + @Extension -public class MonitoringCloudListener extends CloudProvisioningListener { +public class MonitoringCloudListener extends OtelContextAwareAbstractCloudProvisioningListener { private final static Logger LOGGER = Logger.getLogger(MonitoringCloudListener.class.getName()); - protected Meter meter; - private LongCounter failureCloudCounter; private LongCounter totalCloudCount; @PostConstruct public void postConstruct() { - failureCloudCounter = meter.longCounterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_FAILURE) + failureCloudCounter = getMeter().longCounterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_FAILURE) .setDescription("Number of failed cloud agents when provisioning") .setUnit("1") .build(); - totalCloudCount = meter.longCounterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_COMPLETED) + totalCloudCount = getMeter().longCounterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_COMPLETED) .setDescription("Number of provisioned cloud agents") .setUnit("1") .build(); } @Override - public void onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { + public void _onStarted(Cloud cloud, Label label, Collection plannedNodes) { + LOGGER.log(Level.FINE, () -> "_onStarted(" + label + ")"); + if (plannedNodes.size() != 1) { + return; + } + NodeProvisioner.PlannedNode plannedNode = plannedNodes.iterator().next(); + + String rootSpanName = plannedNode.displayName; + SpanBuilder rootSpanBuilder = getTracer().spanBuilder(rootSpanName).setSpanKind(SpanKind.SERVER); + + // TODO move this to a pluggable span enrichment API with implementations for different observability backends + // Regarding the value `unknown`, see https://github.com/jenkinsci/opentelemetry-plugin/issues/51 + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.ELASTIC_TRANSACTION_TYPE, "unknown") + .setAttribute(JenkinsOtelSemanticAttributes.CI_CLOUD_LABEL, label.getExpression()); + + // START ROOT SPAN + Span rootSpan = rootSpanBuilder.startSpan(); + + this.getTraceService().putSpan(plannedNode, rootSpan); + rootSpan.makeCurrent(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); + } + + @Override + public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { + LOGGER.log(Level.FINE, () -> "_onCommit(" + node + ")"); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span runSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(runSpan)); + runSpan.makeCurrent(); + this.getTraceService().putSpan(plannedNode, runSpan); + } + } + + @Override + public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { + LOGGER.log(Level.FINE, () -> "_onFailure(" + plannedNode + ")"); failureCloudCounter.add(1); - LOGGER.log(Level.FINE, () -> "onFailure(" + plannedNode + ")"); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(span)); + } } @Override - public void onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, - @NonNull Throwable t) { + public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, + @NonNull Throwable t){ + LOGGER.log(Level.FINE, () -> "_onRollback(" + plannedNode + ")"); failureCloudCounter.add(1); - LOGGER.log(Level.FINE, () -> "onRollback(" + plannedNode + ")"); } @Override - public void onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { + public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { + LOGGER.log(Level.FINE, () -> "_onComplete(" + plannedNode + ")"); totalCloudCount.add(1); - LOGGER.log(Level.FINE, () -> "onComplete(" + plannedNode + ")"); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); + span.setStatus(StatusCode.OK); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(span)); + } } - /** - * Jenkins doesn't support {@link com.google.inject.Provides} so we manually wire dependencies :-( - */ - @Inject - public void setMeter(@Nonnull OpenTelemetrySdkProvider openTelemetrySdkProvider) { - this.meter = openTelemetrySdkProvider.getMeter(); + @MustBeClosed + @Nonnull + protected Scope endCloudPhaseSpan(@NonNull NodeProvisioner.PlannedNode plannedNode) { + Span cloudPhaseSpan = verifyNotNull(Span.current(), "No cloudPhaseSpan found in context"); + cloudPhaseSpan.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end " + OtelUtils.toDebugString(cloudPhaseSpan)); + + //this.getTraceService().removeJobPhaseSpan(run, pipelinePhaseSpan); + Span newCurrentSpan = this.getTraceService().getSpan(plannedNode); + Scope newScope = newCurrentSpan.makeCurrent(); + Context.current().with(PlannedNodeContextKey.KEY, plannedNode); + return newScope; } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java new file mode 100644 index 000000000..423066b17 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java @@ -0,0 +1,271 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.errorprone.annotations.MustBeClosed; +import hudson.Extension; +import hudson.slaves.NodeProvisioner; +import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.computer.opentelemetry.context.PlannedNodeContextKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import org.jenkinsci.plugins.workflow.graph.FlowNode; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Verify.verify; + +@Extension +public class OtelTraceService { + + private static Logger LOGGER = Logger.getLogger(OtelTraceService.class.getName()); + + private transient ConcurrentMap spansByCloud; + + private Tracer tracer; + + private Tracer noOpTracer; + + public OtelTraceService() { + initialize(); + } + + protected Object readResolve() { + initialize(); + return this; + } + + private void initialize() { + spansByCloud = new ConcurrentHashMap(); + } + + @Nonnull + public Span getSpan(@Nonnull NodeProvisioner.PlannedNode plannedNode) { + return getSpan(plannedNode, true); + } + + @Nonnull + public Span getSpan(@Nonnull NodeProvisioner.PlannedNode plannedNode, boolean verifyIfRemainingSteps) { + PlannedNodeIdentifier plannedNodeIdentifier = PlannedNodeIdentifier.fromRun(plannedNode); + CloudSpans cloudSpans = spansByCloud.computeIfAbsent(plannedNodeIdentifier, plannedNodeIdentifier1 -> new CloudSpans()); // absent when Jenkins restarts during build + + if (verifyIfRemainingSteps) { + verify(cloudSpans.cloudSpansByFlowNodeId.isEmpty(), plannedNode.displayName + " - Can't access run phase span while there are remaining cloud step spans: " + cloudSpans); + } + LOGGER.log(Level.FINEST, () -> "getSpan(" + plannedNode.displayName + ") - " + cloudSpans); + final Span span = Iterables.getLast(cloudSpans.runPhasesSpans, null); + if (span == null) { + LOGGER.log(Level.FINE, () -> "No span found for run " + plannedNode.displayName+ ", Jenkins server may have restarted"); + return noOpTracer.spanBuilder("noop-recovery-planned-node-span-for-" + plannedNode.displayName).startSpan(); + } + return span; + } + + @Nonnull + public Span getSpan(@Nonnull NodeProvisioner.PlannedNode plannedNode, hudson.model.Node flowNode) { + PlannedNodeIdentifier plannedNodeIdentifier = PlannedNodeIdentifier.fromRun(plannedNode); + CloudSpans cloudSpans = spansByCloud.computeIfAbsent(plannedNodeIdentifier, plannedNodeIdentifier1 -> new CloudSpans()); // absent when Jenkins restarts during build + LOGGER.log(Level.FINEST, () -> "getSpan(" + plannedNode.displayName + ", Node[displayName" + flowNode.getDisplayName() + ", nodeName:" + flowNode.getNodeName() + ", label=" + flowNode.getLabelString() + "]) - " + cloudSpans); + + final Span span = Iterables.getLast(cloudSpans.runPhasesSpans, null); + if (span == null) { + LOGGER.log(Level.FINE, () -> "No span found for run " + plannedNode.displayName + ", Jenkins server may have restarted"); + return noOpTracer.spanBuilder("noop-recovery-planned-node-span-for-" + plannedNode.displayName).startSpan(); + } + LOGGER.log(Level.FINEST, () -> "span: " + span.getSpanContext().getSpanId()); + return span; + } + + public void putSpan(@Nonnull NodeProvisioner.PlannedNode plannedNode, @Nonnull Span span) { + PlannedNodeIdentifier plannedNodeIdentifier = PlannedNodeIdentifier.fromRun(plannedNode); + CloudSpans cloudSpans = spansByCloud.computeIfAbsent(plannedNodeIdentifier, plannedNodeIdentifier1 -> new CloudSpans()); + cloudSpans.runPhasesSpans.add(span); + + LOGGER.log(Level.FINEST, () -> "putSpan(" + plannedNode.displayName + "," + span + ") - new stack: " + cloudSpans); + } + + public void putSpan(@Nonnull NodeProvisioner.PlannedNode plannedNode, @Nonnull Span span, @Nonnull hudson.model.Node flowNode) { + PlannedNodeIdentifier plannedNodeIdentifier = PlannedNodeIdentifier.fromRun(plannedNode); + CloudSpans cloudSpans = spansByCloud.computeIfAbsent(plannedNodeIdentifier, plannedNodeIdentifier1 -> new CloudSpans()); + cloudSpans.cloudSpansByFlowNodeId.put(flowNode.getDisplayName(), new CloudSpanContext(span, flowNode)); + + LOGGER.log(Level.FINEST, () -> "putSpan(" + plannedNode.displayName + ", Node[displayName" + flowNode.getDisplayName() + ", nodeName:" + flowNode.getNodeName() + ", label=" + flowNode.getLabelString() + "], Span[id: " + span.getSpanContext().getSpanId() + "]" + ") - " + cloudSpans); + } + + @Inject + public void setJenkinsOtelPlugin(@Nonnull OpenTelemetrySdkProvider openTelemetrySdkProvider) { + this.tracer = openTelemetrySdkProvider.getTracer(); + this.noOpTracer = TracerProvider.noop().get("jenkins"); + } + + /** + * @param plannedNodes + * @return If no span has been found (ie Jenkins restart), then the scope of a NoOp span is returned + */ + @Nonnull + @MustBeClosed + public Scope setupContext(@Nonnull Collection plannedNodes) { + if (plannedNodes.size() == 1) { + return setupContext(plannedNodes.iterator().next()); + } + // NOOP span . TODO + return null; + } + + /** + * @param plannedNode + * @return If no span has been found (ie Jenkins restart), then the scope of a NoOp span is returned + */ + @Nonnull + @MustBeClosed + public Scope setupContext(@Nullable NodeProvisioner.PlannedNode plannedNode) { + if (plannedNode != null) { + Span span = getSpan(plannedNode); + Scope scope = span.makeCurrent(); + Context.current().with(PlannedNodeContextKey.KEY, plannedNode); + return scope; + } + // NOOP span . TODO + return null; + } + + public Tracer getTracer() { + return tracer; + } + + + @Immutable + public static class CloudSpans { + final Multimap cloudSpansByFlowNodeId = ArrayListMultimap.create(); + final List runPhasesSpans = new ArrayList<>(); + + @Override + public String toString() { + // clone the Multimap to prevent a ConcurrentModificationException + // see https://github.com/jenkinsci/opentelemetry-plugin/issues/129 + return "CloudSpans{" + + "runPhasesSpans=" + Collections.unmodifiableList(runPhasesSpans) + + ", pipelineStepSpansByFlowNodeId=" + ArrayListMultimap.create(cloudSpansByFlowNodeId) + + '}'; + } + } + + public static class CloudSpanContext { + final transient Span span; + final String flowNodeId; + final List parentFlowNodeIds; + + public CloudSpanContext(@Nonnull Span span, @Nonnull hudson.model.Node flowNode) { + this.span = span; + this.flowNodeId = flowNode.getNodeName(); + this.parentFlowNodeIds = new ArrayList<>( 1); + this.parentFlowNodeIds.add(flowNode.getNodeName()); + } + + /** + * Return the stack of the parent {@link FlowNode} of this {@link Span}. + * The first id of the list is the {@link FlowNode} on which the {@link Span} has been created, the last item of the list if the oldest parent. + * + * @see FlowNode#getParents() + */ + @Nonnull + public List getParentFlowNodeIds() { + return parentFlowNodeIds; + } + + /** + * FIXME handle cases where the data structure has been deserialized and {@link Span} is null. + */ + @Nonnull + public Span getSpan() { + return span; + } + + @Override + public String toString() { + return "CloudSpanContext{" + + "span=" + span + + "flowNodeId=" + flowNodeId + + ", parentIds=" + parentFlowNodeIds + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CloudSpanContext that = (CloudSpanContext) o; + return Objects.equals(this.span.getSpanContext().getSpanId(), that.span.getSpanContext().getSpanId()) && flowNodeId.equals(that.flowNodeId); + } + + @Override + public int hashCode() { + return Objects.hash(span.getSpanContext().getSpanId(), flowNodeId); + } + } + + @Immutable + public static class PlannedNodeIdentifier implements Comparable { + final String nodeName; + + static PlannedNodeIdentifier fromRun(@Nonnull NodeProvisioner.PlannedNode run) { + return new PlannedNodeIdentifier(run.displayName); + } + + public PlannedNodeIdentifier(@Nonnull String nodeName) { + this.nodeName = nodeName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlannedNodeIdentifier that = (PlannedNodeIdentifier) o; + return nodeName.equals(that.nodeName); + } + + @Override + public int hashCode() { + return Objects.hash(nodeName); + } + + @Override + public String toString() { + return "PlannedNodeIdentifier{" + + "nodeName='" + nodeName + '\'' + + '}'; + } + + public String getNodeName() { + return nodeName; + } + + + @Override + public int compareTo(PlannedNodeIdentifier o) { + return ComparisonChain.start().compare(this.nodeName, o.nodeName).result(); + } + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/OtelContextAwareAbstractCloudProvisioningListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/OtelContextAwareAbstractCloudProvisioningListener.java new file mode 100644 index 000000000..8bb16e33b --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/OtelContextAwareAbstractCloudProvisioningListener.java @@ -0,0 +1,113 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer.opentelemetry; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.Label; +import hudson.model.Node; +import hudson.slaves.Cloud; +import hudson.slaves.CloudProvisioningListener; +import hudson.slaves.NodeProvisioner; +import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.computer.OtelTraceService; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; + +import javax.annotation.Nonnull; +import javax.inject.Inject; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * {@link CloudProvisioningListener} that setups the OpenTelemetry {@link io.opentelemetry.context.Context} + * with the current {@link Span}. + */ +public abstract class OtelContextAwareAbstractCloudProvisioningListener extends CloudProvisioningListener { + + private final static Logger LOGGER = Logger.getLogger(OtelContextAwareAbstractCloudProvisioningListener.class.getName()); + + private OtelTraceService otelTraceService; + private Tracer tracer; + private Meter meter; + + @Inject + public final void setOpenTelemetryTracerService(@Nonnull OtelTraceService otelTraceService, @Nonnull OpenTelemetrySdkProvider openTelemetrySdkProvider) { + LOGGER.log(Level.FINE, () -> "setOpenTelemetryTracerService()"); + this.otelTraceService = otelTraceService; + this.tracer = this.otelTraceService.getTracer(); + this.meter = openTelemetrySdkProvider.getMeter(); + } + + @Override + public void onStarted(Cloud cloud, Label label, Collection plannedNodes) { + LOGGER.log(Level.FINE, () -> "onStarted(" + label.getExpression() + ")"); + this._onStarted(cloud, label, plannedNodes); + } + + public void _onStarted(Cloud cloud, Label label, Collection plannedNodes) { + } + + @Override + public void onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { + LOGGER.log(Level.FINE, () -> "onComplete(" + node + ")"); + try (Scope scope = getTraceService().setupContext(plannedNode)) { + this._onComplete(plannedNode, node); + } + } + + public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { + } + + @Override + public void onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { + LOGGER.log(Level.FINE, () -> "onCommit(" + node + ")"); + try (Scope scope = getTraceService().setupContext(plannedNode)) { + this._onCommit(plannedNode, node); + } + } + + public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { + } + + @Override + public void onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { + LOGGER.log(Level.FINE, () -> "onFailure(" + plannedNode + ")"); + try (Scope scope = getTraceService().setupContext(plannedNode)) { + this._onFailure(plannedNode, t); + } + } + + public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { + } + + @Override + public void onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, + @NonNull Throwable t) { + LOGGER.log(Level.FINE, () -> "onRollback(" + node + ")"); + try (Scope scope = getTraceService().setupContext(plannedNode)) { + this._onRollback(plannedNode, node, t); + } + } + + public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, + @NonNull Throwable t) { + } + + public OtelTraceService getTraceService() { + return otelTraceService; + } + + public Tracer getTracer() { + return tracer; + } + + public Meter getMeter() { + return meter; + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/FlowNodeContextKey.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/FlowNodeContextKey.java new file mode 100644 index 000000000..657015cf7 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/FlowNodeContextKey.java @@ -0,0 +1,21 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer.opentelemetry.context; + +import io.opentelemetry.context.ContextKey; +import org.jenkinsci.plugins.workflow.graph.FlowNode; + +import javax.annotation.concurrent.Immutable; + +/** + * See {@code io.opentelemetry.api.trace.SpanContextKey} + */ +@Immutable +public final class FlowNodeContextKey { + public static final ContextKey KEY = ContextKey.named(FlowNodeContextKey.class.getName()); + + private FlowNodeContextKey(){} +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/PlannedNodeContextKey.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/PlannedNodeContextKey.java new file mode 100644 index 000000000..e3aa703be --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/opentelemetry/context/PlannedNodeContextKey.java @@ -0,0 +1,21 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer.opentelemetry.context; + +import hudson.slaves.NodeProvisioner; +import io.opentelemetry.context.ContextKey; + +import javax.annotation.concurrent.Immutable; + +/** + * See {@code io.opentelemetry.api.trace.SpanContextKey} + */ +@Immutable +public final class PlannedNodeContextKey { + public static final ContextKey KEY = ContextKey.named(NodeProvisioner.PlannedNode.class.getName()); + + private PlannedNodeContextKey(){} +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index 2e5bcc352..82a86cd34 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -110,4 +110,9 @@ public final class JenkinsOtelSemanticAttributes { public static final AttributeKey ELASTIC_TRANSACTION_TYPE = AttributeKey.stringKey("type"); + public static final AttributeKey CI_CLOUD_LABEL = AttributeKey.stringKey("ci.cloud.label"); + public static final String CLOUD_SPAN_PHASE_STARTED_NAME = "Phase: Started"; + public static final String CLOUD_SPAN_PHASE_COMMIT_NAME = "Phase: Commit"; + public static final String CLOUD_SPAN_PHASE_FAILURE_NAME = "Phase: Failure"; + public static final String CLOUD_SPAN_PHASE_COMPLETE_NAME = "Phase: Complete"; } From 38c6cbcf0e2bc8a51c1b6525ee04ac910277bed0 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 24 Jun 2021 22:09:04 +0100 Subject: [PATCH 02/34] Noop --- .../plugins/opentelemetry/computer/OtelTraceService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java index 423066b17..7e8bb97aa 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java @@ -130,8 +130,7 @@ public Scope setupContext(@Nonnull Collection plann if (plannedNodes.size() == 1) { return setupContext(plannedNodes.iterator().next()); } - // NOOP span . TODO - return null; + return noOpTracer.spanBuilder("noop-multiple-planned-nodes-span").startSpan().makeCurrent(); } /** @@ -147,8 +146,7 @@ public Scope setupContext(@Nullable NodeProvisioner.PlannedNode plannedNode) { Context.current().with(PlannedNodeContextKey.KEY, plannedNode); return scope; } - // NOOP span . TODO - return null; + return noOpTracer.spanBuilder("noop-existing-planned-nodes-span").startSpan().makeCurrent(); } public Tracer getTracer() { From 79e16b38e75e45c13b043368d177fa65f5d5a883 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 24 Jun 2021 22:09:34 +0100 Subject: [PATCH 03/34] Fix cloud onFailure with https://github.com/jenkinsci/jenkins/pull/4922 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a8109ae8..3c177de7e 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 0.16 -SNAPSHOT - 2.235.5 + 2.249.3 8 jenkinsci/${project.artifactId}-plugin 1.2.0 From 5ec10da6180f651fd32702f251db1abea8dad22d Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 24 Jun 2021 22:45:57 +0100 Subject: [PATCH 04/34] Add specific plugin details and create started span --- ...nkinsOpenTelemetryPluginConfiguration.java | 3 ++- .../computer/MonitoringCloudListener.java | 26 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java index 1c347dc23..fbbb33445 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java @@ -9,6 +9,7 @@ import com.google.common.base.Strings; import hudson.Extension; import hudson.PluginWrapper; +import hudson.model.Descriptor; import hudson.util.FormValidation; import io.jenkins.plugins.opentelemetry.authentication.NoAuthentication; import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; @@ -239,7 +240,7 @@ public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nonnull Ste } @Nonnull - public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable StepDescriptor descriptor) { + public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable Descriptor descriptor) { StepPlugin data = loadedStepsPlugins.get(stepName); if (data!=null) { LOGGER.log(Level.FINEST, " found the plugin for the step '" + stepName + "' - " + data); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index bd453e327..e6dd26823 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -12,6 +12,7 @@ import hudson.model.Node; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; +import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.OtelContextAwareAbstractCloudProvisioningListener; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.context.PlannedNodeContextKey; @@ -60,21 +61,36 @@ public void _onStarted(Cloud cloud, Label label, Collection plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); + try (final Scope rootSpanScope = rootSpan.makeCurrent()) { + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); + + // START started span + Span startSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_STARTED_NAME) + .setParent(Context.current().with(rootSpan)) + .startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin started " + OtelUtils.toDebugString(startSpan)); + + this.getTraceService().putSpan(plannedNode, startSpan); + startSpan.makeCurrent(); + } } @Override From 301f2ae88c24aab07de3151a5673391bc28e9e65 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 14:42:59 +0100 Subject: [PATCH 05/34] Revert "Add specific plugin details and create started span" This reverts commit 5ec10da6180f651fd32702f251db1abea8dad22d. --- ...nkinsOpenTelemetryPluginConfiguration.java | 3 +-- .../computer/MonitoringCloudListener.java | 26 ++++--------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java index fbbb33445..1c347dc23 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java @@ -9,7 +9,6 @@ import com.google.common.base.Strings; import hudson.Extension; import hudson.PluginWrapper; -import hudson.model.Descriptor; import hudson.util.FormValidation; import io.jenkins.plugins.opentelemetry.authentication.NoAuthentication; import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; @@ -240,7 +239,7 @@ public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nonnull Ste } @Nonnull - public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable Descriptor descriptor) { + public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable StepDescriptor descriptor) { StepPlugin data = loadedStepsPlugins.get(stepName); if (data!=null) { LOGGER.log(Level.FINEST, " found the plugin for the step '" + stepName + "' - " + data); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index e6dd26823..bd453e327 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -12,7 +12,6 @@ import hudson.model.Node; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; -import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.OtelContextAwareAbstractCloudProvisioningListener; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.context.PlannedNodeContextKey; @@ -61,36 +60,21 @@ public void _onStarted(Cloud cloud, Label label, Collection plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); - - // START started span - Span startSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_STARTED_NAME) - .setParent(Context.current().with(rootSpan)) - .startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin started " + OtelUtils.toDebugString(startSpan)); - - this.getTraceService().putSpan(plannedNode, startSpan); - startSpan.makeCurrent(); - } + rootSpan.makeCurrent(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); } @Override From df3556ef4797c1b4937cf946aab7b0a4c75a8aef Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 15:21:44 +0100 Subject: [PATCH 06/34] Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent' --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3c177de7e..3f2c760cd 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,8 @@ io.jenkins.tools.bom - bom-2.235.x - 29 + bom-2.249.x + 887.vae9c8ac09ff7 import pom From baa08c845173420cf75fb06291103b1bad4578d1 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 15:56:33 +0100 Subject: [PATCH 07/34] Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent' --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3f2c760cd..7a7f41a2e 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 0.16 -SNAPSHOT - 2.249.3 + 2.263.1 8 jenkinsci/${project.artifactId}-plugin 1.2.0 @@ -32,7 +32,7 @@ io.jenkins.tools.bom - bom-2.249.x + bom-2.263.x 887.vae9c8ac09ff7 import pom From 091504e8920c75af59219844f0fe5fa8c9a2f99e Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 16:30:06 +0100 Subject: [PATCH 08/34] Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent' --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a7f41a2e..1d4fbf98c 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 0.16 -SNAPSHOT - 2.263.1 + 2.277.1 8 jenkinsci/${project.artifactId}-plugin 1.2.0 From 3fee58104c7ac046b004e125e3b1f7cd4ae29b60 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 16:53:05 +0100 Subject: [PATCH 09/34] Cloud Root naming strategy --- ...nkinsOpenTelemetryPluginConfiguration.java | 12 +++++ .../computer/CloudSpanNamingStrategy.java | 44 +++++++++++++++++++ .../computer/MonitoringCloudListener.java | 13 +++++- .../JenkinsOtelSemanticAttributes.java | 1 + .../computer/CloudSpanNamingStrategyTest.java | 35 +++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategy.java create mode 100644 src/test/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategyTest.java diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java index 1c347dc23..8fc231a10 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java @@ -13,6 +13,7 @@ import io.jenkins.plugins.opentelemetry.authentication.NoAuthentication; import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; import io.jenkins.plugins.opentelemetry.authentication.OtlpAuthentication; +import io.jenkins.plugins.opentelemetry.computer.CloudSpanNamingStrategy; import io.jenkins.plugins.opentelemetry.job.SpanNamingStrategy; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import jenkins.model.GlobalConfiguration; @@ -66,6 +67,8 @@ public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration private transient SpanNamingStrategy spanNamingStrategy; + private transient CloudSpanNamingStrategy cloudSpanNamingStrategy; + private transient ConcurrentMap loadedStepsPlugins = new ConcurrentHashMap<>(); private String serviceName; @@ -219,6 +222,15 @@ public SpanNamingStrategy getSpanNamingStrategy() { return spanNamingStrategy; } + @Inject + public void setCloudSpanNamingStrategy(CloudSpanNamingStrategy cloudSpanNamingStrategy) { + this.cloudSpanNamingStrategy = cloudSpanNamingStrategy; + } + + public CloudSpanNamingStrategy getCloudSpanNamingStrategy() { + return cloudSpanNamingStrategy; + } + @Nonnull public ConcurrentMap getLoadedStepsPlugins() { return loadedStepsPlugins; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategy.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategy.java new file mode 100644 index 000000000..a2b861776 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import com.google.common.annotations.VisibleForTesting; +import hudson.Extension; +import hudson.slaves.NodeProvisioner; +import jenkins.YesNoMaybe; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +import javax.annotation.Nonnull; + +/** + * Use same root span name for all pull cloud labels + */ +@Extension(dynamicLoadable = YesNoMaybe.YES) +@Symbol("cloudSpanNamingStrategy") +public class CloudSpanNamingStrategy { + + @DataBoundConstructor + public CloudSpanNamingStrategy() { + } + + @Nonnull + public String getRootSpanName(@Nonnull NodeProvisioner.PlannedNode plannedNode) { + return getNodeRootSpanName(plannedNode.displayName); + } + + @VisibleForTesting + @Nonnull + protected String getNodeRootSpanName(@Nonnull String displayName) { + // format: - + // e.g. "obs11-ubuntu-18-linux-beyyg2" + // remove last -<.*> + if (displayName.contains("-")) { + return displayName.substring(0, displayName.lastIndexOf("-")) + "-{id}"; + } + return displayName; + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index bd453e327..1096a015f 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -6,6 +6,7 @@ package io.jenkins.plugins.opentelemetry.computer; import com.google.errorprone.annotations.MustBeClosed; +import com.google.inject.Inject; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.model.Label; @@ -24,10 +25,12 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import jenkins.model.Jenkins; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import java.util.Collection; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -40,6 +43,8 @@ public class MonitoringCloudListener extends OtelContextAwareAbstractCloudProvis private LongCounter failureCloudCounter; private LongCounter totalCloudCount; + private CloudSpanNamingStrategy cloudSpanNamingStrategy; + @PostConstruct public void postConstruct() { failureCloudCounter = getMeter().longCounterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_FAILURE) @@ -60,13 +65,14 @@ public void _onStarted(Cloud cloud, Label label, Collection CI_CLOUD_LABEL = AttributeKey.stringKey("ci.cloud.label"); + public static final AttributeKey CI_CLOUD_NAME = AttributeKey.stringKey("ci.cloud.name"); public static final String CLOUD_SPAN_PHASE_STARTED_NAME = "Phase: Started"; public static final String CLOUD_SPAN_PHASE_COMMIT_NAME = "Phase: Commit"; public static final String CLOUD_SPAN_PHASE_FAILURE_NAME = "Phase: Failure"; diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategyTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategyTest.java new file mode 100644 index 000000000..1960041fd --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/computer/CloudSpanNamingStrategyTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import org.junit.Assert; +import org.junit.Test; + +public class CloudSpanNamingStrategyTest { + + CloudSpanNamingStrategy spanNamingStrategy = new CloudSpanNamingStrategy(); + + /** + * Unique node + */ + @Test + public void test_default_name() { + verifyNodeRootSpanName("foo", "foo"); + } + + /** + * Dynamic node + */ + @Test + public void test_with_dynamic_node() { + verifyNodeRootSpanName("obs11-ubuntu-18-linux-beyyg2", "obs11-ubuntu-18-linux-{id}"); + } + + private void verifyNodeRootSpanName(String displayName, String expected) { + final String actual = spanNamingStrategy.getNodeRootSpanName(displayName); + Assert.assertEquals(expected, actual); + } +} \ No newline at end of file From 59c294fa90f32dfa2f85a98a5c4941a0b9ce72b1 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 17:07:09 +0100 Subject: [PATCH 10/34] Collect cloud plugin details --- .../JenkinsOpenTelemetryPluginConfiguration.java | 6 +++--- .../computer/MonitoringCloudListener.java | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java index 8fc231a10..dd040f6dd 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java @@ -9,10 +9,11 @@ import com.google.common.base.Strings; import hudson.Extension; import hudson.PluginWrapper; +import hudson.model.Descriptor; import hudson.util.FormValidation; import io.jenkins.plugins.opentelemetry.authentication.NoAuthentication; -import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; import io.jenkins.plugins.opentelemetry.authentication.OtlpAuthentication; +import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; import io.jenkins.plugins.opentelemetry.computer.CloudSpanNamingStrategy; import io.jenkins.plugins.opentelemetry.job.SpanNamingStrategy; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; @@ -22,7 +23,6 @@ import org.jenkinsci.Symbol; import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode; import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode; -import org.jenkinsci.plugins.workflow.steps.StepDescriptor; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; @@ -251,7 +251,7 @@ public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nonnull Ste } @Nonnull - public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable StepDescriptor descriptor) { + public StepPlugin findStepPluginOrDefault(@Nonnull String stepName, @Nullable Descriptor descriptor) { StepPlugin data = loadedStepsPlugins.get(stepName); if (data!=null) { LOGGER.log(Level.FINEST, " found the plugin for the step '" + stepName + "' - " + data); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 1096a015f..124ce1303 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -13,6 +13,7 @@ import hudson.model.Node; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; +import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.OtelContextAwareAbstractCloudProvisioningListener; import io.jenkins.plugins.opentelemetry.computer.opentelemetry.context.PlannedNodeContextKey; @@ -25,12 +26,10 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import jenkins.model.Jenkins; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import java.util.Collection; -import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -66,6 +65,7 @@ public void _onStarted(Cloud cloud, Label label, Collection Date: Sun, 4 Jul 2021 18:21:03 +0100 Subject: [PATCH 11/34] Add Google Cloud attributes --- pom.xml | 6 ++ .../opentelemetry/computer/CloudHandler.java | 20 +++++ .../computer/GoogleCloudHandler.java | 73 +++++++++++++++++++ .../computer/MonitoringCloudListener.java | 11 +++ .../JenkinsOtelSemanticAttributes.java | 10 +++ .../computer/GoogleCloudHandlerTest.java | 59 +++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java create mode 100644 src/test/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandlerTest.java diff --git a/pom.xml b/pom.xml index 1d4fbf98c..567818389 100644 --- a/pom.xml +++ b/pom.xml @@ -217,6 +217,12 @@ git test + + org.jenkins-ci.plugins + google-compute-engine + 4.3.8 + true + org.jenkins-ci.plugins github-branch-source diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java new file mode 100644 index 000000000..d804ffca1 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import hudson.model.Label; +import hudson.slaves.Cloud; +import io.opentelemetry.api.trace.SpanBuilder; + +import javax.annotation.Nonnull; + +public interface CloudHandler { + + boolean canAddAttributes(@Nonnull Cloud cloud); + + @Nonnull + void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception; +} \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java new file mode 100644 index 000000000..8dcddcc34 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import com.google.jenkins.plugins.computeengine.ComputeEngineCloud; +import com.google.jenkins.plugins.computeengine.ComputeEngineInstance; +import hudson.Extension; +import hudson.model.Label; +import hudson.model.Node; +import hudson.slaves.Cloud; +import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.trace.SpanBuilder; +import jenkins.YesNoMaybe; + +import javax.annotation.Nonnull; +import java.util.Optional; + +/** + * Customization of spans for google cloud attributes. + */ +@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) +public class GoogleCloudHandler implements CloudHandler { + + @Nonnull + @Override + public boolean canAddAttributes(@Nonnull Cloud cloud) { + return cloud.getDescriptor() instanceof ComputeEngineCloud.GoogleCloudDescriptor; + } + + @Nonnull + @Override + public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { + ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud; + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, "gcp") + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ((ComputeEngineCloud) cloud).getProjectId()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, "gcp_compute_engine"); + if (label.getNodes().size() == 1) { + Optional node = label.getNodes().stream().findFirst(); + if (node.isPresent()) { + ComputeEngineInstance instance = (ComputeEngineInstance) node.get(); + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformRegion(instance.getZone())) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(instance.getZone())); + } + } + } + + protected String transformRegion(String region) { + // f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a" + return transform(region); + } + + protected String transformMachineType(String machineType) { + // f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a/machineTypes/n2-standard-2" + return transform(machineType); + } + + protected String transformZone(String zone) { + // f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a" + return transform(zone); + } + + private String transform(String value) { + if (value.contains("/")) { + return value.substring(value.lastIndexOf("/") + 1, value.length()); + } + return value; + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 124ce1303..b8b252963 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -9,6 +9,7 @@ import com.google.inject.Inject; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.ExtensionList; import hudson.model.Label; import hudson.model.Node; import hudson.slaves.Cloud; @@ -78,6 +79,16 @@ public void _onStarted(Cloud cloud, Label label, Collection CI_CLOUD_LABEL = AttributeKey.stringKey("ci.cloud.label"); public static final AttributeKey CI_CLOUD_NAME = AttributeKey.stringKey("ci.cloud.name"); + /** + * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/cloud.md + */ + public static final AttributeKey CLOUD_ACCOUNT_ID = AttributeKey.stringKey("cloud.account.id"); + public static final AttributeKey CLOUD_PROVIDER = AttributeKey.stringKey("cloud.provider"); + public static final AttributeKey CLOUD_PROJECT_ID = AttributeKey.stringKey("cloud.project.id"); + public static final AttributeKey CLOUD_MACHINE_TYPE = AttributeKey.stringKey("cloud.machine.type"); + public static final AttributeKey CLOUD_REGION = AttributeKey.stringKey("cloud.region"); + public static final AttributeKey CLOUD_ZONE = AttributeKey.stringKey("cloud.availability_zone"); + public static final AttributeKey CLOUD_PLATFORM = AttributeKey.stringKey("cloud.platform"); public static final String CLOUD_SPAN_PHASE_STARTED_NAME = "Phase: Started"; public static final String CLOUD_SPAN_PHASE_COMMIT_NAME = "Phase: Commit"; public static final String CLOUD_SPAN_PHASE_FAILURE_NAME = "Phase: Failure"; diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandlerTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandlerTest.java new file mode 100644 index 000000000..01e9906bd --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandlerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import org.junit.Assert; +import org.junit.Test; + +public class GoogleCloudHandlerTest { + + GoogleCloudHandler handler = new GoogleCloudHandler(); + + @Test + public void test_default_region() { + verifyRegion("foo", "foo"); + } + + @Test + public void test_region() { + verifyRegion("https://www.googleapis.com/compute/v1/projects/project-name/regions/us-central1", "us-central1"); + } + + @Test + public void test_default_zone() { + verifyZone("foo", "foo"); + } + + @Test + public void test_zone() { + verifyZone("https://www.googleapis.com/compute/v1/projects/elastic-observability/zones/us-central1-a", "us-central1-a"); + } + + @Test + public void test_default_machineType() { + verifyMachineType("foo", "foo"); + } + + @Test + public void test_machineType() { + verifyMachineType("https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a/machineTypes/n2-standard-2", "n2-standard-2"); + } + + private void verifyMachineType(String machineType, String expected) { + final String actual = handler.transformMachineType(machineType); + Assert.assertEquals(expected, actual); + } + + private void verifyRegion(String region, String expected) { + final String actual = handler.transformRegion(region); + Assert.assertEquals(expected, actual); + } + + private void verifyZone(String zone, String expected) { + final String actual = handler.transformZone(zone); + Assert.assertEquals(expected, actual); + } +} \ No newline at end of file From 4a22a504bb8d7c8a5588f45eeb29f006eb51c6c5 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:29:50 +0100 Subject: [PATCH 12/34] Add InstanceConfiguration google configuration attributes --- .../opentelemetry/computer/GoogleCloudHandler.java | 11 ++++++++++- .../semconv/JenkinsOtelSemanticAttributes.java | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 8dcddcc34..4d0204dd2 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -7,6 +7,7 @@ import com.google.jenkins.plugins.computeengine.ComputeEngineCloud; import com.google.jenkins.plugins.computeengine.ComputeEngineInstance; +import com.google.jenkins.plugins.computeengine.InstanceConfiguration; import hudson.Extension; import hudson.model.Label; import hudson.model.Node; @@ -43,8 +44,16 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn if (node.isPresent()) { ComputeEngineInstance instance = (ComputeEngineInstance) node.get(); rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformRegion(instance.getZone())) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(instance.getZone())); + + InstanceConfiguration configuration = ceCloud.getInstanceConfigurationByDescription(instance.getNodeDescription()); + if (configuration != null) { + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ACCOUNT_ID, configuration.getServiceAccountEmail()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_RUN_AS_USER, configuration.getRunAsUser()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, configuration.getZone()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, configuration.getMachineType()); + } } } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index f80a575f6..89b706170 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -120,6 +120,7 @@ public final class JenkinsOtelSemanticAttributes { public static final AttributeKey CLOUD_PROJECT_ID = AttributeKey.stringKey("cloud.project.id"); public static final AttributeKey CLOUD_MACHINE_TYPE = AttributeKey.stringKey("cloud.machine.type"); public static final AttributeKey CLOUD_REGION = AttributeKey.stringKey("cloud.region"); + public static final AttributeKey CLOUD_RUN_AS_USER = AttributeKey.stringKey("cloud.runAsUser"); public static final AttributeKey CLOUD_ZONE = AttributeKey.stringKey("cloud.availability_zone"); public static final AttributeKey CLOUD_PLATFORM = AttributeKey.stringKey("cloud.platform"); public static final String CLOUD_SPAN_PHASE_STARTED_NAME = "Phase: Started"; From e33cfc252937898ad05ffd27260a6f5066eeef9b Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:38:44 +0100 Subject: [PATCH 13/34] Add cloud name attribute --- .../plugins/opentelemetry/computer/GoogleCloudHandler.java | 5 +++-- .../opentelemetry/semconv/JenkinsOtelSemanticAttributes.java | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 4d0204dd2..0cc48e36a 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -36,9 +36,10 @@ public boolean canAddAttributes(@Nonnull Cloud cloud) { public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud; rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, "gcp") + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, "gcp_compute_engine") .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ((ComputeEngineCloud) cloud).getProjectId()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, "gcp_compute_engine"); + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, "gcp"); if (label.getNodes().size() == 1) { Optional node = label.getNodes().stream().findFirst(); if (node.isPresent()) { diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index 89b706170..baaf7d707 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -117,6 +117,7 @@ public final class JenkinsOtelSemanticAttributes { */ public static final AttributeKey CLOUD_ACCOUNT_ID = AttributeKey.stringKey("cloud.account.id"); public static final AttributeKey CLOUD_PROVIDER = AttributeKey.stringKey("cloud.provider"); + public static final AttributeKey CLOUD_NAME = AttributeKey.stringKey("cloud.name"); public static final AttributeKey CLOUD_PROJECT_ID = AttributeKey.stringKey("cloud.project.id"); public static final AttributeKey CLOUD_MACHINE_TYPE = AttributeKey.stringKey("cloud.machine.type"); public static final AttributeKey CLOUD_REGION = AttributeKey.stringKey("cloud.region"); From 57ef9072d3b3a1421dc53ee285a2556f95ccf8e7 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:41:50 +0100 Subject: [PATCH 14/34] Refactor and use transform functions --- .../opentelemetry/computer/GoogleCloudHandler.java | 12 +++++------- .../semconv/JenkinsOtelSemanticAttributes.java | 3 +++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 0cc48e36a..d9fbc3486 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -37,23 +37,21 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud; rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, "gcp_compute_engine") + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ((ComputeEngineCloud) cloud).getProjectId()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, "gcp"); + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER); if (label.getNodes().size() == 1) { Optional node = label.getNodes().stream().findFirst(); if (node.isPresent()) { ComputeEngineInstance instance = (ComputeEngineInstance) node.get(); - rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(instance.getZone())); - InstanceConfiguration configuration = ceCloud.getInstanceConfigurationByDescription(instance.getNodeDescription()); if (configuration != null) { rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ACCOUNT_ID, configuration.getServiceAccountEmail()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformMachineType(configuration.getMachineType())) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(configuration.getRegion())) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_RUN_AS_USER, configuration.getRunAsUser()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, configuration.getZone()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, configuration.getMachineType()); + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, transformZone(configuration.getZone())); } } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index baaf7d707..488d6e5bb 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -128,4 +128,7 @@ public final class JenkinsOtelSemanticAttributes { public static final String CLOUD_SPAN_PHASE_COMMIT_NAME = "Phase: Commit"; public static final String CLOUD_SPAN_PHASE_FAILURE_NAME = "Phase: Failure"; public static final String CLOUD_SPAN_PHASE_COMPLETE_NAME = "Phase: Complete"; + + public static final String GOOGLE_CLOUD_PROVIDER = "gcp"; + public static final String GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM = "gcp_compute_engine"; } From c89c6a5799ab04503d7639a14c1e32d6e1480538 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:43:28 +0100 Subject: [PATCH 15/34] Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent'" This reverts commit 091504e8920c75af59219844f0fe5fa8c9a2f99e. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 567818389..69cea04e9 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 0.16 -SNAPSHOT - 2.277.1 + 2.263.1 8 jenkinsci/${project.artifactId}-plugin 1.2.0 From bc779893825b3e709349693dc5ad3a667a3e196c Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:44:31 +0100 Subject: [PATCH 16/34] Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent'" This reverts commit baa08c845173420cf75fb06291103b1bad4578d1. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 69cea04e9..9aa8de57e 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 0.16 -SNAPSHOT - 2.263.1 + 2.249.3 8 jenkinsci/${project.artifactId}-plugin 1.2.0 @@ -32,7 +32,7 @@ io.jenkins.tools.bom - bom-2.263.x + bom-2.249.x 887.vae9c8ac09ff7 import pom From 4a72a04f073032eafa58af7669e073917a7bbdfc Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:44:40 +0100 Subject: [PATCH 17/34] Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lambda: Unexpected exception encountered while provisioning agent'" This reverts commit df3556ef4797c1b4937cf946aab7b0a4c75a8aef. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9aa8de57e..04e9dfda5 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,8 @@ io.jenkins.tools.bom - bom-2.249.x - 887.vae9c8ac09ff7 + bom-2.235.x + 29 import pom From 3de87818e22320ecbb8fb891cacbc766d865f74d Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Sun, 4 Jul 2021 18:59:41 +0100 Subject: [PATCH 18/34] Fix dependencies Require upper bound dependencies error for commons-io:commons-io:2.6 paths to dependency are: +-io.jenkins.plugins:opentelemetry:0.16-SNAPSHOT +-org.jenkins-ci.plugins:google-compute-engine:4.3.8 +-commons-io:commons-io:2.6 --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 55ec1b0ea..5803b3cc6 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ pom import + + + commons-io + commons-io + 2.8.0 + From d28294a0c63f448d36e7c587e70fdbcd452e8130 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 8 Jul 2021 22:20:41 +0100 Subject: [PATCH 19/34] Fix javadoc --- .../plugins/opentelemetry/computer/OtelTraceService.java | 4 ++-- .../opentelemetry/semconv/JenkinsOtelSemanticAttributes.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java index 7e8bb97aa..86881e3aa 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/OtelTraceService.java @@ -121,7 +121,7 @@ public void setJenkinsOtelPlugin(@Nonnull OpenTelemetrySdkProvider openTelemetry } /** - * @param plannedNodes + * @param plannedNodes the planned nodes * @return If no span has been found (ie Jenkins restart), then the scope of a NoOp span is returned */ @Nonnull @@ -134,7 +134,7 @@ public Scope setupContext(@Nonnull Collection plann } /** - * @param plannedNode + * @param plannedNode the planned node * @return If no span has been found (ie Jenkins restart), then the scope of a NoOp span is returned */ @Nonnull diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index b4da5661f..2625eed8d 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -117,7 +117,7 @@ public final class JenkinsOtelSemanticAttributes { public static final AttributeKey CI_CLOUD_LABEL = AttributeKey.stringKey("ci.cloud.label"); public static final AttributeKey CI_CLOUD_NAME = AttributeKey.stringKey("ci.cloud.name"); /** - * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/cloud.md + * @see cloud semantic conventions */ public static final AttributeKey CLOUD_ACCOUNT_ID = AttributeKey.stringKey("cloud.account.id"); public static final AttributeKey CLOUD_PROVIDER = AttributeKey.stringKey("cloud.provider"); From 57ff1a63be8d2b8fb10768904aeefe122b695f0e Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 11:45:41 +0100 Subject: [PATCH 20/34] Use object instead a cast --- .../plugins/opentelemetry/computer/GoogleCloudHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index d9fbc3486..36b58633a 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -38,7 +38,7 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ((ComputeEngineCloud) cloud).getProjectId()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ceCloud.getProjectId()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER); if (label.getNodes().size() == 1) { Optional node = label.getNodes().stream().findFirst(); From 4f3e52b04590bd5d7a882253f41fa6242e4ecce1 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 11:49:01 +0100 Subject: [PATCH 21/34] Add k8s span attributes for the cloud transactions --- pom.xml | 17 ++++ .../computer/KubernetesCloudHandler.java | 78 +++++++++++++++++++ .../JenkinsOtelSemanticAttributes.java | 15 ++++ .../computer/KubernetesCloudHandlerTest.java | 44 +++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java create mode 100644 src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java diff --git a/pom.xml b/pom.xml index 1b15433c9..44042e184 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,17 @@ commons-io 2.8.0 + + + com.squareup.okhttp3 + okhttp + 3.14.9 + + + com.squareup.okhttp3 + logging-interceptor + 3.14.9 + @@ -228,6 +239,12 @@ 4.3.8 true + + org.csanchez.jenkins.plugins + kubernetes + 1.27.7 + true + org.jenkins-ci.plugins github-branch-source diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java new file mode 100644 index 000000000..daf31355f --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java @@ -0,0 +1,78 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; +import hudson.Extension; +import hudson.model.Label; +import hudson.model.Node; +import hudson.slaves.Cloud; +import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.trace.SpanBuilder; +import jenkins.YesNoMaybe; +import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; + +import javax.annotation.Nonnull; +import java.util.Optional; + +/** + * Customization of spans for kubernetes cloud attributes. + */ +@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) +public class KubernetesCloudHandler implements CloudHandler { + + @Nonnull + @Override + public boolean canAddAttributes(@Nonnull Cloud cloud) { + return cloud.getDescriptor() instanceof KubernetesCloud.DescriptorImpl; + } + + @Nonnull + @Override + public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { + KubernetesCloud k8sCloud = (KubernetesCloud) cloud; + + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, k8sCloud.getDisplayName()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, k8sCloud.getDisplayName()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER) + .setAttribute(JenkinsOtelSemanticAttributes.K8S_NAMESPACE_NAME, k8sCloud.getNamespace()); + + if (label.getNodes().size() == 1) { + Optional node = label.getNodes().stream().findFirst(); + if (node.isPresent()) { + + KubernetesSlave instance = (KubernetesSlave) node.get(); + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); + + PodTemplate podTemplate = instance.getTemplateOrNull(); + if (podTemplate != null) { + // TODO: add resourceLimit attributes to detect misbehaviours? + rootSpanBuilder + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); + } + } + } + } + + protected String getImageName(String image) { + if (image.contains(":")) { + return image.substring(0, image.lastIndexOf(":")); + } + return image; + } + + protected String getImageTag(String image) { + if (image.contains(":")) { + return image.substring(image.lastIndexOf(":") + 1); + } + return "latest"; + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java index 2625eed8d..c3468421e 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java @@ -135,4 +135,19 @@ public final class JenkinsOtelSemanticAttributes { public static final String GOOGLE_CLOUD_PROVIDER = "gcp"; public static final String GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM = "gcp_compute_engine"; + + /** + * @see kubernetes semantic conventions + */ + public static final AttributeKey K8S_NAMESPACE_NAME = AttributeKey.stringKey("k8s.namespace.name"); + public static final AttributeKey K8S_POD_NAME = AttributeKey.stringKey("k8s.pod.name"); + + /** + * @see container semantic conventions + */ + public static final AttributeKey CONTAINER_IMAGE_NAME = AttributeKey.stringKey("container.image.name"); + public static final AttributeKey CONTAINER_IMAGE_TAG = AttributeKey.stringKey("container.image.tag"); + public static final AttributeKey CONTAINER_NAME = AttributeKey.stringKey("container.name"); + + public static final String K8S_CLOUD_PROVIDER = "k8s"; } diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java new file mode 100644 index 000000000..4091b05aa --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import org.junit.Assert; +import org.junit.Test; + +public class KubernetesCloudHandlerTest { + + KubernetesCloudHandler handler = new KubernetesCloudHandler(); + + @Test + public void test_default_image() { + verifyImage("foo", "foo"); + } + + @Test + public void test_image_with_tag() { + verifyImage("org/foo:1.15.10", "org/foo"); + } + + @Test + public void test_default_image_tag() { + verifyImageTag("foo", "latest"); + } + + @Test + public void test_imagetag_with_tag() { + verifyImageTag("org/foo:1.15.10", "1.15.10"); + } + + private void verifyImage(String image, String expected) { + final String actual = handler.getImageName(image); + Assert.assertEquals(expected, actual); + } + + private void verifyImageTag(String image, String expected) { + final String actual = handler.getImageTag(image); + Assert.assertEquals(expected, actual); + } +} \ No newline at end of file From d06274db3d1996e70c8910d7d5ee8639b19e3afe Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 15:43:35 +0100 Subject: [PATCH 22/34] Add more logs and _onRollback logic --- .../computer/KubernetesCloudHandler.java | 11 ++++++++++- .../computer/MonitoringCloudListener.java | 15 +++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java index daf31355f..51f373370 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java @@ -18,6 +18,8 @@ import javax.annotation.Nonnull; import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Customization of spans for kubernetes cloud attributes. @@ -25,6 +27,8 @@ @Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) public class KubernetesCloudHandler implements CloudHandler { + private final static Logger LOGGER = Logger.getLogger(KubernetesCloudHandler.class.getName()); + @Nonnull @Override public boolean canAddAttributes(@Nonnull Cloud cloud) { @@ -45,7 +49,6 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn if (label.getNodes().size() == 1) { Optional node = label.getNodes().stream().findFirst(); if (node.isPresent()) { - KubernetesSlave instance = (KubernetesSlave) node.get(); rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); @@ -57,8 +60,14 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); + } else { + LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node."); } + } else { + LOGGER.log(Level.FINE, () -> "There is no present node."); } + } else { + LOGGER.log(Level.FINE, () -> "There are more nodes assigned for the same label (total: " + label.getNodes().size() + ")"); } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index b8b252963..d8f599894 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -61,6 +61,7 @@ public void postConstruct() { public void _onStarted(Cloud cloud, Label label, Collection plannedNodes) { LOGGER.log(Level.FINE, () -> "_onStarted(" + label + ")"); if (plannedNodes.size() != 1) { + LOGGER.log(Level.FINE, "There are more plannedNodes, let's skip it"); return; } NodeProvisioner.PlannedNode plannedNode = plannedNodes.iterator().next(); @@ -103,7 +104,7 @@ public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull LOGGER.log(Level.FINE, () -> "_onCommit(" + node + ")"); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span runSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(runSpan)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onCommit " + OtelUtils.toDebugString(runSpan)); runSpan.makeCurrent(); this.getTraceService().putSpan(plannedNode, runSpan); } @@ -118,7 +119,7 @@ public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { span.recordException(t); span.setStatus(StatusCode.ERROR, t.getMessage()); span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(span)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onFailure " + OtelUtils.toDebugString(span)); } } @@ -127,6 +128,13 @@ public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNu @NonNull Throwable t){ LOGGER.log(Level.FINE, () -> "_onRollback(" + plannedNode + ")"); failureCloudCounter.add(1); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onRollback " + OtelUtils.toDebugString(span)); + } } @Override @@ -137,7 +145,7 @@ public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); span.setStatus(StatusCode.OK); span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(span)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); } } @@ -148,7 +156,6 @@ protected Scope endCloudPhaseSpan(@NonNull NodeProvisioner.PlannedNode plannedNo cloudPhaseSpan.end(); LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end " + OtelUtils.toDebugString(cloudPhaseSpan)); - //this.getTraceService().removeJobPhaseSpan(run, pipelinePhaseSpan); Span newCurrentSpan = this.getTraceService().getSpan(plannedNode); Scope newScope = newCurrentSpan.makeCurrent(); Context.current().with(PlannedNodeContextKey.KEY, plannedNode); From bc2297a77b7f22f361ee89b3f3ead2aecf7a7701 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 17:36:34 +0100 Subject: [PATCH 23/34] Support onStarted with a list of plannedNodes and add more debug --- .../computer/MonitoringCloudListener.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index d8f599894..9db6004bd 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -60,11 +60,15 @@ public void postConstruct() { @Override public void _onStarted(Cloud cloud, Label label, Collection plannedNodes) { LOGGER.log(Level.FINE, () -> "_onStarted(" + label + ")"); - if (plannedNodes.size() != 1) { - LOGGER.log(Level.FINE, "There are more plannedNodes, let's skip it"); - return; + for (NodeProvisioner.PlannedNode plannedNode : plannedNodes) { + _onStarted(cloud, label, plannedNode); } - NodeProvisioner.PlannedNode plannedNode = plannedNodes.iterator().next(); + } + + public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode plannedNode) { + LOGGER.log(Level.FINE, () -> "_onStarted(label: " + label + ")"); + LOGGER.log(Level.FINEST, () -> "_onStarted(label.nodes: " + label.getNodes().toString() + ")"); + LOGGER.log(Level.FINEST, () -> "_onStarted(plannedNode: " + plannedNode.toString() + ")"); String rootSpanName = this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); JenkinsOpenTelemetryPluginConfiguration.StepPlugin stepPlugin = JenkinsOpenTelemetryPluginConfiguration.get().findStepPluginOrDefault("cloud", cloud.getDescriptor()); @@ -93,7 +97,6 @@ public void _onStarted(Cloud cloud, Label label, Collection plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); @@ -101,45 +104,48 @@ public void _onStarted(Cloud cloud, Label label, Collection "_onCommit(" + node + ")"); + LOGGER.log(Level.FINE, () -> "_onCommit(node: " + node + ")"); + LOGGER.log(Level.FINEST, () -> "_onCommit(plannedNode: " + plannedNode.toString() + ")"); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { - Span runSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onCommit " + OtelUtils.toDebugString(runSpan)); - runSpan.makeCurrent(); - this.getTraceService().putSpan(plannedNode, runSpan); + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); + span.setStatus(StatusCode.OK); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); } } @Override public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { - LOGGER.log(Level.FINE, () -> "_onFailure(" + plannedNode + ")"); + LOGGER.log(Level.FINE, () -> "_onFailure(plannedNode: " + plannedNode + ")"); failureCloudCounter.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); span.recordException(t); span.setStatus(StatusCode.ERROR, t.getMessage()); span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onFailure " + OtelUtils.toDebugString(span)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onFailure " + OtelUtils.toDebugString(span)); } } @Override public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, @NonNull Throwable t){ - LOGGER.log(Level.FINE, () -> "_onRollback(" + plannedNode + ")"); + LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ")"); + LOGGER.log(Level.FINEST, () -> "_onRollback(node: " + node.toString() + ")"); failureCloudCounter.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); span.recordException(t); span.setStatus(StatusCode.ERROR, t.getMessage()); span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onRollback " + OtelUtils.toDebugString(span)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onRollback " + OtelUtils.toDebugString(span)); } } @Override public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { - LOGGER.log(Level.FINE, () -> "_onComplete(" + plannedNode + ")"); + LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ")"); + LOGGER.log(Level.FINEST, () -> "_onComplete(node: " + node.toString() + ")"); totalCloudCount.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); From 81104e51ce0dc8357688456cb3e4806b71ec3b2a Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 18:24:35 +0100 Subject: [PATCH 24/34] Enrich containers/pods with a new Handler I was not able to find the way to access to those details in the _onStarted method --- .../opentelemetry/computer/CloudHandler.java | 2 +- .../computer/CloudNodeHandler.java | 18 ++++++ .../computer/GoogleCloudHandler.java | 2 - .../computer/KubernetesCloudHandler.java | 50 +-------------- .../computer/KubernetesCloudNodeHandler.java | 63 +++++++++++++++++++ .../computer/MonitoringCloudListener.java | 17 +++++ ...va => KubernetesCloudNodeHandlerTest.java} | 4 +- 7 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java create mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java rename src/test/java/io/jenkins/plugins/opentelemetry/computer/{KubernetesCloudHandlerTest.java => KubernetesCloudNodeHandlerTest.java} (88%) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java index d804ffca1..36301efea 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java @@ -15,6 +15,6 @@ public interface CloudHandler { boolean canAddAttributes(@Nonnull Cloud cloud); - @Nonnull void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception; + } \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java new file mode 100644 index 000000000..13921c209 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java @@ -0,0 +1,18 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import hudson.model.Node; +import io.opentelemetry.api.trace.Span; + +import javax.annotation.Nonnull; + +public interface CloudNodeHandler { + + boolean canAddAttributes(@Nonnull Node node); + + void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span rootSpanBuilder) throws Exception; +} \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 36b58633a..5a2142bfa 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -25,13 +25,11 @@ @Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) public class GoogleCloudHandler implements CloudHandler { - @Nonnull @Override public boolean canAddAttributes(@Nonnull Cloud cloud) { return cloud.getDescriptor() instanceof ComputeEngineCloud.GoogleCloudDescriptor; } - @Nonnull @Override public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java index 51f373370..d6f06815c 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java @@ -5,21 +5,15 @@ package io.jenkins.plugins.opentelemetry.computer; -import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; -import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; import hudson.Extension; import hudson.model.Label; -import hudson.model.Node; import hudson.slaves.Cloud; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.opentelemetry.api.trace.SpanBuilder; import jenkins.YesNoMaybe; -import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; import javax.annotation.Nonnull; -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Customization of spans for kubernetes cloud attributes. @@ -27,61 +21,19 @@ @Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) public class KubernetesCloudHandler implements CloudHandler { - private final static Logger LOGGER = Logger.getLogger(KubernetesCloudHandler.class.getName()); - @Nonnull @Override public boolean canAddAttributes(@Nonnull Cloud cloud) { return cloud.getDescriptor() instanceof KubernetesCloud.DescriptorImpl; } - @Nonnull @Override public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { KubernetesCloud k8sCloud = (KubernetesCloud) cloud; - rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, k8sCloud.getDisplayName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, k8sCloud.getDisplayName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER) .setAttribute(JenkinsOtelSemanticAttributes.K8S_NAMESPACE_NAME, k8sCloud.getNamespace()); - - if (label.getNodes().size() == 1) { - Optional node = label.getNodes().stream().findFirst(); - if (node.isPresent()) { - KubernetesSlave instance = (KubernetesSlave) node.get(); - rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); - - PodTemplate podTemplate = instance.getTemplateOrNull(); - if (podTemplate != null) { - // TODO: add resourceLimit attributes to detect misbehaviours? - rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); - } else { - LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node."); - } - } else { - LOGGER.log(Level.FINE, () -> "There is no present node."); - } - } else { - LOGGER.log(Level.FINE, () -> "There are more nodes assigned for the same label (total: " + label.getNodes().size() + ")"); - } - } - - protected String getImageName(String image) { - if (image.contains(":")) { - return image.substring(0, image.lastIndexOf(":")); - } - return image; - } - - protected String getImageTag(String image) { - if (image.contains(":")) { - return image.substring(image.lastIndexOf(":") + 1); - } - return "latest"; } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java new file mode 100644 index 000000000..db8d55839 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.computer; + +import hudson.Extension; +import hudson.model.Node; +import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.trace.Span; +import jenkins.YesNoMaybe; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; +import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; + +import javax.annotation.Nonnull; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Customization of spans for kubernetes cloud attributes. + */ +@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) +public class KubernetesCloudNodeHandler implements CloudNodeHandler { + + private final static Logger LOGGER = Logger.getLogger(KubernetesCloudNodeHandler.class.getName()); + + @Override + public boolean canAddAttributes(@Nonnull Node node) { + return node instanceof KubernetesSlave; + } + + @Override + public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception { + KubernetesSlave instance = (KubernetesSlave) node; + span + .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); + PodTemplate podTemplate = instance.getTemplateOrNull(); + if (podTemplate != null) { + // TODO: add resourceLimit attributes to detect misbehaviours? + span + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); + } else { + LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node."); + } + } + + protected String getImageName(String image) { + if (image.contains(":")) { + return image.substring(0, image.lastIndexOf(":")); + } + return image; + } + + protected String getImageTag(String image) { + if (image.contains(":")) { + return image.substring(image.lastIndexOf(":") + 1); + } + return "latest"; + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 9db6004bd..1bf0d5d14 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -108,6 +108,7 @@ public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull LOGGER.log(Level.FINEST, () -> "_onCommit(plannedNode: " + plannedNode.toString() + ")"); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); span.setStatus(StatusCode.OK); span.end(); LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); @@ -135,6 +136,7 @@ public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNu failureCloudCounter.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); span.recordException(t); span.setStatus(StatusCode.ERROR, t.getMessage()); span.end(); @@ -149,6 +151,7 @@ public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { totalCloudCount.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); span.setStatus(StatusCode.OK); span.end(); LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); @@ -168,6 +171,20 @@ protected Scope endCloudPhaseSpan(@NonNull NodeProvisioner.PlannedNode plannedNo return newScope; } + private void addCloudSpanAttributes(@NonNull Node node, @NonNull Span span) { + // ENRICH attributes with every Cloud Node specifics + for (CloudNodeHandler cloudNodeHandler : ExtensionList.lookup(CloudNodeHandler.class)) { + if (cloudNodeHandler.canAddAttributes(node)) { + try { + cloudNodeHandler.addCloudSpanAttributes(node, span); + } catch (Exception e) { + LOGGER.log(Level.WARNING, node.getNodeName() + " failure to handle node provider with handler " + cloudNodeHandler, e); + } + break; + } + } + } + @Inject public void setCloudSpanNamingStrategy(CloudSpanNamingStrategy cloudSpanNamingStrategy) { this.cloudSpanNamingStrategy = cloudSpanNamingStrategy; diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java similarity index 88% rename from src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java rename to src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java index 4091b05aa..3ac3d3cf8 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java @@ -8,9 +8,9 @@ import org.junit.Assert; import org.junit.Test; -public class KubernetesCloudHandlerTest { +public class KubernetesCloudNodeHandlerTest { - KubernetesCloudHandler handler = new KubernetesCloudHandler(); + KubernetesCloudNodeHandler handler = new KubernetesCloudNodeHandler(); @Test public void test_default_image() { From b2bfecbe603a08fbb3e73a8bbcc43d682398f8d6 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 18:39:15 +0100 Subject: [PATCH 25/34] Ensure the root transaction last for the whole lifecycle --- .../computer/MonitoringCloudListener.java | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 1bf0d5d14..1b858c05c 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -66,9 +66,7 @@ public void _onStarted(Cloud cloud, Label label, Collection "_onStarted(label: " + label + ")"); - LOGGER.log(Level.FINEST, () -> "_onStarted(label.nodes: " + label.getNodes().toString() + ")"); - LOGGER.log(Level.FINEST, () -> "_onStarted(plannedNode: " + plannedNode.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onStarted(label: " + label + ", plannedNode: " + plannedNode + ")"); String rootSpanName = this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); JenkinsOpenTelemetryPluginConfiguration.StepPlugin stepPlugin = JenkinsOpenTelemetryPluginConfiguration.get().findStepPluginOrDefault("cloud", cloud.getDescriptor()); @@ -104,58 +102,51 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla @Override public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { - LOGGER.log(Level.FINE, () -> "_onCommit(node: " + node + ")"); - LOGGER.log(Level.FINEST, () -> "_onCommit(plannedNode: " + plannedNode.toString() + ")"); - try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); - addCloudSpanAttributes(node, span); - span.setStatus(StatusCode.OK); - span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); - } + LOGGER.log(Level.FINE, () -> "_onCommit(plannedNode: " + plannedNode + ", node: " + node + ")"); + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); + addCloudSpanAttributes(node, span); + span.setStatus(StatusCode.OK); + span.end(); + endCloudPhaseSpan(plannedNode); } @Override public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { LOGGER.log(Level.FINE, () -> "_onFailure(plannedNode: " + plannedNode + ")"); failureCloudCounter.add(1); - try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); - span.recordException(t); - span.setStatus(StatusCode.ERROR, t.getMessage()); - span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onFailure " + OtelUtils.toDebugString(span)); - } + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onFailure " + OtelUtils.toDebugString(span)); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + endCloudPhaseSpan(plannedNode); } @Override public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, @NonNull Throwable t){ - LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ")"); - LOGGER.log(Level.FINEST, () -> "_onRollback(node: " + node.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ", node: " + node.toString() + ")"); failureCloudCounter.add(1); - try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); - addCloudSpanAttributes(node, span); - span.recordException(t); - span.setStatus(StatusCode.ERROR, t.getMessage()); - span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onRollback " + OtelUtils.toDebugString(span)); - } + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onRollback " + OtelUtils.toDebugString(span)); + addCloudSpanAttributes(node, span); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + endCloudPhaseSpan(plannedNode); } @Override public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { - LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ")"); - LOGGER.log(Level.FINEST, () -> "_onComplete(node: " + node.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ", node: " + node + ")"); totalCloudCount.add(1); - try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); - addCloudSpanAttributes(node, span); - span.setStatus(StatusCode.OK); - span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); - } + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); + addCloudSpanAttributes(node, span); + span.setStatus(StatusCode.OK); + span.end(); + endCloudPhaseSpan(plannedNode); } @MustBeClosed From c525190474b34f1c49de9b4e12bd839279097291 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 18:51:37 +0100 Subject: [PATCH 26/34] Revert "Ensure the root transaction last for the whole lifecycle" This reverts commit b2bfecbe603a08fbb3e73a8bbcc43d682398f8d6. --- .../computer/MonitoringCloudListener.java | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 1b858c05c..1bf0d5d14 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -66,7 +66,9 @@ public void _onStarted(Cloud cloud, Label label, Collection "_onStarted(label: " + label + ", plannedNode: " + plannedNode + ")"); + LOGGER.log(Level.FINE, () -> "_onStarted(label: " + label + ")"); + LOGGER.log(Level.FINEST, () -> "_onStarted(label.nodes: " + label.getNodes().toString() + ")"); + LOGGER.log(Level.FINEST, () -> "_onStarted(plannedNode: " + plannedNode.toString() + ")"); String rootSpanName = this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); JenkinsOpenTelemetryPluginConfiguration.StepPlugin stepPlugin = JenkinsOpenTelemetryPluginConfiguration.get().findStepPluginOrDefault("cloud", cloud.getDescriptor()); @@ -102,51 +104,58 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla @Override public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { - LOGGER.log(Level.FINE, () -> "_onCommit(plannedNode: " + plannedNode + ", node: " + node + ")"); - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); - addCloudSpanAttributes(node, span); - span.setStatus(StatusCode.OK); - span.end(); - endCloudPhaseSpan(plannedNode); + LOGGER.log(Level.FINE, () -> "_onCommit(node: " + node + ")"); + LOGGER.log(Level.FINEST, () -> "_onCommit(plannedNode: " + plannedNode.toString() + ")"); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); + span.setStatus(StatusCode.OK); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onCommit " + OtelUtils.toDebugString(span)); + } } @Override public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { LOGGER.log(Level.FINE, () -> "_onFailure(plannedNode: " + plannedNode + ")"); failureCloudCounter.add(1); - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onFailure " + OtelUtils.toDebugString(span)); - span.recordException(t); - span.setStatus(StatusCode.ERROR, t.getMessage()); - span.end(); - endCloudPhaseSpan(plannedNode); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onFailure " + OtelUtils.toDebugString(span)); + } } @Override public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, @NonNull Throwable t){ - LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ", node: " + node.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ")"); + LOGGER.log(Level.FINEST, () -> "_onRollback(node: " + node.toString() + ")"); failureCloudCounter.add(1); - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onRollback " + OtelUtils.toDebugString(span)); - addCloudSpanAttributes(node, span); - span.recordException(t); - span.setStatus(StatusCode.ERROR, t.getMessage()); - span.end(); - endCloudPhaseSpan(plannedNode); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getMessage()); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onRollback " + OtelUtils.toDebugString(span)); + } } @Override public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { - LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ", node: " + node + ")"); + LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ")"); + LOGGER.log(Level.FINEST, () -> "_onComplete(node: " + node.toString() + ")"); totalCloudCount.add(1); - Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); - addCloudSpanAttributes(node, span); - span.setStatus(StatusCode.OK); - span.end(); - endCloudPhaseSpan(plannedNode); + try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { + Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); + addCloudSpanAttributes(node, span); + span.setStatus(StatusCode.OK); + span.end(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); + } } @MustBeClosed From 5ba4c0ccec87b1f4396ead07f6614e70a68c38f7 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 18:53:33 +0100 Subject: [PATCH 27/34] merge log traces --- .../opentelemetry/computer/MonitoringCloudListener.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 1bf0d5d14..52ba2d8d3 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -104,8 +104,7 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla @Override public void _onCommit(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node) { - LOGGER.log(Level.FINE, () -> "_onCommit(node: " + node + ")"); - LOGGER.log(Level.FINEST, () -> "_onCommit(plannedNode: " + plannedNode.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onCommit(plannedNode: " + plannedNode + "node: " + node + ")"); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMMIT_NAME).setParent(Context.current()).startSpan(); addCloudSpanAttributes(node, span); @@ -131,8 +130,7 @@ public void _onFailure(NodeProvisioner.PlannedNode plannedNode, Throwable t) { @Override public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNull Node node, @NonNull Throwable t){ - LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ")"); - LOGGER.log(Level.FINEST, () -> "_onRollback(node: " + node.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onRollback(plannedNode" + plannedNode + ", node: " + node + ")"); failureCloudCounter.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_FAILURE_NAME).setParent(Context.current()).startSpan(); @@ -146,8 +144,7 @@ public void _onRollback(@NonNull NodeProvisioner.PlannedNode plannedNode, @NonNu @Override public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { - LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ")"); - LOGGER.log(Level.FINEST, () -> "_onComplete(node: " + node.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onComplete(plannedNode: " + plannedNode + ", node: " + node + ")"); totalCloudCount.add(1); try (Scope parentScope = endCloudPhaseSpan(plannedNode)) { Span span = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_COMPLETE_NAME).setParent(Context.current()).startSpan(); From 9733536f9f98a8b796522faeb3dd2822733d4881 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 12 Jul 2021 19:03:53 +0100 Subject: [PATCH 28/34] Add attributes in the docs --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index a334b2397..8fec4e90f 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,23 @@ In addition, if the backends were configured then there will be an environment v | ci.pipeline.parameter.name | Name of the parameter | String | | ci.pipeline.parameter.value | Value of the parameter. "Sensitive" values are redacted | String | +Cloud specific: + +| Attribute | Description | Type | +|----------------------------------|--------------|------| +| ci.cloud.label | Labels attached to the agent | String | +| ci.cloud.name | The agent name | String | +| cloud.account.id | The cloud account ID the resource is assigned to | String | +| cloud.provider | Name of the cloud provider | String | +| cloud.name | Step id | String | +| cloud.project.id | Jenkins plugin for that particular step | String | +| cloud.machine.type | Jenkins plugin version | String | +| cloud.region | The geographical region the resource is running | String | +| cloud.availability_zone | The zone where the resource is running | String | +| cloud.runAsUser | The user | String | +| cloud.platform | The cloud platform in use. | String | + + ##### Spans | Attribute | Description | Type | @@ -125,6 +142,16 @@ In addition, if the backends were configured then there will be an environment v | jenkins.url | Jenkins URL | String | | jenkins.computer.name | Name of the agent | String | +Containers/Kubernetes specific: + +| Attribute | Description | Type | +|----------------------------------|--------------|------| +| k8s.pod.name | The name of the Pod | String | +| container.image.name | The name of the image the container was built on | String | +| container.image.tag | Container image tag | String | +| container.name | The Container name | String | + + ### Metrics on Jenkins health indicators | Metrics | Unit | Label key | Label Value | Description | From 07c7c757ecccf7074d735fecd12a06c4a29ae2c7 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 09:24:18 +0100 Subject: [PATCH 29/34] cosmetic log change --- .../opentelemetry/computer/MonitoringCloudListener.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 52ba2d8d3..def520128 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -66,9 +66,7 @@ public void _onStarted(Cloud cloud, Label label, Collection "_onStarted(label: " + label + ")"); - LOGGER.log(Level.FINEST, () -> "_onStarted(label.nodes: " + label.getNodes().toString() + ")"); - LOGGER.log(Level.FINEST, () -> "_onStarted(plannedNode: " + plannedNode.toString() + ")"); + LOGGER.log(Level.FINE, () -> "_onStarted(label: " + label + ", label.nodes: " + label.getNodes().toString() + ", plannedNode: " + plannedNode + ")"); String rootSpanName = this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); JenkinsOpenTelemetryPluginConfiguration.StepPlugin stepPlugin = JenkinsOpenTelemetryPluginConfiguration.get().findStepPluginOrDefault("cloud", cloud.getDescriptor()); From 873dd50e4d540cfc210ac045d8f5133e5cac595f Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 09:34:22 +0100 Subject: [PATCH 30/34] Add cloud name in the transactions if possible --- .../opentelemetry/computer/CloudHandler.java | 1 + .../computer/GoogleCloudHandler.java | 9 ++++++++- .../computer/KubernetesCloudHandler.java | 9 ++++++++- .../computer/MonitoringCloudListener.java | 15 ++++++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java index 36301efea..2b25ca35b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java @@ -17,4 +17,5 @@ public interface CloudHandler { void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception; + String getCloudName(); } \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 5a2142bfa..419c6b06a 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -19,6 +19,8 @@ import javax.annotation.Nonnull; import java.util.Optional; +import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER; + /** * Customization of spans for google cloud attributes. */ @@ -37,7 +39,7 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ceCloud.getProjectId()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER); + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, GOOGLE_CLOUD_PROVIDER); if (label.getNodes().size() == 1) { Optional node = label.getNodes().stream().findFirst(); if (node.isPresent()) { @@ -55,6 +57,11 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn } } + @Override + public String getCloudName() { + return GOOGLE_CLOUD_PROVIDER; + } + protected String transformRegion(String region) { // f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a" return transform(region); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java index d6f06815c..335d5d1c1 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java @@ -15,6 +15,8 @@ import javax.annotation.Nonnull; +import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER; + /** * Customization of spans for kubernetes cloud attributes. */ @@ -33,7 +35,12 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, k8sCloud.getDisplayName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, k8sCloud.getDisplayName()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, K8S_CLOUD_PROVIDER) .setAttribute(JenkinsOtelSemanticAttributes.K8S_NAMESPACE_NAME, k8sCloud.getNamespace()); } + + @Override + public String getCloudName() { + return K8S_CLOUD_PROVIDER; + } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index def520128..f7752885c 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -68,7 +68,11 @@ public void _onStarted(Cloud cloud, Label label, Collection "_onStarted(label: " + label + ", label.nodes: " + label.getNodes().toString() + ", plannedNode: " + plannedNode + ")"); - String rootSpanName = this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); + // Span name format: "(-)?-{id}" + // cloud is optional + // template-name is defined in the cloud configuration + // {id} is the low cardinality + String rootSpanName = getCloudNamePrefix(cloud) + this.cloudSpanNamingStrategy.getRootSpanName(plannedNode); JenkinsOpenTelemetryPluginConfiguration.StepPlugin stepPlugin = JenkinsOpenTelemetryPluginConfiguration.get().findStepPluginOrDefault("cloud", cloud.getDescriptor()); SpanBuilder rootSpanBuilder = getTracer().spanBuilder(rootSpanName).setSpanKind(SpanKind.SERVER); @@ -180,6 +184,15 @@ private void addCloudSpanAttributes(@NonNull Node node, @NonNull Span span) { } } + private String getCloudNamePrefix(@NonNull Cloud cloud) { + for (CloudHandler cloudHandler : ExtensionList.lookup(CloudHandler.class)) { + if (cloudHandler.canAddAttributes(cloud)) { + return cloudHandler.getCloudName() + "-"; + } + } + return ""; + } + @Inject public void setCloudSpanNamingStrategy(CloudSpanNamingStrategy cloudSpanNamingStrategy) { this.cloudSpanNamingStrategy = cloudSpanNamingStrategy; From 56efa1222b3735f52e46bfafe2e24ff779705a84 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 09:35:19 +0100 Subject: [PATCH 31/34] Cosmetic log --- .../plugins/opentelemetry/computer/MonitoringCloudListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index f7752885c..3f4d462d5 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -153,7 +153,7 @@ public void _onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) { addCloudSpanAttributes(node, span); span.setStatus(StatusCode.OK); span.end(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin _onComplete " + OtelUtils.toDebugString(span)); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - end _onComplete " + OtelUtils.toDebugString(span)); } } From 6409b2a978d077ffc4f5a32c716401762ee18fe6 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 11:34:07 +0100 Subject: [PATCH 32/34] Cloud attributes should not use the label but the node --- .../opentelemetry/computer/CloudHandler.java | 9 +-- .../computer/CloudNodeHandler.java | 18 ------ .../computer/GoogleCloudHandler.java | 39 +++++++----- .../computer/KubernetesCloudHandler.java | 47 +++++++++++++- .../computer/KubernetesCloudNodeHandler.java | 63 ------------------- .../computer/MonitoringCloudListener.java | 10 +-- ...t.java => KubernetesCloudHandlerTest.java} | 4 +- 7 files changed, 79 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java delete mode 100644 src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java rename src/test/java/io/jenkins/plugins/opentelemetry/computer/{KubernetesCloudNodeHandlerTest.java => KubernetesCloudHandlerTest.java} (88%) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java index 2b25ca35b..d48ac3d8b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudHandler.java @@ -5,8 +5,9 @@ package io.jenkins.plugins.opentelemetry.computer; -import hudson.model.Label; +import hudson.model.Node; import hudson.slaves.Cloud; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import javax.annotation.Nonnull; @@ -14,8 +15,8 @@ public interface CloudHandler { boolean canAddAttributes(@Nonnull Cloud cloud); - - void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception; - + boolean canAddAttributes(@Nonnull Node node); + void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span rootSpanBuilder) throws Exception; + void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception; String getCloudName(); } \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java deleted file mode 100644 index 13921c209..000000000 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/CloudNodeHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright The Original Author or Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.jenkins.plugins.opentelemetry.computer; - -import hudson.model.Node; -import io.opentelemetry.api.trace.Span; - -import javax.annotation.Nonnull; - -public interface CloudNodeHandler { - - boolean canAddAttributes(@Nonnull Node node); - - void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span rootSpanBuilder) throws Exception; -} \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java index 419c6b06a..925dd9779 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/GoogleCloudHandler.java @@ -9,15 +9,15 @@ import com.google.jenkins.plugins.computeengine.ComputeEngineInstance; import com.google.jenkins.plugins.computeengine.InstanceConfiguration; import hudson.Extension; -import hudson.model.Label; import hudson.model.Node; import hudson.slaves.Cloud; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import jenkins.YesNoMaybe; import javax.annotation.Nonnull; -import java.util.Optional; +import java.util.logging.Logger; import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER; @@ -26,6 +26,7 @@ */ @Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) public class GoogleCloudHandler implements CloudHandler { + private final static Logger LOGGER = Logger.getLogger(GoogleCloudHandler.class.getName()); @Override public boolean canAddAttributes(@Nonnull Cloud cloud) { @@ -33,27 +34,31 @@ public boolean canAddAttributes(@Nonnull Cloud cloud) { } @Override - public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { + public boolean canAddAttributes(@Nonnull Node node) { + return node instanceof ComputeEngineInstance; + } + + @Override + public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud; rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ceCloud.getProjectId()) .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, GOOGLE_CLOUD_PROVIDER); - if (label.getNodes().size() == 1) { - Optional node = label.getNodes().stream().findFirst(); - if (node.isPresent()) { - ComputeEngineInstance instance = (ComputeEngineInstance) node.get(); - InstanceConfiguration configuration = ceCloud.getInstanceConfigurationByDescription(instance.getNodeDescription()); - if (configuration != null) { - rootSpanBuilder - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ACCOUNT_ID, configuration.getServiceAccountEmail()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformMachineType(configuration.getMachineType())) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(configuration.getRegion())) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_RUN_AS_USER, configuration.getRunAsUser()) - .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, transformZone(configuration.getZone())); - } - } + } + + @Override + public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception { + ComputeEngineInstance instance = (ComputeEngineInstance) node; + InstanceConfiguration configuration = instance.getCloud().getInstanceConfigurationByDescription(instance.getNodeDescription()); + if (configuration != null) { + span + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ACCOUNT_ID, configuration.getServiceAccountEmail()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformMachineType(configuration.getMachineType())) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(configuration.getRegion())) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_RUN_AS_USER, configuration.getRunAsUser()) + .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, transformZone(configuration.getZone())); } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java index 335d5d1c1..2c14145a4 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandler.java @@ -6,15 +6,21 @@ package io.jenkins.plugins.opentelemetry.computer; import hudson.Extension; -import hudson.model.Label; +import hudson.model.Node; import hudson.slaves.Cloud; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import jenkins.YesNoMaybe; import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; +import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; import javax.annotation.Nonnull; +import java.util.logging.Level; +import java.util.logging.Logger; + import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER; /** @@ -22,6 +28,7 @@ */ @Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) public class KubernetesCloudHandler implements CloudHandler { + private final static Logger LOGGER = Logger.getLogger(KubernetesCloudHandler.class.getName()); @Nonnull @Override @@ -30,7 +37,12 @@ public boolean canAddAttributes(@Nonnull Cloud cloud) { } @Override - public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { + public boolean canAddAttributes(@Nonnull Node node) { + return node instanceof KubernetesSlave; + } + + @Override + public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception { KubernetesCloud k8sCloud = (KubernetesCloud) cloud; rootSpanBuilder .setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, k8sCloud.getDisplayName()) @@ -39,8 +51,39 @@ public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull Label label, @Nonn .setAttribute(JenkinsOtelSemanticAttributes.K8S_NAMESPACE_NAME, k8sCloud.getNamespace()); } + @Override + public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception { + KubernetesSlave instance = (KubernetesSlave) node; + span + .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); + PodTemplate podTemplate = instance.getTemplateOrNull(); + if (podTemplate != null) { + // TODO: add resourceLimit attributes to detect misbehaviours? + span + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) + .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); + } else { + LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node."); + } + } + @Override public String getCloudName() { return K8S_CLOUD_PROVIDER; } + + protected String getImageName(String image) { + if (image.contains(":")) { + return image.substring(0, image.lastIndexOf(":")); + } + return image; + } + + protected String getImageTag(String image) { + if (image.contains(":")) { + return image.substring(image.lastIndexOf(":") + 1); + } + return "latest"; + } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java deleted file mode 100644 index db8d55839..000000000 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The Original Author or Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.jenkins.plugins.opentelemetry.computer; - -import hudson.Extension; -import hudson.model.Node; -import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; -import io.opentelemetry.api.trace.Span; -import jenkins.YesNoMaybe; -import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; -import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; - -import javax.annotation.Nonnull; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Customization of spans for kubernetes cloud attributes. - */ -@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) -public class KubernetesCloudNodeHandler implements CloudNodeHandler { - - private final static Logger LOGGER = Logger.getLogger(KubernetesCloudNodeHandler.class.getName()); - - @Override - public boolean canAddAttributes(@Nonnull Node node) { - return node instanceof KubernetesSlave; - } - - @Override - public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception { - KubernetesSlave instance = (KubernetesSlave) node; - span - .setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName()); - PodTemplate podTemplate = instance.getTemplateOrNull(); - if (podTemplate != null) { - // TODO: add resourceLimit attributes to detect misbehaviours? - span - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage())) - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage())) - .setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName()); - } else { - LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node."); - } - } - - protected String getImageName(String image) { - if (image.contains(":")) { - return image.substring(0, image.lastIndexOf(":")); - } - return image; - } - - protected String getImageTag(String image) { - if (image.contains(":")) { - return image.substring(image.lastIndexOf(":") + 1); - } - return "latest"; - } -} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 3f4d462d5..13905b3f5 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -89,7 +89,7 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla for (CloudHandler cloudHandler : ExtensionList.lookup(CloudHandler.class)) { if (cloudHandler.canAddAttributes(cloud)) { try { - cloudHandler.addCloudAttributes(cloud, label, rootSpanBuilder); + cloudHandler.addCloudAttributes(cloud, rootSpanBuilder); } catch (Exception e) { LOGGER.log(Level.WARNING, cloud.name + " failure to handle cloud provider with handler " + cloudHandler, e); } @@ -172,12 +172,12 @@ protected Scope endCloudPhaseSpan(@NonNull NodeProvisioner.PlannedNode plannedNo private void addCloudSpanAttributes(@NonNull Node node, @NonNull Span span) { // ENRICH attributes with every Cloud Node specifics - for (CloudNodeHandler cloudNodeHandler : ExtensionList.lookup(CloudNodeHandler.class)) { - if (cloudNodeHandler.canAddAttributes(node)) { + for (CloudHandler cloudHandler : ExtensionList.lookup(CloudHandler.class)) { + if (cloudHandler.canAddAttributes(node)) { try { - cloudNodeHandler.addCloudSpanAttributes(node, span); + cloudHandler.addCloudSpanAttributes(node, span); } catch (Exception e) { - LOGGER.log(Level.WARNING, node.getNodeName() + " failure to handle node provider with handler " + cloudNodeHandler, e); + LOGGER.log(Level.WARNING, node.getNodeName() + " failure to handle node provider with handler " + cloudHandler, e); } break; } diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java similarity index 88% rename from src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java rename to src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java index 3ac3d3cf8..4091b05aa 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudNodeHandlerTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/computer/KubernetesCloudHandlerTest.java @@ -8,9 +8,9 @@ import org.junit.Assert; import org.junit.Test; -public class KubernetesCloudNodeHandlerTest { +public class KubernetesCloudHandlerTest { - KubernetesCloudNodeHandler handler = new KubernetesCloudNodeHandler(); + KubernetesCloudHandler handler = new KubernetesCloudHandler(); @Test public void test_default_image() { From a6dda428166e41b55ee4a6ff1909369a90bc462a Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 11:56:49 +0100 Subject: [PATCH 33/34] Add started span --- .../computer/MonitoringCloudListener.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 13905b3f5..5e195b9ba 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -97,11 +97,21 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla } } - // START ROOT SPAN + // START root span Span rootSpan = rootSpanBuilder.startSpan(); this.getTraceService().putSpan(plannedNode, rootSpan); - rootSpan.makeCurrent(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); + try (final Scope rootSpanScope = rootSpan.makeCurrent()) { + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); + + // START started span + Span startSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_STARTED_NAME) + .setParent(Context.current().with(rootSpan)) + .startSpan(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(startSpan)); + + this.getTraceService().putSpan(plannedNode, startSpan); + startSpan.makeCurrent(); + } } @Override From 1e885f52002b977367f0a2a90c099ec42089e3b6 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 13 Jul 2021 17:49:16 +0100 Subject: [PATCH 34/34] Revert "Add started span" This reverts commit a6dda428166e41b55ee4a6ff1909369a90bc462a. --- .../computer/MonitoringCloudListener.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index 5e195b9ba..13905b3f5 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -97,21 +97,11 @@ public void _onStarted(Cloud cloud, Label label, NodeProvisioner.PlannedNode pla } } - // START root span + // START ROOT SPAN Span rootSpan = rootSpanBuilder.startSpan(); this.getTraceService().putSpan(plannedNode, rootSpan); - try (final Scope rootSpanScope = rootSpan.makeCurrent()) { - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); - - // START started span - Span startSpan = getTracer().spanBuilder(JenkinsOtelSemanticAttributes.CLOUD_SPAN_PHASE_STARTED_NAME) - .setParent(Context.current().with(rootSpan)) - .startSpan(); - LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin " + OtelUtils.toDebugString(startSpan)); - - this.getTraceService().putSpan(plannedNode, startSpan); - startSpan.makeCurrent(); - } + rootSpan.makeCurrent(); + LOGGER.log(Level.FINE, () -> plannedNode.displayName + " - begin root " + OtelUtils.toDebugString(rootSpan)); } @Override