From 30eae7fd1d18303d603483c0d150d4d9ffa05323 Mon Sep 17 00:00:00 2001
From: Adeel <3840695+am11@users.noreply.github.com>
Date: Wed, 11 May 2022 11:53:03 +0300
Subject: [PATCH 1/6] Use CLOCK_BOOTTIME to calculate BootTime on linux
---
.../Linux/procfs/Interop.ProcFsStat.cs | 4 ---
.../System.Native/Interop.GetBootTimeTicks.cs | 14 ++++++++
.../src/System.Diagnostics.Process.csproj | 2 ++
.../src/System/Diagnostics/Process.Linux.cs | 35 +++++--------------
.../tests/ProcessTests.cs | 2 +-
src/native/libs/System.Native/entrypoints.c | 1 +
src/native/libs/System.Native/pal_time.c | 32 ++++++++++++++---
src/native/libs/System.Native/pal_time.h | 5 +++
8 files changed, 59 insertions(+), 36 deletions(-)
create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs
diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
index 8dd29a54221d0..ee9c2b7597188 100644
--- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
+++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
@@ -19,10 +19,6 @@ internal static partial class @procfs
private const string FileDescriptorDirectoryName = "/fd/";
private const string TaskDirectoryName = "/task/";
- internal const string SelfExeFilePath = RootPath + "self" + ExeFileName;
- internal const string SelfCmdLineFilePath = RootPath + "self" + CmdLineFileName;
- internal const string ProcStatFilePath = RootPath + "stat";
-
internal struct ParsedStat
{
// Commented out fields are available in the stat data file but
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs
new file mode 100644
index 0000000000000..d6c327578c218
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetBootTimeTicks.cs
@@ -0,0 +1,14 @@
+// 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 Sys
+ {
+ [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetBootTimeTicks")]
+ [SuppressGCTransition]
+ internal static partial long GetBootTimeTicks();
+ }
+}
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 3de13e8cdc662..bb014cb25fd8c 100644
--- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj
+++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj
@@ -303,6 +303,8 @@
Link="Common\Interop\Linux\Interop.ProcFsStat.ParseMapModules.cs" />
+
diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
index 31b681bd8755b..6606fddb99e90 100644
--- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
+++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
@@ -9,6 +9,7 @@
using System.IO;
using System.Runtime.Versioning;
using System.Text;
+using System.Threading;
namespace System.Diagnostics
{
@@ -78,33 +79,16 @@ internal static DateTime BootTimeToDateTime(TimeSpan timespanAfterBoot)
return dt.ToLocalTime();
}
+ private static long s_bootTimeTicks;
/// Gets the system boot time.
private static DateTime BootTime
{
get
{
- // '/proc/stat -> btime' gets the boot time.
- // btime is the time of system boot in seconds since the Unix epoch.
- // It includes suspended time and is updated based on the system time (settimeofday).
- const string StatFile = Interop.procfs.ProcStatFilePath;
- string text = File.ReadAllText(StatFile);
- int btimeLineStart = text.IndexOf("\nbtime ", StringComparison.Ordinal);
- if (btimeLineStart >= 0)
- {
- int btimeStart = btimeLineStart + "\nbtime ".Length;
- int btimeEnd = text.IndexOf('\n', btimeStart);
- if (btimeEnd > btimeStart)
- {
- if (long.TryParse(text.AsSpan(btimeStart, btimeEnd - btimeStart), out long bootTimeSeconds))
- {
- return DateTime.UnixEpoch + TimeSpan.FromSeconds(bootTimeSeconds);
- }
- }
- }
-
- return DateTime.UtcNow;
- }
- }
+ Interlocked.CompareExchange(ref s_bootTimeTicks, Interop.Sys.GetBootTimeTicks(), 0);
+ return new DateTime(s_bootTimeTicks);
+ }
+ }
/// Gets the parent process ID
private int ParentProcessId =>
@@ -257,11 +241,8 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out
/// The pid for the target process, or -1 for the current process.
internal static string? GetExePath(int processId = -1)
{
- string exeFilePath = processId == -1 ?
- Interop.procfs.SelfExeFilePath :
- Interop.procfs.GetExeFilePathForProcess(processId);
-
- return Interop.Sys.ReadLink(exeFilePath);
+ return processId == -1 ? Environment.ProcessPath :
+ Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(processId));
}
/// Gets the name that was used to start the process, or null if it could not be retrieved.
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
index 3d6500fc7eb07..34919c28357f0 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -938,7 +938,7 @@ public void ProcessStartTime_Deterministic_Across_Instances()
{
using (var p = Process.GetProcessById(_process.Id))
{
- Assert.Equal(_process.StartTime, p.StartTime);
+ Assert.Equal(_process.StartTime, p.StartTime, precision: TimeSpan.FromMilliseconds(10));
}
}
}
diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c
index 6a960a42ae14c..14c7bc6e82f96 100644
--- a/src/native/libs/System.Native/entrypoints.c
+++ b/src/native/libs/System.Native/entrypoints.c
@@ -242,6 +242,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_Abort)
DllImportEntry(SystemNative_UTimensat)
DllImportEntry(SystemNative_GetTimestamp)
+ DllImportEntry(SystemNative_GetBootTimeTicks)
DllImportEntry(SystemNative_GetCpuUtilization)
DllImportEntry(SystemNative_GetPwUidR)
DllImportEntry(SystemNative_GetPwNamR)
diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c
index 4f9b5326cb73c..cdd30c528135f 100644
--- a/src/native/libs/System.Native/pal_time.c
+++ b/src/native/libs/System.Native/pal_time.c
@@ -18,9 +18,10 @@
enum
{
- SecondsToMicroSeconds = 1000000, // 10^6
- SecondsToNanoSeconds = 1000000000, // 10^9
- MicroSecondsToNanoSeconds = 1000 // 10^3
+ MicroSecondsToNanoSeconds = 1000, // 10^3
+ SecondsToNanoSeconds = 1000000000, // 10^9
+ SecondsToTicks = 10000000, // 10^7
+ TicksToNanoSeconds = 100, // 10^2
};
int32_t SystemNative_UTimensat(const char* path, TimeSpec* times)
@@ -68,6 +69,29 @@ uint64_t SystemNative_GetTimestamp()
#endif
}
+int64_t SystemNative_GetBootTimeTicks()
+{
+#ifdef TARGET_LINUX
+ struct timespec ts;
+
+ int result = clock_gettime(CLOCK_BOOTTIME, &ts);
+ assert(result == 0); // only possible errors are if the given clockId isn't supported or &ts is an invalid address
+ (void)result; // suppress unused parameter warning in release builds
+
+ int64_t sinceBootTicks = (ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
+
+ result = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
+ assert(result == 0);
+
+ int64_t sinceEpochTicks = (ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
+ const int64_t UnixEpochTicks = 621355968000000000;
+
+ return UnixEpochTicks + sinceEpochTicks - sinceBootTicks;
+#else
+ return -1;
+#endif
+}
+
int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
{
uint64_t kernelTime = 0;
@@ -82,7 +106,7 @@ int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
else
{
kernelTime =
- ((uint64_t)(resUsage.ru_stime.tv_sec) * SecondsToNanoSeconds) +
+ ((uint64_t)(resUsage.ru_stime.tv_sec) * SecondsToNanoSeconds) +
((uint64_t)(resUsage.ru_stime.tv_usec) * MicroSecondsToNanoSeconds);
userTime =
((uint64_t)(resUsage.ru_utime.tv_sec) * SecondsToNanoSeconds) +
diff --git a/src/native/libs/System.Native/pal_time.h b/src/native/libs/System.Native/pal_time.h
index 12fe500133418..b2c6079602bd2 100644
--- a/src/native/libs/System.Native/pal_time.h
+++ b/src/native/libs/System.Native/pal_time.h
@@ -32,6 +32,11 @@ PALEXPORT int32_t SystemNative_UTimensat(const char* path, TimeSpec* times);
*/
PALEXPORT uint64_t SystemNative_GetTimestamp(void);
+/**
+ * Gets system boot time ticks. (Linux only)
+ */
+PALEXPORT int64_t SystemNative_GetBootTimeTicks(void);
+
/**
* The main purpose of this function is to compute the overall CPU utilization
* for the CLR thread pool to regulate the number of worker threads.
From 895cf6b7585ace2fb085b4d47e375304c152404b Mon Sep 17 00:00:00 2001
From: Adeel Mujahid <3840695+am11@users.noreply.github.com>
Date: Wed, 11 May 2022 14:10:02 +0300
Subject: [PATCH 2/6] Optimize cache hits
Co-authored-by: Adam Sitnik
---
.../src/System/Diagnostics/Process.Linux.cs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
index 6606fddb99e90..dc53c93bceb94 100644
--- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
+++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
@@ -85,8 +85,17 @@ private static DateTime BootTime
{
get
{
- Interlocked.CompareExchange(ref s_bootTimeTicks, Interop.Sys.GetBootTimeTicks(), 0);
- return new DateTime(s_bootTimeTicks);
+ long bootTimeTicks = Interlocked.Read(ref s_bootTimeTicks);
+ if (bootTimeTicks == 0)
+ {
+ bootTimeTicks = Interop.Sys.GetBootTimeTicks();
+ long oldValue = Interlocked.CompareExchange(ref s_bootTimeTicks, bootTimeTicks, 0);
+ if (oldValue != 0) // a different thread has managed to update the ticks first
+ {
+ bootTimeTicks = oldValue; // consistency
+ }
+ }
+ return new DateTime(bootTimeTicks);
}
}
From 88a7f587d81abad1ea854c8a478db438b1fa0317 Mon Sep 17 00:00:00 2001
From: Adeel <3840695+am11@users.noreply.github.com>
Date: Thu, 12 May 2022 02:41:24 +0300
Subject: [PATCH 3/6] Use StartTime in a few existing tests
---
.../System.Diagnostics.Process/tests/ProcessTests.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
index 34919c28357f0..b1c625959683d 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -1147,13 +1147,13 @@ public void TestGetProcesses()
// Get all the processes running on the machine, and check if the current process is one of them.
var foundCurrentProcess = (from p in Process.GetProcesses()
- where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName))
+ where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime)
select p).Any();
Assert.True(foundCurrentProcess, "TestGetProcesses001 failed");
foundCurrentProcess = (from p in Process.GetProcesses(currentProcess.MachineName)
- where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName))
+ where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime)
select p).Any();
Assert.True(foundCurrentProcess, "TestGetProcesses002 failed");
@@ -1243,6 +1243,7 @@ public void GetProcessesByName_ProcessName_ReturnsExpected()
Assert.All(processes, process => Assert.Equal(currentProcess.ProcessName, process.ProcessName));
Assert.All(processes, process => Assert.Equal(".", process.MachineName));
+ Assert.All(processes, process => Assert.Equal(currentProcess.StartTime, process.StartTime));
}
// Outputs a list of active processes in case of failure: https://github.com/dotnet/runtime/issues/28874
From f611586c49b1bca559afa7290ae09bea92fcea8b Mon Sep 17 00:00:00 2001
From: Adeel <3840695+am11@users.noreply.github.com>
Date: Wed, 18 May 2022 18:44:11 +0300
Subject: [PATCH 4/6] Widen the multiplier to avoid overflow on 32-bit `time_t`
on Android 32-bit (arm and x86) is typedef'd as `long`.
---
src/native/libs/System.Native/pal_time.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c
index cdd30c528135f..ccc1fb3d4b784 100644
--- a/src/native/libs/System.Native/pal_time.c
+++ b/src/native/libs/System.Native/pal_time.c
@@ -78,12 +78,12 @@ int64_t SystemNative_GetBootTimeTicks()
assert(result == 0); // only possible errors are if the given clockId isn't supported or &ts is an invalid address
(void)result; // suppress unused parameter warning in release builds
- int64_t sinceBootTicks = (ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
+ int64_t sinceBootTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
result = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
assert(result == 0);
- int64_t sinceEpochTicks = (ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
+ int64_t sinceEpochTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
const int64_t UnixEpochTicks = 621355968000000000;
return UnixEpochTicks + sinceEpochTicks - sinceBootTicks;
From e77e4fc9925447768bbc18867ed2af0add7fcc78 Mon Sep 17 00:00:00 2001
From: Adeel Mujahid <3840695+am11@users.noreply.github.com>
Date: Wed, 18 May 2022 18:47:20 +0300
Subject: [PATCH 5/6] Update
src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
---
src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
index b1c625959683d..5aed1ff0f3fd0 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -938,7 +938,7 @@ public void ProcessStartTime_Deterministic_Across_Instances()
{
using (var p = Process.GetProcessById(_process.Id))
{
- Assert.Equal(_process.StartTime, p.StartTime, precision: TimeSpan.FromMilliseconds(10));
+ Assert.Equal(_process.StartTime, p.StartTime);
}
}
}
From 1363eb9f33f4d55f1d4da6d45f794ef0231cc58d Mon Sep 17 00:00:00 2001
From: Adeel Mujahid <3840695+am11@users.noreply.github.com>
Date: Wed, 7 Sep 2022 17:02:22 +0300
Subject: [PATCH 6/6] Update src/native/libs/System.Native/pal_time.c
Co-authored-by: Simon Rozsival
---
src/native/libs/System.Native/pal_time.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c
index 83b5b616bbbfa..4a7bbeca4d59b 100644
--- a/src/native/libs/System.Native/pal_time.c
+++ b/src/native/libs/System.Native/pal_time.c
@@ -98,7 +98,7 @@ uint64_t SystemNative_GetTimestamp()
int64_t SystemNative_GetBootTimeTicks()
{
-#ifdef TARGET_LINUX
+#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
struct timespec ts;
int result = clock_gettime(CLOCK_BOOTTIME, &ts);