diff --git a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md index db6ed9aa74..3286bb0a11 100644 --- a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +* Updated semantic conventions to + [v1.42.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.42.0/docs/system/process-metrics.md). + ([#4602](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4602)) + * **Breaking Change**: The `process.cpu.time` metric attribute `process.cpu.state` + was renamed to `cpu.mode`. + * Added the `process.uptime` metric. + * Added the `process.windows.handle.count` metric (Windows only). + * Added the `process.unix.file_descriptor.count` metric (Linux only). + ## 1.16.0-beta.1 Released 2026-Jun-24 diff --git a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs index 7f0d993495..a18208204d 100644 --- a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Metrics; +using System.Runtime.InteropServices; using Diagnostics = System.Diagnostics; namespace OpenTelemetry.Instrumentation.Process; internal sealed class ProcessMetrics { - internal static readonly Version SemanticConventionsVersion = new(1, 25, 0); + internal static readonly Version SemanticConventionsVersion = new(1, 42, 0); internal static readonly Meter MeterInstance = Metrics.MeterFactory.Create(SemanticConventionsVersion); static ProcessMetrics() @@ -40,12 +41,12 @@ static ProcessMetrics() using var process = Diagnostics.Process.GetCurrentProcess(); return new[] { - new Measurement(process.UserProcessorTime.TotalSeconds, new KeyValuePair("process.cpu.state", "user")), - new Measurement(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair("process.cpu.state", "system")), + new Measurement(process.UserProcessorTime.TotalSeconds, new KeyValuePair("cpu.mode", "user")), + new Measurement(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair("cpu.mode", "system")), }; }, unit: "s", - description: "Total CPU seconds broken down by different states."); + description: "Total CPU seconds broken down by different CPU modes."); MeterInstance.CreateObservableUpDownCounter( "process.thread.count", @@ -56,5 +57,40 @@ static ProcessMetrics() }, unit: "{thread}", description: "Process threads count."); + + MeterInstance.CreateObservableGauge( + "process.uptime", + () => + { + using var process = Diagnostics.Process.GetCurrentProcess(); + return (DateTime.UtcNow - process.StartTime.ToUniversalTime()).TotalSeconds; + }, + unit: "s", + description: "The time the process has been running."); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + MeterInstance.CreateObservableUpDownCounter( + "process.windows.handle.count", + () => + { + using var process = Diagnostics.Process.GetCurrentProcess(); + return process.HandleCount; + }, + unit: "{handle}", + description: "Number of handles held by the process."); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + MeterInstance.CreateObservableUpDownCounter( + "process.unix.file_descriptor.count", + () => + { + using var process = Diagnostics.Process.GetCurrentProcess(); + return process.HandleCount; + }, + unit: "{file_descriptor}", + description: "Number of unix file descriptors in use by the process."); + } } } diff --git a/src/OpenTelemetry.Instrumentation.Process/README.md b/src/OpenTelemetry.Instrumentation.Process/README.md index 6e17593a7c..8fae8dffd0 100644 --- a/src/OpenTelemetry.Instrumentation.Process/README.md +++ b/src/OpenTelemetry.Instrumentation.Process/README.md @@ -16,7 +16,7 @@ telemetry about process behavior. The process metric instruments being implemented are following OpenTelemetry [metrics semantic -conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/process-metrics.md). +conventions](https://github.com/open-telemetry/semantic-conventions/blob/v1.42.0/docs/system/process-metrics.md). ## Steps to enable OpenTelemetry.Instrumentation.Process @@ -104,11 +104,11 @@ process. ### process.cpu.time -Total CPU seconds broken down by states. +Total CPU seconds broken down by CPU modes. -| Units | Instrument Type | Value Type | Attribute Key(s) | Attribute Values | -| ----- | ----------------- | ---------- | ----------------- | ---------------- | -| `s` | ObservableCounter | `Double` | process.cpu.state | user, system | +| Units | Instrument Type | Value Type | Attribute Key | Attribute Values | +| ----- | ----------------- | ---------- | ------------- | ---------------- | +| `s` | ObservableCounter | `Double` | cpu.mode | user, system | The APIs used to retrieve the values are: @@ -131,6 +131,45 @@ The API used to retrieve the value is: * [Process.Threads](https://learn.microsoft.com/dotnet/api/system.diagnostics.process.threads): Gets the set of threads that are running in the associated process. +### process.uptime + +The time the process has been running. + +| Units | Instrument Type | Value Type | +| ----- | --------------- | ---------- | +| `s` | ObservableGauge | `Double` | + +The API used to retrieve the value is: + +* [Process.StartTime](https://learn.microsoft.com/dotnet/api/system.diagnostics.process.starttime): +Gets the time that the associated process was started. + +### process.windows.handle.count + +Number of handles held by the process. Only emitted on Windows. + +| Units | Instrument Type | Value Type | +| ---------- | ----------------------- | ---------- | +| `{handle}` | ObservableUpDownCounter | `Int32` | + +The API used to retrieve the value is: + +* [Process.HandleCount](https://learn.microsoft.com/dotnet/api/system.diagnostics.process.handlecount): +Gets the number of handles opened by the process. + +### process.unix.file_descriptor.count + +Number of unix file descriptors in use by the process. Only emitted on Linux. + +| Units | Instrument Type | Value Type | +| ------------------- | ----------------------- | ---------- | +| `{file_descriptor}` | ObservableUpDownCounter | `Int32` | + +The API used to retrieve the value is: + +* [Process.HandleCount](https://learn.microsoft.com/dotnet/api/system.diagnostics.process.handlecount): +Gets the number of file descriptors opened by the process. + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs index 1ce65bb09c..eea617d103 100644 --- a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs +++ b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs @@ -1,13 +1,18 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Runtime.InteropServices; using OpenTelemetry.Metrics; namespace OpenTelemetry.Instrumentation.Process.Tests; public class ProcessMetricsTests { - private const int MaxTimeToAllowForFlush = 10000; + private const int MaxTimeToAllowForFlush = 10_000; + + // Windows: process.windows.handle.count; Linux: process.unix.file_descriptor.count; macOS: neither + private static readonly int ExpectedMetricCount = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 6 : 5; [Fact] public void ProcessMetricsAreCaptured() @@ -28,7 +33,21 @@ public void ProcessMetricsAreCaptured() Assert.NotNull(cpuTimeMetric); var threadMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.thread.count"); Assert.NotNull(threadMetric); - Assert.Equal(4, exportedItemsA.Count); + var uptimeMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.uptime"); + Assert.NotNull(uptimeMetric); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var handleCountMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.windows.handle.count"); + Assert.NotNull(handleCountMetric); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var fileDescriptorMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.unix.file_descriptor.count"); + Assert.NotNull(fileDescriptorMetric); + } + + Assert.Equal(ExpectedMetricCount, exportedItemsA.Count); exportedItemsA.Clear(); @@ -48,8 +67,8 @@ public void ProcessMetricsAreCaptured() meterProviderB.ForceFlush(MaxTimeToAllowForFlush); - Assert.Equal(4, exportedItemsA.Count); - Assert.Equal(4, exportedItemsB.Count); + Assert.Equal(ExpectedMetricCount, exportedItemsA.Count); + Assert.Equal(ExpectedMetricCount, exportedItemsB.Count); AssertMetrics(exportedItemsA); AssertMetrics(exportedItemsB); @@ -79,11 +98,11 @@ public void CpuTimeMetricsAreCaptured() { Assert.NotNull(tag.Value); - if (tag.Key == "process.cpu.state" && tag.Value!.ToString() == "user") + if (tag.Key == "cpu.mode" && tag.Value!.ToString() == "user") { userTimeCaptured = true; } - else if (tag.Key == "process.cpu.state" && tag.Value!.ToString() == "system") + else if (tag.Key == "cpu.mode" && tag.Value!.ToString() == "system") { systemTimeCaptured = true; } @@ -137,7 +156,9 @@ public async Task ProcessMetricsAreCapturedWhenTasksOverlap() Assert.NotNull(cpuTimeMetricA); var threadMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.thread.count"); Assert.NotNull(threadMetricA); - Assert.Equal(4, exportedItemsA.Count); + var uptimeMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.uptime"); + Assert.NotNull(uptimeMetricA); + Assert.Equal(ExpectedMetricCount, exportedItemsA.Count); var physicalMemoryMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.memory.usage"); Assert.NotNull(physicalMemoryMetricB); @@ -147,7 +168,9 @@ public async Task ProcessMetricsAreCapturedWhenTasksOverlap() Assert.NotNull(cpuTimeMetricB); var threadMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.thread.count"); Assert.NotNull(threadMetricB); - Assert.Equal(4, exportedItemsB.Count); + var uptimeMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.uptime"); + Assert.NotNull(uptimeMetricB); + Assert.Equal(ExpectedMetricCount, exportedItemsB.Count); AssertMetrics(exportedItemsA); AssertMetrics(exportedItemsB);