From 3c1c72e47d6c6c71a7130570a890dc7098cbc417 Mon Sep 17 00:00:00 2001 From: Ledjon Behluli Date: Thu, 18 Jan 2024 22:25:58 +0100 Subject: [PATCH] incorporate windows statistics --- .../Core/DefaultClientServices.cs | 13 +- src/Orleans.Core/Orleans.Core.csproj | 1 + .../Statistics/EnvironmentStatisticsBase.cs | 179 ++++++++++++ .../EnvironmentStatisticsServices.cs | 39 +++ .../Statistics/LinuxEnvironmentStatistics.cs | 254 +++--------------- ...uxEnvironmentStatisticsLifecycleAdapter.cs | 30 --- .../LinuxEnvironmentStatisticsServices.cs | 30 --- .../LinuxEnvironmentStatisticsValidator.cs | 36 --- .../WindowsEnvironmentStatistics.cs | 35 +++ .../Hosting/DefaultSiloServices.cs | 13 +- 10 files changed, 304 insertions(+), 326 deletions(-) create mode 100644 src/Orleans.Core/Statistics/EnvironmentStatisticsBase.cs create mode 100644 src/Orleans.Core/Statistics/EnvironmentStatisticsServices.cs delete mode 100644 src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsLifecycleAdapter.cs delete mode 100644 src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsServices.cs delete mode 100644 src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsValidator.cs create mode 100644 src/Orleans.Core/Statistics/WindowsEnvironmentStatistics.cs diff --git a/src/Orleans.Core/Core/DefaultClientServices.cs b/src/Orleans.Core/Core/DefaultClientServices.cs index 3853a0919b..bba0c20cdf 100644 --- a/src/Orleans.Core/Core/DefaultClientServices.cs +++ b/src/Orleans.Core/Core/DefaultClientServices.cs @@ -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; @@ -57,16 +56,10 @@ public static void AddDefaultServices(IClientBuilder builder) services.AddSingleton(); services.AddFromExisting, ClientOptionsLogger>(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - LinuxEnvironmentStatisticsServices.RegisterServices(services); - } - else - { - services.TryAddSingleton(); - } - + // Statistics + services.RegisterEnvironmentStatisticsServices(); services.TryAddSingleton(); + services.AddLogging(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Orleans.Core/Orleans.Core.csproj b/src/Orleans.Core/Orleans.Core.csproj index 680c7d518e..eea9fd7c2b 100644 --- a/src/Orleans.Core/Orleans.Core.csproj +++ b/src/Orleans.Core/Orleans.Core.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Orleans.Core/Statistics/EnvironmentStatisticsBase.cs b/src/Orleans.Core/Statistics/EnvironmentStatisticsBase.cs new file mode 100644 index 0000000000..6261a84a04 --- /dev/null +++ b/src/Orleans.Core/Statistics/EnvironmentStatisticsBase.cs @@ -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 + +/// +/// Base class for environment statistics +/// +internal abstract class EnvironmentStatisticsBase : IHostEnvironmentStatistics, ILifecycleObserver, IDisposable + where T : EnvironmentStatisticsBase +{ + private Task? _monitorTask; + private const byte _monitorPeriodSecs = 5; + + private readonly EventCounterListener _eventCounterListener = new(_monitorPeriodSecs.ToString()); + private readonly CancellationTokenSource _cts = new(); + private readonly ObservableCounter _availableMemoryCounter; + private readonly ObservableCounter _totalPhysicalMemoryCounter; + + protected const float OneKiloByte = 1024; + + protected ILogger _logger; + protected TimeSpan _monitorPeriod = TimeSpan.FromSeconds(_monitorPeriodSecs); + + /// + public float? CpuUsage => _eventCounterListener.CpuUsage.HasValue ? (float)_eventCounterListener.CpuUsage : null; + /// + public long? TotalPhysicalMemory => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; + /// + public long? AvailableMemory { get; protected set; } + + protected EnvironmentStatisticsBase(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + + 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 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? 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 eventPayload) + { + if (eventPayload.TryGetValue("Name", out var name) && "cpu-usage".Equals(name)) + { + if (eventPayload.TryGetValue("Mean", out var mean)) + { + CpuUsage = (double)mean; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Orleans.Core/Statistics/EnvironmentStatisticsServices.cs b/src/Orleans.Core/Statistics/EnvironmentStatisticsServices.cs new file mode 100644 index 0000000000..69b5909f54 --- /dev/null +++ b/src/Orleans.Core/Statistics/EnvironmentStatisticsServices.cs @@ -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(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(); + services.AddFromExisting(); + services.AddSingleton, LinuxEnvironmentStatisticsLifecycleAdapter>(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + services.AddSingleton(); + services.AddFromExisting(); + services.AddSingleton, WindowsEnvironmentStatisticsLifecycleAdapter>(); + } + else + { + services.TryAddSingleton(); + } + + return services; + } +} diff --git a/src/Orleans.Core/Statistics/LinuxEnvironmentStatistics.cs b/src/Orleans.Core/Statistics/LinuxEnvironmentStatistics.cs index 1334d6ca66..fb58881217 100644 --- a/src/Orleans.Core/Statistics/LinuxEnvironmentStatistics.cs +++ b/src/Orleans.Core/Statistics/LinuxEnvironmentStatistics.cs @@ -1,242 +1,76 @@ using System; -using System.Diagnostics.Metrics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Orleans.Runtime; -namespace Orleans.Statistics -{ - internal class LinuxEnvironmentStatistics : IHostEnvironmentStatistics, ILifecycleObserver, IDisposable - { - private readonly ILogger _logger; - private readonly ObservableCounter _totalPhysicalMemoryCounter; - private readonly ObservableCounter _availableMemoryCounter; - private const float KB = 1024f; - - /// - public long? TotalPhysicalMemory { get; private set; } - - /// - public float? CpuUsage { get; private set; } - - /// - public long? AvailableMemory { get; private set; } - - /// - private long MemoryUsage => GC.GetTotalMemory(false); - - private readonly TimeSpan MONITOR_PERIOD = TimeSpan.FromSeconds(5); - - private CancellationTokenSource _cts; - private Task _monitorTask; - - private const string MEMINFO_FILEPATH = "/proc/meminfo"; - private const string CPUSTAT_FILEPATH = "/proc/stat"; - - internal static readonly string[] RequiredFiles = new[] - { - MEMINFO_FILEPATH, - CPUSTAT_FILEPATH - }; - - public LinuxEnvironmentStatistics(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _totalPhysicalMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_TOTAL_PHYSICAL_MEMORY_MB, () => (long)(TotalPhysicalMemory / KB / KB), unit: "MB"); - _availableMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_AVAILABLE_MEMORY_MB, () => (long)(AvailableMemory / KB / KB), unit: "MB"); - } - - public void Dispose() - { - if (_cts != null && !_cts.IsCancellationRequested) - { - _cts.Cancel(); - } - } - - public async Task OnStart(CancellationToken ct) - { - _logger.LogTrace($"Starting {nameof(LinuxEnvironmentStatistics)}"); - - _cts = new CancellationTokenSource(); - ct.Register(() => _cts.Cancel()); +namespace Orleans.Statistics; - _monitorTask = await Task.Factory.StartNew( - () => Monitor(_cts.Token), - _cts.Token, - TaskCreationOptions.DenyChildAttach | TaskCreationOptions.RunContinuationsAsynchronously, - TaskScheduler.Default - ); +internal sealed class LinuxEnvironmentStatistics(ILoggerFactory loggerFactory) + : EnvironmentStatisticsBase(loggerFactory) +{ + internal static readonly string MEMINFO_FILEPATH = "/proc/meminfo"; - _logger.LogTrace($"Started {nameof(LinuxEnvironmentStatistics)}"); - } + protected override async ValueTask GetAvailableMemory(CancellationToken cancellationToken) + { + var memAvailableLine = await ReadLineStartingWithAsync(MEMINFO_FILEPATH, "MemAvailable"); - public async Task OnStop(CancellationToken ct) + if (string.IsNullOrWhiteSpace(memAvailableLine)) { - if (_cts == null) - return; - - _logger.LogTrace($"Stopping {nameof(LinuxEnvironmentStatistics)}"); - try - { - _cts.Cancel(); - try - { - await _monitorTask; - } - catch (TaskCanceledException) { } - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error stopping {nameof(LinuxEnvironmentStatistics)}"); - } - finally + memAvailableLine = await ReadLineStartingWithAsync(MEMINFO_FILEPATH, "MemFree"); + if (string.IsNullOrWhiteSpace(memAvailableLine)) { - _logger.LogTrace($"Stopped {nameof(LinuxEnvironmentStatistics)}"); + _logger.LogWarning("Failed to read 'MemAvailable' or 'MemFree' line from {MEMINFO_FILEPATH}", MEMINFO_FILEPATH); + return null; } } - private async Task UpdateTotalPhysicalMemory() + if (!long.TryParse(new string(memAvailableLine.Where(char.IsDigit).ToArray()), out var availableMemInKb)) { - var memTotalLine = await ReadLineStartingWithAsync(MEMINFO_FILEPATH, "MemTotal"); - - if (string.IsNullOrWhiteSpace(memTotalLine)) - { - _logger.LogWarning($"Couldn't read 'MemTotal' line from '{MEMINFO_FILEPATH}'"); - return; - } - - // Format: "MemTotal: 16426476 kB" - if (!long.TryParse(new string(memTotalLine.Where(char.IsDigit).ToArray()), out var totalMemInKb)) - { - _logger.LogWarning("Couldn't parse meminfo output"); - return; - } - - TotalPhysicalMemory = totalMemInKb * 1_000; + _logger.LogWarning("Failed to parse meminfo output: '{MemAvailableLine}'", memAvailableLine); + return null; } - private long _prevIdleTime; - private long _prevTotalTime; + return (long)(availableMemInKb * OneKiloByte); - private async Task UpdateCpuUsage(int i) + static async Task ReadLineStartingWithAsync(string path, string lineStartsWith) { - var cpuUsageLine = await ReadLineStartingWithAsync(CPUSTAT_FILEPATH, "cpu "); + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 512, FileOptions.SequentialScan | FileOptions.Asynchronous); + using var r = new StreamReader(fs, Encoding.ASCII); - if (string.IsNullOrWhiteSpace(cpuUsageLine)) + string line; + while ((line = await r.ReadLineAsync()) != null) { - _logger.LogWarning($"Couldn't read line from '{CPUSTAT_FILEPATH}'"); - return; + if (line.StartsWith(lineStartsWith, StringComparison.Ordinal)) + return line; } - // Format: "cpu 20546715 4367 11631326 215282964 96602 0 584080 0 0 0" - var cpuNumberStrings = cpuUsageLine.Split(' ').Skip(2); - - if (cpuNumberStrings.Any(n => !long.TryParse(n, out _))) - { - _logger.LogWarning($"Failed to parse '{CPUSTAT_FILEPATH}' output correctly. Line: {{CpuUsageLine}}", cpuUsageLine); - return; - } - - var cpuNumbers = cpuNumberStrings.Select(long.Parse).ToArray(); - var idleTime = cpuNumbers[3]; - var iowait = cpuNumbers[4]; // Iowait is not real cpu time - var totalTime = cpuNumbers.Sum() - iowait; - - if (i > 0) - { - var deltaIdleTime = idleTime - _prevIdleTime; - var deltaTotalTime = totalTime - _prevTotalTime; - - // When running in gVisor, /proc/stat returns all zeros, so check here and leave CpuUsage unset. - // see: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/proc/stat.go#L88-L95 - if (deltaTotalTime == 0f) - { - return; - } - - var currentCpuUsage = (1.0f - deltaIdleTime / ((float)deltaTotalTime)) * 100f; - - var previousCpuUsage = CpuUsage ?? 0f; - CpuUsage = (previousCpuUsage + 2 * currentCpuUsage) / 3; - } - - _prevIdleTime = idleTime; - _prevTotalTime = totalTime; - } - - private async Task UpdateAvailableMemory() - { - var memAvailableLine = await ReadLineStartingWithAsync(MEMINFO_FILEPATH, "MemAvailable"); - - if (string.IsNullOrWhiteSpace(memAvailableLine)) - { - memAvailableLine = await ReadLineStartingWithAsync(MEMINFO_FILEPATH, "MemFree"); - if (string.IsNullOrWhiteSpace(memAvailableLine)) - { - _logger.LogWarning($"Failed to read 'MemAvailable' or 'MemFree' line from '{MEMINFO_FILEPATH}'"); - return; - } - } - - if (!long.TryParse(new string(memAvailableLine.Where(char.IsDigit).ToArray()), out var availableMemInKb)) - { - _logger.LogWarning("Failed to parse meminfo output: '{MemAvailableLine}'", memAvailableLine); - return; - } - - AvailableMemory = availableMemInKb * 1_000; + return null; } + } +} - private async Task Monitor(CancellationToken ct) - { - int i = 0; - while (true) - { - if (ct.IsCancellationRequested) - throw new TaskCanceledException("Monitor task canceled"); +internal class LinuxEnvironmentStatisticsLifecycleAdapter + : ILifecycleParticipant, ILifecycleObserver where TLifecycle : ILifecycleObservable +{ + private readonly LinuxEnvironmentStatistics _statistics; - try - { - await Task.WhenAll( - i == 0 ? UpdateTotalPhysicalMemory() : Task.CompletedTask, - UpdateCpuUsage(i), - UpdateAvailableMemory() - ); + public LinuxEnvironmentStatisticsLifecycleAdapter(LinuxEnvironmentStatistics statistics) + { + _statistics = statistics; + } - var logStr = $"LinuxEnvironmentStatistics: CpuUsage={CpuUsage?.ToString("0.0")}, TotalPhysicalMemory={TotalPhysicalMemory}, AvailableMemory={AvailableMemory}"; - _logger.LogTrace(logStr); + public Task OnStart(CancellationToken ct) => _statistics.OnStart(ct); - await Task.Delay(MONITOR_PERIOD, ct); - } - catch (Exception ex) when (ex.GetType() != typeof(TaskCanceledException)) - { - _logger.LogError(ex, "LinuxEnvironmentStatistics: error"); - await Task.Delay(MONITOR_PERIOD + MONITOR_PERIOD + MONITOR_PERIOD, ct); - } - if (i < 2) - i++; - } - } - - private static async Task ReadLineStartingWithAsync(string path, string lineStartsWith) - { - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 512, FileOptions.SequentialScan | FileOptions.Asynchronous)) - using (var r = new StreamReader(fs, Encoding.ASCII)) - { - string line; - while ((line = await r.ReadLineAsync()) != null) - { - if (line.StartsWith(lineStartsWith, StringComparison.Ordinal)) - return line; - } - } + public Task OnStop(CancellationToken ct) => _statistics.OnStop(ct); - return null; - } + public void Participate(TLifecycle lifecycle) + { + lifecycle.Subscribe( + nameof(LinuxEnvironmentStatistics), + ServiceLifecycleStage.RuntimeInitialize, + this); } -} +} \ No newline at end of file diff --git a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsLifecycleAdapter.cs b/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsLifecycleAdapter.cs deleted file mode 100644 index 01db8e03d2..0000000000 --- a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsLifecycleAdapter.cs +++ /dev/null @@ -1,30 +0,0 @@ -#define LOG_MEMORY_PERF_COUNTERS - -using System.Threading; -using System.Threading.Tasks; - -namespace Orleans.Statistics -{ - internal class LinuxEnvironmentStatisticsLifecycleAdapter - : ILifecycleParticipant, ILifecycleObserver where TLifecycle : ILifecycleObservable - { - private readonly LinuxEnvironmentStatistics _statistics; - - public LinuxEnvironmentStatisticsLifecycleAdapter(LinuxEnvironmentStatistics statistics) - { - _statistics = statistics; - } - - public Task OnStart(CancellationToken ct) => _statistics.OnStart(ct); - - public Task OnStop(CancellationToken ct) => _statistics.OnStop(ct); - - public void Participate(TLifecycle lifecycle) - { - lifecycle.Subscribe( - nameof(LinuxEnvironmentStatistics), - ServiceLifecycleStage.RuntimeInitialize, - this); - } - } -} diff --git a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsServices.cs b/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsServices.cs deleted file mode 100644 index ca5acbe950..0000000000 --- a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsServices.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Runtime.InteropServices; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Orleans.Configuration.Internal; - -namespace Orleans.Statistics -{ - internal static class LinuxEnvironmentStatisticsServices - { - /// - /// Registers services. - /// - internal static void RegisterServices(IServiceCollection services) where TLifecycleObservable : ILifecycleObservable - { - var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - if (!isLinux) - { - var logger = services.BuildServiceProvider().GetService>(); - logger?.LogWarning((int)ErrorCode.OS_InvalidOS, LinuxEnvironmentStatisticsValidator.InvalidOS); - - return; - } - - services.AddTransient(); - services.AddSingleton(); - services.AddFromExisting(); - services.AddSingleton, LinuxEnvironmentStatisticsLifecycleAdapter>(); - } - } -} diff --git a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsValidator.cs b/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsValidator.cs deleted file mode 100644 index 4db982db6a..0000000000 --- a/src/Orleans.Core/Statistics/LinuxEnvironmentStatisticsValidator.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using Orleans.Runtime; - -namespace Orleans.Statistics -{ - /// - /// Validates requirements for. - /// - internal class LinuxEnvironmentStatisticsValidator : IConfigurationValidator - { - internal static readonly string InvalidOS = $"Tried to add '{nameof(LinuxEnvironmentStatistics)}' on non-linux OS"; - - /// - public void ValidateConfiguration() - { - var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - if (!isLinux) - { - throw new OrleansConfigurationException(InvalidOS); - } - - var missingFiles = LinuxEnvironmentStatistics.RequiredFiles - .Select(f => new { FilePath = f, FileExists = File.Exists(f) }) - .Where(f => !f.FileExists) - .ToList(); - - if (missingFiles.Any()) - { - var paths = string.Join(", ", missingFiles.Select(f => f.FilePath)); - throw new OrleansConfigurationException($"Missing files for {nameof(LinuxEnvironmentStatistics)}: {paths}"); - } - } - } -} diff --git a/src/Orleans.Core/Statistics/WindowsEnvironmentStatistics.cs b/src/Orleans.Core/Statistics/WindowsEnvironmentStatistics.cs new file mode 100644 index 0000000000..c288dc80f5 --- /dev/null +++ b/src/Orleans.Core/Statistics/WindowsEnvironmentStatistics.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using System.Threading; +using Microsoft.Extensions.Logging; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; + +namespace Orleans.Statistics; + +[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Won't be registered unless the host is Windows")] +internal sealed class WindowsEnvironmentStatistics(ILoggerFactory loggerFactory) + : EnvironmentStatisticsBase(loggerFactory) +{ + private readonly PerformanceCounter _memoryCounter = new("Memory", "Available Bytes"); + + protected override ValueTask GetAvailableMemory(CancellationToken cancellationToken) + => ValueTask.FromResult((long?)_memoryCounter.NextValue()); +} + +internal sealed class WindowsEnvironmentStatisticsLifecycleAdapter : ILifecycleParticipant, ILifecycleObserver + where TLifecycle : ILifecycleObservable +{ + private readonly WindowsEnvironmentStatistics statistics; + + public WindowsEnvironmentStatisticsLifecycleAdapter(WindowsEnvironmentStatistics statistics) + => this.statistics = statistics; + + public Task OnStart(CancellationToken ct) => statistics.OnStart(ct); + public Task OnStop(CancellationToken ct) => statistics.OnStop(ct); + + public void Participate(TLifecycle lifecycle) => + lifecycle.Subscribe( + nameof(WindowsEnvironmentStatistics), + ServiceLifecycleStage.RuntimeInitialize, + this); +} \ No newline at end of file diff --git a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs index ff7647befa..a061faf06e 100644 --- a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs +++ b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs @@ -38,7 +38,6 @@ using Orleans.Serialization.TypeSystem; using Orleans.Serialization.Serializers; using Orleans.Serialization.Cloning; -using System.Runtime.InteropServices; using System.Collections.Generic; using Microsoft.Extensions.Configuration; using Orleans.Serialization.Internal; @@ -75,16 +74,10 @@ internal static void AddDefaultServices(ISiloBuilder builder) services.AddSingleton(); services.AddFromExisting, SiloOptionsLogger>(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - LinuxEnvironmentStatisticsServices.RegisterServices(services); - } - else - { - services.TryAddSingleton(); - } - + // Statistics + services.RegisterEnvironmentStatisticsServices(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton();