Skip to content

Commit 45b07a7

Browse files
gwrAustinWiseam11
committed
SunOS process and thread support
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 <AustinWise@gmail.com> Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
1 parent 32fcc41 commit 45b07a7

15 files changed

Lines changed: 796 additions & 60 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
// See callers:
11+
// ProcessManager.SunOS.cs
12+
// Environment.SunOS etc.
13+
14+
internal static partial class Interop
15+
{
16+
internal static partial class @procfs
17+
{
18+
19+
// Constants from sys/procfs.h
20+
private const int PRARGSZ = 80;
21+
22+
// Output type for GetProcessInfoById()
23+
// Keep in sync with pal_io.h ProcessInfo
24+
[StructLayout(LayoutKind.Sequential)]
25+
internal struct ProcessInfo
26+
{
27+
internal ulong VirtualSize;
28+
internal ulong ResidentSetSize;
29+
internal long StartTime;
30+
internal long StartTimeNsec;
31+
internal long CpuTotalTime;
32+
internal long CpuTotalTimeNsec;
33+
internal int Pid;
34+
internal int ParentPid;
35+
internal int SessionId;
36+
internal int Priority;
37+
internal int NiceVal;
38+
}
39+
40+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessInfo", SetLastError = true)]
41+
private static unsafe partial int ReadProcessInfo(int pid, ProcessInfo* processInfo, byte* argBuf, int argBufSize);
42+
43+
/// <summary>
44+
/// Attempts to get status info for the specified process ID.
45+
/// </summary>
46+
/// <param name="pid">PID of the process to read status info for.</param>
47+
/// <param name="processInfo">The pointer to ProcessInfo instance.</param>
48+
/// <returns>
49+
/// true if the process status was read; otherwise, false.
50+
/// </returns>
51+
internal static unsafe bool GetProcessInfoById(int pid, out ProcessInfo processInfo)
52+
{
53+
fixed (ProcessInfo* pProcessInfo = &processInfo)
54+
{
55+
if (ReadProcessInfo(pid, pProcessInfo, null, 0) < 0)
56+
{
57+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
58+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
59+
}
60+
}
61+
return true;
62+
}
63+
64+
// Variant that also gets the arg string.
65+
internal static unsafe bool GetProcessInfoById(int pid, out ProcessInfo processInfo, out string argString)
66+
{
67+
byte* argBuf = stackalloc byte[PRARGSZ];
68+
fixed (ProcessInfo* pProcessInfo = &processInfo)
69+
{
70+
if (ReadProcessInfo(pid, pProcessInfo, argBuf, PRARGSZ) < 0)
71+
{
72+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
73+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
74+
}
75+
}
76+
argString = Marshal.PtrToStringUTF8((IntPtr)argBuf)!;
77+
return true;
78+
}
79+
}
80+
}
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+
// See callers:
11+
// ProcessManager.SunOS.cs
12+
// ProcessThread.SunOS etc.
13+
14+
internal static partial class Interop
15+
{
16+
internal static partial class @procfs
17+
{
18+
19+
// Output type for GetThreadInfoById()
20+
// Keep in sync with pal_io.h ThreadInfo
21+
[StructLayout(LayoutKind.Sequential)]
22+
internal struct ThreadInfo
23+
{
24+
internal long StartTime;
25+
internal long StartTimeNsec;
26+
internal long CpuTotalTime; // user+sys
27+
internal long CpuTotalTimeNsec;
28+
internal int Tid;
29+
internal int Priority;
30+
internal int NiceVal;
31+
internal char StatusCode;
32+
}
33+
34+
// See caller: ProcessManager.SunOS.cs
35+
36+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadThreadInfo", SetLastError = true)]
37+
private static unsafe partial int ReadThreadInfo(int pid, int tid, ThreadInfo* threadInfo);
38+
39+
/// <summary>
40+
/// Attempts to get status info for the specified thread ID.
41+
/// </summary>
42+
/// <param name="pid">PID of the process to read status info for.</param>
43+
/// <param name="tid">TID of the thread to read status info for.</param>
44+
/// <param name="threadInfo">The pointer to ThreadInfo instance.</param>
45+
/// <returns>
46+
/// true if the process status was read; otherwise, false.
47+
/// </returns>
48+
internal static unsafe bool GetThreadInfoById(int pid, int tid, out ThreadInfo threadInfo)
49+
{
50+
fixed (ThreadInfo* pThreadInfo = &threadInfo)
51+
{
52+
if (ReadThreadInfo(pid, tid, pThreadInfo) < 0)
53+
{
54+
Interop.ErrorInfo errorInfo = Sys.GetLastErrorInfo();
55+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
56+
}
57+
}
58+
return true;
59+
}
60+
}
61+
}

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
@@ -50,6 +50,7 @@ public static partial class PlatformDetection
5050
public static bool IsNotMacCatalyst => !IsMacCatalyst;
5151
public static bool Isillumos => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ILLUMOS"));
5252
public static bool IsSolaris => RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS"));
53+
public static bool IsSunOS => Isillumos || IsSolaris;
5354
public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
5455
public static bool IsWasi => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI"));
5556
public static bool IsWasm => IsBrowser || IsWasi;

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

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

0 commit comments

Comments
 (0)