This commit is contained in:
Gordon Ross 2025-07-30 15:56:33 +02:00 committed by GitHub
commit a49ccbcde9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 835 additions and 62 deletions

View File

@ -51,6 +51,9 @@
<_NativeAotSupportedArch Condition="'$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'loongarch64' or '$(TargetArchitecture)' == 'riscv64' or ('$(TargetOS)' == 'windows' and '$(TargetArchitecture)' == 'x86')">true</_NativeAotSupportedArch>
<NativeAotSupported Condition="'$(_NativeAotSupportedOS)' == 'true' and '$(_NativeAotSupportedArch)' == 'true'">true</NativeAotSupported>
<!-- TODO: workaround for https://github.com/dotnet/runtime/issues/116929 -->
<_Crossgen2Supported Condition="'$(TargetOS)' != 'illumos' and '$(TargetOS)' != 'solaris'">true</_Crossgen2Supported>
<!-- Determine if we support running the .NET SDK on the target platform -->
<_SdkToolsSupportedOS Condition="'$(TargetsMobile)' != 'true' and '$(TargetsLinuxBionic)' != 'true'">true</_SdkToolsSupportedOS>
<_SdkToolsSupportedArch Condition="'$(TargetArchitecture)' != 'armel'">true</_SdkToolsSupportedArch>
@ -451,7 +454,7 @@
<ProjectToBuild Include="$(CoreClrProjectRoot).nuget\Microsoft.CrossOsDiag.Private.CoreCLR\Microsoft.CrossOsDiag.Private.CoreCLR.proj" Category="clr" />
</ItemGroup>
<ItemGroup Condition="$(_subset.Contains('+clr.tools+'))">
<ItemGroup Condition="$(_subset.Contains('+clr.tools+')) and '$(_Crossgen2Supported)' == 'true'">
<ProjectToBuild Include="$(CoreClrProjectRoot)tools\runincontext\runincontext.csproj;
$(CoreClrProjectRoot)tools\tieringtest\tieringtest.csproj;
$(CoreClrProjectRoot)tools\r2rdump\R2RDump.csproj;
@ -683,7 +686,7 @@
<SharedFrameworkProjectToBuild Include="$(InstallerProjectRoot)pkg\sfx\Microsoft.NETCore.App\Microsoft.NETCore.App.Ref.sfxproj" />
</ItemGroup>
<ItemGroup>
<SharedFrameworkProjectToBuild Condition="'$(RuntimeFlavor)' != 'Mono' and ('$(TargetsMobile)' != 'true' and '$(TargetsLinuxBionic)' != 'true')" Include="$(InstallerProjectRoot)pkg\sfx\Microsoft.NETCore.App\Microsoft.NETCore.App.Crossgen2.sfxproj" />
<SharedFrameworkProjectToBuild Condition="'$(RuntimeFlavor)' != 'Mono' and ('$(TargetsMobile)' != 'true' and '$(TargetsLinuxBionic)' != 'true') and '$(_Crossgen2Supported)' == 'true'" Include="$(InstallerProjectRoot)pkg\sfx\Microsoft.NETCore.App\Microsoft.NETCore.App.Crossgen2.sfxproj" />
<SharedFrameworkProjectToBuild Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'" Include="$(InstallerProjectRoot)pkg\sfx\installers\dotnet-host.proj" />
<SharedFrameworkProjectToBuild Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'" Include="$(InstallerProjectRoot)pkg\sfx\installers\dotnet-hostfxr.proj" />
<SharedFrameworkProjectToBuild Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'" Include="$(InstallerProjectRoot)pkg\sfx\installers\dotnet-runtime-deps\*.proj" />
@ -698,7 +701,7 @@
When we're building in the VMR, we need to provide a crossgen2 that runs on the host machine for downstream repos to use to R2R their code.
In non-VMR builds, downstream repos can use the crossgen2 built for the target host SDK from another build leg, but in the VMR we need to provide one to use.
-->
<ProjectToBuild Condition="'$(RuntimeFlavor)' != 'Mono' and '$(TargetsMobile)' != 'true' and '$(TargetsLinuxBionic)' != 'true' and '$(BuildHostTools)' == 'true'" Include="$(InstallerProjectRoot)pkg\sfx\Microsoft.NETCore.App\Microsoft.NETCore.App.Crossgen2.Host.sfxproj" Category="packs" />
<ProjectToBuild Condition="'$(RuntimeFlavor)' != 'Mono' and '$(TargetsMobile)' != 'true' and '$(TargetsLinuxBionic)' != 'true' and '$(BuildHostTools)' == 'true' and '$(_Crossgen2Supported)' == 'true'" Include="$(InstallerProjectRoot)pkg\sfx\Microsoft.NETCore.App\Microsoft.NETCore.App.Crossgen2.Host.sfxproj" Category="packs" />
</ItemGroup>
<ItemGroup>
<SharedFrameworkProjectToBuild Condition="'$(_BuildHostPack)' == 'true'" Include="$(InstallerProjectRoot)pkg\archives\dotnet-nethost.proj" />

View File

@ -44,7 +44,7 @@
LatestRuntimeFrameworkVersion="$(ProductVersion)"
RuntimeFrameworkName="$(LocalFrameworkOverrideName)"
RuntimePackNamePatterns="$(LocalFrameworkOverrideName).Runtime.**RID**"
RuntimePackRuntimeIdentifiers="linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm64;win-x64;win-x86;linux-musl-arm;osx-arm64;maccatalyst-x64;maccatalyst-arm64;linux-s390x;linux-bionic-arm;linux-bionic-arm64;linux-bionic-x64;linux-bionic-x86;freebsd-x64;freebsd-arm64;linux-ppc64le;linux-riscv64;linux-musl-riscv64;linux-loongarch64;linux-musl-loongarch64"
RuntimePackRuntimeIdentifiers="linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm64;win-x64;win-x86;linux-musl-arm;osx-arm64;maccatalyst-x64;maccatalyst-arm64;linux-s390x;linux-bionic-arm;linux-bionic-arm64;linux-bionic-x64;linux-bionic-x86;freebsd-x64;freebsd-arm64;illumos-x64;solaris-x64;linux-ppc64le;linux-riscv64;linux-musl-riscv64;linux-loongarch64;linux-musl-loongarch64"
TargetFramework="$(NetCoreAppCurrent)"
TargetingPackName="$(LocalFrameworkOverrideName).Ref"
TargetingPackVersion="$(ProductVersion)"

View File

@ -9,11 +9,6 @@
<OfficialBuildRID Include="osx-arm64">
<Platform>arm64</Platform>
</OfficialBuildRID>
<!-- Not currently built by CoreFX. -->
<!-- <OfficialBuildRID Include="netbsd-x64" /> -->
<!-- <OfficialBuildRID Include="illumos-x64" /> -->
<!-- <OfficialBuildRID Include="solaris-x64" /> -->
<!-- <OfficialBuildRID Include="haiku-x64" /> -->
<OfficialBuildRID Include="win-x86">
<Platform>x86</Platform>
</OfficialBuildRID>
@ -51,6 +46,10 @@
in our runtime.json to enable others to provide them. -->
<UnofficialBuildRID Include="freebsd-x64" />
<UnofficialBuildRID Include="freebsd-arm64" />
<UnofficialBuildRID Include="illumos-x64" />
<UnofficialBuildRID Include="solaris-x64" />
<UnofficialBuildRID Include="netbsd-x64" />
<UnofficialBuildRID Include="haiku-x64" />
<UnofficialBuildRID Include="tizen.4.0.0-armel">
<Platform>armel</Platform>
</UnofficialBuildRID>

View File

@ -0,0 +1,64 @@
// 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.Runtime.InteropServices;
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;
// Output type for TryGetProcessInfoById()
// Keep in sync with pal_io.h ProcessStatus
[StructLayout(LayoutKind.Sequential)]
internal struct ProcessInfo
{
internal ulong VirtualSize;
internal ulong ResidentSetSize;
internal long StartTime;
internal long StartTimeNsec;
internal long CpuTotalTime;
internal long CpuTotalTimeNsec;
internal int Pid;
internal int ParentPid;
internal int SessionId;
internal int Priority;
internal int NiceVal;
// add more fields when needed.
}
// Output type for TryGetThreadInfoById()
// Keep in sync with pal_io.h ThreadStatus
[StructLayout(LayoutKind.Sequential)]
internal struct ThreadInfo
{
internal long StartTime;
internal long StartTimeNsec;
internal long CpuTotalTime; // user+sys
internal long CpuTotalTimeNsec;
internal int Tid;
internal int Priority;
internal int NiceVal;
internal char StatusCode;
// add more fields when needed.
}
internal static string GetInfoFilePathForProcess(int pid) =>
$"{RootPath}{(uint)pid}{psinfoFileName}";
internal static string GetLwpDirForProcess(int pid) =>
$"{RootPath}{(uint)pid}{lwpDirName}";
internal static string GetInfoFilePathForThread(int pid, int tid) =>
$"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}";
}
}

View File

@ -0,0 +1,61 @@
// 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
internal static partial class Interop
{
internal static partial class @procfs
{
// See caller: ProcessManager.SunOS.cs
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessStatusInfo", SetLastError = true)]
private static unsafe partial int ReadProcessStatusInfo(int pid, ProcessInfo* processInfo, byte* argBuf, int argBufSize);
// Handy helpers for Environment.SunOS etc.
/// <summary>
/// Attempts to get status info for the specified process ID.
/// </summary>
/// <param name="pid">PID of the process to read status info for.</param>
/// <param name="processInfo">The pointer to ProcessInfo instance.</param>
/// <returns>
/// true if the process status was read; otherwise, false.
/// </returns>
internal static unsafe bool TryGetProcessInfoById(int pid, out ProcessInfo processInfo)
{
ProcessInfo info = default;
if (ReadProcessStatusInfo(pid, &info, null, 0) < 0)
{
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
}
processInfo = info;
return true;
}
// Variant that also gets the arg string.
internal static unsafe bool TryGetProcessInfoById(int pid, out ProcessInfo processInfo, out string argString)
{
ProcessInfo info = default;
byte* argBuf = stackalloc byte[PRARGSZ];
if (ReadProcessStatusInfo(pid, &info, argBuf, PRARGSZ) < 0)
{
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
}
processInfo = info;
argString = Marshal.PtrToStringUTF8((IntPtr)argBuf)!;
return true;
}
}
}

View File

@ -0,0 +1,43 @@
// 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
internal static partial class Interop
{
internal static partial class @procfs
{
// See caller: ProcessManager.SunOS.cs
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessLwpInfo", SetLastError = true)]
internal static unsafe partial int ReadProcessLwpInfo(int pid, int tid, ThreadInfo* threadInfo);
/// <summary>
/// Attempts to get status info for the specified thread ID.
/// </summary>
/// <param name="pid">PID of the process to read status info for.</param>
/// <param name="tid">TID of the thread to read status info for.</param>
/// <param name="threadInfo">The pointer to ThreadInfo instance.</param>
/// <returns>
/// true if the process status was read; otherwise, false.
/// </returns>
internal static unsafe bool TryGetThreadInfoById(int pid, int tid, out ThreadInfo threadInfo)
{
ThreadInfo info = default;
if (ReadProcessLwpInfo(pid, tid, &info) < 0)
{
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
}
threadInfo = info;
return true;
}
}
}

View File

@ -1,37 +0,0 @@
// 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
{
/// <summary>
/// Attempts to get status info for the specified process ID.
/// </summary>
/// <param name="pid">PID of the process to read status info for.</param>
/// <param name="processStatus">The pointer to processStatus instance.</param>
/// <returns>
/// true if the process status was read; otherwise, false.
/// </returns>
[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
{
internal nuint ResidentSetSize;
// add more fields when needed.
}
internal static unsafe bool TryReadProcessStatusInfo(int pid, out ProcessStatusInfo statusInfo)
{
statusInfo = default;
fixed (ProcessStatusInfo* pStatusInfo = &statusInfo)
{
return TryReadProcessStatusInfo(pid, pStatusInfo);
}
}
}
}

View File

@ -47,6 +47,7 @@ namespace System
public static bool IsNotMacCatalyst => !IsMacCatalyst;
public static bool Isillumos => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ILLUMOS"));
public static bool IsSolaris => RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS"));
public static bool IsSunOS => Isillumos || IsSolaris;
public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
public static bool IsWasi => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI"));
public static bool IsNotBrowser => !IsBrowser;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent)</TargetFrameworks>
<DefineConstants>$(DefineConstants);FEATURE_REGISTRY</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
@ -369,6 +369,19 @@
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'illumos' or '$(TargetPlatformIdentifier)' == 'solaris'">
<Compile Include="System\Diagnostics\Process.BSD.cs" />
<Compile Include="System\Diagnostics\Process.SunOS.cs" />
<Compile Include="System\Diagnostics\ProcessManager.SunOS.cs" />
<Compile Include="System\Diagnostics\ProcessThread.SunOS.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs"
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.TryGetProcessInfoById.cs"
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.TryGetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.TryGetThreadInfoById.cs"
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.TryGetThreadInfoById.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">
<Compile Include="System\Diagnostics\Process.iOS.cs" />
<Compile Include="System\Diagnostics\ProcessManager.iOS.cs" />

View File

@ -0,0 +1,137 @@
// 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;
namespace System.Diagnostics
{
public partial class Process : IDisposable
{
/// <summary>Gets the time the associated process was started.</summary>
internal DateTime StartTimeCore
{
get
{
Interop.procfs.ProcessInfo iinfo = GetProcInfo();
DateTime startTime = DateTime.UnixEpoch +
TimeSpan.FromSeconds(iinfo.StartTime) +
TimeSpan.FromMicroseconds(iinfo.StartTimeNsec / 1000);
// The return value is expected to be in the local time zone.
return startTime.ToLocalTime();
}
}
/// <summary>Gets the parent process ID</summary>
private int ParentProcessId => GetProcInfo().ParentPid;
/// <summary>Gets execution path</summary>
private static string? GetPathToOpenFile()
{
return FindProgramInPath("xdg-open");
}
/// <summary>
/// Gets the amount of time the associated process has spent utilizing the CPU.
/// It is the sum of the <see cref='System.Diagnostics.Process.UserProcessorTime'/> and
/// <see cref='System.Diagnostics.Process.PrivilegedProcessorTime'/>.
/// </summary>
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
public TimeSpan TotalProcessorTime
{
get
{
// a.k.a. "user" + "system" time
Interop.procfs.ProcessInfo iinfo = GetProcInfo();
TimeSpan ts = TimeSpan.FromSeconds(iinfo.CpuTotalTime) +
TimeSpan.FromMicroseconds(iinfo.CpuTotalTimeNsec / 1000);
return ts;
}
}
/// <summary>
/// Gets the amount of time the associated process has spent running code
/// inside the application portion of the process (not the operating system core).
/// </summary>
[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;
}
}
/// <summary>
/// Gets the amount of time the process has spent running code inside the operating
/// system core.
/// </summary>
[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;
}
}
// ----------------------------------
// ---- Unix PAL layer ends here ----
// ----------------------------------
/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
internal static string? GetUntruncatedProcessName(ref Interop.procfs.ProcessInfo iProcInfo, ref string argString)
{
// This assumes the process name is the first part of the Args string
// ending at the first space. That seems to work well enough for now.
// If someday this need to support a process name containing spaces,
// this could call a new Interop function that reads /proc/$pid/auxv
// (sys/auxv.h) and gets the AT_SUN_EXECNAME string from that file.
if (iProcInfo.Pid != 0 && !string.IsNullOrEmpty(argString))
{
string[] argv = argString.Split(' ', 2);
if (!string.IsNullOrEmpty(argv[0]))
{
return Path.GetFileName(argv[0]);
}
}
return null;
}
/// <summary>Reads the information for this process from the procfs file system.</summary>
private Interop.procfs.ProcessInfo GetProcInfo()
{
EnsureState(State.HaveNonExitedId);
Interop.procfs.ProcessInfo iinfo;
if (!Interop.procfs.TryGetProcessInfoById(_processId, out iinfo))
{
throw new Win32Exception(SR.ProcessInformationUnavailable);
}
return iinfo;
}
}
}

View File

@ -0,0 +1,254 @@
// 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;
namespace System.Diagnostics
{
internal static partial class ProcessManager
{
/// <summary>Gets the IDs of all processes on the current machine.</summary>
public static int[] GetProcessIds()
{
IEnumerable<int> pids = EnumerateProcessIds();
return new List<int>(pids).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)
{
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, processNameFilter);
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)
{
// 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. That appears to be sufficient, at least for now.
// If needed, the full list of loaded modules could be obtained using another
// Interop function to read /proc/$pid/auxv similar to how the "pargs" and "pldd"
// commands do their work.
string argString;
Interop.procfs.ProcessInfo iProcInfo;
if (Interop.procfs.TryGetProcessInfoById(processId, out iProcInfo, out argString))
{
string? fullName = Process.GetUntruncatedProcessName(ref iProcInfo, ref argString);
if (!string.IsNullOrEmpty(fullName))
{
return new ProcessModuleCollection(1)
{
new ProcessModule(fullName, Path.GetFileName(fullName))
};
}
}
return new ProcessModuleCollection(0);
}
/// <summary>
/// Creates a ProcessInfo from the specified process ID.
/// </summary>
internal static ProcessInfo? CreateProcessInfo(int pid, string? processNameFilter = null)
{
// Negative PIDs aren't valid
ArgumentOutOfRangeException.ThrowIfNegative(pid);
Interop.procfs.ProcessInfo iProcInfo;
string argString;
if (!Interop.procfs.TryGetProcessInfoById(pid, out iProcInfo, out argString))
{
return null;
}
string? processName = Process.GetUntruncatedProcessName(ref iProcInfo, ref argString);
if (!string.IsNullOrEmpty(processNameFilter) &&
!string.Equals(processName, processNameFilter, StringComparison.OrdinalIgnoreCase))
{
return null;
}
return CreateProcessInfo(ref iProcInfo, ref argString);
}
// ----------------------------------
// ---- 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>Enumerates the IDs of all threads in the specified process.</summary>
internal static IEnumerable<int> EnumerateThreadIds(int pid)
{
// Parse /proc/$pid/lwp for any directory that's named with a number.
// Each such directory represents a thread.
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;
}
}
}
/// <summary>
/// Creates a ProcessInfo from the data read from a /proc/pid/psinfo file and the associated lwp directory.
/// </summary>
internal static ProcessInfo CreateProcessInfo(ref Interop.procfs.ProcessInfo iProcInfo, ref string argString)
{
int pid = iProcInfo.Pid;
string? name = Process.GetUntruncatedProcessName(ref iProcInfo, ref argString);
if (string.IsNullOrEmpty(name)) {
// We were able to read the psinfo for this process, but the /proc reader
// found no arg string. The process exists, so rather than fail for this,
// just continue with a made-up arg string.
// Debug.Fail($"Failed to get args for proc {0:D}");
name = string.Format("<proc_{0:D}>", pid);
}
var pi = new ProcessInfo()
{
ProcessId = pid,
ProcessName = name,
BasePriority = iProcInfo.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<int> tids = EnumerateThreadIds(pid);
foreach (int tid in tids)
{
Interop.procfs.ThreadInfo iThrInfo;
ThreadInfo? ti;
if (!Interop.procfs.TryGetThreadInfoById(pid, tid, out iThrInfo))
{
continue;
}
ti = CreateThreadInfo(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;
}
/// <summary>
/// Creates a ThreadInfo from the data read from a /proc/pid/lwp/lwpsinfo file.
/// </summary>
internal static ThreadInfo CreateThreadInfo(ref Interop.procfs.ProcessInfo iProcInfo,
ref Interop.procfs.ThreadInfo iThrInfo)
{
var ti = new ThreadInfo()
{
_processId = iProcInfo.Pid,
_threadId = (ulong)iThrInfo.Tid,
_basePriority = iThrInfo.Priority,
_currentPriority = iThrInfo.Priority,
_startAddress = null,
_threadState = ProcFsStateToThreadState(iThrInfo.StatusCode),
_threadWaitReason = ThreadWaitReason.Unknown
};
return ti;
}
/// <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 '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;
case '\0': // new, not started yet
return ThreadState.Initialized;
default:
Debug.Fail($"Unexpected status character: {(int)c}");
return ThreadState.Unknown;
}
}
}
}

View File

@ -0,0 +1,133 @@
// 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;
namespace System.Diagnostics
{
public partial class ProcessThread
{
/// <summary>Gets the time this thread was started.</summary>
internal DateTime GetStartTime()
{
Interop.procfs.ThreadInfo iinfo = GetThreadInfo();
DateTime startTime = DateTime.UnixEpoch +
TimeSpan.FromSeconds(iinfo.StartTime) +
TimeSpan.FromMicroseconds(iinfo.StartTimeNsec / 1000);
// The return value is expected to be in the local time zone.
return startTime.ToLocalTime();
}
/// <summary>
/// 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.
/// </summary>
private ThreadPriorityLevel PriorityLevelCore
{
get
{
Interop.procfs.ThreadInfo iinfo = GetThreadInfo();
return GetThreadPriorityFromSysPri(iinfo.Priority);
}
set
{
// Raising priority is a privileged operation.
// Might be able to adjust our "nice" value. Maybe later...
throw new PlatformNotSupportedException();
}
}
/// <summary>
/// Gets 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.
/// </summary>
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
public TimeSpan TotalProcessorTime
{
get
{
// a.k.a. "user" + "system" time
Interop.procfs.ThreadInfo iinfo = GetThreadInfo();
TimeSpan ts = TimeSpan.FromSeconds(iinfo.CpuTotalTime) +
TimeSpan.FromMicroseconds(iinfo.CpuTotalTimeNsec / 1000);
return ts;
}
}
/// <summary>
/// Gets the amount of time the associated thread has spent running code
/// inside the application (not the operating system core).
/// </summary>
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
public TimeSpan UserProcessorTime
{
get
{
// a.k.a. "user" time
// Could get this from /proc/$pid/lwp/$lwpid/lwpstatus
// Just say it's all user time for now
return TotalProcessorTime;
}
}
/// <summary>
/// Gets the amount of time the thread has spent running code inside the operating
/// system core.
/// </summary>
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
public TimeSpan PrivilegedProcessorTime
{
get
{
// a.k.a. "system" time
// Could get this from /proc/$pid/lwp/$lwpid/lwpstatus
// Just say it's all user time for now
return TimeSpan.Zero;
}
}
// ----------------------------------
// ---- exported stuff ends here ----
// ----------------------------------
// System priorities go from 1 to 100, where 60 and above are for "system" things
// These mappingsare relatively arbitrary. Normal user processes run at priority 59.
// and the other values above and below are simply distributed somewhat evenly.
private static System.Diagnostics.ThreadPriorityLevel GetThreadPriorityFromSysPri(int pri)
{
Debug.Assert((pri >= 0) && (pri <= 100));
return
(pri >= 90) ? ThreadPriorityLevel.TimeCritical :
(pri >= 80) ? ThreadPriorityLevel.Highest :
(pri >= 60) ? ThreadPriorityLevel.AboveNormal :
(pri == 59) ? ThreadPriorityLevel.Normal :
(pri >= 40) ? ThreadPriorityLevel.BelowNormal :
(pri >= 20) ? ThreadPriorityLevel.Lowest :
ThreadPriorityLevel.Idle;
}
/// <summary>Reads the information for this thread from the procfs file system.</summary>
private Interop.procfs.ThreadInfo GetThreadInfo()
{
Interop.procfs.ThreadInfo iinfo;
if (!Interop.procfs.TryGetThreadInfoById(_processId, tid: Id, out iinfo))
{
throw new InvalidOperationException(SR.Format(SR.ThreadExited, Id));
}
return iinfo;
}
}
}

View File

@ -652,7 +652,7 @@ namespace System.Diagnostics.Tests
Assert.InRange((long)p.MinWorkingSet, 0, long.MaxValue);
}
if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD()) {
if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsSunOS) {
return; // doesn't support getting/setting working set for other processes
}
@ -700,7 +700,7 @@ namespace System.Diagnostics.Tests
Assert.InRange((long)p.MinWorkingSet, 0, long.MaxValue);
}
if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD()) {
if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsSunOS) {
return; // doesn't support getting/setting working set for other processes
}

View File

@ -2625,7 +2625,8 @@
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)System\IO\StringParser.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\System\IO\StringParser.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.GetProcessInfoById.cs" Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true'" Link="Common\Interop\OSX\Interop.libproc.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" Link="Common\Interop\SunOS\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" Link="Common\Interop\SunOS\Interop.ProcFs.Definitions.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.TryGetProcessInfoById.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" Link="Common\Interop\SunOS\Interop.ProcFs.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\Linux\os-release\Interop.OSReleaseFile.cs" Condition="'$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.FreeBSD.cs" Condition="'$(TargetsFreeBSD)' == 'true'" />

View File

@ -7,7 +7,6 @@ namespace System
{
public static partial class Environment
{
public static long WorkingSet =>
(long)(Interop.procfs.TryReadProcessStatusInfo(ProcessId, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0);
public static long WorkingSet => (long)(Interop.procfs.TryGetProcessInfoById(ProcessId, out Interop.procfs.ProcessInfo iProcInfo) ? iProcInfo.ResidentSetSize : 0);
}
}

View File

@ -120,6 +120,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag)
DllImportEntry(SystemNative_FChflags)
DllImportEntry(SystemNative_CanGetHiddenFlag)
DllImportEntry(SystemNative_ReadProcessLwpInfo)
DllImportEntry(SystemNative_ReadProcessStatusInfo)
DllImportEntry(SystemNative_Log)
DllImportEntry(SystemNative_LogError)

View File

@ -1814,9 +1814,57 @@ int32_t SystemNative_CanGetHiddenFlag(void)
#endif
}
int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStatus)
int32_t SystemNative_ReadProcessLwpInfo(int32_t pid, int32_t tid, ThreadStatus* threadStatus)
{
#ifdef __sun
char statusFilename[64];
snprintf(statusFilename, sizeof(statusFilename), "/proc/%d/lwp/%d/lwpsinfo", pid, tid);
intptr_t fd;
while ((fd = open(statusFilename, O_RDONLY)) < 0 && errno == EINTR);
if (fd < 0)
{
return 0;
}
lwpsinfo_t pr;
int result = Common_Read(fd, &pr, sizeof(pr));
close(fd);
if (result < sizeof (pr))
{
errno = EIO;
return -1;
}
threadStatus->Tid = pr.pr_lwpid;
threadStatus->Priority = pr.pr_pri;
threadStatus->NiceVal = pr.pr_nice;
// Status code, a char: ...
threadStatus->StatusCode = (char)pr.pr_sname;
// Thread start time and CPU time
threadStatus->StartTime = pr.pr_start.tv_sec;
threadStatus->StartTimeNsec = pr.pr_start.tv_nsec;
threadStatus->CpuTotalTime = pr.pr_time.tv_sec;
threadStatus->CpuTotalTimeNsec = pr.pr_time.tv_nsec;
return 0;
#else
(void)pid, (void)tid, (void)threadStatus;
errno = ENOTSUP;
return -1;
#endif // __sun
}
// The struct passing is limited, so the args string is handled separately here.
int32_t SystemNative_ReadProcessStatusInfo(int32_t pid, ProcessStatus* processStatus, uint8_t *argBuf, int32_t argBufSize)
{
#ifdef __sun
if (argBufSize != 0 && argBufSize < PRARGSZ)
{
errno = EINVAL;
return -1;
}
char statusFilename[64];
snprintf(statusFilename, sizeof(statusFilename), "/proc/%d/psinfo", pid);
@ -1827,18 +1875,36 @@ int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStat
return 0;
}
psinfo_t status;
int result = Common_Read(fd, &status, sizeof(psinfo_t));
psinfo_t pr;
int result = Common_Read(fd, &pr, sizeof(pr));
close(fd);
if (result >= 0)
if (result < sizeof (pr))
{
processStatus->ResidentSetSize = status.pr_rssize * 1024; // pr_rssize is in Kbytes
return 1;
errno = EIO;
return -1;
}
processStatus->Pid = pr.pr_pid;
processStatus->ParentPid = pr.pr_ppid;
processStatus->SessionId = pr.pr_sid;
processStatus->Priority = pr.pr_lwp.pr_pri;
processStatus->NiceVal = pr.pr_lwp.pr_nice;
// pr_size and pr_rsize are in Kbytes.
processStatus->VirtualSize = (uint64_t)pr.pr_size * 1024;
processStatus->ResidentSetSize = (uint64_t)pr.pr_rssize * 1024;
processStatus->StartTime = pr.pr_start.tv_sec;
processStatus->StartTimeNsec = pr.pr_start.tv_nsec;
processStatus->CpuTotalTime = pr.pr_time.tv_sec;
processStatus->CpuTotalTimeNsec = pr.pr_time.tv_nsec;
if (argBuf != NULL && argBufSize != 0)
{
SafeStringCopy((char*)argBuf, PRARGSZ, pr.pr_psargs);
}
return 0;
#else
(void)pid, (void)processStatus;
(void)pid, (void)processStatus, (void)argBuf, (void)argBufSize;
errno = ENOTSUP;
return -1;
#endif // __sun

View File

@ -38,10 +38,35 @@ typedef struct
typedef struct
{
size_t ResidentSetSize;
// note: sorted by size to avoid alignment padding
uint64_t VirtualSize;
uint64_t ResidentSetSize;
int64_t StartTime; // time proc. started
int64_t StartTimeNsec;
int64_t CpuTotalTime; // Cumulative CPU time (user+sys)
int64_t CpuTotalTimeNsec;
int32_t Pid;
int32_t ParentPid;
int32_t SessionId;
int32_t Priority;
int32_t NiceVal;
// add more fields when needed.
} ProcessStatus;
typedef struct
{
// note: sorted by size to avoid alignment padding
int64_t StartTime; // time thread started
int64_t StartTimeNsec;
int64_t CpuTotalTime; // cumulative CPU time (user+sys)
int64_t CpuTotalTimeNsec;
int32_t Tid;
int32_t Priority;
int32_t NiceVal;
uint8_t StatusCode; // [ORSTWZ] See ProcFsStateToThreadState()
// add more fields when needed.
} ThreadStatus;
// 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
@ -804,12 +829,19 @@ PALEXPORT int32_t SystemNative_LChflagsCanSetHiddenFlag(void);
*/
PALEXPORT int32_t SystemNative_CanGetHiddenFlag(void);
/**
* Reads the lwpsinfo_t struct and converts into ThreadStatus.
*
* Returns 0 on success; otherwise, returns -1 and sets errno.
*/
PALEXPORT int32_t SystemNative_ReadProcessLwpInfo(int32_t pid, int32_t tid, ThreadStatus* threadStatus);
/**
* Reads the psinfo_t struct and converts into ProcessStatus.
*
* Returns 1 if the process status was read; otherwise, 0.
* Returns 0 on success; otherwise, returns -1 and sets errno.
*/
PALEXPORT int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStatus);
PALEXPORT int32_t SystemNative_ReadProcessStatusInfo(int32_t pid, ProcessStatus* processStatus, uint8_t *argBuf, int32_t argBufSize);
/**
* Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor at specified offset.

View File

@ -553,6 +553,9 @@ static int32_t ConvertRLimitResourcesPalToPlatform(RLimitResources value)
#ifdef RLIMIT_RSS
case PAL_RLIMIT_RSS:
return RLIMIT_RSS;
#elif defined(RLIMIT_VMEM)
case PAL_RLIMIT_RSS:
return RLIMIT_VMEM;
#endif
#ifdef RLIMIT_MEMLOCK
case PAL_RLIMIT_MEMLOCK: