| /* -*- 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 GetTickCount(). |
| |
| #include "mozilla/MathAlgorithms.h" |
| #include "mozilla/TimeStamp.h" |
| |
| #include <stdio.h> |
| #include <intrin.h> |
| #include <windows.h> |
| |
| // To enable logging define to your favorite logging API |
| #define LOG(x) |
| |
| class AutoCriticalSection |
| { |
| public: |
| 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 |
| // ---------------------------------------------------------------------------- |
| |
| /** |
| * The [mt] unit: |
| * |
| * Many values are kept in ticks of the Performance Coutner x 1000, |
| * further just referred as [mt], meaning milli-ticks. |
| * |
| * This is needed to preserve maximum precision of the performance frequency |
| * representation. GetTickCount values in milliseconds are multiplied with |
| * frequency per second. Therefor we need to multiply QPC value by 1000 to |
| * have the same units to allow simple arithmentic with both QPC and GTC. |
| */ |
| |
| #define ms2mt(x) ((x) * sFrequencyPerSec) |
| #define mt2ms(x) ((x) / sFrequencyPerSec) |
| #define mt2ms_f(x) (double(x) / sFrequencyPerSec) |
| |
| // Result of QueryPerformanceFrequency |
| static LONGLONG sFrequencyPerSec = 0; |
| |
| // 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 sGTCResulutionThreshold. |
| // |
| // Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - |
| // (QPC_now - QPC_epoch)) was in [-sGTCResulutionThreshold, sGTCResulutionThreshold] |
| // 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 sGTCResulutionThreshold; |
| |
| // 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; |
| |
| // Used only when GetTickCount64 is not available on the platform. |
| // Last result of GetTickCount call. |
| // |
| // Kept in [ms] |
| static DWORD sLastGTCResult = 0; |
| |
| // Higher part of the 64-bit value of MozGetTickCount64, |
| // incremented atomically. |
| static DWORD sLastGTCRollover = 0; |
| |
| namespace mozilla { |
| |
| typedef ULONGLONG (WINAPI* GetTickCount64_t)(); |
| static GetTickCount64_t sGetTickCount64 = nullptr; |
| |
| // Function protecting GetTickCount result from rolling over, |
| // result is in [ms] |
| static ULONGLONG WINAPI |
| MozGetTickCount64() |
| { |
| DWORD GTC = ::GetTickCount(); |
| |
| // Cheaper then CMPXCHG8B |
| AutoCriticalSection lock(&sTimeStampLock); |
| |
| // Pull the rollover counter forward only if new value of GTC goes way |
| // down under the last saved result |
| if ((sLastGTCResult > GTC) && ((sLastGTCResult - GTC) > (1UL << 30))) { |
| ++sLastGTCRollover; |
| } |
| |
| sLastGTCResult = GTC; |
| return ULONGLONG(sLastGTCRollover) << 32 | sLastGTCResult; |
| } |
| |
| // Result is in [mt] |
| static inline ULONGLONG |
| PerformanceCounter() |
| { |
| LARGE_INTEGER pc; |
| ::QueryPerformanceCounter(&pc); |
| 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. |
| sGTCResulutionThreshold = |
| 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; |
| 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; |
| } |
| |
| // 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) |
| : mGTC(aGTC) |
| , mQPC(aQPC) |
| , mHasQPC(aHasQPC) |
| , mIsNull(false) |
| { |
| } |
| |
| 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 <= sGTCResulutionThreshold) { |
| return deltaQPC; |
| } |
| |
| // Treat absolutely for calibration purposes |
| int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); |
| int64_t overflow = diff - sGTCResulutionThreshold; |
| |
| 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(sGetTickCount64()); |
| |
| 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 (mIsNull && aOther.mIsNull) { |
| 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 > INT64_MAX) { |
| return INT64_MAX; |
| } else if (result < INT64_MIN) { |
| return INT64_MIN; |
| } |
| |
| return result; |
| } |
| |
| MFBT_API int64_t |
| BaseTimeDurationPlatformUtils::ResolutionInTicks() |
| { |
| return static_cast<int64_t>(sResolution); |
| } |
| |
| static bool |
| HasStableTSC() |
| { |
| union |
| { |
| int regs[4]; |
| struct |
| { |
| int nIds; |
| char cpuString[12]; |
| }; |
| } cpuInfo; |
| |
| __cpuid(cpuInfo.regs, 0); |
| // Only allow Intel 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))) { |
| return false; |
| } |
| |
| int regs[4]; |
| |
| // detect if the Advanced Power Management feature is supported |
| __cpuid(regs, 0x80000000); |
| if (regs[0] < 0x80000007) { |
| return false; |
| } |
| |
| __cpuid(regs, 0x80000007); |
| // if bit 8 is set than TSC will run at a constant rate |
| // in all ACPI P-state, C-states and T-states |
| return regs[3] & (1 << 8); |
| } |
| |
| MFBT_API void |
| TimeStamp::Startup() |
| { |
| // Decide which implementation to use for the high-performance timer. |
| |
| HMODULE kernelDLL = GetModuleHandleW(L"kernel32.dll"); |
| sGetTickCount64 = reinterpret_cast<GetTickCount64_t>( |
| GetProcAddress(kernelDLL, "GetTickCount64")); |
| if (!sGetTickCount64) { |
| // If the platform does not support the GetTickCount64 (Windows XP doesn't), |
| // then use our fallback implementation based on GetTickCount. |
| sGetTickCount64 = MozGetTickCount64; |
| } |
| |
| InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); |
| |
| sHasStableTSC = HasStableTSC(); |
| LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); |
| |
| LARGE_INTEGER freq; |
| sUseQPC = ::QueryPerformanceFrequency(&freq); |
| if (!sUseQPC) { |
| // No Performance Counter. Fall back to use GetTickCount. |
| InitResolution(); |
| |
| LOG(("TimeStamp: using GetTickCount")); |
| return; |
| } |
| |
| sFrequencyPerSec = freq.QuadPart; |
| LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); |
| |
| InitThresholds(); |
| InitResolution(); |
| |
| return; |
| } |
| |
| MFBT_API void |
| TimeStamp::Shutdown() |
| { |
| DeleteCriticalSection(&sTimeStampLock); |
| } |
| |
| MFBT_API TimeStamp |
| TimeStamp::Now(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(sGetTickCount64()); |
| return TimeStamp(TimeStampValue(GTC, QPC, useQPC)); |
| } |
| |
| // Computes and returns the process uptime in microseconds. |
| // Returns 0 if an error was encountered. |
| |
| MFBT_API uint64_t |
| TimeStamp::ComputeProcessUptime() |
| { |
| SYSTEMTIME nowSys; |
| GetSystemTime(&nowSys); |
| |
| FILETIME now; |
| bool success = SystemTimeToFileTime(&nowSys, &now); |
| |
| if (!success) { |
| return 0; |
| } |
| |
| FILETIME start, foo, bar, baz; |
| success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); |
| |
| if (!success) { |
| return 0; |
| } |
| |
| ULARGE_INTEGER startUsec = {{ |
| start.dwLowDateTime, |
| start.dwHighDateTime |
| }}; |
| ULARGE_INTEGER nowUsec = {{ |
| now.dwLowDateTime, |
| now.dwHighDateTime |
| }}; |
| |
| return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; |
| } |
| |
| } // namespace mozilla |