Skip to content

Commit

Permalink
incorporate windows statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
Ledjon Behluli authored and Ledjon Behluli committed Jan 18, 2024
1 parent 3ff5bbe commit 3c1c72e
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 326 deletions.
13 changes: 3 additions & 10 deletions src/Orleans.Core/Core/DefaultClientServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using Orleans.Serialization.Serializers;
using Orleans.Serialization.Cloning;
using Microsoft.Extensions.Hosting;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using Orleans.Serialization.Internal;
using System;
Expand Down Expand Up @@ -57,16 +56,10 @@ public static void AddDefaultServices(IClientBuilder builder)
services.AddSingleton<ClientOptionsLogger>();
services.AddFromExisting<ILifecycleParticipant<IClusterClientLifecycle>, ClientOptionsLogger>();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
LinuxEnvironmentStatisticsServices.RegisterServices<IClusterClientLifecycle>(services);
}
else
{
services.TryAddSingleton<IHostEnvironmentStatistics, NoOpHostEnvironmentStatistics>();
}

// Statistics
services.RegisterEnvironmentStatisticsServices<IClusterClientLifecycle>();
services.TryAddSingleton<IAppEnvironmentStatistics, AppEnvironmentStatistics>();

services.AddLogging();
services.TryAddSingleton<GrainBindingsResolver>();
services.TryAddSingleton<LocalClientDetails>();
Expand Down
1 change: 1 addition & 0 deletions src/Orleans.Core/Orleans.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Memory.Data" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" />
</ItemGroup>

</Project>
179 changes: 179 additions & 0 deletions src/Orleans.Core/Statistics/EnvironmentStatisticsBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Extensions.Logging;
using System.Diagnostics.Metrics;
using Orleans.Runtime;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

namespace Orleans.Statistics;

#nullable enable

/// <summary>
/// Base class for environment statistics
/// </summary>
internal abstract class EnvironmentStatisticsBase<T> : IHostEnvironmentStatistics, ILifecycleObserver, IDisposable
where T : EnvironmentStatisticsBase<T>
{
private Task? _monitorTask;
private const byte _monitorPeriodSecs = 5;

private readonly EventCounterListener _eventCounterListener = new(_monitorPeriodSecs.ToString());
private readonly CancellationTokenSource _cts = new();
private readonly ObservableCounter<long> _availableMemoryCounter;
private readonly ObservableCounter<long> _totalPhysicalMemoryCounter;

protected const float OneKiloByte = 1024;

protected ILogger _logger;
protected TimeSpan _monitorPeriod = TimeSpan.FromSeconds(_monitorPeriodSecs);

/// <inheritdoc />
public float? CpuUsage => _eventCounterListener.CpuUsage.HasValue ? (float)_eventCounterListener.CpuUsage : null;
/// <inheritdoc />
public long? TotalPhysicalMemory => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes;
/// <inheritdoc />
public long? AvailableMemory { get; protected set; }

protected EnvironmentStatisticsBase(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<T>();

if (GC.CollectionCount(0) == 0)
{
// We make sure the GC structure wont be empty, also performing a blocking GC guarantees immediate completion.
GC.Collect(0, GCCollectionMode.Forced, true);
}

_availableMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_AVAILABLE_MEMORY_MB, () => (long)(AvailableMemory ?? 0 / OneKiloByte / OneKiloByte), unit: "MB");
_totalPhysicalMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_TOTAL_PHYSICAL_MEMORY_MB, () => (long)(TotalPhysicalMemory ?? 0 / OneKiloByte / OneKiloByte), unit: "MB");
}

protected abstract ValueTask<long?> GetAvailableMemory(CancellationToken cancellationToken);

private async Task Monitor(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException("Monitor task canceled");
}

try
{
AvailableMemory = await GetAvailableMemory(cancellationToken);

LogStatistics();

await Task.Delay(_monitorPeriod, cancellationToken);
}
catch (Exception ex) when (ex.GetType() != typeof(TaskCanceledException))
{
_logger.LogError(ex, "{Statistics}: error", nameof(LinuxEnvironmentStatistics));
await Task.Delay(3 * _monitorPeriod, cancellationToken);
}
}
}

public async Task OnStart(CancellationToken cancellationToken)
{
if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("Starting {Statistics}", typeof(T).Name);

using var _ = cancellationToken.Register(_cts.Cancel);

_monitorTask = await Task.Factory.StartNew(
() => Monitor(_cts.Token),
_cts.Token,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.RunContinuationsAsynchronously,
TaskScheduler.Default
);

if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("Started {Statistics}", typeof(T).Name);
}

public async Task OnStop(CancellationToken cancellationToken)
{
if (_cts == null)
return;

if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("Stopping {Statistics}", typeof(T).Name);

try
{
_cts.Cancel();

if (_monitorTask is null)
{
return;
}

await _monitorTask;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping {Statistics}", typeof(T).Name);
}
finally
{
if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("Stopped {Statistics}", typeof(T).Name);
}
}

protected void LogStatistics()
{
if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("{Statistics}: CpuUsage={CpuUsageValue}, TotalPhysicalMemory={TotalPhysicalMemoryValue}, AvailableMemory={AvailableMemoryValue}",
typeof(T).Name, CpuUsage?.ToString("0.0"), TotalPhysicalMemory, AvailableMemory);
}

public void Dispose()
{
if (_cts != null && !_cts.IsCancellationRequested)
{
_cts.Cancel();
}
}

private sealed class EventCounterListener(string pollingInterval) : EventListener
{
private readonly string _pollingInterval = pollingInterval;

public double? CpuUsage { get; private set; }

protected override void OnEventSourceCreated(EventSource source)
{
if (source.Name.Equals("System.Runtime"))
{
Dictionary<string, string?>? refreshInterval = new() { ["EventCounterIntervalSec"] = _pollingInterval };
EnableEvents(source, EventLevel.Informational, (EventKeywords)(-1), refreshInterval);
}
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (eventData.EventName!.Equals("EventCounters"))
{
for (int i = 0; i < eventData.Payload!.Count; i++)
{
if (eventData.Payload![i] is IDictionary<string, object> eventPayload)
{
if (eventPayload.TryGetValue("Name", out var name) && "cpu-usage".Equals(name))
{
if (eventPayload.TryGetValue("Mean", out var mean))
{
CpuUsage = (double)mean;
}
}
}
}
}
}
}
}
39 changes: 39 additions & 0 deletions src/Orleans.Core/Statistics/EnvironmentStatisticsServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Orleans.Configuration.Internal;
using Orleans.Runtime;

namespace Orleans.Statistics;

internal static class EnvironmentStatisticsServices
{
internal static IServiceCollection RegisterEnvironmentStatisticsServices<TLifecycleObservable>(this IServiceCollection services)
where TLifecycleObservable : ILifecycleObservable
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
if (!File.Exists(LinuxEnvironmentStatistics.MEMINFO_FILEPATH))
{
throw new OrleansConfigurationException($"{LinuxEnvironmentStatistics.MEMINFO_FILEPATH} file is missing");
}

services.AddSingleton<LinuxEnvironmentStatistics>();
services.AddFromExisting<IHostEnvironmentStatistics, LinuxEnvironmentStatistics>();
services.AddSingleton<ILifecycleParticipant<TLifecycleObservable>, LinuxEnvironmentStatisticsLifecycleAdapter<TLifecycleObservable>>();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
services.AddSingleton<WindowsEnvironmentStatistics>();
services.AddFromExisting<IHostEnvironmentStatistics, WindowsEnvironmentStatistics>();
services.AddSingleton<ILifecycleParticipant<TLifecycleObservable>, WindowsEnvironmentStatisticsLifecycleAdapter<TLifecycleObservable>>();
}
else
{
services.TryAddSingleton<IHostEnvironmentStatistics, NoOpHostEnvironmentStatistics>();
}

return services;
}
}
Loading

0 comments on commit 3c1c72e

Please sign in to comment.