Skip to content

Commit

Permalink
Read values from /proc/[pid]/status (dotnet/corefx#41122)
Browse files Browse the repository at this point in the history
* Fix dotnet/corefx#23449 - Read values from /proc/[pid]/status

* Fix tests for new returned data from /prod/[pid]/status

* Update tests of process info on OSX

* Rename property ParsedStatus.pid --> ParsedStatus.Pid

* 1. Avoid reallocation of delimiter array
2. Use tryParse instead of Parse to avoid exceptions

* Rename static field and mark it as readonly

* Change pid from /status to be read only from debug mode

* Change status file read implementation using spans

* Remove unecessary debug statement

* Revert "Remove unecessary debug statement"

This reverts commit dotnet/corefx@f60b4f3.

* Refactor logic using if statements with reduction in code redundancy

* Improve condition arrangement to avoid reading subsequent files if process name doesn't match

* Avoid failure of process info creation in case of fail read from one of many files.

* Refactor Interop.ProcFsStat for better understanding

* Change process creation logic to fail on read /stat

* Change logic for unit and non unit values in file


Commit migrated from dotnet/corefx@8086f36
  • Loading branch information
shubhamranjan authored and stephentoub committed Nov 4, 2019
1 parent 35ed210 commit 4b42cd3
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 34 deletions.
165 changes: 152 additions & 13 deletions src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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/";

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<char> 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<char> 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<char> 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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public static Process[] GetProcessesByName(string processName, string machineNam
var processes = new List<Process>();
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));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,19 @@ internal static ProcessModuleCollection GetModules(int processId)
/// </summary>
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;
}

/// <summary>
/// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory.
/// </summary>
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;

Expand All @@ -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
Expand Down
24 changes: 18 additions & 6 deletions src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -632,7 +644,7 @@ public void TestPeakVirtualMemorySize64()
{
CreateDefaultProcess();

AssertNonZeroWindowsZeroUnix(_process.PeakVirtualMemorySize64);
AssertNonZeroAllZeroDarwin(_process.PeakVirtualMemorySize64);
}

[Fact]
Expand All @@ -647,7 +659,7 @@ public void TestPeakWorkingSet64()
{
CreateDefaultProcess();

AssertNonZeroWindowsZeroUnix(_process.PeakWorkingSet64);
AssertNonZeroAllZeroDarwin(_process.PeakWorkingSet64);
}

[Fact]
Expand All @@ -662,7 +674,7 @@ public void TestPrivateMemorySize64()
{
CreateDefaultProcess();

AssertNonZeroWindowsZeroUnix(_process.PrivateMemorySize64);
AssertNonZeroAllZeroDarwin(_process.PrivateMemorySize64);
}

[Fact]
Expand Down Expand Up @@ -1643,7 +1655,7 @@ public void TestPeakVirtualMemorySize()
CreateDefaultProcess();

#pragma warning disable 0618
AssertNonZeroWindowsZeroUnix(_process.PeakVirtualMemorySize);
AssertNonZeroAllZeroDarwin(_process.PeakVirtualMemorySize);
#pragma warning restore 0618
}

Expand All @@ -1662,7 +1674,7 @@ public void TestPeakWorkingSet()
CreateDefaultProcess();

#pragma warning disable 0618
AssertNonZeroWindowsZeroUnix(_process.PeakWorkingSet);
AssertNonZeroAllZeroDarwin(_process.PeakWorkingSet);
#pragma warning restore 0618
}

Expand All @@ -1681,7 +1693,7 @@ public void TestPrivateMemorySize()
CreateDefaultProcess();

#pragma warning disable 0618
AssertNonZeroWindowsZeroUnix(_process.PrivateMemorySize);
AssertNonZeroAllZeroDarwin(_process.PrivateMemorySize);
#pragma warning restore 0618
}

Expand Down

0 comments on commit 4b42cd3

Please sign in to comment.