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
9 changes: 9 additions & 0 deletions src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 40 additions & 4 deletions src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProcessMetrics>(SemanticConventionsVersion);

static ProcessMetrics()
Expand Down Expand Up @@ -40,12 +41,12 @@ static ProcessMetrics()
using var process = Diagnostics.Process.GetCurrentProcess();
return new[]
{
new Measurement<double>(process.UserProcessorTime.TotalSeconds, new KeyValuePair<string, object?>("process.cpu.state", "user")),
new Measurement<double>(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair<string, object?>("process.cpu.state", "system")),
new Measurement<double>(process.UserProcessorTime.TotalSeconds, new KeyValuePair<string, object?>("cpu.mode", "user")),
new Measurement<double>(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair<string, object?>("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",
Expand All @@ -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;
},
Comment thread
martincostello marked this conversation as resolved.
unit: "{file_descriptor}",
description: "Number of unix file descriptors in use by the process.");
}
}
}
49 changes: 44 additions & 5 deletions src/OpenTelemetry.Instrumentation.Process/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:

Expand All @@ -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
Comment thread
martincostello marked this conversation as resolved.

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.
Comment thread
martincostello marked this conversation as resolved.

## References

* [OpenTelemetry Project](https://opentelemetry.io/)
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down