OpenTelemetry Runtime Metrics
Overview
Runtime metrics provide insights into application performance, including memory usage, garbage collection, and parallelization. Datadog SDKs offer runtime metrics collection for each supported language, and OpenTelemetry (OTel) also collects compatible runtime metrics that can be sent to Datadog through the OpenTelemetry SDKs.
Compatibility
Datadog supports OpenTelemetry runtime metrics for the following languages:
For details about host and container metrics mapping, see OpenTelemetry Metrics Mapping.
Setup instructions
1. Prerequisites
Select your language to see instructions for configuring the OpenTelemetry SDK to send runtime metrics:
Note: Runtime metrics are only exported if a MeterProvider and metric exporter are configured. Set the OTEL_METRICS_EXPORTER environment variable or programmatically configure a metricReader in your SDK initialization. For Go, Node.js, and Python, the MeterProvider must be configured manually; Java and .NET auto-configure it via their auto-instrumentation agents.
Automatic instrumentation
If you use OpenTelemetry automatic instrumentation for Java applications, runtime metrics are enabled by default.
Manual instrumentation
If you use OpenTelemetry manual instrumentation, follow the guides for your Java version:
Experimental telemetry
Enabling experimental OpenTelemetry runtime telemetry may provide additional metric mappings between Datadog and OpenTelemetry for Java. To enable it, set the following environment variable:
OTEL_INSTRUMENTATION_RUNTIME_TELEMETRY_EMIT_EXPERIMENTAL_TELEMETRY=true
Additional JMX metrics
To collect additional JVM metrics beyond the default runtime instrumentation, install the OpenTelemetry JMX Metric Scraper. The scraper scrapes MBeans over JMX and emits them as OpenTelemetry metrics, which Datadog then maps to runtime metrics (see the JVM Contrib table in Data collected).
Configure the scraper with otel.jmx.target.source=legacy to collect these additional metrics. The scraper’s instrumentation source emits the same semantic-convention metrics already produced natively by the OpenTelemetry Java SDK, so it does not provide additional coverage.
Collector configuration
The jvm.gc.collections.count and jvm.gc.collections.elapsed metrics require the Delta-to-Rate Processor in the OpenTelemetry Collector. Set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta or use the cumulativetodelta processor.
processors:
deltatorate:
metrics:
- jvm.gc.collections.count
- jvm.gc.collections.elapsed
Note: The minimum supported version of the OpenTelemetry Java agent is 2.0.0.
Manual instrumentation
OpenTelemetry Go applications are instrumented manually. To enable runtime metrics, see the documentation for the runtime package.
Collector configuration
Some Go runtime metrics are reported as monotonic sums, but Datadog expects them as gauges. The Transform Processor converts these sums to gauges, and the Cumulative-to-Delta Processor excludes them from delta conversion. Verify that OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is not set to delta. Otherwise, metrics excluded by cumulativetodelta are ignored.
processors:
transform/go-runtime:
error_mode: ignore
metric_statements:
- context: metric
statements:
- convert_sum_to_gauge() where name == "process.runtime.go.gc.pause_total_ns" or name == "process.runtime.go.gc.count" or name == "go.memory.allocated" or name == "go.memory.allocations"
cumulativetodelta:
exclude:
metrics:
- process.runtime.go.gc.pause_total_ns
- process.runtime.go.gc.count
- go.memory.allocated
- go.memory.allocations
match_type: strict
Note: The minimum supported version of go.opentelemetry.io/contrib/instrumentation/runtime is v0.46.0, which also requires Go 1.20+ and OpenTelemetry Go SDK v1.21.0+.
Automatic instrumentation
If you use OpenTelemetry automatic instrumentation for .NET applications, runtime metrics are enabled by default.
Manual instrumentation
If you use OpenTelemetry manual instrumentation, see the documentation for the OpenTelemetry.Instrumentation.Runtime library.
Metric export interval
The default metric export interval for the .NET OTel SDK differs from the default for the Datadog .NET SDK. Datadog recommends setting the OTEL_METRIC_EXPORT_INTERVAL environment variable on your .NET service to match the default Datadog metric export interval:
OTEL_METRIC_EXPORT_INTERVAL=10000
Collector configuration
The dotnet.process.cpu.time and process.cpu.time metrics require the Delta-to-Rate Processor in the OpenTelemetry Collector. Set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta or use the cumulativetodelta processor.
processors:
deltatorate:
metrics:
- dotnet.process.cpu.time
- process.cpu.time
Note: The minimum supported version of the .NET OpenTelemetry SDK is 1.5.0.
Installation
Runtime metrics are not enabled by default for Python applications. Install the opentelemetry-instrumentation-system-metrics package:
pip install opentelemetry-instrumentation-system-metrics
Automatic instrumentation
If you use OpenTelemetry automatic instrumentation for Python applications, opentelemetry-instrument discovers and enables the package after installation.
Manual instrumentation
If you use OpenTelemetry manual instrumentation, enable the package in your application:
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
SystemMetricsInstrumentor().instrument()
View runtime metric dashboards
After setup is complete, you can view runtime metrics in:
- The service’s details page (see Java example below)
- The flame graph metrics tab
- Default runtime dashboards (Java Example)
Data collected
The following tables list the OpenTelemetry runtime metrics collected for Datadog’s out-of-the-box in-app experiences, along with a description and any attribute conditions (Filter) that Datadog uses to identify the correct data point.
For Collector processor configuration required to make these metrics compatible with Datadog, see the Collector configuration instructions above.
JVM Instrumentation
These metrics are collected when using the latest version of the OpenTelemetry Java SDK.
| OTEL | DESCRIPTION | FILTER |
|---|
| jvm.buffer.count | Number of buffers in the pool. | jvm.buffer.pool.name: direct |
| jvm.buffer.count | Number of buffers in the pool. | jvm.buffer.pool.name: mapped |
| jvm.buffer.memory.limit | Measure of total memory capacity of buffers. | jvm.buffer.pool.name: direct |
| jvm.buffer.memory.limit | Measure of total memory capacity of buffers. | jvm.buffer.pool.name: mapped |
| jvm.buffer.memory.used | Measure of memory used by buffers. | jvm.buffer.pool.name: direct |
| jvm.buffer.memory.used | Measure of memory used by buffers. | jvm.buffer.pool.name: mapped |
| jvm.class.count | Number of classes currently loaded. | |
| jvm.cpu.recent_utilization | Recent CPU utilization for the process as reported by the JVM. Note: The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike system.cpu.utilization). Reference. | |
| jvm.file_descriptor.count | Number of open file descriptors as reported by the JVM. | |
| jvm.memory.used | Measure of memory used. | jvm.memory.pool.name: g1_old_gen, zgc_old_generation, tenured_gen, ps_old_gen, cms_old_gen
jvm.memory.type: heap |
| jvm.memory.used | Measure of memory used. | jvm.memory.pool.name: g1_eden_space, zgc_young_generation, eden_space, ps_eden_space, par_eden_space
jvm.memory.type: heap |
| jvm.memory.used | Measure of memory used. | jvm.memory.pool.name: g1_survivor_space, survivor_space, ps_survivor_space, par_survivor_space
jvm.memory.type: heap |
| jvm.memory.used | Measure of memory used. | jvm.memory.pool.name: metaspace
jvm.memory.type: non_heap |
| jvm.system.cpu.utilization | Recent CPU utilization for the whole system as reported by the JVM. Note: The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike system.cpu.utilization). Reference. | |
JVM Contrib
These metrics are collected when using the OpenTelemetry JMX Metric Scraper.
| OTEL | DESCRIPTION | FILTER |
|---|
| jvm.classes.loaded | The number of loaded classes | |
| jvm.gc.collections.count | The total number of garbage collections that have occurred | name: copy, ps_scavenge, parnew, g1_young_generation, zgc_minor_cycles, zgc_minor_pauses |
| jvm.gc.collections.count | The total number of garbage collections that have occurred | name: marksweepcompact, ps_marksweep, concurrentmarksweep, g1_mixed_generation, g1_old_generation, shenandoah_cycles, zgc_major_cycles, zgc_major_pauses |
| jvm.gc.collections.elapsed | The approximate accumulated collection elapsed time | name: copy, ps_scavenge, parnew, g1_young_generation, zgc_minor_cycles, zgc_minor_pauses |
| jvm.gc.collections.elapsed | The approximate accumulated collection elapsed time | name: marksweepcompact, ps_marksweep, concurrentmarksweep, g1_mixed_generation, g1_old_generation, shenandoah_cycles, zgc_major_cycles, zgc_major_pauses |
| jvm.memory.heap.committed | The amount of memory that is guaranteed to be available for the heap | |
| jvm.memory.heap.init | The initial amount of memory that the JVM requests from the operating system for the heap | |
| jvm.memory.heap.max | The maximum amount of memory can be used for the heap | |
| jvm.memory.heap.used | The current heap memory usage | |
| jvm.memory.nonheap.committed | The amount of memory that is guaranteed to be available for non-heap purposes | |
| jvm.memory.nonheap.init | The initial amount of memory that the JVM requests from the operating system for non-heap purposes | |
| jvm.memory.nonheap.max | The maximum amount of memory can be used for non-heap purposes | |
| jvm.memory.nonheap.used | The current non-heap memory usage | |
| jvm.memory.pool.used | The current memory pool memory usage | name: metaspace |
| jvm.memory.pool.used | The current memory pool memory usage | name: g1_old_gen, zgc_old_generation, tenured_gen, ps_old_gen, cms_old_gen |
| jvm.memory.pool.used | The current memory pool memory usage | name: g1_eden_space, zgc_young_generation, eden_space, ps_eden_space, par_eden_space |
| jvm.memory.pool.used | The current memory pool memory usage | name: g1_survivor_space, survivor_space, ps_survivor_space, par_survivor_space |
| jvm.threads.count | The current number of threads | |
Deprecated JVM Metrics
These metrics are collected when using OpenTelemetry Java SDK versions 1.32.0 and earlier.
| OTEL | DESCRIPTION | FILTER |
|---|
| process.runtime.jvm.buffer.count | Number of buffers in the pool. | pool: direct |
| process.runtime.jvm.buffer.count | Number of buffers in the pool. | pool: mapped |
| process.runtime.jvm.buffer.usage | Measure of memory used by buffers. | pool: direct |
| process.runtime.jvm.buffer.usage | Measure of memory used by buffers. | pool: mapped |
| process.runtime.jvm.classes.current_loaded | Number of classes currently loaded. | |
| process.runtime.jvm.cpu.utilization | Recent CPU utilization for the process. Note: The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike system.cpu.utilization). Reference. | |
| process.runtime.jvm.memory.usage | Measure of memory used. | pool: g1_old_gen, zgc_old_generation, tenured_gen, ps_old_gen, cms_old_gen
type: heap |
| process.runtime.jvm.memory.usage | Measure of memory used. | pool: g1_eden_space, zgc_young_generation, eden_space, ps_eden_space, par_eden_space
type: heap |
| process.runtime.jvm.memory.usage | Measure of memory used. | pool: g1_survivor_space, survivor_space, ps_survivor_space, par_survivor_space
type: heap |
| process.runtime.jvm.memory.usage | Measure of memory used. | pool: metaspace
type: non_heap |
| process.runtime.jvm.system.cpu.utilization | Recent CPU utilization for the whole system as reported by the JVM. Note: The value range is [0.0,1.0]. This utilization is not defined as being for the specific interval since last measurement (unlike system.cpu.utilization). Reference. | |
Go Runtime Metrics
These metrics are collected by the OpenTelemetry Go runtime instrumentation package.
| OTEL | DESCRIPTION |
|---|
| go.config.gogc | Heap size target percentage configured by the user, otherwise 100. Note: The value range is [0.0,100.0]. Computed from /gc/gogc:percent. |
| go.goroutine.count | Count of live goroutines. Note: Computed from /sched/goroutines:goroutines. |
| go.memory.allocated | Memory allocated to the heap by the application. Note: Computed from /gc/heap/allocs:bytes. |
| go.memory.allocations | Count of allocations to the heap by the application. Note: Computed from /gc/heap/allocs:objects. |
| go.memory.gc.goal | Heap size target for the end of the GC cycle. Note: Computed from /gc/heap/goal:bytes. |
| go.memory.limit | Go runtime memory limit configured by the user, if a limit exists. Note: Computed from /gc/gomemlimit:bytes. This metric is excluded if the limit obtained from the Go runtime is math.MaxInt64. |
| go.processor.limit | The number of OS threads that can execute user-level Go code simultaneously. Note: Computed from /sched/gomaxprocs:threads. |
| process.runtime.go.cgo.calls | Number of cgo calls made by the current process |
| process.runtime.go.gc.count | Number of completed garbage collection cycles |
| process.runtime.go.gc.pause_total_ns | Cumulative nanoseconds in GC stop-the-world pauses since the program started |
| process.runtime.go.goroutines | Number of goroutines that currently exist |
| process.runtime.go.mem.heap_alloc | Bytes of allocated heap objects |
| process.runtime.go.mem.heap_idle | Bytes in idle (unused) spans |
| process.runtime.go.mem.heap_inuse | Bytes in in-use spans |
| process.runtime.go.mem.heap_objects | Number of allocated heap objects |
| process.runtime.go.mem.heap_released | Bytes of idle spans whose physical memory has been returned to the OS |
| process.runtime.go.mem.heap_sys | Bytes of heap memory obtained from the OS |
.NET System.Runtime
These metrics are emitted by the .NET runtime's built-in System.Runtime meter on .NET 9.0 and later. The OpenTelemetry SDK collects and exports them automatically.
| OTEL | DESCRIPTION | FILTER |
|---|
| dotnet.gc.last_collection.heap.size | The managed GC heap size (including fragmentation), as observed during the latest garbage collection. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as calling GC.GetGCMemoryInfo().GenerationInfo.SizeAfterBytes. | gc.heap.generation: gen0 |
| dotnet.gc.last_collection.heap.size | The managed GC heap size (including fragmentation), as observed during the latest garbage collection. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as calling GC.GetGCMemoryInfo().GenerationInfo.SizeAfterBytes. | gc.heap.generation: gen1 |
| dotnet.gc.last_collection.heap.size | The managed GC heap size (including fragmentation), as observed during the latest garbage collection. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as calling GC.GetGCMemoryInfo().GenerationInfo.SizeAfterBytes. | gc.heap.generation: gen2 |
| dotnet.gc.last_collection.heap.size | The managed GC heap size (including fragmentation), as observed during the latest garbage collection. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as calling GC.GetGCMemoryInfo().GenerationInfo.SizeAfterBytes. | gc.heap.generation: loh |
| dotnet.process.cpu.time | CPU time used by the process. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as accessing the corresponding processor time properties on System.Diagnostics.Process. | cpu.mode: user |
| dotnet.process.cpu.time | CPU time used by the process. Note: Meter name: System.Runtime; Added in: .NET 9.0. This metric reports the same values as accessing the corresponding processor time properties on System.Diagnostics.Process. | cpu.mode: system |
.NET Contrib Runtime
These metrics are collected by the OpenTelemetry.Instrumentation.Runtime package. On .NET 9.0 and later, these overlap with the System.Runtime metrics above.
| OTEL | DESCRIPTION | FILTER |
|---|
| process.runtime.dotnet.gc.heap.size | The heap size (including fragmentation), as observed during the | generation: gen0 |
| process.runtime.dotnet.gc.heap.size | The heap size (including fragmentation), as observed during the | generation: gen1 |
| process.runtime.dotnet.gc.heap.size | The heap size (including fragmentation), as observed during the | generation: gen2 |
| process.runtime.dotnet.gc.heap.size | The heap size (including fragmentation), as observed during the | generation: loh |
| process.runtime.dotnet.thread_pool.threads.count | The number of thread pool threads that currently exist. | |
.NET Contrib Process
These metrics are collected by the OpenTelemetry.Instrumentation.Process package.
| OTEL | DESCRIPTION |
|---|
| process.thread.count | Process threads count. |
Node.js Contrib Runtime
These metrics are emitted from auto-instrumentation with the latest version of the OpenTelemetry Node.js SDK.
Node.js Contrib Host
These metrics are collected by the @opentelemetry/host-metrics package. This package is not included in OpenTelemetry automatic instrumentation and must be installed and configured manually.
| OTEL | DESCRIPTION | FILTER |
|---|
| process.memory.usage | The amount of physical memory in use. | |
| system.memory.usage | Bytes of memory in use. | |
| system.memory.usage | Bytes of memory in use. | state: free, cached, buffered |
| system.memory.usage | Bytes of memory in use. | state: free |
| system.memory.usage | Bytes of memory in use. | system.memory.state: free |
Python Runtime Metrics
These metrics are collected by the opentelemetry-instrumentation-system-metrics package. The following table lists the conceptual equivalences between OpenTelemetry and Datadog Python runtime metrics. There are no direct metric-name mappings because the metric types differ between the two systems.
| OTEL | Datadog | Description | Filter |
|---|
process.cpu.time | runtime.python.cpu.time.sys | Number of seconds executing in the kernel. | type: system |
process.cpu.time | runtime.python.cpu.time.user | Number of seconds executing outside the kernel. | type: user |
process.cpu.utilization | runtime.python.cpu.percent | CPU utilization percentage. OTel divides the raw value by 100 times the number of CPU cores. | |
process.context_switches | runtime.python.cpu.ctx_switch.voluntary | Number of voluntary context switches. | type: voluntary |
process.context_switches | runtime.python.cpu.ctx_switch.involuntary | Number of involuntary context switches. | type: involuntary |
process.runtime.{python_implementation}.gc_count | runtime.python.gc.count.gen0 | Number of generation 0 objects. | count: 0 |
process.runtime.{python_implementation}.gc_count | runtime.python.gc.count.gen1 | Number of generation 1 objects. | count: 1 |
process.runtime.{python_implementation}.gc_count | runtime.python.gc.count.gen2 | Number of generation 2 objects. | count: 2 |
process.memory.usage | runtime.python.mem.rss | Resident set memory. | |
process.thread.count | runtime.python.thread_count | Number of threads. | |
Troubleshooting
Metric name mapping
OpenTelemetry runtime metrics are mapped to Datadog by metric name. Do not rename host metrics for OpenTelemetry runtime metrics as this breaks the mapping.
Further reading
Additional helpful documentation, links, and articles: