Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Run;
import io.jenkins.plugins.opentelemetry.semconv.ExtendedJenkinsAttributes;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand All @@ -21,8 +18,8 @@
import jenkins.scm.api.mixin.ChangeRequestSCMHead;

/**
* Use same root span name for all pull change request pipelines (pull request, merge request)
* Use different span names for different branches.
* Use same short pipeline name for all change request pipelines (pull request, merge request)
* Use different short pipeline name for different branches.
*/
@Extension
public class DefaultRunHandler implements RunHandler {
Expand All @@ -32,21 +29,21 @@
"-" + ChangeRequestCheckoutStrategy.MERGE.name().toLowerCase(Locale.ENGLISH)));

@Override
public boolean canCreateSpanBuilder(@NonNull Run<?, ?> run) {
public boolean matches(@NonNull Run<?, ?> run) {
return true;
}

@NonNull
@Override
public SpanBuilder createSpanBuilder(@NonNull Run<?, ?> run, @NonNull Tracer tracer) {
public String getPipelineShortName(@NonNull Run<?, ?> run) {
SCMHead head = SCMHead.HeadByItem.findHead(run.getParent());
String spanName;
String pipelineShortName;
if (head instanceof ChangeRequestSCMHead) {
spanName = getChangeRequestRootSpanName(run.getParent().getFullName());
pipelineShortName = getChangeRequestRootSpanName(run.getParent().getFullName());

Check warning on line 42 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/DefaultRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 42 is not covered by tests
} else {
spanName = run.getParent().getFullName();
pipelineShortName = run.getParent().getFullName();
}
return tracer.spanBuilder(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_ROOT_SPAN_NAME_PREFIX + spanName);
return pipelineShortName;
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import hudson.model.Run;
import io.jenkins.plugins.opentelemetry.semconv.ExtendedJenkinsAttributes;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.Collection;
import java.util.Optional;
import javaposse.jobdsl.plugin.actions.SeedJobAction;
import javaposse.jobdsl.plugin.actions.SeedJobTransientActionFactory;
import javax.inject.Inject;
Expand All @@ -34,7 +34,7 @@
}

@Override
public boolean canCreateSpanBuilder(@NonNull Run<?, ?> run) {
public boolean matches(@NonNull Run<?, ?> run) {
Job<?, ?> job = run.getParent();
// perf optimization: directly lookup up in the SeedJobTransientActionFactory over
// `job.getAction(SeedJobAction.class)`
Expand All @@ -44,7 +44,7 @@

@NonNull
@Override
public SpanBuilder createSpanBuilder(@NonNull Run<?, ?> run, @NonNull Tracer tracer) {
public String getPipelineShortName(@NonNull Run<?, ?> run) {
Job<?, ?> job = run.getParent();
// perf optimization: directly lookup up in the SeedJobTransientActionFactory over
// `job.getAction(SeedJobAction.class)`
Expand All @@ -56,30 +56,36 @@
.orElseThrow(IllegalStateException::new);

// TODO understand the difference between seedJobAction.getTemplateJob() and seedJobAction.getSeedJob()
Item seedJob = seedJobAction.getSeedJob();

String templateFullName;
String templateUrl;
String spanName;
if (seedJob == null) {
templateFullName = null;
templateUrl = null;
spanName = job.getFullName();
} else {
templateFullName = seedJob.getFullName();
templateUrl = seedJob.getUrl();
spanName = collapseJobName ? "Job from seed '" + templateFullName + "'" : job.getFullName();
}

SpanBuilder spanBuilder =
tracer.spanBuilder(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_ROOT_SPAN_NAME_PREFIX + spanName);
if (templateFullName != null) {
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_TEMPLATE_ID, templateFullName);
}
if (templateUrl != null) {
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_TEMPLATE_URL, templateUrl);
}
return spanBuilder;
Optional<Item> seedJob = Optional.ofNullable(seedJobAction.getSeedJob());

return collapseJobName

Check warning on line 61 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/JobDslRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 61 is only partially covered, one branch is missing
? job.getFullName()

Check warning on line 62 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/JobDslRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 62 is not covered by tests
: seedJob.map(Item::getFullName)
.map(fn -> "Job from seed '" + fn + "'")
.orElse(job.getFullName());
}

@Override
public void enrichPipelineRunSpan(@NonNull Run<?, ?> run, @NonNull SpanBuilder spanBuilder) {
Job<?, ?> job = run.getParent();
// perf optimization: directly lookup up in the SeedJobTransientActionFactory over
// `job.getAction(SeedJobAction.class)`
Collection<? extends Action> actions = seedJobTransientActionFactory.createFor(job);

SeedJobAction seedJobAction = (SeedJobAction) actions.stream()
.filter(action -> action instanceof SeedJobAction)
.findFirst()
.orElseThrow(IllegalStateException::new);

// TODO understand the difference between seedJobAction.getTemplateJob() and seedJobAction.getSeedJob()

Check warning on line 80 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/JobDslRunHandler.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: understand the difference between seedJobAction.getTemplateJob() and seedJobAction.getSeedJob()
Optional<Item> seedJob = Optional.ofNullable(seedJobAction.getSeedJob());

seedJob.map(Item::getFullName)
.ifPresent(templateFullName ->
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_TEMPLATE_ID, templateFullName));
seedJob.map(Item::getUrl)
.ifPresent(templateUrl ->
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_TEMPLATE_URL, templateUrl));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import hudson.model.Run;
import io.jenkins.plugins.opentelemetry.semconv.ExtendedJenkinsAttributes;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -32,22 +31,29 @@
}

@Override
public boolean canCreateSpanBuilder(@NonNull Run<?, ?> run) {
public boolean matches(@NonNull Run<?, ?> run) {
return run instanceof MatrixRun || run instanceof MatrixBuild;
}

@NonNull
@Override
public SpanBuilder createSpanBuilder(@NonNull Run<?, ?> run, @NonNull Tracer tracer) {
public String getPipelineShortName(@NonNull Run<?, ?> run) {
if (run instanceof MatrixRun matrixRun) {
MatrixConfiguration matrixConfiguration = matrixRun.getParent();

MatrixProject matrixProject = matrixConfiguration.getParent();
String spanName =
expandJobName ? run.getParent().getFullName() : matrixProject.getFullName() + "/execution";
SpanBuilder spanBuilder =
tracer.spanBuilder(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_ROOT_SPAN_NAME_PREFIX + spanName);
Combination combination = matrixConfiguration.getCombination();
return expandJobName ? run.getParent().getFullName() : matrixProject.getFullName() + "/execution";

Check warning on line 44 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/MatrixRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 44 is only partially covered, one branch is missing
} else if (run instanceof MatrixBuild matrixBuild) {

Check warning on line 45 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/MatrixRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 45 is only partially covered, one branch is missing
return matrixBuild.getParent().getFullName();
} else {
throw new IllegalStateException("Unsupported run type " + run);

Check warning on line 48 in src/main/java/io/jenkins/plugins/opentelemetry/job/runhandler/MatrixRunHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 48 is not covered by tests
}
}

@Override
public void enrichPipelineRunSpan(@NonNull Run<?, ?> run, @NonNull SpanBuilder spanBuilder) {
if (run instanceof MatrixRun matrixRun) {

Combination combination = matrixRun.getParent().getCombination();
List<String> axisNames = new ArrayList<>();
List<String> axisValues = new ArrayList<>();

Expand All @@ -57,13 +63,6 @@
});
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_AXIS_NAMES, axisNames);
spanBuilder.setAttribute(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_AXIS_VALUES, axisValues);

return spanBuilder;
} else if (run instanceof MatrixBuild matrixBuild) {
return tracer.spanBuilder(ExtendedJenkinsAttributes.CI_PIPELINE_RUN_ROOT_SPAN_NAME_PREFIX
+ matrixBuild.getParent().getFullName());
} else {
throw new IllegalStateException("Unsupported run type " + run);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Run;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;

public interface RunHandler extends Comparable<RunHandler> {

default void configure(ConfigProperties config) {}

boolean canCreateSpanBuilder(@NonNull Run<?, ?> run);
boolean matches(@NonNull Run<?, ?> run);

/**
* Low cardinality pipeline name that fits with
* {@link io.opentelemetry.semconv.incubating.CicdIncubatingAttributes#CICD_PIPELINE_NAME}.
* High cardinality elements like the SCM pull request names of a Jenkins multibranch pipeline
* should be excluded from the pipeline short name.
*/
@NonNull
SpanBuilder createSpanBuilder(@NonNull Run<?, ?> run, @NonNull Tracer tracer);
String getPipelineShortName(@NonNull Run<?, ?> run);

default void enrichPipelineRunSpan(@NonNull Run<?, ?> run, @NonNull SpanBuilder spanBuilder) {}

/**
* @return the ordinal of this handler to execute run handlers in predictable order. The smallest ordinal is executed first.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.jenkins.plugins.opentelemetry.opentelemetry;

import hudson.Extension;
import io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.YesNoMaybe;

/**
* Manages the configuration for semantic convention stability opt-in.
* Users can configure which version of the CI/CD semantic conventions to emit.
* TODO support hot config changes or document the need to restart Jenkins on config change.

Check warning on line 17 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: support hot config changes or document the need to restart Jenkins on config change.
*/
@Extension(dynamicLoadable = YesNoMaybe.MAYBE, optional = true)
public class SemconvStability implements OpenTelemetryLifecycleListener {

private static final Logger logger = Logger.getLogger(SemconvStability.class.getName());

private final AtomicInteger configurationCounter = new AtomicInteger(0);

private boolean emitOldCicdSemconv = true;
private boolean emitStableCicdSemconv = true;

public boolean emitOldCicdSemconv() {
return emitOldCicdSemconv;
}

public boolean emitStableCicdSemconv() {
return emitStableCicdSemconv;
}

@Override
public void afterConfiguration(ConfigProperties configProperties) {
boolean oldCicd = true;
boolean stableCicd = true;

// default to just emitting the old style metrics
String value = configProperties.getString("otel.semconv-stability.opt-in", "cicd/old");
if (value != null) {

Check warning on line 44 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 44 is only partially covered, one branch is missing
Set<String> values = new HashSet<>(Arrays.asList(value.split(",")));

// technically it's possible to set "cicd,cicd/dup" or "cicd,cicd/old"
if (values.contains("cicd/dup")) {

Check warning on line 48 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 48 is only partially covered, one branch is missing
oldCicd = true;
stableCicd = true;

Check warning on line 50 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 49-50 are not covered by tests
} else if (values.contains("cicd/old")) {

Check warning on line 51 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 51 is only partially covered, one branch is missing
oldCicd = true;
stableCicd = false;
} else if (values.contains("cicd")) {
oldCicd = false;
stableCicd = true;

Check warning on line 56 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 54-56 are not covered by tests
}
}
if (configurationCounter.get() > 0 && (emitOldCicdSemconv != oldCicd || emitStableCicdSemconv != stableCicd)) {

Check warning on line 59 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 59 is only partially covered, 2 branches are missing
logger.log(

Check warning on line 60 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 60 is not covered by tests
Level.INFO,
"SemconvStability: configuration changes from " + "emitOldCicdSemconv="
+ emitOldCicdSemconv + " to " + oldCicd + ", emitStableCicdSemconv="
+ emitStableCicdSemconv + " to " + stableCicd
+ " may not support hot reload and may require restart");
}
emitOldCicdSemconv = oldCicd;
emitStableCicdSemconv = stableCicd;
configurationCounter.incrementAndGet();
logger.log(
Level.FINE,
() -> "SemconvStability: emitOldCicdSemconv=" + emitOldCicdSemconv + ", emitStableCicdSemconv="

Check warning on line 72 in src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/SemconvStability.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 72 is not covered by tests
+ emitStableCicdSemconv);
}

@Override
public int ordinal() {
return Integer.MIN_VALUE;
}
}
Loading
Loading