Skip to content
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

Add CPU metrics collector and instrumentation #437

Closed
wants to merge 2 commits into from
Closed
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
115 changes: 115 additions & 0 deletions src/OpenTelemetry.Instrumentation.Runtime/CpuCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// <copyright file="CpuCollector.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Diagnostics;

namespace OpenTelemetry.Instrumentation.Runtime
{

/// <summary>
/// .NET cpu metrics collector
/// </summary>
internal class CpuCollector : IDisposable
{
private long TRIGER_PERIOD {get; set;} = 1000;

private const short DUE_TIME = 1000;

private readonly Process _process = Process.GetCurrentProcess();
private System.Threading.Timer _timer;
public static MetricsRecord Record { get; private set; } = new MetricsRecord();

private DateTime _lastTimeStamp;
private TimeSpan _lastTotalProcTime = TimeSpan.Zero;
private TimeSpan _lastUserProcTime = TimeSpan.Zero;
private TimeSpan _lastPrivilegedProcTime = TimeSpan.Zero;

public struct MetricsRecord
{
public MetricsRecord(double totalCpuUsed, double privilegedCpuUsed, double userCpuUsed)
{
TotalCpuUsed = totalCpuUsed;

PrivilegedCpuUsed = privilegedCpuUsed;

UserCpuUsed = userCpuUsed;
}

public double TotalCpuUsed { get; internal set;}
public double PrivilegedCpuUsed { get; internal set;}
public double UserCpuUsed { get; internal set;}
}

/// <summary>
/// Initializes a new instance of the <see cref="CpuCollector"/> class.
/// </summary>
public CpuCollector(RuntimeMetricsOptions options)
{
_lastTimeStamp = _process.StartTime;

if(options.CpuCollectInterval.HasValue && options.CpuCollectInterval.Value < 1000){
TRIGER_PERIOD = 1000;
}

_timer = new System.Threading.Timer(CollectData!, null, DUE_TIME, TRIGER_PERIOD);
}

/// <summary>
/// Collects CPU metrics.
/// </summary>
private void CollectData(object state)
{
_process.Refresh();

var totalCpuTimeUsed = _process.TotalProcessorTime.TotalMilliseconds - _lastTotalProcTime.TotalMilliseconds;
var privilegedCpuTimeUsed = _process.PrivilegedProcessorTime.TotalMilliseconds - _lastPrivilegedProcTime.TotalMilliseconds;
var userCpuTimeUsed = _process.UserProcessorTime.TotalMilliseconds - _lastUserProcTime.TotalMilliseconds;

_lastTotalProcTime = _process.TotalProcessorTime;
_lastPrivilegedProcTime = _process.PrivilegedProcessorTime;
_lastUserProcTime = _process.UserProcessorTime;

try
{
var cpuTimeElapsed = (DateTime.UtcNow - _lastTimeStamp).TotalMilliseconds * Environment.ProcessorCount;
_lastTimeStamp = DateTime.UtcNow;

var totla_cpu = totalCpuTimeUsed * 100 / cpuTimeElapsed;
var privilaged_cpu = privilegedCpuTimeUsed * 100 / cpuTimeElapsed;
var user_cpu = userCpuTimeUsed * 100 / cpuTimeElapsed;

Record = new MetricsRecord()
{
TotalCpuUsed = totalCpuTimeUsed * 100 / cpuTimeElapsed,
PrivilegedCpuUsed = privilegedCpuTimeUsed * 100 / cpuTimeElapsed,
UserCpuUsed = userCpuTimeUsed * 100 / cpuTimeElapsed,
};

}
catch
{

}
}

/// <inheritdoc/>
public void Dispose()
{
this._timer?.Dispose();
}
}
}
12 changes: 12 additions & 0 deletions src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

namespace OpenTelemetry.Instrumentation.Runtime
{

/// <summary>
/// .NET runtime instrumentation.
/// </summary>
Expand All @@ -38,6 +39,7 @@ internal class RuntimeMetrics : IDisposable
private static readonly int NumberOfGenerations = 3;
private static string metricPrefix = "process.runtime.dotnet.";
private readonly Meter meter;
private CpuCollector CpuCollector { get; set;}

/// <summary>
/// Initializes a new instance of the <see cref="RuntimeMetrics"/> class.
Expand All @@ -47,6 +49,14 @@ public RuntimeMetrics(RuntimeMetricsOptions options)
{
this.meter = new Meter(InstrumentationName, InstrumentationVersion);

if(options.IsCpuEnabled){
CpuCollector = new CpuCollector(options);

this.meter.CreateObservableGauge<double>($"{metricPrefix}cpu.total", () => CpuCollector.Record.TotalCpuUsed, "%", "Total CPU Percentage Used");
this.meter.CreateObservableGauge<double>($"{metricPrefix}cpu.privilage", () => CpuCollector.Record.PrivilegedCpuUsed, "%", "Privileged CPU Percentage Used");
this.meter.CreateObservableGauge<double>($"{metricPrefix}cpu.user", () => CpuCollector.Record.UserCpuUsed, "%", "User CPU Percentage Used");
}

if (options.IsGcEnabled)
{
// TODO: Almost all the ObservableGauge should be ObservableUpDownCounter (except for CPU utilization and fragmentation.ratio.
Expand Down Expand Up @@ -105,6 +115,8 @@ public RuntimeMetrics(RuntimeMetricsOptions options)
public void Dispose()
{
this.meter?.Dispose();

CpuCollector?.Dispose();
}

private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ public class RuntimeMetricsOptions
public bool? ThreadingEnabled { get; set; }
#endif

/// <summary>
/// Gets or sets a value indicating whether CPU metrics should be collected.
/// </summary>
public bool? CpuEnabled { get; set; }

/// <summary>
/// Set CPU collect interval, default value is 1s
/// </summary>
public int? CpuCollectInterval { get; set; }

/// <summary>
/// Gets or sets a value indicating whether process metrics should be collected.
/// </summary>
Expand All @@ -61,8 +71,14 @@ public class RuntimeMetricsOptions
&& this.ThreadingEnabled == null
#endif
&& this.ProcessEnabled == null
&& this.CpuEnabled == null
&& this.AssembliesEnabled == null;

/// <summary>
/// Gets a value indicating whether CPU collection metrics is enabled.
/// </summary>
internal bool IsCpuEnabled => this.CpuEnabled == true || this.IsAllEnabled;

/// <summary>
/// Gets a value indicating whether garbage collection metrics is enabled.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void Enable_All_If_Nothing_Was_Defined()
#if NETCOREAPP3_1_OR_GREATER
Assert.True(options.IsThreadingEnabled);
#endif
Assert.True(options.IsCpuEnabled);
Assert.True(options.IsProcessEnabled);
Assert.True(options.IsAssembliesEnabled);
Assert.True(options.IsAllEnabled);
Expand All @@ -46,6 +47,26 @@ public void Enable_Gc_Only()
#if NET6_0_OR_GREATER
Assert.False(options.IsJitEnabled);
#endif
#if NETCOREAPP3_1_OR_GREATER
Assert.False(options.IsThreadingEnabled);
#endif
Assert.False(options.IsCpuEnabled);
Assert.False(options.IsProcessEnabled);
Assert.False(options.IsAssembliesEnabled);
Assert.False(options.IsAllEnabled);
}


[Fact]
public void Enable_Cpu_Only()
{
var options = new RuntimeMetricsOptions { CpuEnabled = true };

Assert.False(options.IsGcEnabled);
#if NET6_0_OR_GREATER
Assert.False(options.IsJitEnabled);
#endif
Assert.True(options.IsCpuEnabled);
#if NETCOREAPP3_1_OR_GREATER
Assert.False(options.IsThreadingEnabled);
#endif
Expand All @@ -62,6 +83,7 @@ public void Enable_Jit_Only()

Assert.False(options.IsGcEnabled);
Assert.True(options.IsJitEnabled);
Assert.False(options.IsCpuEnabled);
Assert.False(options.IsThreadingEnabled);
Assert.False(options.IsProcessEnabled);
Assert.False(options.IsAssembliesEnabled);
Expand All @@ -81,6 +103,7 @@ public void Enable_Threading_Only()
#endif
Assert.True(options.IsThreadingEnabled);
Assert.False(options.IsProcessEnabled);
Assert.False(options.IsCpuEnabled);
Assert.False(options.IsAssembliesEnabled);
Assert.False(options.IsAllEnabled);
}
Expand All @@ -99,6 +122,7 @@ public void Enable_Process_Only()
Assert.False(options.IsThreadingEnabled);
#endif
Assert.True(options.IsProcessEnabled);
Assert.False(options.IsCpuEnabled);
Assert.False(options.IsAssembliesEnabled);
Assert.False(options.IsAllEnabled);
}
Expand All @@ -116,6 +140,7 @@ public void Enable_Assemblies_Only()
Assert.False(options.IsThreadingEnabled);
#endif
Assert.False(options.IsProcessEnabled);
Assert.False(options.IsCpuEnabled);
Assert.True(options.IsAssembliesEnabled);
Assert.False(options.IsAllEnabled);
}
Expand All @@ -133,6 +158,7 @@ public void Enable_Multiple()
Assert.False(options.IsThreadingEnabled);
#endif
Assert.True(options.IsProcessEnabled);
Assert.False(options.IsCpuEnabled);
Assert.False(options.IsAssembliesEnabled);
Assert.False(options.IsAllEnabled);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public void RuntimeMetricsAreCaptured()

options.JitEnabled = true;
#endif

options.CpuEnabled = true;

options.AssembliesEnabled = true;
})
.AddInMemoryExporter(exportedItems)
Expand Down