Skip to content

Commit 912ac01

Browse files
gwrAustinWiseam11
committed
SunOS process and thread support (native)
Read /proc (binary) psinfo for System.Diagnostic.Process using src/native/libs/System.Native C functions. Add native/libs/System.Native/pal_io.c etc. Add src/libraries/Common/src/Interop/SunOS/procfs Add src/libraries/System.Diagnostics.Process Co-authored-by: Austin Wise <[email protected]> Co-authored-by: Adeel Mujahid <[email protected]>
1 parent 6da0fa9 commit 912ac01

File tree

17 files changed

+825
-52
lines changed

17 files changed

+825
-52
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class @procfs
10+
{
11+
internal const string RootPath = "/proc/";
12+
private const string psinfoFileName = "/psinfo";
13+
private const string lwpDirName = "/lwp";
14+
private const string lwpsinfoFileName = "/lwpsinfo";
15+
16+
// Constants from sys/procfs.h
17+
private const int PRARGSZ = 80;
18+
19+
// Output type for TryGetProcessInfoById()
20+
// Keep in sync with pal_io.h ProcessStatus
21+
[StructLayout(LayoutKind.Sequential)]
22+
internal struct ProcessInfo
23+
{
24+
internal ulong VirtualSize;
25+
internal ulong ResidentSetSize;
26+
internal long StartTime;
27+
internal long StartTimeNsec;
28+
internal long CpuTotalTime;
29+
internal long CpuTotalTimeNsec;
30+
internal int Pid;
31+
internal int ParentPid;
32+
internal int SessionId;
33+
internal int Priority;
34+
internal int NiceVal;
35+
// add more fields when needed.
36+
}
37+
38+
// Output type for TryGetThreadInfoById()
39+
// Keep in sync with pal_io.h ThreadStatus
40+
[StructLayout(LayoutKind.Sequential)]
41+
internal struct ThreadInfo
42+
{
43+
internal long StartTime;
44+
internal long StartTimeNsec;
45+
internal long CpuTotalTime; // user+sys
46+
internal long CpuTotalTimeNsec;
47+
internal int Tid;
48+
internal int Priority;
49+
internal int NiceVal;
50+
internal char StatusCode;
51+
// add more fields when needed.
52+
}
53+
54+
internal static string GetInfoFilePathForProcess(int pid) =>
55+
$"{RootPath}{(uint)pid}{psinfoFileName}";
56+
57+
internal static string GetLwpDirForProcess(int pid) =>
58+
$"{RootPath}{(uint)pid}{lwpDirName}";
59+
60+
internal static string GetInfoFilePathForThread(int pid, int tid) =>
61+
$"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}";
62+
63+
}
64+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.IO;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class @procfs
13+
{
14+
15+
// See caller: ProcessManager.SunOS.cs
16+
17+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessStatusInfo", SetLastError = true)]
18+
private static unsafe partial int ReadProcessStatusInfo(int pid, ProcessInfo* processInfo, byte* argBuf, int argBufSize);
19+
20+
// Handy helpers for Environment.SunOS etc.
21+
22+
/// <summary>
23+
/// Attempts to get status info for the specified process ID.
24+
/// </summary>
25+
/// <param name="pid">PID of the process to read status info for.</param>
26+
/// <param name="processInfo">The pointer to ProcessInfo instance.</param>
27+
/// <returns>
28+
/// true if the process status was read; otherwise, false.
29+
/// </returns>
30+
internal static unsafe bool TryGetProcessInfoById(int pid, out ProcessInfo processInfo)
31+
{
32+
ProcessInfo info = default;
33+
if (ReadProcessStatusInfo(pid, &info, null, 0) < 0)
34+
{
35+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
36+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
37+
}
38+
processInfo = info;
39+
40+
return true;
41+
}
42+
43+
// Variant that also gets the arg string.
44+
internal static unsafe bool TryGetProcessInfoById(int pid, out ProcessInfo processInfo, out string argString)
45+
{
46+
ProcessInfo info = default;
47+
byte* argBuf = stackalloc byte[PRARGSZ];
48+
if (ReadProcessStatusInfo(pid, &info, argBuf, PRARGSZ) < 0)
49+
{
50+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
51+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
52+
}
53+
processInfo = info;
54+
argString = Marshal.PtrToStringUTF8((IntPtr)argBuf)!;
55+
56+
return true;
57+
}
58+
59+
60+
}
61+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.IO;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class @procfs
13+
{
14+
15+
// See caller: ProcessManager.SunOS.cs
16+
17+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessLwpInfo", SetLastError = true)]
18+
internal static unsafe partial int ReadProcessLwpInfo(int pid, int tid, ThreadInfo* threadInfo);
19+
20+
/// <summary>
21+
/// Attempts to get status info for the specified thread ID.
22+
/// </summary>
23+
/// <param name="pid">PID of the process to read status info for.</param>
24+
/// <param name="tid">TID of the thread to read status info for.</param>
25+
/// <param name="threadInfo">The pointer to ThreadInfo instance.</param>
26+
/// <returns>
27+
/// true if the process status was read; otherwise, false.
28+
/// </returns>
29+
internal static unsafe bool TryGetThreadInfoById(int pid, int tid, out ThreadInfo threadInfo)
30+
{
31+
ThreadInfo info = default;
32+
if (ReadProcessLwpInfo(pid, tid, &info) < 0)
33+
{
34+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
35+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
36+
}
37+
threadInfo = info;
38+
39+
return true;
40+
}
41+
42+
}
43+
}

src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static partial class PlatformDetection
4747
public static bool IsNotMacCatalyst => !IsMacCatalyst;
4848
public static bool Isillumos => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ILLUMOS"));
4949
public static bool IsSolaris => RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS"));
50+
public static bool IsSunOS => Isillumos || IsSolaris;
5051
public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
5152
public static bool IsWasi => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI"));
5253
public static bool IsNotBrowser => !IsBrowser;

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
4+
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent)</TargetFrameworks>
55
<DefineConstants>$(DefineConstants);FEATURE_REGISTRY</DefineConstants>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
@@ -369,6 +369,19 @@
369369
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
370370
</ItemGroup>
371371

372+
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'illumos' or '$(TargetPlatformIdentifier)' == 'solaris'">
373+
<Compile Include="System\Diagnostics\Process.BSD.cs" />
374+
<Compile Include="System\Diagnostics\Process.SunOS.cs" />
375+
<Compile Include="System\Diagnostics\ProcessManager.SunOS.cs" />
376+
<Compile Include="System\Diagnostics\ProcessThread.SunOS.cs" />
377+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs"
378+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs" />
379+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.TryGetProcessInfoById.cs"
380+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.TryGetProcessInfoById.cs" />
381+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.TryGetThreadInfoById.cs"
382+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.TryGetThreadInfoById.cs" />
383+
</ItemGroup>
384+
372385
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">
373386
<Compile Include="System\Diagnostics\Process.iOS.cs" />
374387
<Compile Include="System\Diagnostics\ProcessManager.iOS.cs" />
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.ComponentModel;
8+
using System.Globalization;
9+
using System.IO;
10+
using System.Runtime.InteropServices;
11+
using System.Runtime.Versioning;
12+
using System.Text;
13+
using System.Threading;
14+
15+
namespace System.Diagnostics
16+
{
17+
public partial class Process : IDisposable
18+
{
19+
20+
/// <summary>Gets the time the associated process was started.</summary>
21+
internal DateTime StartTimeCore
22+
{
23+
get
24+
{
25+
Interop.procfs.ProcessInfo iinfo = GetProcInfo();
26+
27+
DateTime startTime = DateTime.UnixEpoch +
28+
TimeSpan.FromSeconds(iinfo.StartTime) +
29+
TimeSpan.FromMicroseconds(iinfo.StartTimeNsec / 1000);
30+
31+
// The return value is expected to be in the local time zone.
32+
return startTime.ToLocalTime();
33+
}
34+
}
35+
36+
/// <summary>Gets the parent process ID</summary>
37+
private int ParentProcessId => GetProcInfo().ParentPid;
38+
39+
/// <summary>Gets execution path</summary>
40+
private static string? GetPathToOpenFile()
41+
{
42+
return FindProgramInPath("xdg-open");
43+
}
44+
45+
/// <summary>
46+
/// Gets the amount of time the associated process has spent utilizing the CPU.
47+
/// It is the sum of the <see cref='System.Diagnostics.Process.UserProcessorTime'/> and
48+
/// <see cref='System.Diagnostics.Process.PrivilegedProcessorTime'/>.
49+
/// </summary>
50+
[UnsupportedOSPlatform("ios")]
51+
[UnsupportedOSPlatform("tvos")]
52+
[SupportedOSPlatform("maccatalyst")]
53+
public TimeSpan TotalProcessorTime
54+
{
55+
get
56+
{
57+
// a.k.a. "user" + "system" time
58+
Interop.procfs.ProcessInfo iinfo = GetProcInfo();
59+
TimeSpan ts = TimeSpan.FromSeconds(iinfo.CpuTotalTime) +
60+
TimeSpan.FromMicroseconds(iinfo.CpuTotalTimeNsec / 1000);
61+
return ts;
62+
}
63+
}
64+
65+
/// <summary>
66+
/// Gets the amount of time the associated process has spent running code
67+
/// inside the application portion of the process (not the operating system core).
68+
/// </summary>
69+
[UnsupportedOSPlatform("ios")]
70+
[UnsupportedOSPlatform("tvos")]
71+
[SupportedOSPlatform("maccatalyst")]
72+
public TimeSpan UserProcessorTime
73+
{
74+
get
75+
{
76+
// a.k.a. "user" time
77+
// Could get this from /proc/$pid/status
78+
// Just say it's all user time for now
79+
return TotalProcessorTime;
80+
}
81+
}
82+
83+
/// <summary>
84+
/// Gets the amount of time the process has spent running code inside the operating
85+
/// system core.
86+
/// </summary>
87+
[UnsupportedOSPlatform("ios")]
88+
[UnsupportedOSPlatform("tvos")]
89+
[SupportedOSPlatform("maccatalyst")]
90+
public TimeSpan PrivilegedProcessorTime
91+
{
92+
get
93+
{
94+
// a.k.a. "system" time
95+
// Could get this from /proc/$pid/status
96+
// Just say it's all user time for now
97+
EnsureState(State.HaveNonExitedId);
98+
return TimeSpan.Zero;
99+
}
100+
}
101+
102+
// ----------------------------------
103+
// ---- Unix PAL layer ends here ----
104+
// ----------------------------------
105+
106+
/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
107+
internal static string? GetUntruncatedProcessName(ref Interop.procfs.ProcessInfo iProcInfo, ref string argString)
108+
{
109+
// This assumes the process name is the first part of the Args string
110+
// ending at the first space. That seems to work well enough for now.
111+
// If someday this need to support a process name containing spaces,
112+
// this could call a new Interop function that reads /proc/$pid/auxv
113+
// (sys/auxv.h) and gets the AT_SUN_EXECNAME string from that file.
114+
if (iProcInfo.Pid != 0 && !string.IsNullOrEmpty(argString))
115+
{
116+
string[] argv = argString.Split(' ', 2);
117+
if (!string.IsNullOrEmpty(argv[0]))
118+
{
119+
return Path.GetFileName(argv[0]);
120+
}
121+
}
122+
return null;
123+
}
124+
125+
/// <summary>Reads the information for this process from the procfs file system.</summary>
126+
private Interop.procfs.ProcessInfo GetProcInfo()
127+
{
128+
EnsureState(State.HaveNonExitedId);
129+
Interop.procfs.ProcessInfo iinfo;
130+
if (!Interop.procfs.TryGetProcessInfoById(_processId, out iinfo))
131+
{
132+
throw new Win32Exception(SR.ProcessInformationUnavailable);
133+
}
134+
return iinfo;
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)