Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:rmi:javaagent'
- type: gradle
path: ./
target: ':instrumentation:runtime-telemetry:javaagent'
- type: gradle
path: ./
target: ':instrumentation:runtime-telemetry:library'
- type: gradle
path: ./
target: ':instrumentation:scala-fork-join-2.8:javaagent'
Expand Down
21 changes: 1 addition & 20 deletions buildscripts/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,26 +88,7 @@
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<property name="ignoreEnhancedForColon" value="false"/>
<property name="tokens"
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
Comment on lines -91 to -110
Copy link
Copy Markdown
Member Author

@trask trask Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this rule conflicted with spotless handling of empty default expression in Java 17 switch statement

we could probably get rid of a lot of checkstyle checks that are already covered by spotless


<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
Expand Down
23 changes: 16 additions & 7 deletions instrumentation/runtime-telemetry/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Settings for the Runtime Telemetry instrumentation

| System property | Type | Default | Description |
| ------------------------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------- |
| `otel.instrumentation.runtime-telemetry.emit-experimental-metrics` | Boolean | `false` | Enable the capture of experimental metrics. |
| `otel.instrumentation.runtime-telemetry.experimental.prefer-jfr` | Boolean | `false` | Prefer JFR over JMX for metrics where both collection methods are available (Java 17+). |
| `otel.instrumentation.runtime-telemetry.experimental.package-emitter.enabled` | Boolean | `false` | Enable creating events for JAR libraries used by the application. |
| `otel.instrumentation.runtime-telemetry.experimental.package-emitter.jars-per-second` | Integer | 10 | The number of JAR files processed per second. |

## Deprecated Properties (to be removed in 3.0)

| System property | Type | Default | Description |
|--------------------------------------------------------------------------|---------|---------|-----------------------------------------------------------------------------------|
| `otel.instrumentation.runtime-telemetry.capture-gc-cause` | Boolean | `false` | Enable the capture of the jvm.gc.cause attribute with the jvm.gc.duration metric. |
| `otel.instrumentation.runtime-telemetry.emit-experimental-telemetry` | Boolean | `false` | Enable the capture of experimental metrics. |
| `otel.instrumentation.runtime-telemetry-java17.enable-all` | Boolean | `false` | Enable the capture of all JFR based metrics. |
| `otel.instrumentation.runtime-telemetry-java17.enabled` | Boolean | `false` | Enable the capture of JFR based metrics. |
| `otel.instrumentation.runtime-telemetry.package-emitter.enabled` | Boolean | `false` | Enable creating events for JAR libraries used by the application. |
| `otel.instrumentation.runtime-telemetry.package-emitter.jars-per-second` | Integer | 10 | The number of JAR files processed per second. |
| ------------------------------------------------------------------------ | ------- | ------- | --------------------------------------------------------------------------------- |
| `otel.instrumentation.runtime-telemetry.capture-gc-cause` | Boolean | `false` | Enable the capture of the jvm.gc.cause attribute. Will always be captured in 3.0. |
| `otel.instrumentation.runtime-telemetry.emit-experimental-telemetry` | Boolean | `false` | Use `emit-experimental-metrics` instead. |
| `otel.instrumentation.runtime-telemetry.package-emitter.enabled` | Boolean | `false` | Use `experimental.package-emitter.enabled` instead. |
| `otel.instrumentation.runtime-telemetry.package-emitter.jars-per-second` | Integer | 10 | Use `experimental.package-emitter.jars-per-second` instead. |
| `otel.instrumentation.runtime-telemetry-java17.enabled` | Boolean | `false` | Deprecated. Use `emit-experimental-metrics` for experimental JFR features. |
| `otel.instrumentation.runtime-telemetry-java17.enable-all` | Boolean | `false` | Deprecated. Use `emit-experimental-metrics` and `experimental.prefer-jfr`. |
16 changes: 16 additions & 0 deletions instrumentation/runtime-telemetry/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("otel.javaagent-instrumentation")
}

dependencies {
implementation(project(":instrumentation:runtime-telemetry:library"))

compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
compileOnly("io.opentelemetry:opentelemetry-api-incubator")
}

tasks {
test {
jvmArgs("-Dotel.instrumentation.runtime-telemetry.experimental.package-emitter.enabled=true")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.runtimemetrics.java8;
package io.opentelemetry.javaagent.instrumentation.runtimetelemetry;

import static io.opentelemetry.javaagent.instrumentation.runtimemetrics.java8.JarDetails.EAR_EXTENSION;
import static io.opentelemetry.javaagent.instrumentation.runtimemetrics.java8.JarDetails.JAR_EXTENSION;
import static io.opentelemetry.javaagent.instrumentation.runtimemetrics.java8.JarDetails.WAR_EXTENSION;
import static io.opentelemetry.javaagent.instrumentation.runtimetelemetry.JarDetails.EAR_EXTENSION;
import static io.opentelemetry.javaagent.instrumentation.runtimetelemetry.JarDetails.JAR_EXTENSION;
import static io.opentelemetry.javaagent.instrumentation.runtimetelemetry.JarDetails.WAR_EXTENSION;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.INFO;
Expand All @@ -17,8 +17,8 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.internal.DaemonThreadFactory;
import io.opentelemetry.sdk.common.internal.RateLimiter;
Expand All @@ -34,7 +34,6 @@
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.logging.Logger;

/**
* {@link JarAnalyzer} is a {@link ClassFileTransformer} which processes the {@link
Expand All @@ -43,7 +42,8 @@
*/
final class JarAnalyzer implements ClassFileTransformer {

private static final Logger logger = Logger.getLogger(JarAnalyzer.class.getName());
private static final java.util.logging.Logger logger =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use import

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't because also using io.opentelemetry.api.logs.Logger in this class

java.util.logging.Logger.getLogger(JarAnalyzer.class.getName());

private static final String EVENT_NAME_INFO = "package.info";
static final AttributeKey<String> PACKAGE_NAME = AttributeKey.stringKey("package.name");
Expand All @@ -59,25 +59,27 @@ final class JarAnalyzer implements ClassFileTransformer {
private final Set<URI> seenUris = new HashSet<>();
private final BlockingQueue<URL> toProcess = new LinkedBlockingDeque<>();

private JarAnalyzer(OpenTelemetry openTelemetry, int jarsPerSecond) {
ExtendedLogRecordBuilder logRecordBuilder =
(ExtendedLogRecordBuilder)
openTelemetry
.getLogsBridge()
.loggerBuilder(JmxRuntimeMetricsUtil.getInstrumentationName())
.setInstrumentationVersion(JmxRuntimeMetricsUtil.getInstrumentationVersion())
.build()
.logRecordBuilder();
Worker worker = new Worker(logRecordBuilder, toProcess, jarsPerSecond);
private JarAnalyzer(OpenTelemetry openTelemetry, String instrumentationName, int jarsPerSecond) {
String instrumentationVersion =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version is nullable - add requireNonNull

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

EmbeddedInstrumentationProperties.findVersion(instrumentationName);
Logger logger =
openTelemetry
.getLogsBridge()
.loggerBuilder(instrumentationName)
.setInstrumentationVersion(instrumentationVersion)
.build();
Worker worker = new Worker(logger, toProcess, jarsPerSecond);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated improvement: Using Logger instead LogRecordBuilder (since it's used to emit multiple logs)

Thread workerThread =
new DaemonThreadFactory(JarAnalyzer.class.getSimpleName() + "_WorkerThread")
.newThread(worker);
workerThread.start();
}

/** Create {@link JarAnalyzer} and start the worker thread. */
public static JarAnalyzer create(OpenTelemetry unused, int jarsPerSecond) {
return new JarAnalyzer(unused, jarsPerSecond);
// TODO can remove instrumentationName parameter in 3.0
public static JarAnalyzer create(
OpenTelemetry openTelemetry, String instrumentationName, int jarsPerSecond) {
return new JarAnalyzer(openTelemetry, instrumentationName, jarsPerSecond);
}

/**
Expand Down Expand Up @@ -156,20 +158,19 @@ private void handle(ProtectionDomain protectionDomain) {

private static final class Worker implements Runnable {

private final ExtendedLogRecordBuilder eventLogger;
private final Logger logger;
private final BlockingQueue<URL> toProcess;
private final RateLimiter rateLimiter;

private Worker(
ExtendedLogRecordBuilder eventLogger, BlockingQueue<URL> toProcess, int jarsPerSecond) {
this.eventLogger = eventLogger;
private Worker(Logger logger, BlockingQueue<URL> toProcess, int jarsPerSecond) {
this.logger = logger;
this.toProcess = toProcess;
this.rateLimiter = new RateLimiter(jarsPerSecond, jarsPerSecond, Clock.getDefault());
}

/**
* Continuously poll the {@link #toProcess} for archive {@link URL}s, and process each wit
* {@link #processUrl(ExtendedLogRecordBuilder, URL)}.
* {@link #processUrl(Logger, URL)}.
*/
@Override
public void run() {
Expand All @@ -190,25 +191,26 @@ public void run() {
try {
// TODO(jack-berg): add ability to optionally re-process urls periodically to re-emit
// events
processUrl(eventLogger, archiveUrl);
processUrl(logger, archiveUrl);
} catch (Throwable e) {
logger.log(WARNING, "Unexpected error processing archive URL: " + archiveUrl, e);
JarAnalyzer.logger.log(
WARNING, "Unexpected error processing archive URL: " + archiveUrl, e);
}
}
logger.warning("JarAnalyzer stopped");
JarAnalyzer.logger.warning("JarAnalyzer stopped");
}
}

/**
* Process the {@code archiveUrl}, extracting metadata from it and emitting an event with the
* content.
*/
static void processUrl(ExtendedLogRecordBuilder eventLogger, URL archiveUrl) {
static void processUrl(Logger logger, URL archiveUrl) {
JarDetails jarDetails;
try {
jarDetails = JarDetails.forUrl(archiveUrl);
} catch (IOException e) {
logger.log(WARNING, "Error reading package for archive URL: " + archiveUrl, e);
JarAnalyzer.logger.log(WARNING, "Error reading package for archive URL: " + archiveUrl, e);
return;
}
AttributesBuilder builder = Attributes.builder();
Expand Down Expand Up @@ -242,6 +244,10 @@ static void processUrl(ExtendedLogRecordBuilder eventLogger, URL archiveUrl) {
builder.put(PACKAGE_CHECKSUM, packageChecksum);
builder.put(PACKAGE_CHECKSUM_ALGORITHM, "SHA1");

eventLogger.setEventName(EVENT_NAME_INFO).setAllAttributes(builder.build()).emit();
logger
.logRecordBuilder()
.setEventName(EVENT_NAME_INFO)
.setAllAttributes(builder.build())
.emit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.runtimetelemetry;

import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.instrument.Instrumentation;
import java.util.logging.Logger;

/** Installs the {@link JarAnalyzer}. */
@AutoService(BeforeAgentListener.class)
public class JarAnalyzerInstaller implements BeforeAgentListener {

private static final Logger logger = Logger.getLogger(JarAnalyzerInstaller.class.getName());
private static final int DEFAULT_JARS_PER_SECOND = 10;

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
DeclarativeConfigProperties config =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do I get it right that we're no longer checking if a module is active?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's still being checked, a few lines down

DeclarativeConfigUtil.getInstrumentationConfig(openTelemetry, "runtime_telemetry");

// Support both new config (package_emitter/development) and old path
// (package_emitter)
DeclarativeConfigProperties newPackageEmitterConfig = config.get("package_emitter/development");
DeclarativeConfigProperties oldPackageEmitterConfig = config.get("package_emitter");

boolean enabledNew = newPackageEmitterConfig.getBoolean("enabled", false);
boolean enabledOld = oldPackageEmitterConfig.getBoolean("enabled", false);
if (enabledOld) {
logger.warning(
"otel.instrumentation.runtime-telemetry.package-emitter.enabled is deprecated and will"
+ " be removed in 3.0. Use"
+ " otel.instrumentation.runtime-telemetry.experimental.package-emitter.enabled"
+ " instead.");
}
if (!enabledNew && !enabledOld) {
return;
}
Instrumentation inst = InstrumentationHolder.getInstrumentation();
if (inst == null) {
return;
}

// Use appropriate instrumentation name based on which config is active
String instrumentationName =
enabledNew
? "io.opentelemetry.runtime-telemetry"
: "io.opentelemetry.runtime-telemetry-java8";

int newJarsPerSecond = newPackageEmitterConfig.getInt("jars_per_second", -1);
int oldJarsPerSecond = oldPackageEmitterConfig.getInt("jars_per_second", -1);

if (oldJarsPerSecond >= 0) {
logger.warning(
"otel.instrumentation.runtime-telemetry.package-emitter.jars-per-second is deprecated"
+ " and will be removed in 3.0. Use"
+ " otel.instrumentation.runtime-telemetry.experimental.package-emitter.jars-per-second"
+ " instead.");
}

int jarsPerSecond;
if (newJarsPerSecond >= 0) {
jarsPerSecond = newJarsPerSecond;
} else if (oldJarsPerSecond >= 0) {
jarsPerSecond = oldJarsPerSecond;
} else {
jarsPerSecond = DEFAULT_JARS_PER_SECOND;
}

JarAnalyzer jarAnalyzer = JarAnalyzer.create(openTelemetry, instrumentationName, jarsPerSecond);
inst.addTransformer(jarAnalyzer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.runtimemetrics.java8;
package io.opentelemetry.javaagent.instrumentation.runtimetelemetry;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.collectingAndThen;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,30 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.runtimemetrics.java17;
package io.opentelemetry.javaagent.instrumentation.runtimetelemetry;

import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil;
import io.opentelemetry.instrumentation.runtimetelemetry.RuntimeTelemetry;
import io.opentelemetry.instrumentation.runtimetelemetry.internal.Internal;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.extension.instrumentation.internal.AgentDistributionConfig;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;

/** An {@link AgentListener} that enables runtime metrics during agent startup. */
@AutoService(AgentListener.class)
public class Java17RuntimeMetricsInstaller implements AgentListener {
public class RuntimeTelemetryInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
RuntimeMetrics runtimeMetrics =
RuntimeMetricsConfigUtil.configure(
RuntimeMetrics.builder(GlobalOpenTelemetry.get()),
RuntimeTelemetry runtimeTelemetry =
Internal.configure(
GlobalOpenTelemetry.get(),
AgentDistributionConfig.get().isInstrumentationDefaultEnabled());
if (runtimeMetrics != null) {
if (runtimeTelemetry != null) {
Runtime.getRuntime()
.addShutdownHook(
new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook"));
new Thread(runtimeTelemetry::close, "OpenTelemetry RuntimeTelemetryShutdownHook"));
}
}
}
Loading
Loading