|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace System.Diagnostics
{
internal static partial class ProcessManager
{
/// <summary>Gets the IDs of all processes on the current machine.</summary>
public static int[] GetProcessIds() => new List<int>(EnumerateProcessIds()).ToArray();
/// <summary>Gets process infos for each process on the specified machine.</summary>
/// <param name="processNameFilter">Optional process name to use as an inclusion filter.</param>
/// <param name="machineName">The target machine.</param>
/// <returns>An array of process infos, one per found process.</returns>
public static ProcessInfo[] GetProcessInfos(string? processNameFilter, string machineName)
{
Debug.Assert(processNameFilter is null, "Not used on Linux");
ThrowIfRemoteMachine(machineName);
// Iterate through all process IDs to load information about each process
IEnumerable<int> pids = EnumerateProcessIds();
ArrayBuilder<ProcessInfo> processes = default;
foreach (int pid in pids)
{
ProcessInfo? pi = CreateProcessInfo(pid);
if (pi != null)
{
processes.Add(pi);
}
}
return processes.ToArray();
}
/// <summary>Gets an array of module infos for the specified process.</summary>
/// <param name="processId">The ID of the process whose modules should be enumerated.</param>
/// <returns>The array of modules.</returns>
internal static ProcessModuleCollection GetModules(int processId)
{
ProcessModuleCollection modules = Interop.procfs.ParseMapsModules(processId) ?? new(capacity: 0);
// Move the main executable module to be the first in the list if it's not already
if (Process.GetExePath(processId) is string exePath)
{
for (int i = 0; i < modules.Count; i++)
{
ProcessModule module = modules[i];
if (module.FileName == exePath)
{
if (i > 0)
{
modules.RemoveAt(i);
modules.Insert(0, module);
}
break;
}
}
}
// Return the set of modules found
return modules;
}
/// <summary>
/// Creates a ProcessInfo from the specified process ID.
/// </summary>
internal static ProcessInfo? CreateProcessInfo(int pid)
{
if (Interop.procfs.TryReadStatFile(pid, out Interop.procfs.ParsedStat stat))
{
Interop.procfs.TryReadStatusFile(pid, out Interop.procfs.ParsedStatus status);
return CreateProcessInfo(ref stat, ref status);
}
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, ref Interop.procfs.ParsedStatus procFsStatus, string? processName = null)
{
int pid = procFsStat.pid;
var pi = new ProcessInfo()
{
ProcessId = pid,
ProcessName = processName ?? Process.GetUntruncatedProcessName(ref procFsStat) ?? string.Empty,
BasePriority = (int)procFsStat.nice,
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
// arbitrary other processes.
};
// Then read through /proc/pid/task/ to find each thread in the process...
string tasksDir = Interop.procfs.GetTaskDirectoryPathForProcess(pid);
try
{
foreach (string taskDir in Directory.EnumerateDirectories(tasksDir))
{
// ...and read its associated /proc/pid/task/tid/stat file to create a ThreadInfo
string dirName = Path.GetFileName(taskDir);
int tid;
Interop.procfs.ParsedStat stat;
if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid) &&
Interop.procfs.TryReadStatFile(pid, tid, out stat))
{
pi._threadInfoList.Add(new ThreadInfo()
{
_processId = pid,
_threadId = (ulong)tid,
_basePriority = pi.BasePriority,
_currentPriority = (int)stat.nice,
_startAddress = null,
_threadState = ProcFsStateToThreadState(stat.state),
_threadWaitReason = ThreadWaitReason.Unknown
});
}
}
}
catch (IOException)
{
// Between the time that we get an ID and the time that we try to read the associated
// directories and files in procfs, the process could be gone.
}
// Finally return what we've built up
return pi;
}
// ----------------------------------
// ---- Unix PAL layer ends here ----
// ----------------------------------
/// <summary>Enumerates the IDs of all processes on the current machine.</summary>
internal static IEnumerable<int> EnumerateProcessIds()
{
// Parse /proc for any directory that's named with a number. Each such
// directory represents a process.
foreach (string procDir in Directory.EnumerateDirectories(Interop.procfs.RootPath))
{
string dirName = Path.GetFileName(procDir);
int pid;
if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out pid))
{
Debug.Assert(pid >= 0);
yield return pid;
}
}
}
/// <summary>Gets a ThreadState to represent the value returned from the status field of /proc/pid/stat.</summary>
/// <param name="c">The status field value.</param>
/// <returns></returns>
private static ThreadState ProcFsStateToThreadState(char c)
{
// Information on these in fs/proc/array.c
// `man proc` does not document them all
switch (c)
{
case 'R': // Running
return ThreadState.Running;
case 'D': // Waiting on disk
case 'P': // Parked
case 'S': // Sleeping in a wait
case 't': // Tracing/debugging
case 'T': // Stopped on a signal
return ThreadState.Wait;
case 'x': // dead
case 'X': // Dead
case 'Z': // Zombie
return ThreadState.Terminated;
case 'W': // Paging or waking
case 'K': // Wakekill
return ThreadState.Transition;
case 'I': // Idle
return ThreadState.Ready;
default:
Debug.Fail($"Unexpected status character: {c}");
return ThreadState.Unknown;
}
}
}
}
|