/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuClock.cpp Date: 2021-6-13 Author: Reece ***/ #include #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; 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 #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 static inline T NormalizeEpoch(T sysEpoch) { return sysEpoch - gEpoch; } template static inline T DecodeEpoch(T auroraEpoch) { return auroraEpoch + gEpoch; } template static auto TimeFromDurationSinceEpoch(Duration_t in) { auto duration = std::chrono::duration_cast(in); return std::chrono::time_point(DecodeEpoch(duration)); } template static time_t CalculateTimeT(AuUInt64 in) { return sys_clock::to_time_t(TimeFromDurationSinceEpoch(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(time); } AUKN_SYM time_t NSToCTime(AuInt64 time) { return CalculateTimeT(time); } AUKN_SYM time_t MSToCTime(AuInt64 time) { return CalculateTimeT(time); } AUKN_SYM AuInt64 CurrentClock() { return NormalizeEpoch(sys_clock::now().time_since_epoch()).count(); } AUKN_SYM AuInt64 CurrentClockMS() { return std::chrono::duration_cast(NormalizeEpoch(sys_clock::now().time_since_epoch())).count(); } AUKN_SYM AuInt64 CurrentClockNS() { return std::chrono::duration_cast(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(spec.tv_sec) + AuNSToMS(spec.tv_nsec); } #endif #if defined(AURORA_IS_MODERNNT_DERIVED) return std::chrono::duration_cast(high_res_clock::now().time_since_epoch()).count(); #endif return std::chrono::duration_cast(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(AuSToMS(spec.tv_sec)) + (AuUInt64)spec.tv_nsec; } #endif #if defined(AURORA_IS_MODERNNT_DERIVED) return std::chrono::duration_cast(high_res_clock::now().time_since_epoch()).count(); #endif return std::chrono::duration_cast(steady_clock::now().time_since_epoch()).count(); } AUKN_SYM AuInt64 CTimeToMS(time_t time) { return std::chrono::duration_cast(NormalizeEpoch(sys_clock::from_time_t(time).time_since_epoch())).count(); } AuInt64 CTimeNSNormalize(AuUInt64 time) { return std::chrono::duration_cast(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(tv->tv_sec); auto uNS = AuMSToNS(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(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(GetPOSIXTime AU_WHAT(posixCall)); \ } \ return AuNSToMS(fn ## ClockNS()); \ } \ \ AUKN_SYM AuUInt64 fn ## ClockNS() \ { \ if (!posixId) \ { \ return GetPOSIXTime AU_WHAT(posixCall); \ } \ ::timespec spec {}; \ if (::clock_gettime(posixId, &spec) == 0) \ { \ return AuMSToNS(AuSToMS(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, (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, (false, EPseudoPosixClock::eUser)); ADD_CLOCK_FAMILY(Thread, Thread, (ullUser.QuadPart + ullKernel.QuadPart), CLOCK_THREAD_CPUTIME_ID, (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, (true, EPseudoPosixClock::eUser)); AUKN_SYM AuInt64 ConvertAuroraToUnixMS(AuInt64 in) { return std::chrono::duration_cast(std::chrono::milliseconds(in) + gUnixDelta).count(); } AUKN_SYM AuInt64 ConvertAuroraToUnixNS(AuInt64 in) { return std::chrono::duration_cast(std::chrono::nanoseconds(in) + gUnixDelta).count(); } AUKN_SYM AuInt64 ConvertUnixToAuroraMS(AuInt64 in) { return std::chrono::duration_cast(std::chrono::milliseconds(in) - gUnixDelta).count(); } AUKN_SYM AuInt64 ConvertUnixToAuroraNS(AuInt64 in) { return std::chrono::duration_cast(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(steady_clock::period::den) / static_cast(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(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 }