diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs index d783f5de44c9a6..98c5b0d29773e7 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs @@ -18,6 +18,7 @@ internal static partial class procfs private const string CmdLineFileName = "/cmdline"; private const string StatFileName = "/stat"; private const string MapsFileName = "/maps"; + private const string StatusFileName = "/status"; private const string FileDescriptorDirectoryName = "/fd/"; private const string TaskDirectoryName = "/task/"; @@ -78,6 +79,19 @@ internal struct ParsedStat //internal long cguest_time; } + internal struct ParsedStatus + { +#if DEBUG + internal int Pid; +#endif + internal ulong VmHWM; + internal ulong VmRSS; + internal ulong VmData; + internal ulong VmSwap; + internal ulong VmSize; + internal ulong VmPeak; + } + internal struct ParsedMapsModule { internal string FileName; @@ -99,6 +113,11 @@ internal static string GetStatFilePathForProcess(int pid) return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatFileName; } + internal static string GetStatusFilePathForProcess(int pid) + { + return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatusFileName; + } + internal static string GetMapsFilePathForProcess(int pid) { return RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; @@ -209,24 +228,22 @@ internal static bool TryReadStatFile(int pid, out ParsedStat result, ReusableTex internal static bool TryReadStatFile(int pid, int tid, out ParsedStat result, ReusableTextReader reusableReader) { bool b = TryParseStatFile(GetStatFilePathForThread(pid, tid), out result, reusableReader); - // - // This assert currently fails in the Windows Subsystem For Linux. See https://github.com/Microsoft/BashOnWindows/issues/967. - // - //Debug.Assert(!b || result.pid == tid, "Expected thread ID from stat file to match supplied tid"); + Debug.Assert(!b || result.pid == tid, "Expected thread ID from stat file to match supplied tid"); + return b; + } + + internal static bool TryReadStatusFile(int pid, out ParsedStatus result, ReusableTextReader reusableReader) + { + bool b = TryParseStatusFile(GetStatusFilePathForProcess(pid), out result, reusableReader); +#if DEBUG + Debug.Assert(!b || result.Pid == pid, "Expected process ID from status file to match supplied pid"); +#endif return b; } internal static bool TryParseStatFile(string statFilePath, out ParsedStat result, ReusableTextReader reusableReader) { - string statFileContents; - try - { - using (var source = new FileStream(statFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, useAsync: false)) - { - statFileContents = reusableReader.ReadAllText(source); - } - } - catch (IOException) + if (!TryReadFile(statFilePath, reusableReader, out string statFileContents)) { // Between the time that we get an ID and the time that we try to read the associated stat // file(s), the process could be gone. @@ -292,5 +309,127 @@ internal static bool TryParseStatFile(string statFilePath, out ParsedStat result result = results; return true; } + + internal static bool TryParseStatusFile(string statusFilePath, out ParsedStatus result, ReusableTextReader reusableReader) + { + if (!TryReadFile(statusFilePath, reusableReader, out string fileContents)) + { + // Between the time that we get an ID and the time that we try to read the associated stat + // file(s), the process could be gone. + result = default(ParsedStatus); + return false; + } + + ParsedStatus results = default(ParsedStatus); + ReadOnlySpan statusFileContents = fileContents.AsSpan(); + int unitSliceLength = -1; +#if DEBUG + int nonUnitSliceLength = -1; +#endif + while (!statusFileContents.IsEmpty) + { + int startIndex = statusFileContents.IndexOf(':'); + if (startIndex == -1) + { + // Reached end of file + break; + } + + ReadOnlySpan title = statusFileContents.Slice(0, startIndex); + statusFileContents = statusFileContents.Slice(startIndex + 1); + int endIndex = statusFileContents.IndexOf('\n'); + if (endIndex == -1) + { + endIndex = statusFileContents.Length - 1; + unitSliceLength = statusFileContents.Length - 3; +#if DEBUG + nonUnitSliceLength = statusFileContents.Length; +#endif + } + else + { + unitSliceLength = endIndex - 3; +#if DEBUG + nonUnitSliceLength = endIndex; +#endif + } + + ReadOnlySpan value = default; + bool valueParsed = true; +#if DEBUG + if (title.SequenceEqual("Pid".AsSpan())) + { + value = statusFileContents.Slice(0, nonUnitSliceLength); + valueParsed = int.TryParse(value, out results.Pid); + } +#endif + if (title.SequenceEqual("VmHWM".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out results.VmHWM); + } + else if (title.SequenceEqual("VmRSS".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out results.VmRSS); + } + else if (title.SequenceEqual("VmData".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out ulong vmData); + results.VmData += vmData; + } + else if (title.SequenceEqual("VmSwap".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out results.VmSwap); + } + else if (title.SequenceEqual("VmSize".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out results.VmSize); + } + else if (title.SequenceEqual("VmPeak".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out results.VmPeak); + } + else if (title.SequenceEqual("VmStk".AsSpan())) + { + value = statusFileContents.Slice(0, unitSliceLength); + valueParsed = ulong.TryParse(value, out ulong vmStack); + results.VmData += vmStack; + } + + Debug.Assert(valueParsed); + statusFileContents = statusFileContents.Slice(endIndex + 1); + } + + results.VmData *= 1024; + results.VmPeak *= 1024; + results.VmSize *= 1024; + results.VmSwap *= 1024; + results.VmRSS *= 1024; + results.VmHWM *= 1024; + result = results; + return true; + } + + private static bool TryReadFile(string filePath, ReusableTextReader reusableReader, out string fileContents) + { + try + { + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, useAsync: false)) + { + fileContents = reusableReader.ReadAllText(fileStream); + return true; + } + } + catch (IOException) + { + fileContents = null; + return false; + } + } } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs index e6266efdc7945c..7ad4c85800ac3a 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs @@ -30,11 +30,11 @@ public static Process[] GetProcessesByName(string processName, string machineNam var processes = new List(); foreach (int pid in ProcessManager.EnumerateProcessIds()) { - Interop.procfs.ParsedStat parsedStat; - if (Interop.procfs.TryReadStatFile(pid, out parsedStat, reusableReader) && - string.Equals(processName, Process.GetUntruncatedProcessName(ref parsedStat), StringComparison.OrdinalIgnoreCase)) + if (Interop.procfs.TryReadStatFile(pid, out Interop.procfs.ParsedStat parsedStat, reusableReader) && + string.Equals(processName, Process.GetUntruncatedProcessName(ref parsedStat), StringComparison.OrdinalIgnoreCase) && + Interop.procfs.TryReadStatusFile(pid, out Interop.procfs.ParsedStatus parsedStatus, reusableReader)) { - ProcessInfo processInfo = ProcessManager.CreateProcessInfo(ref parsedStat, reusableReader, processName); + ProcessInfo processInfo = ProcessManager.CreateProcessInfo(ref parsedStat, ref parsedStatus, reusableReader, processName); processes.Add(new Process(machineName, false, processInfo.ProcessId, processInfo)); } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs index 49ae529204a6fd..a8dfca6b6279b5 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs @@ -110,21 +110,19 @@ internal static ProcessModuleCollection GetModules(int processId) /// internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null) { - if (reusableReader == null) + reusableReader ??= new ReusableTextReader(); + if (Interop.procfs.TryReadStatFile(pid, out Interop.procfs.ParsedStat stat, reusableReader)) { - reusableReader = new ReusableTextReader(); + Interop.procfs.TryReadStatusFile(pid, out Interop.procfs.ParsedStatus status, reusableReader); + return CreateProcessInfo(ref stat, ref status, reusableReader); } - - Interop.procfs.ParsedStat stat; - return Interop.procfs.TryReadStatFile(pid, out stat, reusableReader) ? - CreateProcessInfo(ref stat, reusableReader) : - null; + return null; } /// /// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory. /// - internal static ProcessInfo CreateProcessInfo(ref Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader, string processName = null) + internal static ProcessInfo CreateProcessInfo(ref Interop.procfs.ParsedStat procFsStat, ref Interop.procfs.ParsedStatus procFsStatus, ReusableTextReader reusableReader, string processName = null) { int pid = procFsStat.pid; @@ -133,10 +131,14 @@ internal static ProcessInfo CreateProcessInfo(ref Interop.procfs.ParsedStat proc ProcessId = pid, ProcessName = processName ?? Process.GetUntruncatedProcessName(ref procFsStat) ?? string.Empty, BasePriority = (int)procFsStat.nice, - VirtualBytes = (long)procFsStat.vsize, - WorkingSet = procFsStat.rss * Environment.SystemPageSize, SessionId = procFsStat.session, - + PoolPagedBytes = (long)procFsStatus.VmSwap, + VirtualBytes = (long)procFsStatus.VmSize, + VirtualBytesPeak = (long)procFsStatus.VmPeak, + WorkingSetPeak = (long)procFsStatus.VmHWM, + WorkingSet = (long)procFsStatus.VmRSS, + PageFileBytes = (long)procFsStatus.VmSwap, + PrivateBytes = (long)procFsStatus.VmData, // We don't currently fill in the other values. // A few of these could probably be filled in from getrusage, // but only for the current process or its children, not for diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index 6515153d09fbd6..5d71d0c7646250 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -62,6 +62,18 @@ private void AssertNonZeroWindowsZeroUnix(long value) } } + private void AssertNonZeroAllZeroDarwin(long value) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.Equal(0, value); + } + else + { + Assert.NotEqual(0, value); + } + } + [Fact] [PlatformSpecific(TestPlatforms.Windows)] // Expected behavior varies on Windows and Unix public void TestBasePriorityOnWindows() @@ -632,7 +644,7 @@ public void TestPeakVirtualMemorySize64() { CreateDefaultProcess(); - AssertNonZeroWindowsZeroUnix(_process.PeakVirtualMemorySize64); + AssertNonZeroAllZeroDarwin(_process.PeakVirtualMemorySize64); } [Fact] @@ -647,7 +659,7 @@ public void TestPeakWorkingSet64() { CreateDefaultProcess(); - AssertNonZeroWindowsZeroUnix(_process.PeakWorkingSet64); + AssertNonZeroAllZeroDarwin(_process.PeakWorkingSet64); } [Fact] @@ -662,7 +674,7 @@ public void TestPrivateMemorySize64() { CreateDefaultProcess(); - AssertNonZeroWindowsZeroUnix(_process.PrivateMemorySize64); + AssertNonZeroAllZeroDarwin(_process.PrivateMemorySize64); } [Fact] @@ -1643,7 +1655,7 @@ public void TestPeakVirtualMemorySize() CreateDefaultProcess(); #pragma warning disable 0618 - AssertNonZeroWindowsZeroUnix(_process.PeakVirtualMemorySize); + AssertNonZeroAllZeroDarwin(_process.PeakVirtualMemorySize); #pragma warning restore 0618 } @@ -1662,7 +1674,7 @@ public void TestPeakWorkingSet() CreateDefaultProcess(); #pragma warning disable 0618 - AssertNonZeroWindowsZeroUnix(_process.PeakWorkingSet); + AssertNonZeroAllZeroDarwin(_process.PeakWorkingSet); #pragma warning restore 0618 } @@ -1681,7 +1693,7 @@ public void TestPrivateMemorySize() CreateDefaultProcess(); #pragma warning disable 0618 - AssertNonZeroWindowsZeroUnix(_process.PrivateMemorySize); + AssertNonZeroAllZeroDarwin(_process.PrivateMemorySize); #pragma warning restore 0618 }