| File: ProcessCodeMethods.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.Extensions.BlameDataCollector\Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj (Microsoft.TestPlatform.Extensions.BlameDataCollector) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; /// <summary> /// Helper functions for process info. /// </summary> internal static class ProcessCodeMethods { private const int InvalidProcessId = -1; public static void Suspend(this Process process) { if (process.HasExited) { EqtTrace.Verbose($"ProcessCodeMethods.Suspend: Process {process.Id} - {process.ProcessName} already exited, skipping."); return; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // There is no supported or documented API to suspend a process. If there was you should call it here. // SuspendWindows(process); } else { // TODO: do not suspend on Mac and Linux, this prevents the process from being dumped when we use the net client dumper, checking if we can post a different signal // SuspendLinuxMacOs(process); } } public static List<ProcessTreeNode> GetProcessTree(this Process process) { var childProcesses = Process.GetProcesses() .Where(p => IsChildCandidate(p, process)) .ToList(); var acc = new List<ProcessTreeNode>(); foreach (var c in childProcesses) { try { var parentId = GetParentPid(c); // c.ParentId = parentId; acc.Add(new ProcessTreeNode { ParentId = parentId, Process = c }); } catch { // many things can go wrong with this // just ignore errors } } var level = 1; var limit = 10; ResolveChildren(process, acc, level, limit); return new List<ProcessTreeNode> { new() { Process = process, Level = 0 } }.Concat(acc.Where(a => a.Level > 0)).ToList(); } /// <summary> /// Returns the parent id of a process or -1 if it fails. /// </summary> /// <param name="process">The process to find parent of.</param> /// <returns>The pid of the parent process.</returns> internal static int GetParentPid(Process process) { return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? GetParentPidWindows(process) : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? GetParentPidLinux(process) : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? GetParentPidMacOs(process) : throw new PlatformNotSupportedException(); } internal static int GetParentPidWindows(Process process) { try { var handle = process.Handle; var res = NtQueryInformationProcess(handle, 0, out var pbi, Marshal.SizeOf<ProcessBasicInformation>(), out int size); var p = res != 0 ? InvalidProcessId : pbi.InheritedFromUniqueProcessId.ToInt32(); return p; } catch (Exception ex) { EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidWindows: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); return InvalidProcessId; } } /// <summary>Read the /proc file system for information about the parent.</summary> /// <param name="process">The process to get the parent process from.</param> /// <returns>The process id.</returns> internal static int GetParentPidLinux(Process process) { var pid = process.Id; // read /proc/<pid>/stat // 4th column will contain the ppid, 92 in the example below // ex: 93 (bash) S 92 93 2 4294967295 ... var path = $"/proc/{pid}/stat"; try { var stat = File.ReadAllText(path); var parts = stat.Split(' '); return parts.Length < 5 ? InvalidProcessId : int.Parse(parts[3], CultureInfo.CurrentCulture); } catch (Exception ex) { EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidLinux: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); return InvalidProcessId; } } internal static int GetParentPidMacOs(Process process) { try { var output = new StringBuilder(); var err = new StringBuilder(); Process ps = new(); ps.StartInfo.FileName = "ps"; ps.StartInfo.Arguments = $"-o ppid= {process.Id}"; ps.StartInfo.UseShellExecute = false; ps.StartInfo.RedirectStandardOutput = true; ps.OutputDataReceived += (_, e) => output.Append(e.Data); ps.ErrorDataReceived += (_, e) => err.Append(e.Data); ps.Start(); ps.BeginOutputReadLine(); ps.WaitForExit(5_000); var o = output.ToString(); var parent = int.TryParse(o.Trim(), out var ppid) ? ppid : InvalidProcessId; if (err.ToString() is string error && !error.IsNullOrWhiteSpace()) { EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {error}."); } return parent; } catch (Exception ex) { EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); return InvalidProcessId; } } private static void ResolveChildren(Process parent, List<ProcessTreeNode> acc, int level, int limit) { if (limit < 0) { // hit recursion limit, just returning return; } // only take children that are newer than the parent, because process ids (PIDs) get recycled var children = acc.Where(p => p.ParentId == parent.Id && p.Process?.StartTime > parent.StartTime).ToList(); foreach (var child in children) { child.Level = level; ResolveChildren(child.Process!, acc, level + 1, limit); } } private static bool IsChildCandidate(Process child, Process parent) { // this is extremely slow under debugger, but fast without it try { return child.StartTime > parent.StartTime && child.Id != parent.Id; } catch { /* access denied or process has exits most likely */ return false; } } [StructLayout(LayoutKind.Sequential)] private struct ProcessBasicInformation { public readonly IntPtr ExitStatus; public readonly IntPtr PebBaseAddress; public readonly IntPtr AffinityMask; public readonly IntPtr BasePriority; public readonly IntPtr UniqueProcessId; public IntPtr InheritedFromUniqueProcessId; } [DllImport("ntdll.dll", SetLastError = true)] private static extern int NtQueryInformationProcess( IntPtr processHandle, int processInformationClass, out ProcessBasicInformation processInformation, int processInformationLength, out int returnLength); // This call is undocumented api, and should not be used. //[DllImport("ntdll.dll", SetLastError = true)] //private static extern IntPtr NtSuspendProcess(IntPtr processHandle); }