in src/Elastic.Apm/Metrics/Linux/GlobalMemoryStatus.cs [31:188]
public static (long totalMemory, long availableMemory) GetTotalAndAvailableSystemMemory(IApmLogger logger)
=> GetTotalAndAvailableSystemMemory(logger, null, false);
internal static (long totalMemory, long availableMemory) GetTotalAndAvailableSystemMemory(
IApmLogger logger, string pathPrefix, bool ignoreOs)
{
(long, long) failure = (-1, -1);
long totalMemory = -1, availableMemory = -1;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !ignoreOs)
{
logger.Trace()
?.Log("{ClassName} detected a non-Linux OS, therefore"
+ " proc/meminfo will not be reported", nameof(GlobalMemoryStatus));
return failure;
}
var memInfoPath = !string.IsNullOrEmpty(pathPrefix)
? Path.Combine(pathPrefix, ProcMemInfo.Substring(1))
: ProcMemInfo;
if (!File.Exists(memInfoPath))
{
logger.Error()?.Log("Unable to load memory information from {ProcMemInfo}", ProcMemInfo);
return failure;
}
try
{
#if NET8_0_OR_GREATER
using var fs = new FileStream(memInfoPath, Options);
var buffer = ArrayPool<byte>.Shared.Rent(8192); // Should easily be large enough for max meminfo file.
try
{
// We read from the file into our rented buffer so that we can parse data from the meminfo file.
// Specifically, we try to parse the values for `MemAvailable` and `MemTotal`. When these values
// use the KB unit, we multiply them to return bytes.
var read = fs.Read(buffer);
if (read == 0)
return failure;
var span = buffer.AsSpan().Slice(0, read);
var memAvailable = span.IndexOf(MemAvailable);
if (memAvailable >= 0)
{
var slice = span.Slice(memAvailable + MemAvailable.Length + 1);
var position = 0;
while (true)
{
if (slice[position] != Space)
break;
position++;
}
if (position > 0 && Utf8Parser.TryParse(slice.Slice(position), out long value, out var consumed))
{
availableMemory = slice.Slice(position + consumed, 3).SequenceEqual(KB)
? value *= 1024
: value;
}
}
var memTotal = span.IndexOf(MemTotal);
if (memTotal >= 0)
{
var slice = span.Slice(memTotal + MemTotal.Length + 1);
var position = 0;
while (true)
{
if (slice[position] != Space)
break;
position++;
}
if (position > 0 && Utf8Parser.TryParse(slice.Slice(position), out long value, out var consumed))
{
totalMemory = slice.Slice(position + consumed, 3).SequenceEqual(KB)
? value *= 1024
: value;
}
}
return (totalMemory, availableMemory);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
#else
using var sr = new StreamReader(memInfoPath);
var hasMemFree = false;
var hasMemTotal = false;
var samples = 0;
var line = sr.ReadLine();
while (samples != 2)
{
if (line != null && line.Contains("MemAvailable:"))
{
availableMemory = GetEntry(line, "MemAvailable:");
samples++;
hasMemFree = true;
}
if (line != null && line.Contains("MemTotal:"))
{
totalMemory = GetEntry(line, "MemTotal:");
samples++;
hasMemTotal = true;
}
if (hasMemFree && hasMemTotal)
break;
line = sr.ReadLine();
if (line is null)
break;
}
return (totalMemory, availableMemory);
static long GetEntry(string line, string name)
{
var nameIndex = line.IndexOf(name, StringComparison.Ordinal);
if (nameIndex < 0)
return -1;
var values = line.Substring(line.IndexOf(name, StringComparison.Ordinal) + name.Length);
if (string.IsNullOrWhiteSpace(values))
return -1;
var items = values.Trim().Split(' ');
return items.Length switch
{
1 when long.TryParse(items[0], out var res) => res,
2 when items[1].ToLowerInvariant() == "kb" && long.TryParse(items[0], out var res) => res * 1024,
_ => -1,
};
}
#endif
}
catch (IOException e)
{
logger.Info()?.LogException(e, "Error collecting memory data from {path}.", memInfoPath);
}
return failure;
}