File: Windows\Disk\WindowsDiskIoRatePerfCounter.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Diagnostics.ResourceMonitoring\Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj (Microsoft.Extensions.Diagnostics.ResourceMonitoring)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.Versioning;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk;
 
[SupportedOSPlatform("windows")]
internal sealed class WindowsDiskIoRatePerfCounter
{
    private readonly List<IPerformanceCounter> _counters = [];
    private readonly IPerformanceCounterFactory _performanceCounterFactory;
    private readonly TimeProvider _timeProvider;
    private readonly string _categoryName;
    private readonly string _counterName;
    private readonly string[] _instanceNames;
    private long _lastTimeTicks;
 
    internal WindowsDiskIoRatePerfCounter(
        IPerformanceCounterFactory performanceCounterFactory,
        TimeProvider timeProvider,
        string categoryName,
        string counterName,
        string[] instanceNames)
    {
        _performanceCounterFactory = performanceCounterFactory;
        _timeProvider = timeProvider;
        _categoryName = categoryName;
        _counterName = counterName;
        _instanceNames = instanceNames;
    }
 
    /// <summary>
    /// Gets the disk I/O measurements.
    /// Key: Disk name, Value: Total count.
    /// </summary>
    internal IDictionary<string, long> TotalCountDict { get; } = new ConcurrentDictionary<string, long>();
 
    internal void InitializeDiskCounters()
    {
        foreach (string instanceName in _instanceNames)
        {
            // Create counters for each disk
            _counters.Add(_performanceCounterFactory.Create(_categoryName, _counterName, instanceName));
            TotalCountDict[instanceName] = 0L;
        }
 
        // Initialize the counters to get the first value
        foreach (IPerformanceCounter counter in _counters)
        {
            _ = counter.NextValue();
        }
 
        _lastTimeTicks = _timeProvider.GetUtcNow().Ticks;
    }
 
    internal void UpdateDiskCounters()
    {
        long currentTimeTicks = _timeProvider.GetUtcNow().Ticks;
        long elapsedTimeTicks = currentTimeTicks - _lastTimeTicks;
 
        // For the kind of "rate" perf counters, this algorithm calculates the total value over a time interval
        // by multiplying the per-second rate (e.g., Disk Bytes/sec) by the time interval between two samples.
        // This effectively reverses the per-second rate calculation to a total amount (e.g., total bytes transferred) during that period.
        // See https://learn.microsoft.com/zh-cn/archive/blogs/askcore/windows-performance-monitor-disk-counters-explained#windows-performance-monitor-disk-counters-explained
        foreach (IPerformanceCounter counter in _counters)
        {
            // total value = per-second rate * elapsed seconds
            double value = counter.NextValue() * (double)elapsedTimeTicks;
            TotalCountDict[counter.InstanceName] += (long)value / TimeSpan.TicksPerSecond;
        }
 
        _lastTimeTicks = currentTimeTicks;
    }
}