|
// 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);
}
}
}
|