public static()

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