diff --git a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs index f4e1c74283..30c9b2a7d8 100644 --- a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Reflection; using Diagnostics = System.Diagnostics; @@ -28,6 +29,8 @@ internal sealed class ProcessMetrics private readonly Diagnostics.Process currentProcess = Diagnostics.Process.GetCurrentProcess(); private double? memoryUsage; private double? virtualMemoryUsage; + private double? userProcessorTime; + private double? privilegedProcessorTime; public ProcessMetrics(ProcessInstrumentationOptions options) { @@ -64,6 +67,29 @@ public ProcessMetrics(ProcessInstrumentationOptions options) }, unit: "By", description: "The amount of virtual memory allocated for this process that cannot be shared with other processes."); + + this.MeterInstance.CreateObservableCounter( + "process.cpu.time", + () => + { + if (!this.userProcessorTime.HasValue || !this.privilegedProcessorTime.HasValue) + { + this.Snapshot(); + } + + var userProcessorTimeValue = this.userProcessorTime.Value; + var privilegedProcessorTimeValue = this.privilegedProcessorTime.Value; + this.userProcessorTime = null; + this.privilegedProcessorTime = null; + + return new[] + { + new Measurement(userProcessorTimeValue, new KeyValuePair("state", "user")), + new Measurement(privilegedProcessorTimeValue, new KeyValuePair("state", "system")), + }; + }, + unit: "s", + description: "Total CPU seconds broken down by different states."); } private void Snapshot() @@ -71,5 +97,7 @@ private void Snapshot() this.currentProcess.Refresh(); this.memoryUsage = this.currentProcess.WorkingSet64; this.virtualMemoryUsage = this.currentProcess.PrivateMemorySize64; + this.userProcessorTime = this.currentProcess.UserProcessorTime.TotalSeconds; + this.privilegedProcessorTime = this.currentProcess.PrivilegedProcessorTime.TotalSeconds; } } diff --git a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs index 7adc9fa789..82631a34ab 100644 --- a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs +++ b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs @@ -37,11 +37,50 @@ public void ProcessMetricsAreCaptured() meterProvider.ForceFlush(MaxTimeToAllowForFlush); - Assert.True(exportedItems.Count == 2); + Assert.True(exportedItems.Count == 3); var physicalMemoryMetric = exportedItems.FirstOrDefault(i => i.Name == "process.memory.usage"); Assert.NotNull(physicalMemoryMetric); var virtualMemoryMetric = exportedItems.FirstOrDefault(i => i.Name == "process.memory.virtual"); Assert.NotNull(virtualMemoryMetric); + var cpuTimeMetric = exportedItems.FirstOrDefault(i => i.Name == "process.cpu.time"); + Assert.NotNull(cpuTimeMetric); + } + + [Fact] + public void CpuTimeMetricsAreCaptured() + { + var exportedItems = new List(); + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddProcessInstrumentation() + .AddInMemoryExporter(exportedItems) + .Build(); + + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + + var cpuTimeMetric = exportedItems.FirstOrDefault(i => i.Name == "process.cpu.time"); + Assert.NotNull(cpuTimeMetric); + + var userTimeCaptured = false; + var systemTimeCaptured = false; + + var points = cpuTimeMetric.GetMetricPoints().GetEnumerator(); + while (points.MoveNext() && (!userTimeCaptured || !systemTimeCaptured)) + { + foreach (var tag in points.Current.Tags) + { + if (tag.Key == "state" && tag.Value.ToString() == "user") + { + userTimeCaptured = true; + } + else if (tag.Key == "state" && tag.Value.ToString() == "system") + { + systemTimeCaptured = true; + } + } + } + + Assert.True(userTimeCaptured); + Assert.True(systemTimeCaptured); } [Fact]