Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stable JVM semconv implementation: classes #9821

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,43 @@ public final class SemconvStability {

private static final boolean emitOldHttpSemconv;
private static final boolean emitStableHttpSemconv;
private static final boolean emitOldJvmSemconv;
private static final boolean emitStableJvmSemconv;

static {
boolean old = true;
boolean stable = false;
boolean oldHttp = true;
boolean stableHttp = false;
boolean oldJvm = true;
boolean stableJvm = false;

String value = ConfigPropertiesUtil.getString("otel.semconv-stability.opt-in");
if (value != null) {
Set<String> values = new HashSet<>(asList(value.split(",")));
if (values.contains("http")) {
old = false;
stable = true;
oldHttp = false;
stableHttp = true;
}
// no else -- technically it's possible to set "http,http/dup", in which case we should emit
// both sets of attributes
if (values.contains("http/dup")) {
old = true;
stable = true;
oldHttp = true;
stableHttp = true;
}

if (values.contains("jvm")) {
oldJvm = false;
stableJvm = true;
}
if (values.contains("jvm/dup")) {
oldJvm = true;
stableJvm = true;
}
}

emitOldHttpSemconv = old;
emitStableHttpSemconv = stable;
emitOldHttpSemconv = oldHttp;
emitStableHttpSemconv = stableHttp;
emitOldJvmSemconv = oldJvm;
emitStableJvmSemconv = stableJvm;
}

public static boolean emitOldHttpSemconv() {
Expand All @@ -50,5 +65,13 @@ public static boolean emitStableHttpSemconv() {
return emitStableHttpSemconv;
}

public static boolean emitOldJvmSemconv() {
return emitOldJvmSemconv;
}

public static boolean emitStableJvmSemconv() {
return emitStableJvmSemconv;
}

private SemconvStability() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,27 @@ dependencies {

testImplementation(project(":testing-common"))
}

testing {
suites {
val testStableSemconv by registering(JvmTestSuite::class) {
dependencies {
implementation(project())
implementation(project(":testing-common"))
}
targets {
all {
testTask.configure {
jvmArgs("-Dotel.semconv-stability.opt-in=jvm")
}
}
}
}
}
}

tasks {
check {
dependsOn(testing.suites)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
Expand All @@ -29,6 +30,19 @@
* process.runtime.jvm.classes.unloaded 2
* process.runtime.jvm.classes.current_loaded 98
* </pre>
*
* <p>In case you enable the preview of stable JVM semantic conventions (e.g. by setting the {@code
* otel.semconv-stability.opt-in} system property to {@code jvm}), the metrics being exported will
* follow <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md">the
* most recent JVM semantic conventions</a>. This is how the example above looks when stable JVM
* semconv is enabled:
*
* <pre>
* jvm.class.loaded 100
* jvm.class.unloaded 2
* jvm.class.count 98
* </pre>
*/
public final class Classes {

Expand All @@ -44,31 +58,61 @@ public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry)
List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean) {
Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry);
List<AutoCloseable> observables = new ArrayList<>();
observables.add(
meter
.counterBuilder("process.runtime.jvm.classes.loaded")
.setDescription("Number of classes loaded since JVM start")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getTotalLoadedClassCount())));

observables.add(
meter
.counterBuilder("process.runtime.jvm.classes.unloaded")
.setDescription("Number of classes unloaded since JVM start")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getUnloadedClassCount())));
observables.add(
meter
.upDownCounterBuilder("process.runtime.jvm.classes.current_loaded")
.setDescription("Number of classes currently loaded")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getLoadedClassCount())));
if (SemconvStability.emitOldJvmSemconv()) {
observables.add(
meter
.counterBuilder("process.runtime.jvm.classes.loaded")
.setDescription("Number of classes loaded since JVM start")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getTotalLoadedClassCount())));
observables.add(
meter
.counterBuilder("process.runtime.jvm.classes.unloaded")
.setDescription("Number of classes unloaded since JVM start")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getUnloadedClassCount())));
observables.add(
meter
.upDownCounterBuilder("process.runtime.jvm.classes.current_loaded")
.setDescription("Number of classes currently loaded")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getLoadedClassCount())));
}

if (SemconvStability.emitStableJvmSemconv()) {
observables.add(
meter
.counterBuilder("jvm.class.loaded")
.setDescription("Number of classes loaded since JVM start.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getTotalLoadedClassCount())));
observables.add(
meter
.counterBuilder("jvm.class.unloaded")
.setDescription("Number of classes unloaded since JVM start.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getUnloadedClassCount())));
observables.add(
meter
.upDownCounterBuilder("jvm.class.count")
.setDescription("Number of classes currently loaded.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getLoadedClassCount())));
}

return observables;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics.java8;

import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.mockito.Mockito.when;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import java.lang.management.ClassLoadingMXBean;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ClassesStableSemconvTest {

@RegisterExtension
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();

@Mock private ClassLoadingMXBean classBean;

@Test
void registerObservers() {
when(classBean.getTotalLoadedClassCount()).thenReturn(3L);
when(classBean.getUnloadedClassCount()).thenReturn(2L);
when(classBean.getLoadedClassCount()).thenReturn(1);

Classes.INSTANCE.registerObservers(testing.getOpenTelemetry(), classBean);

testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-telemetry-java8",
"jvm.class.loaded",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(EXPECTED_SCOPE)
.hasDescription("Number of classes loaded since JVM start.")
.hasUnit("{class}")
.hasLongSumSatisfying(
sum ->
sum.isMonotonic()
.hasPointsSatisfying(
point ->
point.hasValue(3).hasAttributes(Attributes.empty())))));
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-telemetry-java8",
"jvm.class.unloaded",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(EXPECTED_SCOPE)
.hasDescription("Number of classes unloaded since JVM start.")
.hasUnit("{class}")
.hasLongSumSatisfying(
sum ->
sum.isMonotonic()
.hasPointsSatisfying(
point ->
point.hasValue(2).hasAttributes(Attributes.empty())))));
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-telemetry-java8",
"jvm.class.count",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(EXPECTED_SCOPE)
.hasDescription("Number of classes currently loaded.")
.hasUnit("{class}")
.hasLongSumSatisfying(
sum ->
sum.isNotMonotonic()
.hasPointsSatisfying(
point ->
point.hasValue(1).hasAttributes(Attributes.empty())))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics.java8;

import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
import javax.annotation.Nullable;

class ScopeUtil {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.runtime-telemetry-java8";

@Nullable
private static final String INSTRUMENTATION_VERSION =
EmbeddedInstrumentationProperties.findVersion(INSTRUMENTATION_NAME);

static final InstrumentationScopeInfo EXPECTED_SCOPE;

static {
InstrumentationScopeInfoBuilder builder =
InstrumentationScopeInfo.builder(INSTRUMENTATION_NAME);
if (INSTRUMENTATION_VERSION != null) {
builder.setVersion(INSTRUMENTATION_VERSION);
}
EXPECTED_SCOPE = builder.build();
}

private ScopeUtil() {}
}
Loading