Skip to content

Commit

Permalink
Reconfigurable MeterProvider to reload the OpenTelemetry configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrille-leclerc authored Jul 2, 2024
2 parents 35d71e4 + be1a271 commit 943ee5a
Show file tree
Hide file tree
Showing 31 changed files with 3,689 additions and 888 deletions.
24 changes: 7 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
~ SPDX-License-Identifier: Apache-2.0
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down Expand Up @@ -34,7 +35,8 @@
Monitor Jenkins with dashboards and alerts on critical health metrics.
Troubleshoot Jenkins problems using traces of job executions and HTTP requests.
Store pipeline build logs in an Observability backend like Elastic or Grafana/Loki
to improve Jenkins reliability and scalability while improving the traceability of builds.</description>
to improve Jenkins reliability and scalability while improving the traceability of builds.
</description>
<url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>

<dependencyManagement>
Expand Down Expand Up @@ -74,20 +76,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>${opentelemetry-instrumentation.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom-alpha</artifactId>
<version>${opentelemetry-instrumentation.version}-alpha</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
Expand Down Expand Up @@ -139,7 +127,6 @@
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-api</artifactId>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
Expand Down Expand Up @@ -515,6 +502,9 @@
<io.jenkins.plugins.opentelemetry.job.log>INFO</io.jenkins.plugins.opentelemetry.job.log>
<io.jenkins.plugins.opentelemetry.job.MonitoringPipelineListener>INFO</io.jenkins.plugins.opentelemetry.job.MonitoringPipelineListener>
<io.jenkins.plugins.opentelemetry.job.MonitoringRunListener>INFO</io.jenkins.plugins.opentelemetry.job.MonitoringRunListener>
<io.jenkins.plugins.opentelemetry.init.JenkinsExecutorMonitoringInitializer>INFO</io.jenkins.plugins.opentelemetry.init.JenkinsExecutorMonitoringInitializer>
<io.jenkins.plugins.opentelemetry.job.MonitoringRunListener>INFO</io.jenkins.plugins.opentelemetry.job.MonitoringRunListener>
<io.jenkins.plugins.opentelemetry.opentelemetry.ReconfigurableMeterProvider>INFO</io.jenkins.plugins.opentelemetry.opentelemetry.ReconfigurableMeterProvider>
</loggers>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@
import javax.annotation.PreDestroy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.stream.Collectors;

/**
* {@link OpenTelemetry} instance intended to live on the Jenkins Controller.
*/
@Extension
@Extension(ordinal = Integer.MAX_VALUE)
public class JenkinsControllerOpenTelemetry extends ReconfigurableOpenTelemetry implements OpenTelemetry {

/**
Expand All @@ -38,9 +37,11 @@ public class JenkinsControllerOpenTelemetry extends ReconfigurableOpenTelemetry
public final static AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);

@NonNull
private final transient Tracer defaultTracer;
protected transient Meter defaultMeter;
protected final transient EventLogger defaultEventLogger;
private final Tracer defaultTracer;
@NonNull
private final Meter defaultMeter;
@NonNull
private final EventLogger defaultEventLogger;

public JenkinsControllerOpenTelemetry() {
super();
Expand All @@ -60,13 +61,28 @@ public JenkinsControllerOpenTelemetry() {
.eventLoggerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
.setInstrumentationVersion(opentelemetryPluginVersion)
.build();

this.defaultMeter = getMeterProvider()
.meterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
.setInstrumentationVersion(opentelemetryPluginVersion)
.build();
}

@NonNull
public Tracer getDefaultTracer() {
return defaultTracer;
}

@NonNull
public Meter getDefaultMeter() {
return defaultMeter;
}

@NonNull
public EventLogger getDefaultEventLogger() {
return defaultEventLogger;
}

public boolean isLogsEnabled() {
String otelLogsExporter = getConfig().getString("otel.logs.exporter");
return otelLogsExporter != null && !otelLogsExporter.equals("none");
Expand Down Expand Up @@ -101,20 +117,7 @@ public void initialize(@NonNull OpenTelemetryConfiguration configuration) {

@Override
protected void postOpenTelemetrySdkConfiguration() {
String opentelemetryPluginVersion = OtelUtils.getOpentelemetryPluginVersion();

this.defaultMeter = getMeterProvider()
.meterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
.setInstrumentationVersion(opentelemetryPluginVersion)
.build();

logger.log(Level.FINER, () -> "Configure OpenTelemetryLifecycleListeners: " + ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream().sorted().map(e -> e.getClass().getName()).collect(Collectors.joining(", ")));
ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream()
.sorted()
.forEachOrdered(otelComponent -> {
otelComponent.afterSdkInitialized(defaultMeter, getOpenTelemetryDelegate().getLogsBridge(), defaultEventLogger, defaultTracer, getConfig());
otelComponent.afterSdkInitialized(getOpenTelemetryDelegate(), getConfig());
});
ExtensionList.lookup(OpenTelemetryLifecycleListener.class).forEach(l -> l.afterConfiguration(getConfig()));
}

static public JenkinsControllerOpenTelemetry get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
import static io.jenkins.plugins.opentelemetry.OtelUtils.UNKNOWN;
import static io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend.ICONS_PREFIX;

@Extension
@Extension(ordinal = Integer.MAX_VALUE-1 /* initialize OTel ASAP, just after loading JenkinsControllerOpenTelemetry as GlobalOpenTelemetry */)
@Symbol("openTelemetry")
public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration {
private final static Logger LOGGER = Logger.getLogger(JenkinsOpenTelemetryPluginConfiguration.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,12 @@

package io.jenkins.plugins.opentelemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.incubator.events.EventLogger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongCounter;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import io.opentelemetry.api.trace.Tracer;
import hudson.ExtensionPoint;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;

/**
* Interface for components that want to be notified when the Otel SDK has been initialized or will be shutdown.
* <p>
* The life cycle of consumers of the OpenTelemetry SDK (consumers of {@link io.opentelemetry.api.trace.TracerProvider},
* {@link io.opentelemetry.api.metrics.MeterProvider}, and {@link io.opentelemetry.sdk.logs.SdkLoggerProvider}) can NOT
* use the Jenkins life cycle because those consumers of the Otel SDK need to perform initialization tasks after the
* Otel SDK has been initialized and have to shut down things before the Otel SDK is shutdown due to a reconfiguration.
* <p>
* Used by components that create counters...
*/
public interface OpenTelemetryLifecycleListener extends Comparable<OpenTelemetryLifecycleListener>{
public interface OpenTelemetryLifecycleListener extends ExtensionPoint, Comparable<OpenTelemetryLifecycleListener> {

/**
* Invoked soon after the Otel SDK has been initialized.
* Created {@link AutoCloseable} metering instruments don't have to be closed by Otel components, the OpenTelemetry
* plugin takes care of this (eg {@link ObservableLongUpDownCounter}, {@link ObservableLongCounter}...)
*
* @param meter {@link Meter} of the newly initialized Otel SDK
* @param loggerProvider {@link io.opentelemetry.api.logs.Logger} of the newly initialized Otel SDK
* @param eventLogger
* @param tracer {@link Tracer} of the newly initialized Otel SDK
* @param configProperties {@link ConfigProperties} of the newly initialized Otel SDK
*/
default void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) {}

/**
* Invoked soon after the Otel SDK has been initialized.
* Created {@link AutoCloseable} metering instruments don't have to be closed by Otel components, the OpenTelemetry
* plugin takes care of this (eg {@link ObservableLongUpDownCounter}, {@link ObservableLongCounter}...)
*
* @param openTelemetry
* @param configProperties {@link ConfigProperties} of the newly initialized Otel SDK
*/
default void afterSdkInitialized(OpenTelemetry openTelemetry, ConfigProperties configProperties) {}

/**
* Invoked just before the Otel SDK is shutdown.
* Created {@link AutoCloseable} metering instruments don't have to be closed by Otel components, the OpenTelemetry
* plugin takes care of this (eg {@link ObservableLongUpDownCounter}, {@link ObservableLongCounter}...)
*/
default void beforeSdkShutdown() {}
default void afterConfiguration(ConfigProperties configProperties){}

/**
* @return the ordinal of this otel component to execute step handlers in predictable order. The smallest ordinal is handled first.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@
import hudson.model.Node;
import hudson.slaves.CloudProvisioningListener;
import hudson.slaves.NodeProvisioner;
import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry;
import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
import io.opentelemetry.api.incubator.events.EventLogger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import jenkins.YesNoMaybe;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -30,8 +29,14 @@ public class MonitoringCloudListener extends CloudProvisioningListener implement
private LongCounter failureCloudCounter;
private LongCounter totalCloudCount;

@Override
public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) {
@Inject
private JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry;

@PostConstruct
public void postConstruct() {
Meter meter = jenkinsControllerOpenTelemetry.getDefaultMeter();
LOGGER.log(Level.FINE, () -> "Start monitoring Jenkins controller cloud agent provisioning...");

failureCloudCounter = meter.counterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_FAILURE)
.setDescription("Number of failed cloud agents when provisioning")
.setUnit("1")
Expand All @@ -41,7 +46,6 @@ public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, Even
.setUnit("1")
.build();

LOGGER.log(Level.FINE, () -> "Start monitoring Jenkins cloud agent provisioning...");
}

@Override
Expand All @@ -62,9 +66,4 @@ public void onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) {
totalCloudCount.add(1);
LOGGER.log(Level.FINE, () -> "onComplete(" + plannedNode + ")");
}

@Override
public void beforeSdkShutdown() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,21 @@
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.slaves.ComputerListener;
import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry;
import io.jenkins.plugins.opentelemetry.OpenTelemetryAttributesAction;
import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.events.EventLogger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.semconv.incubating.HostIncubatingAttributes;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
Expand All @@ -41,8 +40,13 @@ public class MonitoringComputerListener extends ComputerListener implements Open

private LongCounter failureAgentCounter;

@Override
public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) {
@Inject
protected JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry;

@PostConstruct
public void postConstruct() {
Meter meter = jenkinsControllerOpenTelemetry.getDefaultMeter();

final Jenkins jenkins = Jenkins.get();
Computer controllerComputer = jenkins.getComputer("");
if (controllerComputer == null) {
Expand Down Expand Up @@ -70,12 +74,12 @@ public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, Even
.setDescription("Number of offline agents")
.setUnit("1")
.buildWithCallback(valueObserver -> valueObserver.record(this.getOfflineAgentsCount()));
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_ONLINE)
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_ONLINE)
.ofLongs()
.setDescription("Number of online agents")
.setUnit("1")
.buildWithCallback(valueObserver -> valueObserver.record(this.getOnlineAgentsCount()));
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_TOTAL)
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_TOTAL)
.ofLongs()
.setDescription("Number of agents")
.setUnit("1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@
import com.cloudbees.simplediskusage.QuickDiskUsagePlugin;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry;
import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
import io.opentelemetry.api.incubator.events.EventLogger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -33,20 +32,25 @@ public class DiskUsageMonitoringInitializer implements OpenTelemetryLifecycleLis
private final static Logger LOGGER = Logger.getLogger(DiskUsageMonitoringInitializer.class.getName());

/**
* Don't inject the `quickDiskUsagePlugin` using @{@link Inject} because the injected instance is not the right once.
* Don't inject the `quickDiskUsagePlugin` using @{@link Inject} because the injected instance is not the right one.
* Lazy load it using {@link Jenkins#getPlugin(Class)}.
*/
protected QuickDiskUsagePlugin quickDiskUsagePlugin;

@Override
public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) {
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_DISK_USAGE_BYTES)
.ofLongs()
.setDescription("Disk usage of first level folder in JENKINS_HOME.")
.setUnit("byte")
.buildWithCallback(valueObserver -> valueObserver.record(calculateDiskUsageInBytes()));
@Inject
protected JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry;

@PostConstruct
public void postConstruct() {
LOGGER.log(Level.FINE, () -> "Start monitoring Jenkins controller disk usage...");

Meter meter = Objects.requireNonNull(jenkinsControllerOpenTelemetry).getDefaultMeter();
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_DISK_USAGE_BYTES)
.ofLongs()
.setDescription("Disk usage of first level folder in JENKINS_HOME.")
.setUnit("byte")
.buildWithCallback(valueObserver -> valueObserver.record(calculateDiskUsageInBytes()));

}

private long calculateDiskUsageInBytes() {
Expand Down
Loading

0 comments on commit 943ee5a

Please sign in to comment.