File: Windows\Disk\WindowsDiskIoTimePerfCounter.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;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk;
 
internal sealed class WindowsDiskIoTimePerfCounter
{
    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 WindowsDiskIoTimePerfCounter(
        IPerformanceCounterFactory performanceCounterFactory,
        TimeProvider timeProvider,
        string categoryName,
        string counterName,
        string[] instanceNames)
    {
        _performanceCounterFactory = performanceCounterFactory;
        _timeProvider = timeProvider;
        _categoryName = categoryName;
        _counterName = counterName;
        _instanceNames = instanceNames;
    }
 
    /// <summary>
    /// Gets the disk time measurements.
    /// Key: Disk name, Value: Real elapsed time used in busy state.
    /// </summary>
    internal IDictionary<string, double> TotalSeconds { get; } = new ConcurrentDictionary<string, double>();
 
    internal void InitializeDiskCounters()
    {
        foreach (string instanceName in _instanceNames)
        {
            // Create counters for each disk
            _counters.Add(_performanceCounterFactory.Create(_categoryName, _counterName, instanceName));
            TotalSeconds[instanceName] = 0f;
        }
 
        // 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;
 
        // The real elapsed time ("wall clock") used in the I/O path (time from operations running in parallel are not counted).
        // Measured as the complement of "Disk\% Idle Time" performance counter: uptime * (100 - "Disk\% Idle Time") / 100
        // See https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskio_time
        foreach (IPerformanceCounter counter in _counters)
        {
            // io busy time = (1 - (% idle time / 100)) * elapsed seconds
            float idleTimePercentage = Math.Min(counter.NextValue(), 100f);
            double busyTimeTicks = (1 - (idleTimePercentage / 100f)) * (double)elapsedTimeTicks;
            TotalSeconds[counter.InstanceName] += busyTimeTicks / TimeSpan.TicksPerSecond;
        }
 
        _lastTimeTicks = currentTimeTicks;
    }
}