Skip to content

Commit 43fbc96

Browse files
Use semantic conventions for GC metrics + fix lifecycle of Otel metrics instruments recreating metrics instruments upon reconfiguration of Otel SDK. (#426)
* Use semantic conventions for GC metrics + fix lifecycle of Otel metrics instruments recreating metrics instruments upon reconfiguration of Otel SDK. Old GC metric names: "runtime.jvm.memory.area" New GC metric names: "process.runtime.jvm.memory.*" * Disable broken unit tests * Bump dependencies * Use smaller GitHub repository in tests. Fix #427 Co-authored-by: Ivan Fernandez Calvo <[email protected]>
1 parent 795212f commit 43fbc96

19 files changed

+634
-516
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<dependency>
4040
<groupId>io.jenkins.tools.bom</groupId>
4141
<artifactId>bom-2.289.x</artifactId>
42-
<version>1280.vd669827e38cd</version>
42+
<version>1342.v729ca_3818e88</version>
4343
<scope>import</scope>
4444
<type>pom</type>
4545
</dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The Original Author or Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.jenkins.plugins.opentelemetry;
7+
8+
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
9+
import io.opentelemetry.api.metrics.ObservableLongCounter;
10+
import io.opentelemetry.api.metrics.ObservableLongGauge;
11+
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
12+
13+
import java.util.logging.Logger;
14+
15+
/**
16+
* Helper to implement {@link OtelComponent} and to manage metric instruments
17+
*/
18+
public abstract class AbstractOtelComponent implements OtelComponent {
19+
20+
private final OtelComponent.State state = new OtelComponent.State();
21+
22+
protected void registerInstrument(ObservableLongCounter instrument) {
23+
state.registerInstrument(instrument);
24+
}
25+
26+
protected void registerInstrument(ObservableLongGauge instrument) {
27+
state.registerInstrument(instrument);
28+
}
29+
30+
protected void registerInstrument(ObservableLongUpDownCounter instrument) {
31+
state.registerInstrument(instrument);
32+
}
33+
34+
protected void registerInstrument(ObservableDoubleGauge instrument) {
35+
state.registerInstrument(instrument);
36+
}
37+
38+
@Override
39+
public void beforeSdkShutdown() {
40+
state.closeInstruments();
41+
}
42+
}

src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProvider.java

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import com.google.common.base.Preconditions;
1010
import hudson.Extension;
1111
import hudson.ExtensionList;
12+
import hudson.init.InitMilestone;
13+
import hudson.init.Initializer;
1214
import io.jenkins.plugins.opentelemetry.opentelemetry.GlobalOpenTelemetrySdk;
1315
import io.jenkins.plugins.opentelemetry.opentelemetry.autoconfigure.ConfigPropertiesUtils;
1416
import io.jenkins.plugins.opentelemetry.opentelemetry.trace.TracerDelegate;
@@ -99,6 +101,10 @@ protected OpenTelemetrySdk getOpenTelemetrySdk() {
99101
@PreDestroy
100102
public void preDestroy() {
101103
if (this.openTelemetrySdk != null) {
104+
for(OtelComponent otelComponent: ExtensionList.lookup(OtelComponent.class)) {
105+
LOGGER.log(Level.FINE, () -> "beforeSdkShutdown() " + otelComponent);
106+
otelComponent.beforeSdkShutdown();
107+
}
102108
this.openTelemetrySdk.getSdkTracerProvider().shutdown();
103109
this.openTelemetrySdk.getSdkMeterProvider().shutdown();
104110
this.openTelemetrySdk.getSdkLogEmitterProvider().shutdown();
@@ -159,6 +165,14 @@ public void initializeNoOp() {
159165
LOGGER.log(Level.FINE, "OpenTelemetry initialized as NoOp");
160166
}
161167

168+
@Initializer(after = InitMilestone.JOB_CONFIG_ADAPTED)
169+
public void postInitialize() {
170+
for (OtelComponent otelComponent : ExtensionList.lookup(OtelComponent.class)) {
171+
LOGGER.log(Level.FINE, () -> "Initialize Otel SDK on " + otelComponent);
172+
otelComponent.afterSdkInitialized(meter, logEmitter, tracer, config);
173+
}
174+
}
175+
162176
static public OpenTelemetrySdkProvider get() {
163177
return ExtensionList.lookupSingleton(OpenTelemetrySdkProvider.class);
164178
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright The Original Author or Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.jenkins.plugins.opentelemetry;
7+
8+
import io.opentelemetry.api.metrics.Meter;
9+
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
10+
import io.opentelemetry.api.metrics.ObservableLongCounter;
11+
import io.opentelemetry.api.metrics.ObservableLongGauge;
12+
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
13+
import io.opentelemetry.api.trace.Tracer;
14+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
15+
import io.opentelemetry.sdk.logs.LogEmitter;
16+
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.logging.Level;
21+
import java.util.logging.Logger;
22+
23+
/**
24+
* Interface for components that want to be notified when the Otel SDK has been initialized or will be shutdown.
25+
*
26+
* The life cycle of consumers of the OpenTelemetry SDK (consumers of {@link io.opentelemetry.api.trace.TracerProvider},
27+
* {@link io.opentelemetry.api.metrics.MeterProvider}, and {@link SdkLogEmitterProvider}) can NOT use the Jenkins life
28+
* cycle because those consumers of the Otel SDK need to perform initialization tasks after the Otel SDK has been
29+
* initialized and have to shut down things before the Otel SDK is shutdown due to a reconfiguration.
30+
*
31+
* Used by components that create counters...
32+
*/
33+
public interface OtelComponent {
34+
35+
/**
36+
* Invoked soon after the Otel SDK has been initialized.
37+
*
38+
* @param meter {@link Meter} of the newly initialized Otel SDK
39+
* @param logEmitter {@link LogEmitter} of the newly initialized Otel SDK
40+
* @param tracer {@link Tracer} of the newly initialized Otel SDK
41+
* @param configProperties {@link ConfigProperties} of the newly initialized Otel SDK
42+
*/
43+
void afterSdkInitialized(Meter meter, LogEmitter logEmitter, Tracer tracer, ConfigProperties configProperties);
44+
45+
/**
46+
* Invoked just before the Otel SDK is shutdown
47+
*/
48+
void beforeSdkShutdown();
49+
50+
/**
51+
* Helper for {@link OtelComponent} implementations to manage the created metric instruments
52+
*/
53+
class State {
54+
private final static Logger logger = Logger.getLogger(State.class.getName());
55+
private final List<AutoCloseable> instruments = new ArrayList<>();
56+
57+
public void registerInstrument(ObservableLongCounter instrument) {
58+
instruments.add(instrument);
59+
}
60+
61+
public void registerInstrument(ObservableLongGauge instrument) {
62+
instruments.add(instrument);
63+
}
64+
65+
public void registerInstrument(ObservableLongUpDownCounter instrument) {
66+
instruments.add(instrument);
67+
}
68+
69+
public void registerInstrument(ObservableDoubleGauge instrument) {
70+
instruments.add(instrument);
71+
}
72+
73+
public void closeInstruments() {
74+
List<AutoCloseable> instruments = this.instruments;
75+
this.instruments.clear(); // reset the list of instruments for reuse
76+
for (AutoCloseable instrument : instruments) {
77+
try {
78+
instrument.close();
79+
} catch (Exception e) {
80+
// should never happen, Otel instruments override the #close method to indicate they don't throw exceptions
81+
logger.log(Level.INFO, "Exception closing instrument " + instrument, e);
82+
}
83+
}
84+
}
85+
}
86+
}

src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
import hudson.slaves.CloudProvisioningListener;
1212
import hudson.slaves.NodeProvisioner;
1313
import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider;
14+
import io.jenkins.plugins.opentelemetry.OtelComponent;
1415
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
1516
import io.opentelemetry.api.metrics.LongCounter;
1617
import io.opentelemetry.api.metrics.Meter;
18+
import io.opentelemetry.api.trace.Tracer;
19+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
20+
import io.opentelemetry.sdk.logs.LogEmitter;
1721

1822
import javax.annotation.Nonnull;
1923
import javax.annotation.PostConstruct;
@@ -22,16 +26,14 @@
2226
import java.util.logging.Logger;
2327

2428
@Extension
25-
public class MonitoringCloudListener extends CloudProvisioningListener {
29+
public class MonitoringCloudListener extends CloudProvisioningListener implements OtelComponent {
2630
private final static Logger LOGGER = Logger.getLogger(MonitoringCloudListener.class.getName());
2731

28-
protected Meter meter;
29-
3032
private LongCounter failureCloudCounter;
3133
private LongCounter totalCloudCount;
3234

33-
@PostConstruct
34-
public void postConstruct() {
35+
@Override
36+
public void afterSdkInitialized(Meter meter, LogEmitter logEmitter, Tracer tracer, ConfigProperties configProperties) {
3537
failureCloudCounter = meter.counterBuilder(JenkinsSemanticMetrics.JENKINS_CLOUD_AGENTS_FAILURE)
3638
.setDescription("Number of failed cloud agents when provisioning")
3739
.setUnit("1")
@@ -61,11 +63,8 @@ public void onComplete(NodeProvisioner.PlannedNode plannedNode, Node node) {
6163
LOGGER.log(Level.FINE, () -> "onComplete(" + plannedNode + ")");
6264
}
6365

64-
/**
65-
* Jenkins doesn't support {@link com.google.inject.Provides} so we manually wire dependencies :-(
66-
*/
67-
@Inject
68-
public void setMeter(@Nonnull OpenTelemetrySdkProvider openTelemetrySdkProvider) {
69-
this.meter = openTelemetrySdkProvider.getMeter();
66+
@Override
67+
public void beforeSdkShutdown() {
68+
7069
}
7170
}

src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringComputerListener.java

+22-22
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@
1212
import hudson.remoting.Channel;
1313
import hudson.slaves.ComputerListener;
1414
import io.jenkins.plugins.opentelemetry.OpenTelemetryAttributesAction;
15-
import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider;
15+
import io.jenkins.plugins.opentelemetry.OtelComponent;
1616
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
1717
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
1818
import io.opentelemetry.api.common.AttributeKey;
1919
import io.opentelemetry.api.metrics.LongCounter;
2020
import io.opentelemetry.api.metrics.Meter;
21+
import io.opentelemetry.api.trace.Tracer;
22+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
23+
import io.opentelemetry.sdk.logs.LogEmitter;
2124
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
2225
import jenkins.model.Jenkins;
2326
import jenkins.security.MasterToSlaveCallable;
2427

25-
import javax.annotation.Nonnull;
26-
import javax.annotation.PostConstruct;
27-
import javax.inject.Inject;
2828
import java.io.IOException;
2929
import java.net.InetAddress;
3030
import java.util.Arrays;
@@ -34,15 +34,15 @@
3434
import java.util.logging.Logger;
3535

3636
@Extension
37-
public class MonitoringComputerListener extends ComputerListener {
37+
public class MonitoringComputerListener extends ComputerListener implements OtelComponent {
3838
private final static Logger LOGGER = Logger.getLogger(MonitoringComputerListener.class.getName());
3939

40-
protected Meter meter;
41-
4240
private LongCounter failureAgentCounter;
4341

44-
@PostConstruct
45-
public void postConstruct() {
42+
private OtelComponent.State state = new State();
43+
44+
@Override
45+
public void afterSdkInitialized(Meter meter, LogEmitter logEmitter, Tracer tracer, ConfigProperties configProperties) {
4646
final Jenkins jenkins = Jenkins.get();
4747
Computer controllerComputer = jenkins.getComputer("");
4848
if (controllerComputer == null) {
@@ -65,21 +65,24 @@ public void postConstruct() {
6565
LOGGER.log(Level.WARNING, "Failure getting attributes for Jenkins Controller computer " + controllerComputer, e);
6666
}
6767
}
68+
state.registerInstrument(
6869
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_OFFLINE)
6970
.ofLongs()
7071
.setDescription("Number of offline agents")
7172
.setUnit("1")
72-
.buildWithCallback(valueObserver -> valueObserver.record(this.getOfflineAgentsCount()));
73-
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_ONLINE)
73+
.buildWithCallback(valueObserver -> valueObserver.record(this.getOfflineAgentsCount())));
74+
state.registerInstrument(
75+
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_ONLINE)
7476
.ofLongs()
7577
.setDescription("Number of online agents")
7678
.setUnit("1")
77-
.buildWithCallback(valueObserver -> valueObserver.record(this.getOnlineAgentsCount()));
78-
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_TOTAL)
79+
.buildWithCallback(valueObserver -> valueObserver.record(this.getOnlineAgentsCount())));
80+
state.registerInstrument(
81+
meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_TOTAL)
7982
.ofLongs()
8083
.setDescription("Number of agents")
8184
.setUnit("1")
82-
.buildWithCallback(valueObserver -> valueObserver.record(this.getAgentsCount()));
85+
.buildWithCallback(valueObserver -> valueObserver.record(this.getAgentsCount())));
8386
failureAgentCounter = meter.counterBuilder(JenkinsSemanticMetrics.JENKINS_AGENTS_LAUNCH_FAILURE)
8487
.setDescription("Number of ComputerLauncher failures")
8588
.setUnit("1")
@@ -130,6 +133,11 @@ public void onLaunchFailure(Computer computer, TaskListener taskListener) throws
130133
LOGGER.log(Level.FINE, () -> "onLaunchFailure(" + computer + "): ");
131134
}
132135

136+
@Override
137+
public void beforeSdkShutdown() {
138+
state.closeInstruments();
139+
}
140+
133141
private static class GetComputerAttributes extends MasterToSlaveCallable<Map<String, String>, IOException> {
134142
@Override
135143
public Map<String, String> call() throws IOException {
@@ -152,12 +160,4 @@ public Map<String, String> call() throws IOException {
152160
return attributes;
153161
}
154162
}
155-
156-
/**
157-
* Jenkins doesn't support {@link com.google.inject.Provides} so we manually wire dependencies :-(
158-
*/
159-
@Inject
160-
public void setMeter(@Nonnull OpenTelemetrySdkProvider openTelemetrySdkProvider) {
161-
this.meter = openTelemetrySdkProvider.getMeter();
162-
}
163163
}

0 commit comments

Comments
 (0)