Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CLOCK_BOOTTIME to calculate BootTime on linux #67589

Merged
merged 9 commits into from
Sep 8, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@
Link="Common\Interop\Linux\Interop.ProcFsStat.ParseMapModules.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs"
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetBootTimeTicks.cs"
Link="Common\Interop\Linux\Interop.GetBootTimeTicks.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SchedGetSetAffinity.cs"
Link="Common\Interop\Linux\Interop.SchedGetSetAffinity.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;

namespace System.Diagnostics
{
Expand Down Expand Up @@ -79,33 +80,25 @@ internal static DateTime BootTimeToDateTime(TimeSpan timespanAfterBoot)
return dt.ToLocalTime();
}

private static long s_bootTimeTicks;
/// <summary>Gets the system boot time.</summary>
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)
long bootTimeTicks = Interlocked.Read(ref s_bootTimeTicks);
if (bootTimeTicks == 0)
{
bootTimeTicks = Interop.Sys.GetBootTimeTicks();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should check if the value is -1 and throw PlatformNotSupportedException in that case.

long oldValue = Interlocked.CompareExchange(ref s_bootTimeTicks, bootTimeTicks, 0);
if (oldValue != 0) // a different thread has managed to update the ticks first
{
if (long.TryParse(text.AsSpan(btimeStart, btimeEnd - btimeStart), out long bootTimeSeconds))
{
return DateTime.UnixEpoch + TimeSpan.FromSeconds(bootTimeSeconds);
}
bootTimeTicks = oldValue; // consistency
}
}

return DateTime.UtcNow;
}
}
}
return new DateTime(bootTimeTicks);
}
}

/// <summary>Gets the parent process ID</summary>
private int ParentProcessId =>
Expand Down Expand Up @@ -258,11 +251,8 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out
/// <param name="processId">The pid for the target process, or -1 for the current process.</param>
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));
}

/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1152,13 +1152,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");
Expand Down Expand Up @@ -1248,6 +1248,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
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_UTimensat)
DllImportEntry(SystemNative_FUTimens)
DllImportEntry(SystemNative_GetTimestamp)
DllImportEntry(SystemNative_GetBootTimeTicks)
DllImportEntry(SystemNative_GetCpuUtilization)
DllImportEntry(SystemNative_GetPwUidR)
DllImportEntry(SystemNative_GetPwNamR)
Expand Down
30 changes: 27 additions & 3 deletions src/native/libs/System.Native/pal_time.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -95,6 +96,29 @@ uint64_t SystemNative_GetTimestamp()
#endif
}

int64_t SystemNative_GetBootTimeTicks()
{
#ifdef TARGET_LINUX
am11 marked this conversation as resolved.
Show resolved Hide resolved
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 = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);

result = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
assert(result == 0);

int64_t sinceEpochTicks = ((int64_t)ts.tv_sec * SecondsToTicks) + (ts.tv_nsec / TicksToNanoSeconds);
const int64_t UnixEpochTicks = 621355968000000000;

return UnixEpochTicks + sinceEpochTicks - sinceBootTicks;
#else
return -1;
#endif
}

double SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
{
uint64_t kernelTime = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/native/libs/System.Native/pal_time.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ PALEXPORT int32_t SystemNative_FUTimens(intptr_t fd, 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.
Expand Down