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);