AuroraRuntime/Source/Time/AuClock.cpp

572 lines
24 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_MODERNNT_DERIVED)
// TODO (Reece): ....
// benchmarking:
// https://github.com/microsoft/STL/issues/2085
struct steady_clock_fast
{ // wraps QueryPerformanceCounter
using rep = long long;
using period = std::nano;
using duration = std::chrono::nanoseconds;
using time_point = _CHRONO time_point<steady_clock_fast>;
static constexpr bool is_steady = true;
_NODISCARD static time_point now() noexcept
{ // get current time
static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot
const long long _Ctr = _Query_perf_counter();
static_assert(period::num == 1, "This assumes period::num == 1.");
// Instead of just having "(_Ctr * period::den) / _Freq",
// the algorithm below prevents overflow when _Ctr is sufficiently large.
// It assumes that _Freq * period::den does not overflow, which is currently true for nano period.
// It is not realistic for _Ctr to accumulate to large values from zero with this assumption,
// but the initial value of _Ctr could be large.
// 10 MHz is a very common QPC frequency on modern PCs. Optimizing for
// this specific frequency can double the performance of this function by
// avoiding the expensive frequency conversion path.
if (_Freq == 10000000)
{
return time_point(duration(_Ctr * 100));
}
else
{
const long long _Whole = (_Ctr / _Freq) * period::den;
const long long _Part = (_Ctr % _Freq) * period::den / _Freq;
return time_point(duration(_Whole + _Part));
}
}
};
// ~3.0741 seconds
using high_res_clock = steady_clock_fast;
// holy fuck, we're keeping this
// ~2x improvement
#else
// ~6.07 seconds
using high_res_clock = std::chrono::high_resolution_clock;
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
#include <sys/resource.h>
#endif
using sys_clock = std::chrono::system_clock;
using steady_clock = std::chrono::steady_clock;
#if defined(AURORA_PLATFORM_WIN32)
#define timegm _mkgmtime
#endif
static sys_clock::duration gEpoch;
static sys_clock::duration gUnixDelta;
static auto InitEpoch()
{
std::tm start{0, 15, 10, 29, 7, 101, 0, 0, 0};
auto epoch = sys_clock::from_time_t(timegm(&start)).time_since_epoch();
std::tm unixStart{};
unixStart.tm_mday = 1;
unixStart.tm_year = 70;
// dont care what the spec says, you can't trust some ms stls
// sys_clock can have its own epoch for all we care
auto nixEpoch = sys_clock::from_time_t(timegm(&unixStart)).time_since_epoch();
gUnixDelta = epoch - nixEpoch;
gEpoch = epoch;
return 0;
}
static auto ___ = InitEpoch();
template<typename T>
static inline T NormalizeEpoch(T sysEpoch)
{
return sysEpoch - gEpoch;
}
template<typename T>
static inline T DecodeEpoch(T auroraEpoch)
{
return auroraEpoch + gEpoch;
}
template<typename Clock_t, typename Duration_t>
static auto TimeFromDurationSinceEpoch(Duration_t in)
{
auto duration = std::chrono::duration_cast<typename Clock_t::duration>(in);
return std::chrono::time_point<Clock_t>(DecodeEpoch(duration));
}
template<typename Duration_t>
static time_t CalculateTimeT(AuUInt64 in)
{
return sys_clock::to_time_t(TimeFromDurationSinceEpoch<sys_clock>(Duration_t(in)));
}
namespace Aurora::Time
{
// removed from public header / deprecating
AUKN_SYM AuUInt64 HighResClock();
AUKN_SYM AuUInt64 HighResClockNS();
AUKN_SYM AuUInt64 HighResClockMS();
AUKN_SYM AuUInt64 HighResClockJiffies();
AUKN_SYM time_t SToCTime(AuInt64 time)
{
return CalculateTimeT<std::chrono::seconds>(time);
}
AUKN_SYM time_t NSToCTime(AuInt64 time)
{
return CalculateTimeT<std::chrono::nanoseconds>(time);
}
AUKN_SYM time_t MSToCTime(AuInt64 time)
{
return CalculateTimeT<std::chrono::milliseconds>(time);
}
AUKN_SYM AuInt64 CurrentClock()
{
return NormalizeEpoch(sys_clock::now().time_since_epoch()).count();
}
AUKN_SYM AuInt64 CurrentClockMS()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
}
AUKN_SYM AuInt64 CurrentClockNS()
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
}
AUKN_SYM AuUInt64 SteadyClock()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
return _Query_perf_counter();
#endif
return SteadyClockNS() / (1000000000ull / SteadyClockJiffies());
}
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);
}
#endif
#if defined(AURORA_IS_MODERNNT_DERIVED)
return std::chrono::duration_cast<std::chrono::milliseconds>(high_res_clock::now().time_since_epoch()).count();
#endif
return std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now().time_since_epoch()).count();
}
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;
}
#endif
#if defined(AURORA_IS_MODERNNT_DERIVED)
return std::chrono::duration_cast<std::chrono::nanoseconds>(high_res_clock::now().time_since_epoch()).count();
#endif
return std::chrono::duration_cast<std::chrono::nanoseconds>(steady_clock::now().time_since_epoch()).count();
}
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();
}
AuInt64 CTimeNSNormalize(AuUInt64 time)
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(NormalizeEpoch(std::chrono::nanoseconds(time))).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);
}
#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 HighResClockNS(); \
} \
\
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 HighResClockNS(); \
} \
\
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 HighResClockJiffies(); \
}
#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));
AUKN_SYM AuInt64 ConvertAuroraToUnixMS(AuInt64 in)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(in) + gUnixDelta).count();
}
AUKN_SYM AuInt64 ConvertAuroraToUnixNS(AuInt64 in)
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::nanoseconds(in) + gUnixDelta).count();
}
AUKN_SYM AuInt64 ConvertUnixToAuroraMS(AuInt64 in)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(in) - gUnixDelta).count();
}
AUKN_SYM AuInt64 ConvertUnixToAuroraNS(AuInt64 in)
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::nanoseconds(in) - gUnixDelta).count();
}
AUKN_SYM AuUInt64 SteadyClockJiffies()
{
static AuUInt64 frequency = 0;
if (frequency != 0)
{
return frequency;
}
#if defined(AURORA_COMPILER_MSVC)
return frequency = _Query_perf_frequency();
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
::timespec spec {};
if (::clock_getres(CLOCK_MONOTONIC, &spec) == 0)
{
if (spec.tv_nsec && !spec.tv_sec)
{
return frequency = 1000000000ull / spec.tv_nsec;
}
}
#endif
return frequency = static_cast<double>(steady_clock::period::den) / static_cast<double>(steady_clock::period::num);
}
AUKN_SYM tm ToCivilTime(AuInt64 time, bool UTC)
{
std::tm ret {};
auto timet = MSToCTime(time);
if (UTC)
{
#if defined(AURORA_COMPILER_MSVC)
auto tm = gmtime_s(&ret, &timet);
#else
auto tm = gmtime_r(&timet, &ret);
#endif
#if defined(AURORA_COMPILER_MSVC)
SysAssert(!tm, "couldn't convert civil time");
#else
SysAssert(tm, "couldn't convert civil time");
#endif
}
else
{
#if defined(AURORA_COMPILER_MSVC)
if (localtime_s(&ret, &timet))
#else
if (!localtime_r(&timet, &ret))
#endif
{
SysPushErrorGeneric("Couldn't convert local civil time");
return ToCivilTime(time, true);
}
}
tm _;
_.CopyFrom(ret);
return _;
}
AUKN_SYM AuInt64 FromCivilTime(const tm &time, bool UTC)
{
::tm tm;
time_t timet;
time.CopyTo(tm);
if (UTC)
{
tm.tm_isdst = 0;
timet = timegm(&tm);
}
else
{
tm.tm_isdst = -1; // out of the 2 crts i've bothered to check, out of 3, this is legal
timet = mktime(&tm);
}
if ((timet == 0) || (timet == -1))
{
return 0;
}
return std::chrono::duration_cast<std::chrono::milliseconds>(NormalizeEpoch(std::chrono::system_clock::from_time_t(timet).time_since_epoch())).count();
}
AUKN_SYM tm NormalizeCivilTimezone(const Time::tm &time, ETimezoneShift shift)
{
if ((time.tm_isdst == 0) && (shift == ETimezoneShift::eUTC))
{
return time;
}
return ToCivilTime(FromCivilTime(time, shift == ETimezoneShift::eUTC));
}
#pragma region TO_DEPRECATE
// [soon to be] @deprecated / was used by benchmarks to measure thread time
AUKN_SYM AuUInt64 HighResClock()
{
return SteadyClock();
}
AUKN_SYM AuUInt64 HighResClockMS()
{
return SteadyClockMS();
}
AUKN_SYM AuUInt64 HighResClockNS()
{
return SteadyClockNS();
}
AUKN_SYM AuUInt64 HighResClockJiffies()
{
return SteadyClockJiffies();
}
#pragma endregion TO_DEPRECATE
}