File: Linux\Disk\DiskStatsReader.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.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Shared.Pools;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
 
/// <summary>
/// Handles reading and parsing of Linux procfs-diskstats file(/proc/diskstats).
/// </summary>
internal sealed class DiskStatsReader(IFileSystem fileSystem) : IDiskStatsReader
{
    private static readonly FileInfo _diskStatsFile = new("/proc/diskstats");
    private static readonly ObjectPool<BufferWriter<char>> _sharedBufferWriterPool = BufferWriterPool.CreateBufferWriterPool<char>();
 
    /// <summary>
    /// Reads and returns all disk statistics entries.
    /// </summary>
    /// <returns>List of <see cref="DiskStats"/>.</returns>
    public List<DiskStats> ReadAll()
    {
        var diskStatsList = new List<DiskStats>();
 
        using ReturnableBufferWriter<char> bufferWriter = new(_sharedBufferWriterPool);
        using IEnumerator<ReadOnlyMemory<char>> enumerableLines = fileSystem.ReadAllByLines(_diskStatsFile, bufferWriter.Buffer).GetEnumerator();
 
        while (enumerableLines.MoveNext())
        {
            string line = enumerableLines.Current.Trim().ToString();
            if (string.IsNullOrWhiteSpace(line))
            {
                continue;
            }
 
            try
            {
                DiskStats stat = DiskStatsReader.ParseLine(line);
                diskStatsList.Add(stat);
            }
#pragma warning disable CA1031
            catch (Exception)
#pragma warning restore CA1031
            {
                // ignore parsing errors
            }
        }
 
        return diskStatsList;
    }
 
    /// <summary>
    /// Parses one line of text into a DiskStats object.
    /// </summary>
    /// <param name="line">one line in "/proc/diskstats".</param>
    /// <returns>parsed DiskStats object.</returns>
    [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", Justification = "These numbers represent fixed field indices in the Linux /proc/diskstats format")]
    private static DiskStats ParseLine(string line)
    {
        // Split by any whitespace and remove empty entries
#pragma warning disable EA0009
        string[] parts = line.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);
#pragma warning restore EA0009
 
        if (parts.Length < 14)
        {
            throw new FormatException($"Not enough fields: expected at least 14, got {parts.Length}");
        }
 
        // See https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
        var diskStats = new DiskStats
        {
            MajorNumber = int.Parse(parts[0], CultureInfo.InvariantCulture),
            MinorNumber = int.Parse(parts[1], CultureInfo.InvariantCulture),
            DeviceName = parts[2],
            ReadsCompleted = ulong.Parse(parts[3], CultureInfo.InvariantCulture),
            ReadsMerged = ulong.Parse(parts[4], CultureInfo.InvariantCulture),
            SectorsRead = ulong.Parse(parts[5], CultureInfo.InvariantCulture),
            TimeReadingMs = uint.Parse(parts[6], CultureInfo.InvariantCulture),
            WritesCompleted = ulong.Parse(parts[7], CultureInfo.InvariantCulture),
            WritesMerged = ulong.Parse(parts[8], CultureInfo.InvariantCulture),
            SectorsWritten = ulong.Parse(parts[9], CultureInfo.InvariantCulture),
            TimeWritingMs = uint.Parse(parts[10], CultureInfo.InvariantCulture),
            IoInProgress = uint.Parse(parts[11], CultureInfo.InvariantCulture),
            TimeIoMs = uint.Parse(parts[12], CultureInfo.InvariantCulture),
            WeightedTimeIoMs = uint.Parse(parts[13], CultureInfo.InvariantCulture)
        };
 
        // Parse additional fields if present
        if (parts.Length >= 18)
        {
            diskStats.DiscardsCompleted = ulong.Parse(parts[14], CultureInfo.InvariantCulture);
            diskStats.DiscardsMerged = ulong.Parse(parts[15], CultureInfo.InvariantCulture);
            diskStats.SectorsDiscarded = ulong.Parse(parts[16], CultureInfo.InvariantCulture);
            diskStats.TimeDiscardingMs = uint.Parse(parts[17], CultureInfo.InvariantCulture);
        }
 
        if (parts.Length >= 20)
        {
            diskStats.FlushRequestsCompleted = ulong.Parse(parts[18], CultureInfo.InvariantCulture);
            diskStats.TimeFlushingMs = uint.Parse(parts[19], CultureInfo.InvariantCulture);
        }
 
        return diskStats;
    }
}