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

[release/6.0] Use CLOCK_BOOTTIME to calculate BootTime on linux (#67589) #83965

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetBootTimeTicks")]
[SuppressGCTransition]
internal static extern long GetBootTimeTicks();
}
}
1 change: 1 addition & 0 deletions src/libraries/Native/Unix/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ else ()
endif ()

if (CLR_CMAKE_TARGET_LINUX)
add_definitions(-DTARGET_LINUX)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE")
endif ()

Expand Down
1 change: 1 addition & 0 deletions src/libraries/Native/Unix/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_LowLevelMonitor_Signal_Release)
DllImportEntry(SystemNative_UTimensat)
DllImportEntry(SystemNative_GetTimestamp)
DllImportEntry(SystemNative_GetBootTimeTicks)
DllImportEntry(SystemNative_GetCpuUtilization)
DllImportEntry(SystemNative_GetPwUidR)
DllImportEntry(SystemNative_GetPwNamR)
Expand Down
32 changes: 28 additions & 4 deletions src/libraries/Native/Unix/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 @@ -62,6 +63,29 @@ uint64_t SystemNative_GetTimestamp()
#endif
}

int64_t SystemNative_GetBootTimeTicks()
{
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
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
}

int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
{
uint64_t kernelTime = 0;
Expand All @@ -76,7 +100,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) +
Expand Down
5 changes: 5 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_time.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,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 @@ -75,33 +76,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();
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 @@ -248,11 +241,8 @@ private void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out IntPtr
/// <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 @@ -1126,13 +1126,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