-
Notifications
You must be signed in to change notification settings - Fork 335
Support OTLP runtime metrics with OTel-native naming #11318
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
Open
link04
wants to merge
12
commits into
master
Choose a base branch
from
maximo/otlp-runtime-metrics
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
d308063
Adding configs and metrics creation
link04 5e8567f
Adding test file to check metrics are collected
link04 4a2901f
Doing clean up after testing
link04 6a04bcc
Merge branch 'master' into maximo/otlp-runtime-metrics
link04 953c871
Merge branch 'master' into maximo/otlp-runtime-metrics
link04 9759292
move JvmOtlpRuntimeMetrics.java to agent-jmxfetch
mhlidd 729a87e
prevent JMXFetch from emitting jvm metrics when otlp is enabled; migr…
mhlidd 5a3a4d1
update JMXFetch to only emit either OTLP or JMX runtime metrics
mhlidd 8e79c2e
Merge branch 'master' into maximo/otlp-runtime-metrics
mhlidd 414112d
send otlp_jmx_config when otlp runtime metrics enabled
mhlidd 4533351
update test to assert on guarantees instead of dependent on GC collec…
mhlidd 0f455be
Merge branch 'master' into maximo/otlp-runtime-metrics
mhlidd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
291 changes: 291 additions & 0 deletions
291
...tel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,291 @@ | ||
| package datadog.opentelemetry.shim.metrics; | ||
|
|
||
| import com.sun.management.OperatingSystemMXBean; | ||
| import io.opentelemetry.api.common.AttributeKey; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.api.metrics.Meter; | ||
| import java.lang.management.BufferPoolMXBean; | ||
| import java.lang.management.ClassLoadingMXBean; | ||
| import java.lang.management.ManagementFactory; | ||
| import java.lang.management.MemoryMXBean; | ||
| import java.lang.management.MemoryPoolMXBean; | ||
| import java.lang.management.MemoryUsage; | ||
| import java.lang.management.ThreadMXBean; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.function.ToLongFunction; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * Registers JVM runtime metrics with OTel-native names against the agent's MeterProvider. See | ||
| * https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/. | ||
| */ | ||
| public final class JvmOtlpRuntimeMetrics { | ||
|
mhlidd marked this conversation as resolved.
Outdated
|
||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(JvmOtlpRuntimeMetrics.class); | ||
| private static final String INSTRUMENTATION_SCOPE = "datadog.jvm.runtime"; | ||
| private static final AttributeKey<String> MEMORY_TYPE = AttributeKey.stringKey("jvm.memory.type"); | ||
| private static final AttributeKey<String> MEMORY_POOL = | ||
| AttributeKey.stringKey("jvm.memory.pool.name"); | ||
| private static final AttributeKey<String> BUFFER_POOL = | ||
| AttributeKey.stringKey("jvm.buffer.pool.name"); | ||
| private static final Attributes HEAP_ATTRS = Attributes.of(MEMORY_TYPE, "heap"); | ||
| private static final Attributes NON_HEAP_ATTRS = Attributes.of(MEMORY_TYPE, "non_heap"); | ||
|
|
||
| private static final AtomicBoolean started = new AtomicBoolean(false); | ||
|
|
||
| /** Registers all JVM runtime metric instruments on the OTel MeterProvider. */ | ||
| public static void start() { | ||
| if (!started.compareAndSet(false, true)) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| Meter meter = OtelMeterProvider.INSTANCE.get(INSTRUMENTATION_SCOPE); | ||
| registerMemoryMetrics(meter); | ||
| registerBufferMetrics(meter); | ||
| registerThreadMetrics(meter); | ||
| registerClassLoadingMetrics(meter); | ||
| registerCpuMetrics(meter); | ||
| log.debug("Started OTLP runtime metrics with OTel-native naming (jvm.*)"); | ||
| } catch (Exception e) { | ||
| log.error("Failed to start JVM OTLP runtime metrics", e); | ||
| } | ||
| } | ||
|
|
||
| // jvm.gc.duration is excluded — spec requires Histogram, JMX only exposes cumulative time. | ||
|
|
||
| /** | ||
| * jvm.memory.used, jvm.memory.committed, jvm.memory.limit, jvm.memory.init, | ||
| * jvm.memory.used_after_last_gc — all UpDownCounter per spec. | ||
| */ | ||
| private static void registerMemoryMetrics(Meter meter) { | ||
| MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); | ||
| List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans(); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.memory.used") | ||
| .setDescription("Measure of memory used.") | ||
| .setUnit("By") | ||
| .buildWithCallback( | ||
|
mhlidd marked this conversation as resolved.
Outdated
|
||
| measurement -> { | ||
| measurement.record(memoryBean.getHeapMemoryUsage().getUsed(), HEAP_ATTRS); | ||
| measurement.record(memoryBean.getNonHeapMemoryUsage().getUsed(), NON_HEAP_ATTRS); | ||
| for (MemoryPoolMXBean pool : pools) { | ||
| measurement.record(pool.getUsage().getUsed(), poolAttributes(pool)); | ||
| } | ||
| }); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.memory.committed") | ||
| .setDescription("Measure of memory committed.") | ||
| .setUnit("By") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| measurement.record(memoryBean.getHeapMemoryUsage().getCommitted(), HEAP_ATTRS); | ||
| measurement.record(memoryBean.getNonHeapMemoryUsage().getCommitted(), NON_HEAP_ATTRS); | ||
| for (MemoryPoolMXBean pool : pools) { | ||
| measurement.record(pool.getUsage().getCommitted(), poolAttributes(pool)); | ||
| } | ||
| }); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.memory.limit") | ||
| .setDescription("Measure of max obtainable memory.") | ||
| .setUnit("By") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| long heapMax = memoryBean.getHeapMemoryUsage().getMax(); | ||
| if (heapMax > 0) { | ||
| measurement.record(heapMax, HEAP_ATTRS); | ||
| } | ||
| long nonHeapMax = memoryBean.getNonHeapMemoryUsage().getMax(); | ||
| if (nonHeapMax > 0) { | ||
| measurement.record(nonHeapMax, NON_HEAP_ATTRS); | ||
| } | ||
| for (MemoryPoolMXBean pool : pools) { | ||
| long max = pool.getUsage().getMax(); | ||
| if (max > 0) { | ||
| measurement.record(max, poolAttributes(pool)); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.memory.init") | ||
| .setDescription("Measure of initial memory requested.") | ||
| .setUnit("By") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| long heapInit = memoryBean.getHeapMemoryUsage().getInit(); | ||
| if (heapInit > 0) { | ||
| measurement.record(heapInit, HEAP_ATTRS); | ||
| } | ||
| long nonHeapInit = memoryBean.getNonHeapMemoryUsage().getInit(); | ||
| if (nonHeapInit > 0) { | ||
| measurement.record(nonHeapInit, NON_HEAP_ATTRS); | ||
| } | ||
| }); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.memory.used_after_last_gc") | ||
| .setDescription("Measure of memory used after the most recent garbage collection event.") | ||
| .setUnit("By") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| for (MemoryPoolMXBean pool : pools) { | ||
| MemoryUsage collectionUsage = pool.getCollectionUsage(); | ||
| if (collectionUsage != null && collectionUsage.getUsed() >= 0) { | ||
| measurement.record(collectionUsage.getUsed(), poolAttributes(pool)); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** jvm.buffer.* (UpDownCounter, Development) — direct + mapped pool metrics. */ | ||
| private static void registerBufferMetrics(Meter meter) { | ||
| List<BufferPoolMXBean> bufferPools = | ||
| ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); | ||
| bufferPoolMetric( | ||
| meter, | ||
| "jvm.buffer.memory.used", | ||
| "Measure of memory used by buffers.", | ||
| "By", | ||
| bufferPools, | ||
| BufferPoolMXBean::getMemoryUsed); | ||
| bufferPoolMetric( | ||
| meter, | ||
| "jvm.buffer.memory.limit", | ||
| "Measure of total memory capacity of buffers.", | ||
| "By", | ||
| bufferPools, | ||
| BufferPoolMXBean::getTotalCapacity); | ||
| bufferPoolMetric( | ||
| meter, | ||
| "jvm.buffer.count", | ||
| "Number of buffers in the pool.", | ||
| "{buffer}", | ||
| bufferPools, | ||
| BufferPoolMXBean::getCount); | ||
| } | ||
|
|
||
| /** jvm.thread.count (UpDownCounter, Stable). */ | ||
| private static void registerThreadMetrics(Meter meter) { | ||
| ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); | ||
| meter | ||
| .upDownCounterBuilder("jvm.thread.count") | ||
| .setDescription("Number of executing platform threads.") | ||
| .setUnit("{thread}") | ||
| .buildWithCallback(measurement -> measurement.record(threadBean.getThreadCount())); | ||
| } | ||
|
|
||
| /** | ||
| * jvm.class.loaded (Counter), jvm.class.unloaded (Counter), jvm.class.count (UpDownCounter) — all | ||
| * Stable per spec. | ||
| */ | ||
| private static void registerClassLoadingMetrics(Meter meter) { | ||
| ClassLoadingMXBean classLoadingBean = ManagementFactory.getClassLoadingMXBean(); | ||
| meter | ||
| .counterBuilder("jvm.class.loaded") | ||
| .setDescription("Number of classes loaded since JVM start.") | ||
| .setUnit("{class}") | ||
| .buildWithCallback( | ||
| measurement -> measurement.record(classLoadingBean.getTotalLoadedClassCount())); | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.class.count") | ||
| .setDescription("Number of classes currently loaded.") | ||
| .setUnit("{class}") | ||
| .buildWithCallback( | ||
| measurement -> measurement.record(classLoadingBean.getLoadedClassCount())); | ||
|
|
||
| meter | ||
| .counterBuilder("jvm.class.unloaded") | ||
| .setDescription("Number of classes unloaded since JVM start.") | ||
| .setUnit("{class}") | ||
| .buildWithCallback( | ||
| measurement -> measurement.record(classLoadingBean.getUnloadedClassCount())); | ||
| } | ||
|
|
||
| /** | ||
| * jvm.cpu.time (Counter), jvm.cpu.count (UpDownCounter), jvm.cpu.recent_utilization (Gauge) — all | ||
| * Stable per spec. | ||
| */ | ||
| private static void registerCpuMetrics(Meter meter) { | ||
| java.lang.management.OperatingSystemMXBean rawOsBean = | ||
| ManagementFactory.getOperatingSystemMXBean(); | ||
| OperatingSystemMXBean osBean = | ||
| rawOsBean instanceof OperatingSystemMXBean ? (OperatingSystemMXBean) rawOsBean : null; | ||
|
|
||
| if (osBean != null) { | ||
| meter | ||
| .counterBuilder("jvm.cpu.time") | ||
| .ofDoubles() | ||
| .setDescription("CPU time used by the process as reported by the JVM.") | ||
| .setUnit("s") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| long nanos = osBean.getProcessCpuTime(); | ||
| if (nanos >= 0) { | ||
| measurement.record(nanos / 1e9); | ||
| } | ||
| }); | ||
|
|
||
| meter | ||
| .gaugeBuilder("jvm.cpu.recent_utilization") | ||
| .setDescription("Recent CPU utilization for the process as reported by the JVM.") | ||
| .setUnit("1") | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| double cpuLoad = osBean.getProcessCpuLoad(); | ||
| if (cpuLoad >= 0) { | ||
| measurement.record(cpuLoad); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| meter | ||
| .upDownCounterBuilder("jvm.cpu.count") | ||
| .setDescription("Number of processors available to the JVM.") | ||
| .setUnit("{cpu}") | ||
| .buildWithCallback( | ||
| measurement -> measurement.record(Runtime.getRuntime().availableProcessors())); | ||
| } | ||
|
|
||
| /** | ||
| * Builds an UpDownCounter that iterates each platform buffer pool and records {@code getter} with | ||
| * the {@code jvm.buffer.pool.name} attribute. Skips negative readings. | ||
| */ | ||
| private static void bufferPoolMetric( | ||
| Meter meter, | ||
| String name, | ||
| String description, | ||
| String unit, | ||
| List<BufferPoolMXBean> bufferPools, | ||
| ToLongFunction<BufferPoolMXBean> getter) { | ||
| meter | ||
| .upDownCounterBuilder(name) | ||
| .setDescription(description) | ||
| .setUnit(unit) | ||
| .buildWithCallback( | ||
| measurement -> { | ||
| for (BufferPoolMXBean pool : bufferPools) { | ||
| long value = getter.applyAsLong(pool); | ||
| if (value >= 0) { | ||
| measurement.record(value, Attributes.of(BUFFER_POOL, pool.getName())); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** Returns Attributes carrying jvm.memory.type and jvm.memory.pool.name for the given pool. */ | ||
| private static Attributes poolAttributes(MemoryPoolMXBean pool) { | ||
| return Attributes.of( | ||
| MEMORY_TYPE, pool.getType().name().toLowerCase(Locale.ROOT), | ||
| MEMORY_POOL, pool.getName()); | ||
| } | ||
|
|
||
| private JvmOtlpRuntimeMetrics() {} | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.