File: Linux\OSFileSystem.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.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Shared.Pools;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux;
 
/// <remarks>
/// We are reading files from /proc and /cgroup. Those files are dynamically generated by kernel, when the access to them is requested.
/// Those files then, are stored entirely in RAM; it is called Virtual File System (VFS). The access to the files is done by syscall that is non blocking.
/// Thus, this API can synchronous without performance loss.
/// </remarks>
internal sealed class OSFileSystem : IFileSystem
{
    public bool Exists(FileInfo fileInfo)
    {
        return fileInfo.Exists;
    }
 
    public IReadOnlyCollection<string> GetDirectoryNames(string directory, string pattern)
    {
        return Directory.GetDirectories(directory, pattern)
                .ToArray();
    }
 
    public int Read(FileInfo file, int length, Span<char> destination)
    {
        using var stream = file.OpenRead();
        using var rentedBuffer = new RentedSpan<byte>(length);
 
        var read = stream.Read(rentedBuffer.Span);
 
        return Encoding.ASCII.GetChars(rentedBuffer.Span.Slice(0, read), destination);
    }
 
    public void ReadFirstLine(FileInfo file, BufferWriter<char> destination)
        => ReadUntilTerminatorOrEnd(file, destination, (byte)'\n');
 
    public void ReadAll(FileInfo file, BufferWriter<char> destination)
        => ReadUntilTerminatorOrEnd(file, destination, null);
 
    public IEnumerable<ReadOnlyMemory<char>> ReadAllByLines(FileInfo file, BufferWriter<char> destination)
    {
        const int MaxStackalloc = 256;
 
        if (!file.Exists)
        {
            throw new FileNotFoundException();
        }
 
        Memory<byte> buffer = ArrayPool<byte>.Shared.Rent(MaxStackalloc);
        try
        {
            using FileStream stream = file.OpenRead();
 
            int read = stream.Read(buffer.Span);
            while (read > 0)
            {
                var start = 0;
                var end = 0;
 
                for (end = 0; end < read; end++)
                {
                    if (buffer.Span[end] == (byte)'\n')
                    {
                        var length = end - start;
                        _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length));
                        destination.Advance(length);
                        start = end + 1;
                        yield return destination.WrittenMemory;
                        destination.Reset();
                    }
                }
 
                // Set the comparison in the while loop to end when the file has not been completely read into the buffer.
                // It will then advance the last character to the destination for the next time yield return is called.
                if (start < read)
                {
                    var length = read - start;
                    _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length));
                    destination.Advance(length);
                }
 
                read = stream.Read(buffer.Span);
            }
        }
        finally
        {
            if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> arraySegment) && arraySegment.Array != null)
            {
                ArrayPool<byte>.Shared.Return(arraySegment.Array);
            }
        }
    }
 
    [SkipLocalsInit]
    private static void ReadUntilTerminatorOrEnd(FileInfo file, BufferWriter<char> destination, byte? terminator)
    {
        const int MaxStackalloc = 256;
 
        using var stream = file.OpenRead();
 
        Span<byte> buffer = stackalloc byte[MaxStackalloc];
        var read = stream.Read(buffer);
 
        while (read != 0)
        {
            var end = 0;
 
            for (end = 0; end < read; end++)
            {
                if (buffer[end] == terminator)
                {
                    _ = Encoding.ASCII.GetChars(buffer.Slice(0, end), destination.GetSpan(end));
                    destination.Advance(end);
 
                    return;
                }
            }
 
            _ = Encoding.ASCII.GetChars(buffer.Slice(0, end), destination.GetSpan(end));
            destination.Advance(end);
            read = stream.Read(buffer);
        }
    }
}