/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with // values of GetTickCount64(). #include "mozilla/DynamicallyLinkedFunctionPtr.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/TimeStamp.h" #include "mozilla/Uptime.h" #include #include #include #include // To enable logging define to your favorite logging API #define LOG(x) class AutoCriticalSection { public: explicit AutoCriticalSection(LPCRITICAL_SECTION aSection) : mSection(aSection) { ::EnterCriticalSection(mSection); } ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); } private: LPCRITICAL_SECTION mSection; }; // Estimate of the smallest duration of time we can measure. static volatile ULONGLONG sResolution; static volatile ULONGLONG sResolutionSigDigs; static const double kNsPerSecd = 1000000000.0; static const LONGLONG kNsPerMillisec = 1000000; // ---------------------------------------------------------------------------- // Global constants // ---------------------------------------------------------------------------- // Tolerance to failures settings. // // What is the interval we want to have failure free. // in [ms] static const uint32_t kFailureFreeInterval = 5000; // How many failures we are willing to tolerate in the interval. static const uint32_t kMaxFailuresPerInterval = 4; // What is the threshold to treat fluctuations as actual failures. // in [ms] static const uint32_t kFailureThreshold = 50; // If we are not able to get the value of GTC time increment, use this value // which is the most usual increment. static const DWORD kDefaultTimeIncrement = 156001; // ---------------------------------------------------------------------------- // Global variables, not changing at runtime // ---------------------------------------------------------------------------- // Result of QueryPerformanceFrequency // We use default of 1 for the case we can't use QueryPerformanceCounter // to make mt/ms conversions work despite that. static uint64_t sFrequencyPerSec = 1; namespace mozilla { MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec() { return sFrequencyPerSec; } } // namespace mozilla // How much we are tolerant to GTC occasional loose of resoltion. // This number says how many multiples of the minimal GTC resolution // detected on the system are acceptable. This number is empirical. static const LONGLONG kGTCTickLeapTolerance = 4; // Base tolerance (more: "inability of detection" range) threshold is calculated // dynamically, and kept in sGTCResolutionThreshold. // // Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - // (QPC_now - QPC_epoch)) was in [-sGTCResolutionThreshold, // sGTCResolutionThreshold] interval every time we'd compared two time stamps. // If not, then we check the overflow behind this basic threshold // is in kFailureThreshold. If not, we condider it as a QPC failure. If too // many failures in short time are detected, QPC is considered faulty and // disabled. // // Kept in [mt] static LONGLONG sGTCResolutionThreshold; // If QPC is found faulty for two stamps in this interval, we engage // the fault detection algorithm. For duration larger then this limit // we bypass using durations calculated from QPC when jitter is detected, // but don't touch the sUseQPC flag. // // Value is in [ms]. static const uint32_t kHardFailureLimit = 2000; // Conversion to [mt] static LONGLONG sHardFailureLimit; // Conversion of kFailureFreeInterval and kFailureThreshold to [mt] static LONGLONG sFailureFreeInterval; static LONGLONG sFailureThreshold; // ---------------------------------------------------------------------------- // Systemm status flags // ---------------------------------------------------------------------------- // Flag for stable TSC that indicates platform where QPC is stable. static bool sHasStableTSC = false; // ---------------------------------------------------------------------------- // Global state variables, changing at runtime // ---------------------------------------------------------------------------- // Initially true, set to false when QPC is found unstable and never // returns back to true since that time. static bool volatile sUseQPC = true; // ---------------------------------------------------------------------------- // Global lock // ---------------------------------------------------------------------------- // Thread spin count before entering the full wait state for sTimeStampLock. // Inspired by Rob Arnold's work on PRMJ_Now(). static const DWORD kLockSpinCount = 4096; // Common mutex (thanks the relative complexity of the logic, this is better // then using CMPXCHG8B.) // It is protecting the globals bellow. static CRITICAL_SECTION sTimeStampLock; // ---------------------------------------------------------------------------- // Global lock protected variables // ---------------------------------------------------------------------------- // Timestamp in future until QPC must behave correctly. // Set to now + kFailureFreeInterval on first QPC failure detection. // Set to now + E * kFailureFreeInterval on following errors, // where E is number of errors detected during last kFailureFreeInterval // milliseconds, calculated simply as: // E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. // When E > kMaxFailuresPerInterval -> disable QPC. // // Kept in [mt] static ULONGLONG sFaultIntoleranceCheckpoint = 0; namespace mozilla { // Result is in [mt] static inline ULONGLONG PerformanceCounter() { LARGE_INTEGER pc; ::QueryPerformanceCounter(&pc); // QueryPerformanceCounter may slightly jitter (not be 100% monotonic.) // This is a simple go-backward protection for such a faulty hardware. AutoCriticalSection lock(&sTimeStampLock); static decltype(LARGE_INTEGER::QuadPart) last; if (last > pc.QuadPart) { return last * 1000ULL; } last = pc.QuadPart; return pc.QuadPart * 1000ULL; } static void InitThresholds() { DWORD timeAdjustment = 0, timeIncrement = 0; BOOL timeAdjustmentDisabled; GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, &timeAdjustmentDisabled); LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); if (!timeIncrement) { timeIncrement = kDefaultTimeIncrement; } // Ceiling to a millisecond // Example values: 156001, 210000 DWORD timeIncrementCeil = timeIncrement; // Don't want to round up if already rounded, values will be: 156000, 209999 timeIncrementCeil -= 1; // Convert to ms, values will be: 15, 20 timeIncrementCeil /= 10000; // Round up, values will be: 16, 21 timeIncrementCeil += 1; // Convert back to 100ns, values will be: 160000, 210000 timeIncrementCeil *= 10000; // How many milli-ticks has the interval rounded up LONGLONG ticksPerGetTickCountResolutionCeiling = (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. sGTCResolutionThreshold = LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); sHardFailureLimit = ms2mt(kHardFailureLimit); sFailureFreeInterval = ms2mt(kFailureFreeInterval); sFailureThreshold = ms2mt(kFailureThreshold); } static void InitResolution() { // 10 total trials is arbitrary: what we're trying to avoid by // looping is getting unlucky and being interrupted by a context // switch or signal, or being bitten by paging/cache effects ULONGLONG minres = ~0ULL; if (sUseQPC) { int loops = 10; do { ULONGLONG start = PerformanceCounter(); ULONGLONG end = PerformanceCounter(); ULONGLONG candidate = (end - start); if (candidate < minres) { minres = candidate; } } while (--loops && minres); if (0 == minres) { minres = 1; } } else { // GetTickCount has only ~16ms known resolution minres = ms2mt(16); } // Converting minres that is in [mt] to nanosecods, multiplicating // the argument to preserve resolution. ULONGLONG result = mt2ms(minres * kNsPerMillisec); if (0 == result) { result = 1; } sResolution = result; // find the number of significant digits in mResolution, for the // sake of ToSecondsSigDigits() ULONGLONG sigDigs; for (sigDigs = 1; !(sigDigs == result || 10 * sigDigs > result); sigDigs *= 10) ; sResolutionSigDigs = sigDigs; } // ---------------------------------------------------------------------------- // TimeStampValue implementation // ---------------------------------------------------------------------------- MFBT_API TimeStampValue::TimeStampValue(ULONGLONG aGTC, ULONGLONG aQPC, bool aHasQPC, bool aUsedCanonicalNow) : mGTC(aGTC), mQPC(aQPC), mUsedCanonicalNow(aUsedCanonicalNow), mHasQPC(aHasQPC) { mIsNull = aGTC == 0 && aQPC == 0; } MFBT_API TimeStampValue& TimeStampValue::operator+=(const int64_t aOther) { mGTC += aOther; mQPC += aOther; return *this; } MFBT_API TimeStampValue& TimeStampValue::operator-=(const int64_t aOther) { mGTC -= aOther; mQPC -= aOther; return *this; } // If the duration is less then two seconds, perform check of QPC stability // by comparing both GTC and QPC calculated durations of this and aOther. MFBT_API uint64_t TimeStampValue::CheckQPC(const TimeStampValue& aOther) const { uint64_t deltaGTC = mGTC - aOther.mGTC; if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC return deltaGTC; } uint64_t deltaQPC = mQPC - aOther.mQPC; if (sHasStableTSC) { // For stable TSC there is no need to check return deltaQPC; } // Check QPC is sane before using it. int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); if (diff <= sGTCResolutionThreshold) { return deltaQPC; } // Treat absolutely for calibration purposes int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); int64_t overflow = diff - sGTCResolutionThreshold; LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", mt2ms(duration), mt2ms_f(overflow))); if (overflow <= sFailureThreshold) { // We are in the limit, let go. return deltaQPC; } // QPC deviates, don't use it, since now this method may only return deltaGTC. if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance // algorithm. return deltaGTC; } LOG(("TimeStamp: QPC jittered over failure threshold")); if (duration < sHardFailureLimit) { // Interval between the two time stamps is very short, consider // QPC as unstable and record a failure. uint64_t now = ms2mt(GetTickCount64()); AutoCriticalSection lock(&sTimeStampLock); if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { // There's already been an error in the last fault intollerant interval. // Time since now to the checkpoint actually holds information on how many // failures there were in the failure free interval we have defined. uint64_t failureCount = (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / sFailureFreeInterval; if (failureCount > kMaxFailuresPerInterval) { sUseQPC = false; LOG(("TimeStamp: QPC disabled")); } else { // Move the fault intolerance checkpoint more to the future, prolong it // to reflect the number of detected failures. ++failureCount; sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; LOG(("TimeStamp: recording %dth QPC failure", failureCount)); } } else { // Setup fault intolerance checkpoint in the future for first detected // error. sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; LOG(("TimeStamp: recording 1st QPC failure")); } } return deltaGTC; } MFBT_API uint64_t TimeStampValue::operator-(const TimeStampValue& aOther) const { if (IsNull() && aOther.IsNull()) { return uint64_t(0); } return CheckQPC(aOther); } // ---------------------------------------------------------------------------- // TimeDuration and TimeStamp implementation // ---------------------------------------------------------------------------- MFBT_API double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { // Converting before arithmetic avoids blocked store forward return double(aTicks) / (double(sFrequencyPerSec) * 1000.0); } MFBT_API double BaseTimeDurationPlatformUtils::ToSecondsSigDigits( int64_t aTicks) { // don't report a value < mResolution ... LONGLONG resolution = sResolution; LONGLONG resolutionSigDigs = sResolutionSigDigs; LONGLONG valueSigDigs = resolution * (aTicks / resolution); // and chop off insignificant digits valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); return double(valueSigDigs) / kNsPerSecd; } MFBT_API int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) { double result = ms2mt(aMilliseconds); if (result > double(INT64_MAX)) { return INT64_MAX; } else if (result < double(INT64_MIN)) { return INT64_MIN; } return result; } MFBT_API int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { return static_cast(sResolution); } static bool HasStableTSC() { #if defined(_M_ARM64) // AArch64 defines that its system counter run at a constant rate // regardless of the current clock frequency of the system. See "The // Generic Timer", section D7, in the ARMARM for ARMv8. return true; #else union { int regs[4]; struct { int nIds; char cpuString[12]; }; } cpuInfo; __cpuid(cpuInfo.regs, 0); // Only allow Intel or AMD CPUs for now. // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the // string so that we can compare in one go. if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString)) && _strnicmp(cpuInfo.cpuString, "AuthcAMDenti", sizeof(cpuInfo.cpuString))) { return false; } int regs[4]; // detect if the Advanced Power Management feature is supported __cpuid(regs, 0x80000000); if ((unsigned int)regs[0] < 0x80000007) { // XXX should we return true here? If there is no APM there may be // no way how TSC can run out of sync among cores. return false; } __cpuid(regs, 0x80000007); // if bit 8 is set than TSC will run at a constant rate // in all ACPI P-states, C-states and T-states return regs[3] & (1 << 8); #endif } static bool gInitialized = false; MFBT_API void TimeStamp::Startup() { if (gInitialized) { return; } gInitialized = true; // Decide which implementation to use for the high-performance timer. InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); bool forceGTC = false; bool forceQPC = false; char* modevar = getenv("MOZ_TIMESTAMP_MODE"); if (modevar) { if (!strcmp(modevar, "QPC")) { forceQPC = true; } else if (!strcmp(modevar, "GTC")) { forceGTC = true; } } LARGE_INTEGER freq; sUseQPC = !forceGTC && ::QueryPerformanceFrequency(&freq); if (!sUseQPC) { // No Performance Counter. Fall back to use GetTickCount64. InitResolution(); LOG(("TimeStamp: using GetTickCount64")); return; } sHasStableTSC = forceQPC || HasStableTSC(); LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); sFrequencyPerSec = freq.QuadPart; LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); InitThresholds(); InitResolution(); return; } MFBT_API void TimeStamp::Shutdown() { DeleteCriticalSection(&sTimeStampLock); } TimeStampValue NowInternal(bool aHighResolution) { // sUseQPC is volatile bool useQPC = (aHighResolution && sUseQPC); // Both values are in [mt] units. ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); ULONGLONG GTC = ms2mt(GetTickCount64()); return TimeStampValue(GTC, QPC, useQPC, false); } MFBT_API TimeStamp TimeStamp::Now(bool aHighResolution) { return TimeStamp::NowFuzzy(NowInternal(aHighResolution)); } MFBT_API TimeStamp TimeStamp::NowUnfuzzed(bool aHighResolution) { return TimeStamp(NowInternal(aHighResolution)); } // Computes and returns the process uptime in microseconds. // Returns 0 if an error was encountered. MFBT_API uint64_t TimeStamp::ComputeProcessUptime() { FILETIME start, foo, bar, baz; bool success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); if (!success) { return 0; } static const StaticDynamicallyLinkedFunctionPtr pGetSystemTimePreciseAsFileTime(L"kernel32.dll", "GetSystemTimePreciseAsFileTime"); FILETIME now; if (pGetSystemTimePreciseAsFileTime) { pGetSystemTimePreciseAsFileTime(&now); } else { GetSystemTimeAsFileTime(&now); } ULARGE_INTEGER startUsec = {{start.dwLowDateTime, start.dwHighDateTime}}; ULARGE_INTEGER nowUsec = {{now.dwLowDateTime, now.dwHighDateTime}}; return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; } } // namespace mozilla