AuroraRuntime/Source/Time/AuClock.cpp

488 lines
23 KiB
C++

/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: AuClock.cpp
Date: 2021-6-13
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "AuClock.hpp"
#include "Time.hpp"
#if defined(AURORA_IS_POSIX_DERIVED)
#include <sys/resource.h>
#elif defined(AURORA_IS_MODERNNT_DERIVED)
// benchmarking: https://github.com/microsoft/STL/issues/2085
static AuUInt64 _GetSteadyTimeNS()
{
static const long long gFreq = _Query_perf_frequency();
const long long uCounter = _Query_perf_counter();
if (gFreq == 10000000)
{
return uCounter * 100;
}
else if (gFreq == 1000000)
{
return uCounter * 1000;
}
else if (gFreq == 100000)
{
return uCounter * 10000;
}
else if (gFreq == 100000000)
{
return uCounter * 10;
}
else if (gFreq == 1000000000)
{
return uCounter;
}
else
{
// 6 branches: the default threshold for most jit and language compiler backends to decide to pick a jump table, if the values were in a close range
// otherwise, back to a tree of paths. either way, im sure 6 if elses are faster than grug math with large numbers, modulus, division, and multiplication
const long long uWhole = (uCounter / gFreq) * 1'000'000'000ull;
const long long uPart = (uCounter % gFreq) * 1'000'000'000ull / gFreq;
return uWhole + uPart;
}
}
// ~3.0741 seconds
// using high_res_clock = std::chrono::high_resolution_clock;
// ~6.07 seconds
// holy fuck, we're keeping this
// ~2x improvement
#else
using steady_clock = std::chrono::steady_clock;
#endif
using sys_clock = std::chrono::system_clock; // more stds to remove
sys_clock::duration __NormalizeEpoch(sys_clock::duration sysEpoch);
static AuInt64 _CurrentClock()
{
return __NormalizeEpoch(sys_clock::now().time_since_epoch()).count();
}
static AuInt64 _CurrentClockMS()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(__NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
}
static AuInt64 _CurrentClockNS()
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(__NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
}
namespace Aurora::Time
{
AUKN_SYM AuInt64 CurrentClock()
{
return _CurrentClock();
}
AUKN_SYM AuInt64 CurrentClockMS()
{
return _CurrentClockMS();
}
AUKN_SYM AuInt64 CurrentClockNS()
{
return _CurrentClockNS();
}
AUKN_SYM AuUInt64 SteadyClock()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
return _Query_perf_counter();
#else
return SteadyClockNS() / (1000000000ull / SteadyClockJiffies());
#endif
}
AUKN_SYM AuUInt64 SteadyClockMS()
{
#if defined(AURORA_IS_POSIX_DERIVED)
::timespec spec {};
if (::clock_gettime(CLOCK_MONOTONIC, &spec) == 0)
{
return AuSToMS<AuUInt64>(spec.tv_sec) + AuNSToMS<AuUInt64>(spec.tv_nsec);
}
else
{
return 0;
}
#elif defined(AURORA_IS_MODERNNT_DERIVED)
return AuNSToMS<AuUInt64>(_GetSteadyTimeNS());
#else
return std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now().time_since_epoch()).count();
#endif
}
AUKN_SYM AuUInt64 SteadyClockNS()
{
#if defined(AURORA_IS_POSIX_DERIVED)
::timespec spec {};
if (::clock_gettime(CLOCK_MONOTONIC, &spec) == 0)
{
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec;
}
else
{
return 0;
}
#elif defined(AURORA_IS_MODERNNT_DERIVED)
return _GetSteadyTimeNS();
#else
return std::chrono::duration_cast<std::chrono::nanoseconds>(steady_clock::now().time_since_epoch()).count();
#endif
}
AUKN_SYM AuUInt64 SteadyClockJiffies()
{
static AuUInt64 gFrequency = 0;
if (gFrequency != 0)
{
return gFrequency;
}
#if defined(AURORA_COMPILER_MSVC)
return gFrequency = _Query_perf_frequency();
#elif defined(AURORA_IS_POSIX_DERIVED)
::timespec spec {};
if (::clock_getres(CLOCK_MONOTONIC, &spec) == 0)
{
if (spec.tv_nsec && !spec.tv_sec)
{
return gFrequency = 1000000000ull / spec.tv_nsec;
}
}
return gFrequency = (1000000000ull / 100ull);
#else
return gFrequency = static_cast<double>(steady_clock::period::den) / static_cast<double>(steady_clock::period::num);
#endif
}
AUKN_SYM AuInt64 CTimeToMS(time_t time)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(__NormalizeEpoch(sys_clock::from_time_t(time).time_since_epoch())).count();
}
#if defined(AURORA_IS_POSIX_DERIVED)
enum class EPseudoPosixClock
{
eUser,
eKernel,
eAll
};
static AuUInt64 GetPOSIXTimeEx(struct rusage *usage, EPseudoPosixClock e)
{
struct timeval *tv {};
switch (e)
{
case EPseudoPosixClock::eAll:
{
return GetPOSIXTimeEx(usage, EPseudoPosixClock::eKernel) +
GetPOSIXTimeEx(usage, EPseudoPosixClock::eUser);
}
case EPseudoPosixClock::eUser:
{
tv = &usage->ru_utime;
break;
}
case EPseudoPosixClock::eKernel:
{
tv = &usage->ru_stime;
break;
}
};
auto uMS = AuSToMS<AuUInt64>(tv->tv_sec);
auto uNS = AuMSToNS<AuUInt64>(uMS) +
tv->tv_usec * 1'000ull;
return uNS;
}
static AuUInt64 GetPOSIXTime(bool bThread, EPseudoPosixClock e)
{
struct rusage usage;
getrusage(bThread ? RUSAGE_THREAD : RUSAGE_SELF,
&usage);
return GetPOSIXTimeEx(&usage, e);
}
static AuPair<AuUInt64, AuUInt64> GetPOSIXTimePair(bool bThread)
{
struct rusage usage;
getrusage(bThread ? RUSAGE_THREAD : RUSAGE_SELF,
&usage);
return {
GetPOSIXTimeEx(&usage, EPseudoPosixClock::eKernel),
GetPOSIXTimeEx(&usage, EPseudoPosixClock::eUser)
};
}
#if !defined(CLOCK_THREAD_CPUTIME_ID)
#define CLOCK_THREAD_CPUTIME_ID 0
#endif
#if !defined(CLOCK_PROCESS_CPUTIME_ID)
#define CLOCK_PROCESS_CPUTIME_ID 0
#endif
#endif
#if defined(AURORA_IS_MODERNNT_DERIVED)
#define ADD_CLOCK_FAMILY(fn, type, expr, posixId, posixCall) \
AUKN_SYM AuUInt64 fn ## ClockJiffies(); \
\
AUKN_SYM AuUInt64 fn ## ClockMS() \
{ \
return AuNSToMS<AuUInt64>(fn ## ClockNS()); \
} \
\
AUKN_SYM AuUInt64 fn ## ClockNS() \
{ \
FILETIME creation, exit, kernel, user; \
if (::Get ## type ## Times(GetCurrent ## type(), &creation, &exit, &kernel, &user)) \
{ \
ULARGE_INTEGER ullUser; \
{ \
ullUser.LowPart = user.dwLowDateTime; \
ullUser.HighPart = user.dwHighDateTime; \
} \
\
ULARGE_INTEGER ullKernel; \
{ \
ullKernel.LowPart = kernel.dwLowDateTime; \
ullKernel.HighPart = kernel.dwHighDateTime; \
} \
return (expr) * 100ull; \
} \
return 0; \
} \
\
AUKN_SYM AuUInt64 fn ## Clock() \
{ \
FILETIME creation, exit, kernel, user; \
\
if (::Get ## type ## Times(GetCurrent ## type(), &creation, &exit, &kernel, &user)) \
{ \
ULARGE_INTEGER ullUser; \
{ \
ullUser.LowPart = user.dwLowDateTime; \
ullUser.HighPart = user.dwHighDateTime; \
} \
\
ULARGE_INTEGER ullKernel; \
{ \
ullKernel.LowPart = kernel.dwLowDateTime; \
ullKernel.HighPart = kernel.dwHighDateTime; \
} \
return expr; \
} \
\
return fn ##ClockNS() / (1000000000ull / fn ## ClockJiffies()); \
} \
\
AUKN_SYM AuUInt64 fn ## ClockJiffies() \
{ \
return 1000000000ull / 100u; \
}
#elif defined(AURORA_IS_POSIX_DERIVED)
#define ADD_CLOCK_FAMILY(fn, type, expr, posixId, posixCall) \
AUKN_SYM AuUInt64 fn ## ClockJiffies(); \
\
AUKN_SYM AuUInt64 fn ## ClockMS() \
{ \
if (!posixId) \
{ \
return AuNSToMS<AuUInt64>(GetPOSIXTime AU_WHAT(posixCall)); \
} \
return AuNSToMS<AuUInt64>(fn ## ClockNS()); \
} \
\
AUKN_SYM AuUInt64 fn ## ClockNS() \
{ \
if (!posixId) \
{ \
return GetPOSIXTime AU_WHAT(posixCall); \
} \
::timespec spec {}; \
if (::clock_gettime(posixId, &spec) == 0) \
{ \
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec; \
} \
return 0; \
} \
\
AUKN_SYM AuUInt64 fn ## Clock() \
{ \
if (!posixId) \
{ \
return fn ##ClockNS() / 1000ull; \
} \
return fn ##ClockNS() / (1000000000ull / fn ## ClockJiffies()); \
} \
\
AUKN_SYM AuUInt64 fn ## ClockJiffies() \
{ \
if (!posixId) \
{ \
return 1'000'000ull; \
} \
static AuUInt64 frequency = 0; \
if (frequency != 0) \
{ \
return frequency; \
} \
\
::timespec spec {}; \
if (::clock_getres(posixId, &spec) == 0) \
{ \
if (spec.tv_nsec && !spec.tv_sec) \
{ \
return frequency = 1000000000ull / spec.tv_nsec; \
} \
else \
{ \
SysUnreachable(); \
return 0; \
} \
} \
\
return 0; \
}
#else
AUKN_SYM AuUInt64 fn ## ClockMS()
{
return 0;
}
AUKN_SYM AuUInt64 fn ## ClockNS()
{
return 0;
}
AUKN_SYM AuUInt64 fn ## Clock()
{
return 0;
}
AUKN_SYM AuUInt64 fn ## ClockJiffies()
{
return 0;
}
#endif
ADD_CLOCK_FAMILY(Process, Process, (ullUser.QuadPart + ullKernel.QuadPart), /*CLOCK_PROCESS_CPUTIME_ID*/ 0, (false, EPseudoPosixClock::eAll));
ADD_CLOCK_FAMILY(ProcessKernel, Process, (ullKernel.QuadPart), 0, (false, EPseudoPosixClock::eKernel));
ADD_CLOCK_FAMILY(ProcessUser, Process, (ullUser.QuadPart), /*CLOCK_PROCESS_CPUTIME_ID*/0, (false, EPseudoPosixClock::eUser));
ADD_CLOCK_FAMILY(Thread, Thread, (ullUser.QuadPart + ullKernel.QuadPart), /*CLOCK_THREAD_CPUTIME_ID*/0, (true, EPseudoPosixClock::eAll));
ADD_CLOCK_FAMILY(ThreadKernel, Thread, (ullKernel.QuadPart), 0, (true, EPseudoPosixClock::eKernel));
ADD_CLOCK_FAMILY(ThreadUser, Thread, (ullUser.QuadPart), /*CLOCK_THREAD_CPUTIME_ID*/0, (true, EPseudoPosixClock::eUser));
#if defined(AURORA_IS_MODERNNT_DERIVED)
#define ADD_CLOCK_FAMILY_PAIR(type, bIsThread, exprA, exprB) \
\
static AuPair<AuUInt64, AuUInt64> type ## ClockPairNS() \
{ \
FILETIME creation, exit, kernel, user; \
if (::Get ## type ## Times(GetCurrent ## type(), &creation, &exit, &kernel, &user)) \
{ \
ULARGE_INTEGER ullUser; \
{ \
ullUser.LowPart = user.dwLowDateTime; \
ullUser.HighPart = user.dwHighDateTime; \
} \
\
ULARGE_INTEGER ullKernel; \
{ \
ullKernel.LowPart = kernel.dwLowDateTime; \
ullKernel.HighPart = kernel.dwHighDateTime; \
} \
return AuMakePair((exprA) * 100ull, (exprB) * 100ull); \
} \
return {}; \
} \
#elif defined(AURORA_IS_POSIX_DERIVED)
#define ADD_CLOCK_FAMILY_PAIR(type, bIsThread, exprA, exprB) \
\
static AuPair<AuUInt64, AuUInt64> type ## ClockPairNS() \
{ \
return GetPOSIXTimePair(bIsThread); \
} \
#else
#define ADD_CLOCK_FAMILY_PAIR(type, bIsThread, exprA, exprB) \
\
static AuPair<AuUInt64, AuUInt64> type ## ClockPairNS() \
{ \
return {}; \
} \
#endif
ADD_CLOCK_FAMILY_PAIR(Thread, true, (ullKernel.QuadPart),
(ullUser.QuadPart));
ADD_CLOCK_FAMILY_PAIR(Process, false, (ullKernel.QuadPart),
(ullUser.QuadPart));
AUKN_SYM AuPair<AuUInt64, AuUInt64> GetClockUserAndKernelTimeNS(EClock clock)
{
AuPair<AuUInt64, AuUInt64> swizzle;
switch (clock)
{
case EClock::eWall:
case EClock::eSteady:
SysPushErrorArg("Invalid clock");
return {};
case EClock::eProcessTime:
case EClock::eProcessUserTime:
case EClock::eProcessKernelTime:
swizzle = ProcessClockPairNS();
break;
case EClock::eThreadTime:
case EClock::eThreadUserTime:
case EClock::eThreadKernelTime:
swizzle = ThreadClockPairNS();
break;
default:
SysPushErrorArg("Invalid clock");
return {};
}
return { swizzle.second, swizzle.first };
}
}