diff --git a/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs b/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs index 5d835f241cda1b..376f3560304e37 100644 --- a/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs +++ b/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs @@ -1,37 +1,256 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using System.IO; + +// The const int PRARGSZ show up as unused. Not sure why. +#pragma warning disable CA1823 internal static partial class Interop { internal static partial class @procfs { + internal const string RootPath = "/proc/"; + private const string psinfoFileName = "/psinfo"; + private const string lwpDirName = "/lwp"; + private const string lwpsinfoFileName = "/lwpsinfo"; + + // Constants from sys/procfs.h + private const int PRARGSZ = 80; + private const int PRCLSZ = 8; + private const int PRFNSZ = 16; + + [StructLayout(LayoutKind.Sequential)] + internal struct @timestruc_t + { + public long tv_sec; + public long tv_nsec; + } + + // lwp ps(1) information file. /proc//lwp//lwpsinfo + // "unsafe" because it has fixed sized arrays. + [StructLayout(LayoutKind.Sequential)] + public unsafe struct @lwpsinfo + { + private int pr_flag; /* lwp flags (DEPRECATED; do not use) */ + public uint pr_lwpid; /* lwp id */ + private long pr_addr; /* internal address of lwp */ + private long pr_wchan; /* wait addr for sleeping lwp */ + public byte pr_stype; /* synchronization event type */ + public byte pr_state; /* numeric lwp state */ + public byte pr_sname; /* printable character for pr_state */ + public byte pr_nice; /* nice for cpu usage */ + private short pr_syscall; /* system call number (if in syscall) */ + private byte pr_oldpri; /* pre-SVR4, low value is high priority */ + private byte pr_cpu; /* pre-SVR4, cpu usage for scheduling */ + public int pr_pri; /* priority, high value is high priority */ + private ushort pr_pctcpu; /* fixed pt. % of recent cpu time */ + private ushort pr_pad; + public timestruc_t pr_start; /* lwp start time, from the epoch */ + public timestruc_t pr_time; /* usr+sys cpu time for this lwp */ + private fixed byte pr_clname[PRCLSZ]; /* scheduling class name */ + private fixed byte pr_name[PRFNSZ]; /* name of system lwp */ + private int pr_onpro; /* processor which last ran this lwp */ + private int pr_bindpro; /* processor to which lwp is bound */ + private int pr_bindpset; /* processor set to which lwp is bound */ + private int pr_lgrp; /* lwp home lgroup */ + private fixed int pr_filler[4]; /* reserved for future use */ + } + + // process ps(1) information file. /proc//psinfo + // "unsafe" because it has fixed sized arrays. + [StructLayout(LayoutKind.Sequential)] + public unsafe struct @psinfo + { + private int pr_flag; /* process flags (DEPRECATED; do not use) */ + public int pr_nlwp; /* number of active lwps in the process */ + public int pr_pid; /* unique process id */ + public int pr_ppid; /* process id of parent */ + public int pr_pgid; /* pid of process group leader */ + public int pr_sid; /* session id */ + public uint pr_uid; /* real user id */ + public uint pr_euid; /* effective user id */ + public uint pr_gid; /* real group id */ + public uint pr_egid; /* effective group id */ + private long pr_addr; /* address of process */ + public ulong pr_size; /* size of process image in Kbytes */ + public ulong pr_rssize; /* resident set size in Kbytes */ + private ulong pr_pad1; + private ulong pr_ttydev; /* controlling tty device (or PRNODEV) */ + private ushort pr_pctcpu; /* % of recent cpu time used by all lwps */ + private ushort pr_pctmem; /* % of system memory used by process */ + public timestruc_t pr_start; /* process start time, from the epoch */ + public timestruc_t pr_time; /* usr+sys cpu time for this process */ + public timestruc_t pr_ctime; /* usr+sys cpu time for reaped children */ + public fixed byte pr_fname[PRFNSZ]; /* name of execed file */ + public fixed byte pr_psargs[PRARGSZ]; /* initial characters of arg list */ + public int pr_wstat; /* if zombie, the wait() status */ + public int pr_argc; /* initial argument count */ + private long pr_argv; /* address of initial argument vector */ + private long pr_envp; /* address of initial environment vector */ + private byte pr_dmodel; /* data model of the process */ + private fixed byte pr_pad2[3]; + public int pr_taskid; /* task id */ + public int pr_projid; /* project id */ + public int pr_nzomb; /* number of zombie lwps in the process */ + public int pr_poolid; /* pool id */ + public int pr_zoneid; /* zone id */ + public int pr_contract; /* process contract */ + private fixed int pr_filler[1]; /* reserved for future use */ + public lwpsinfo pr_lwp; /* information for representative lwp */ + } + + // Ouput type for TryReadProcessThreadInfo() + internal struct ProcessThreadInfo + { + internal uint Tid; + internal int Priority; + internal int NiceVal; + internal char Status; + // add more fields when needed. + } + + // Ouput type for TryReadProcessStatusInfo() + internal struct ProcessStatusInfo + { + internal int Pid; + internal int ParentPid; + internal int SessionId; + internal nuint VirtualSize; + internal nuint ResidentSetSize; + internal Interop.Sys.TimeSpec StartTime; + internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys + internal string? Args; + // add more fields when needed. + internal ProcessThreadInfo Lwp1; + } + + internal static string GetInfoFilePathForProcess(int pid) => + string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{psinfoFileName}"); + + internal static string GetLwpDirForProcess(int pid) => + string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{lwpDirName}"); + + internal static string GetInfoFilePathForThread(int pid, int tid) => + string.Create(null, stackalloc char[256], + $"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}"); + /// /// Attempts to get status info for the specified process ID. /// /// PID of the process to read status info for. - /// The pointer to processStatus instance. + /// The pointer to processStatusInfo instance. /// /// true if the process status was read; otherwise, false. /// - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessStatusInfo", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static unsafe partial bool TryReadProcessStatusInfo(int pid, ProcessStatusInfo* processStatus); - internal struct ProcessStatusInfo + // ProcessManager.SunOS.cs calls this + // "unsafe" due to use of fixed-size buffers + + internal static unsafe bool TryReadProcessStatusInfo(int pid, out ProcessStatusInfo result) { - internal nuint ResidentSetSize; - // add more fields when needed. + result = default; + bool ret = false; + string fileName = "?"; + IntPtr ptr = 0; + + try + { + fileName = GetInfoFilePathForProcess(pid); + int size = Marshal.SizeOf(); + ptr = Marshal.AllocHGlobal(size); + + BinaryReader br = new BinaryReader(File.OpenRead(fileName)); + byte[] buf = br.ReadBytes(size); + Marshal.Copy(buf, 0, ptr, size); + + procfs.psinfo pr = Marshal.PtrToStructure(ptr); + + result.Pid = pr.pr_pid; + result.ParentPid = pr.pr_ppid; + result.SessionId = pr.pr_sid; + result.VirtualSize = (nuint)pr.pr_size * 1024; // pr_rssize is in Kbytes + result.ResidentSetSize = (nuint)pr.pr_rssize * 1024; // pr_rssize is in Kbytes + result.StartTime.TvSec = pr.pr_start.tv_sec; + result.StartTime.TvNsec = pr.pr_start.tv_nsec; + result.CpuTotalTime.TvSec = pr.pr_time.tv_sec; + result.CpuTotalTime.TvNsec = pr.pr_time.tv_nsec; + result.Args = Marshal.PtrToStringUTF8((IntPtr)pr.pr_psargs); + + // We get LWP[1] for "free" + result.Lwp1.Tid = pr.pr_lwp.pr_lwpid; + result.Lwp1.Priority = pr.pr_lwp.pr_pri; + result.Lwp1.NiceVal = (int)pr.pr_lwp.pr_nice; + result.Lwp1.Status = (char)pr.pr_lwp.pr_sname; + + ret = true; + } + catch (Exception e) + { + Debug.Fail($"Failed to read \"{fileName}\": {e}"); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return ret; } - internal static unsafe bool TryReadProcessStatusInfo(int pid, out ProcessStatusInfo statusInfo) + /// + /// Attempts to get status info for the specified Thread ID. + /// + /// PID of the process to read status info for. + /// TID of the thread to read status info for. + /// The pointer to processStatusInfo instance. + /// + /// true if the process status was read; otherwise, false. + /// + + // ProcessManager.SunOS.cs calls this + // "unsafe" due to use of fixed-size buffers + + internal static unsafe bool TryReadProcessThreadInfo(int pid, int tid, out ProcessThreadInfo result) { - statusInfo = default; - fixed (ProcessStatusInfo* pStatusInfo = &statusInfo) + result = default; + bool ret = false; + string fileName = "?"; + IntPtr ptr = 0; + + try { - return TryReadProcessStatusInfo(pid, pStatusInfo); + fileName = GetInfoFilePathForThread(pid, tid); + int size = Marshal.SizeOf(); + ptr = Marshal.AllocHGlobal(size); + + BinaryReader br = new BinaryReader(File.OpenRead(fileName)); + byte[] buf = br.ReadBytes(size); + Marshal.Copy(buf, 0, ptr, size); + + procfs.lwpsinfo lwp = Marshal.PtrToStructure(ptr); + + result.Tid = lwp.pr_lwpid; + result.Priority = lwp.pr_pri; + result.NiceVal = (int)lwp.pr_nice; + result.Status = (char)lwp.pr_sname; + + ret = true; + } + catch (Exception e) + { + Debug.Fail($"Failed to read \"{fileName}\": {e}"); } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return ret; } + } } diff --git a/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.cs b/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.cs new file mode 100644 index 00000000000000..0594c30156176a --- /dev/null +++ b/src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class @procfs + { + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 97e289045e324a..d0f8b039dd58a4 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -1,7 +1,7 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent) $(DefineConstants);FEATURE_REGISTRY true false @@ -363,6 +363,19 @@ Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" /> + + + + + + + + + + diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.SunOS.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.SunOS.cs new file mode 100644 index 00000000000000..e52b6af93a3d81 --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.SunOS.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; + +// TODO: remove +#pragma warning disable CA1822 + +namespace System.Diagnostics +{ + public partial class Process : IDisposable + { + + /// Gets the time the associated process was started. + internal DateTime StartTimeCore + { + get + { + Interop.procfs.ProcessStatusInfo status = GetProcStatus(); + + DateTime startTime = DateTime.UnixEpoch + + TimeSpan.FromSeconds(status.StartTime.TvSec) + + TimeSpan.FromMicroseconds(status.StartTime.TvNsec / 1000); + + // The return value is expected to be in the local time zone. + return startTime.ToLocalTime(); + } + } + + /// Gets the parent process ID + private int ParentProcessId => GetProcStatus().ParentPid; + + /// Gets execution path + private static string? GetPathToOpenFile() + { + return FindProgramInPath("xdg-open"); + } + + /// + /// Gets the amount of time the associated process has spent utilizing the CPU. + /// It is the sum of the and + /// . + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan TotalProcessorTime + { + get + { + // a.k.a. "user" + "system" time + Interop.procfs.ProcessStatusInfo status = GetProcStatus(); + TimeSpan ts = TimeSpan.FromSeconds(status.CpuTotalTime.TvSec) + + TimeSpan.FromMicroseconds(status.CpuTotalTime.TvNsec / 1000); + return ts; + } + } + + /// + /// Gets the amount of time the associated process has spent running code + /// inside the application portion of the process (not the operating system core). + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan UserProcessorTime + { + get + { + // a.k.a. "user" time + // Could get this from /proc/$pid/status + // Just say it's all user time for now + return TotalProcessorTime; + } + } + + /// Gets the amount of time the process has spent running code inside the operating system core. + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan PrivilegedProcessorTime + { + get + { + // a.k.a. "system" time + // Could get this from /proc/$pid/status + // Just say it's all user time for now + EnsureState(State.HaveNonExitedId); + return TimeSpan.Zero; + } + } + + partial void EnsureHandleCountPopulated() + { + // TODO: remove this method if not needed + } + + + // ---------------------------------- + // ---- Unix PAL layer ends here ---- + // ---------------------------------- + + /// Gets the name that was used to start the process, or null if it could not be retrieved. + internal static string GetUntruncatedProcessName(ref Interop.procfs.ProcessStatusInfo iinfo) + { + // Todo: If exec_fname matches the leading part of the args string, + // use the args string up through the first space (XXX needs work). + if (!string.IsNullOrEmpty(iinfo.Args)) + { + string[] argv = iinfo.Args.Split(' ', 2); + if (!string.IsNullOrEmpty(argv[0])) + { + return Path.GetFileName(argv[0]); + } + } + return "?"; + } + + /// Reads the stats information for this process from the procfs file system. + private Interop.procfs.ProcessStatusInfo GetProcStatus() + { + EnsureState(State.HaveNonExitedId); + Interop.procfs.ProcessStatusInfo status; + if (!Interop.procfs.TryReadProcessStatusInfo(_processId, out status)) + { + throw new Win32Exception(SR.ProcessInformationUnavailable); + } + return status; + } + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.SunOS.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.SunOS.cs new file mode 100644 index 00000000000000..7983333d7c20d9 --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.SunOS.cs @@ -0,0 +1,253 @@ +// 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; +using System.Runtime.InteropServices; + +// TODO: remove or scope to just the methods that need them +#pragma warning disable CA1822 +#pragma warning disable IDE0060 + +namespace System.Diagnostics +{ + internal static partial class ProcessManager + { + /// Gets the IDs of all processes on the current machine. + public static int[] GetProcessIds() + { + IEnumerable pids = EnumerateProcessIds(); + return new List(pids).ToArray(); + } + + /// Gets process infos for each process on the specified machine. + /// Optional process name to use as an inclusion filter. + /// The target machine. + /// An array of process infos, one per found process. + public static ProcessInfo[] GetProcessInfos(string? processNameFilter, string machineName) + { + ThrowIfRemoteMachine(machineName); + + // Iterate through all process IDs to load information about each process + IEnumerable pids = EnumerateProcessIds(); + ArrayBuilder processes = default; + foreach (int pid in pids) + { + ProcessInfo? pi = CreateProcessInfo(pid, processNameFilter); + if (pi != null) + { + processes.Add(pi); + } + } + + return processes.ToArray(); + } + + /// Gets an array of module infos for the specified process. + /// The ID of the process whose modules should be enumerated. + /// The array of modules. + internal static ProcessModuleCollection GetModules(int processId) + { + + // Negative PIDs aren't valid + ArgumentOutOfRangeException.ThrowIfNegative(processId); + + // GetModules(x)[0].FileName is often used to find the path to the executable, so at least + // get that. + // TODO: is there better way to get loaded modules? + + Interop.procfs.ProcessStatusInfo iProcInfo; + if (Interop.procfs.TryReadProcessStatusInfo(processId, out iProcInfo)) + { + string fullName = Process.GetUntruncatedProcessName(ref iProcInfo); + if (!string.IsNullOrEmpty(fullName)) + { + return new ProcessModuleCollection(1) + { + new ProcessModule(fullName, Path.GetFileName(fullName)) + }; + } + } + return new ProcessModuleCollection(0); + } + + /// + /// Creates a ProcessInfo from the specified process ID. + /// + internal static ProcessInfo? CreateProcessInfo(int pid, string? processNameFilter = null) + { + // Negative PIDs aren't valid + ArgumentOutOfRangeException.ThrowIfNegative(pid); + + Interop.procfs.ProcessStatusInfo iProcInfo; + if (! Interop.procfs.TryReadProcessStatusInfo(pid, out iProcInfo)) + { + return null; + } + + string processName = Process.GetUntruncatedProcessName(ref iProcInfo); + if (!string.IsNullOrEmpty(processNameFilter) && + !string.Equals(processName, processNameFilter, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return iCreateProcessInfo(ref iProcInfo); + } + + // ---------------------------------- + // ---- Unix PAL layer ends here ---- + // ---------------------------------- + + /// Enumerates the IDs of all processes on the current machine. + internal static IEnumerable 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; + } + } + } + + /// Enumerates the IDs of all threads in the specified process. + internal static IEnumerable EnumerateThreadIds(int pid) + { + // Parse /proc/$pid/lwp for any directory that's named with a number. Each such + // directory represents a process. + string dir = Interop.procfs.GetLwpDirForProcess(pid); + foreach (string lwpDir in Directory.EnumerateDirectories(dir)) + { + string dirName = Path.GetFileName(lwpDir); + int tid; + if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid)) + { + Debug.Assert(tid >= 0); + yield return tid; + } + } + } + + /// + /// Creates a ProcessInfo from the data read from a /proc/pid/psinfo file and the associated lwp directory. + /// + internal static ProcessInfo iCreateProcessInfo(ref Interop.procfs.ProcessStatusInfo iProcInfo) + { + int pid = iProcInfo.Pid; + + string name = Process.GetUntruncatedProcessName(ref iProcInfo); + var pi = new ProcessInfo() + { + ProcessId = pid, + ProcessName = name, + BasePriority = iProcInfo.Lwp1.Priority, + SessionId = iProcInfo.SessionId, + VirtualBytes = (long)iProcInfo.VirtualSize, + WorkingSet = (long)iProcInfo.ResidentSetSize, + // StartTime: See Process.StartTimeCore() + }; + + // Then read through /proc/pid/lwp/ to find each thread in the process... + // Can we use a "get" method to avoid loading this for every process until it's asked for? + try + { + + // Iterate through all thread IDs to load information about each thread + IEnumerable tids = EnumerateThreadIds(pid); + + // We already have the first LWP in iiProcInfo.Lwp1 + ThreadInfo? ti = iCreateThreadInfo(ref iProcInfo, ref iProcInfo.Lwp1); + if (ti != null) + { + pi._threadInfoList.Add(ti); + } + + foreach (int tid in tids) + { + if (tid == iProcInfo.Lwp1.Tid) + { + continue; + } + + Interop.procfs.ProcessThreadInfo iThrInfo; + if (! Interop.procfs.TryReadProcessThreadInfo(pid, tid, out iThrInfo)) + { + continue; + } + + ti = iCreateThreadInfo(ref iProcInfo, ref iThrInfo); + if (ti != null) + { + pi._threadInfoList.Add(ti); + } + } + } + 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; + } + + /// + /// Creates a ThreadInfo from the data read from a /proc/pid/lwp/lwpsinfo file. + /// + internal static ThreadInfo iCreateThreadInfo(ref Interop.procfs.ProcessStatusInfo iProcInfo, + ref Interop.procfs.ProcessThreadInfo iThrInfo) + { + + var ti = new ThreadInfo() + { + _processId = iProcInfo.Pid, + _threadId = (ulong)iThrInfo.Tid, + _basePriority = iThrInfo.Priority, + _currentPriority = iThrInfo.Priority, + _startAddress = null, + _threadState = ProcFsStateToThreadState(iThrInfo.Status), + _threadWaitReason = ThreadWaitReason.Unknown + }; + + return ti; + } + + /// Gets a ThreadState to represent the value returned from the status field of /proc/pid/stat. + /// The status field value. + /// + private static ThreadState ProcFsStateToThreadState(char c) + { + // Information on these in fs/proc/array.c + // `man proc` does not document them all + switch (c) + { + case 'O': // On-CPU + case 'R': // Runnable + return ThreadState.Running; + + case 'S': // Sleeping in a wait + case 'T': // Stopped on a signal + return ThreadState.Wait; + + case 'Z': // Zombie + return ThreadState.Terminated; + + case 'W': // Waiting for CPU + return ThreadState.Transition; + + default: + Debug.Fail($"Unexpected status character: {c}"); + return ThreadState.Unknown; + } + } + + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.SunOS.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.SunOS.cs new file mode 100644 index 00000000000000..b9b3d142900b0f --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.SunOS.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Versioning; + +// TODO: remove +#pragma warning disable CA1822 + +namespace System.Diagnostics +{ + public partial class ProcessThread + { + /// + /// Returns or sets the priority level of the associated thread. The priority level is + /// not an absolute level, but instead contributes to the actual thread priority by + /// considering the priority class of the process. + /// + private ThreadPriorityLevel PriorityLevelCore + { + get + { + // XXX not yet + // Interop.procfs.ParsedStat stat = GetThrStat(); + // return GetThreadPriorityFromSysPri(stat.Priority); + throw new PlatformNotSupportedException(); + } + set + { + throw new PlatformNotSupportedException(); + } + } + + // kinfo_proc has one entry per thread but ki_start seems to be same for + // all threads e.g. reflects process start. This may be re-visited later. + private static DateTime GetStartTime() => throw new PlatformNotSupportedException(); + + /// + /// Returns the amount of time the associated thread has spent utilizing the CPU. + /// It is the sum of the System.Diagnostics.ProcessThread.UserProcessorTime and + /// System.Diagnostics.ProcessThread.PrivilegedProcessorTime. + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan TotalProcessorTime + { + get + { + throw new PlatformNotSupportedException(); + } + } + + /// + /// Returns the amount of time the associated thread has spent running code + /// inside the application (not the operating system core). + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan UserProcessorTime + { + get + { + throw new PlatformNotSupportedException(); + } + } + + /// + /// Returns the amount of time the thread has spent running code inside the operating + /// system core. + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan PrivilegedProcessorTime + { + get + { + throw new PlatformNotSupportedException(); + } + + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 84199a3d306d28..c63e7ff4f9bba6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -7,7 +7,20 @@ namespace System { public static partial class Environment { - public static long WorkingSet => - (long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); + public static long WorkingSet + { + get + { + Interop.procfs.ProcessStatusInfo iProcInfo; + if (Interop.procfs.TryReadProcessStatusInfo(ProcessId, out iProcInfo)) + { + return (long)iProcInfo.ResidentSetSize; + } + else + { + return 0; + } + } + } } } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 51c761109159b5..a7071b0fc5334f 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -119,7 +119,6 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag) DllImportEntry(SystemNative_FChflags) DllImportEntry(SystemNative_CanGetHiddenFlag) - DllImportEntry(SystemNative_ReadProcessStatusInfo) DllImportEntry(SystemNative_Log) DllImportEntry(SystemNative_LogError) DllImportEntry(SystemNative_AlignedAlloc) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index d8a437130f2e8e..c129ee9944ebe4 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -1826,36 +1826,6 @@ int32_t SystemNative_CanGetHiddenFlag(void) #endif } -int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStatus) -{ -#ifdef __sun - char statusFilename[64]; - snprintf(statusFilename, sizeof(statusFilename), "/proc/%d/psinfo", pid); - - intptr_t fd; - while ((fd = open(statusFilename, O_RDONLY)) < 0 && errno == EINTR); - if (fd < 0) - { - return 0; - } - - psinfo_t status; - int result = Common_Read(fd, &status, sizeof(psinfo_t)); - close(fd); - if (result >= 0) - { - processStatus->ResidentSetSize = status.pr_rssize * 1024; // pr_rssize is in Kbytes - return 1; - } - - return 0; -#else - (void)pid, (void)processStatus; - errno = ENOTSUP; - return -1; -#endif // __sun -} - int32_t SystemNative_PRead(intptr_t fd, void* buffer, int32_t bufferSize, int64_t fileOffset) { assert(buffer != NULL); diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 03fd94cea25417..541f22c0f0c61a 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -36,12 +36,6 @@ typedef struct uint32_t UserFlags; // user defined flags } FileStatus; -typedef struct -{ - size_t ResidentSetSize; - // add more fields when needed. -} ProcessStatus; - // NOTE: the layout of this type is intended to exactly match the layout of a `struct iovec`. There are // assertions in pal_networking.c that validate this. typedef struct @@ -795,13 +789,6 @@ PALEXPORT int32_t SystemNative_LChflagsCanSetHiddenFlag(void); */ PALEXPORT int32_t SystemNative_CanGetHiddenFlag(void); -/** - * Reads the psinfo_t struct and converts into ProcessStatus. - * - * Returns 1 if the process status was read; otherwise, 0. - */ -PALEXPORT int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStatus); - /** * Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor at specified offset. *