Skip to content

Commit fa133ae

Browse files
simonrozsivaldotnet-maestro[bot]am11adamsitnikjeffhandley
authored
[release/7.0] Use CLOCK_BOOTTIME to calculate BootTime on linux (#67589) (#83966)
* Update dependencies from https://github.com/dotnet/emsdk build 20230323.1 (#83860) Microsoft.NET.Workload.Emscripten.net6.Manifest-7.0.100 , Microsoft.NET.Workload.Emscripten.net7.Manifest-7.0.100 From Version 7.0.4 -> To Version 7.0.5 Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com> * Use CLOCK_BOOTTIME to calculate BootTime on linux (#67589) Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com> Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com> Co-authored-by: Simon Rozsival <simon@rozsival.com> * Do no test StartTime in GetProcessesByName test (#75334) * Revert "Update dependencies from https://github.com/dotnet/emsdk build 20230323.1 (#83860)" This reverts commit 6a533c1. --------- Co-authored-by: dotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com> Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com> Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com> Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com>
1 parent 641393e commit fa133ae

File tree

8 files changed

+66
-34
lines changed

8 files changed

+66
-34
lines changed

src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ internal static partial class @procfs
1919
private const string FileDescriptorDirectoryName = "/fd/";
2020
private const string TaskDirectoryName = "/task/";
2121

22-
internal const string SelfExeFilePath = RootPath + "self" + ExeFileName;
23-
internal const string SelfCmdLineFilePath = RootPath + "self" + CmdLineFileName;
24-
internal const string ProcStatFilePath = RootPath + "stat";
25-
2622
internal struct ParsedStat
2723
{
2824
// Commented out fields are available in the stat data file but
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.Runtime.InteropServices;
5+
6+
internal static partial class Interop
7+
{
8+
internal static partial class Sys
9+
{
10+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetBootTimeTicks")]
11+
[SuppressGCTransition]
12+
internal static partial long GetBootTimeTicks();
13+
}
14+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@
302302
Link="Common\Interop\Linux\Interop.ProcFsStat.ParseMapModules.cs" />
303303
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs"
304304
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
305+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetBootTimeTicks.cs"
306+
Link="Common\Interop\Linux\Interop.GetBootTimeTicks.cs" />
305307
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SchedGetSetAffinity.cs"
306308
Link="Common\Interop\Linux\Interop.SchedGetSetAffinity.cs" />
307309
</ItemGroup>

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.IO;
1010
using System.Runtime.Versioning;
1111
using System.Text;
12+
using System.Threading;
1213

1314
namespace System.Diagnostics
1415
{
@@ -79,33 +80,25 @@ internal static DateTime BootTimeToDateTime(TimeSpan timespanAfterBoot)
7980
return dt.ToLocalTime();
8081
}
8182

83+
private static long s_bootTimeTicks;
8284
/// <summary>Gets the system boot time.</summary>
8385
private static DateTime BootTime
8486
{
8587
get
8688
{
87-
// '/proc/stat -> btime' gets the boot time.
88-
// btime is the time of system boot in seconds since the Unix epoch.
89-
// It includes suspended time and is updated based on the system time (settimeofday).
90-
const string StatFile = Interop.procfs.ProcStatFilePath;
91-
string text = File.ReadAllText(StatFile);
92-
int btimeLineStart = text.IndexOf("\nbtime ", StringComparison.Ordinal);
93-
if (btimeLineStart >= 0)
94-
{
95-
int btimeStart = btimeLineStart + "\nbtime ".Length;
96-
int btimeEnd = text.IndexOf('\n', btimeStart);
97-
if (btimeEnd > btimeStart)
89+
long bootTimeTicks = Interlocked.Read(ref s_bootTimeTicks);
90+
if (bootTimeTicks == 0)
91+
{
92+
bootTimeTicks = Interop.Sys.GetBootTimeTicks();
93+
long oldValue = Interlocked.CompareExchange(ref s_bootTimeTicks, bootTimeTicks, 0);
94+
if (oldValue != 0) // a different thread has managed to update the ticks first
9895
{
99-
if (long.TryParse(text.AsSpan(btimeStart, btimeEnd - btimeStart), out long bootTimeSeconds))
100-
{
101-
return DateTime.UnixEpoch + TimeSpan.FromSeconds(bootTimeSeconds);
102-
}
96+
bootTimeTicks = oldValue; // consistency
10397
}
104-
}
105-
106-
return DateTime.UtcNow;
107-
}
108-
}
98+
}
99+
return new DateTime(bootTimeTicks);
100+
}
101+
}
109102

110103
/// <summary>Gets the parent process ID</summary>
111104
private int ParentProcessId =>
@@ -258,11 +251,8 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out
258251
/// <param name="processId">The pid for the target process, or -1 for the current process.</param>
259252
internal static string? GetExePath(int processId = -1)
260253
{
261-
string exeFilePath = processId == -1 ?
262-
Interop.procfs.SelfExeFilePath :
263-
Interop.procfs.GetExeFilePathForProcess(processId);
264-
265-
return Interop.Sys.ReadLink(exeFilePath);
254+
return processId == -1 ? Environment.ProcessPath :
255+
Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(processId));
266256
}
267257

268258
/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>

src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,13 +1151,13 @@ public void TestGetProcesses()
11511151

11521152
// Get all the processes running on the machine, and check if the current process is one of them.
11531153
var foundCurrentProcess = (from p in Process.GetProcesses()
1154-
where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName))
1154+
where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime)
11551155
select p).Any();
11561156

11571157
Assert.True(foundCurrentProcess, "TestGetProcesses001 failed");
11581158

11591159
foundCurrentProcess = (from p in Process.GetProcesses(currentProcess.MachineName)
1160-
where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName))
1160+
where (p.Id == currentProcess.Id) && (p.ProcessName.Equals(currentProcess.ProcessName)) && (p.StartTime == currentProcess.StartTime)
11611161
select p).Any();
11621162

11631163
Assert.True(foundCurrentProcess, "TestGetProcesses002 failed");

src/native/libs/System.Native/entrypoints.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ static const Entry s_sysNative[] =
245245
DllImportEntry(SystemNative_UTimensat)
246246
DllImportEntry(SystemNative_FUTimens)
247247
DllImportEntry(SystemNative_GetTimestamp)
248+
DllImportEntry(SystemNative_GetBootTimeTicks)
248249
DllImportEntry(SystemNative_GetCpuUtilization)
249250
DllImportEntry(SystemNative_GetPwUidR)
250251
DllImportEntry(SystemNative_GetPwNamR)

src/native/libs/System.Native/pal_time.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
enum
2020
{
21-
SecondsToMicroSeconds = 1000000, // 10^6
22-
SecondsToNanoSeconds = 1000000000, // 10^9
23-
MicroSecondsToNanoSeconds = 1000 // 10^3
21+
MicroSecondsToNanoSeconds = 1000, // 10^3
22+
SecondsToNanoSeconds = 1000000000, // 10^9
23+
SecondsToTicks = 10000000, // 10^7
24+
TicksToNanoSeconds = 100, // 10^2
2425
};
2526

2627
int32_t SystemNative_UTimensat(const char* path, TimeSpec* times)
@@ -95,6 +96,29 @@ uint64_t SystemNative_GetTimestamp()
9596
#endif
9697
}
9798

99+
int64_t SystemNative_GetBootTimeTicks()
100+
{
101+
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
102+
struct timespec ts;
103+
104+
int result = clock_gettime(CLOCK_BOOTTIME, &ts);
105+
assert(result == 0); // only possible errors are if the given clockId isn't supported or &ts is an invalid address
106+
(void)result; // suppress unused parameter warning in release builds
107+
108+
int64_t sinceBootTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
109+
110+
result = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
111+
assert(result == 0);
112+
113+
int64_t sinceEpochTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
114+
const int64_t UnixEpochTicks = 621355968000000000;
115+
116+
return UnixEpochTicks + sinceEpochTicks - sinceBootTicks;
117+
#else
118+
return -1;
119+
#endif
120+
}
121+
98122
double SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
99123
{
100124
uint64_t kernelTime = 0;

src/native/libs/System.Native/pal_time.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ PALEXPORT int32_t SystemNative_FUTimens(intptr_t fd, TimeSpec* times);
3939
*/
4040
PALEXPORT uint64_t SystemNative_GetTimestamp(void);
4141

42+
/**
43+
* Gets system boot time ticks. (Linux only)
44+
*/
45+
PALEXPORT int64_t SystemNative_GetBootTimeTicks(void);
46+
4247
/**
4348
* The main purpose of this function is to compute the overall CPU utilization
4449
* for the CLR thread pool to regulate the number of worker threads.

0 commit comments

Comments
 (0)