michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with michael@0: // values of GetTickCount(). michael@0: michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsWindowsHelpers.h" michael@0: #include michael@0: michael@0: #include "nsCRT.h" michael@0: #include "prlog.h" michael@0: #include "prprf.h" michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // Log module for mozilla::TimeStamp for Windows logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=TimeStampWindows:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: static PRLogModuleInfo* michael@0: GetTimeStampLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("TimeStampWindows"); michael@0: return sLog; michael@0: } michael@0: #define LOG(x) PR_LOG(GetTimeStampLog(), PR_LOG_DEBUG, x) michael@0: #else michael@0: #define LOG(x) michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: // Estimate of the smallest duration of time we can measure. michael@0: static volatile ULONGLONG sResolution; michael@0: static volatile ULONGLONG sResolutionSigDigs; michael@0: static const double kNsPerSecd = 1000000000.0; michael@0: static const LONGLONG kNsPerSec = 1000000000; michael@0: static const LONGLONG kNsPerMillisec = 1000000; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Global constants michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: // Tolerance to failures settings. michael@0: // michael@0: // What is the interval we want to have failure free. michael@0: // in [ms] michael@0: static const uint32_t kFailureFreeInterval = 5000; michael@0: // How many failures we are willing to tolerate in the interval. michael@0: static const uint32_t kMaxFailuresPerInterval = 4; michael@0: // What is the threshold to treat fluctuations as actual failures. michael@0: // in [ms] michael@0: static const uint32_t kFailureThreshold = 50; michael@0: michael@0: // If we are not able to get the value of GTC time increment, use this value michael@0: // which is the most usual increment. michael@0: static const DWORD kDefaultTimeIncrement = 156001; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Global variables, not changing at runtime michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * The [mt] unit: michael@0: * michael@0: * Many values are kept in ticks of the Performance Coutner x 1000, michael@0: * further just referred as [mt], meaning milli-ticks. michael@0: * michael@0: * This is needed to preserve maximum precision of the performance frequency michael@0: * representation. GetTickCount values in milliseconds are multiplied with michael@0: * frequency per second. Therefor we need to multiply QPC value by 1000 to michael@0: * have the same units to allow simple arithmentic with both QPC and GTC. michael@0: */ michael@0: michael@0: #define ms2mt(x) ((x) * sFrequencyPerSec) michael@0: #define mt2ms(x) ((x) / sFrequencyPerSec) michael@0: #define mt2ms_f(x) (double(x) / sFrequencyPerSec) michael@0: michael@0: // Result of QueryPerformanceFrequency michael@0: static LONGLONG sFrequencyPerSec = 0; michael@0: michael@0: // How much we are tolerant to GTC occasional loose of resoltion. michael@0: // This number says how many multiples of the minimal GTC resolution michael@0: // detected on the system are acceptable. This number is empirical. michael@0: static const LONGLONG kGTCTickLeapTolerance = 4; michael@0: michael@0: // Base tolerance (more: "inability of detection" range) threshold is calculated michael@0: // dynamically, and kept in sGTCResulutionThreshold. michael@0: // michael@0: // Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - michael@0: // (QPC_now - QPC_epoch)) was in [-sGTCResulutionThreshold, sGTCResulutionThreshold] michael@0: // interval every time we'd compared two time stamps. michael@0: // If not, then we check the overflow behind this basic threshold michael@0: // is in kFailureThreshold. If not, we condider it as a QPC failure. If too many michael@0: // failures in short time are detected, QPC is considered faulty and disabled. michael@0: // michael@0: // Kept in [mt] michael@0: static LONGLONG sGTCResulutionThreshold; michael@0: michael@0: // If QPC is found faulty for two stamps in this interval, we engage michael@0: // the fault detection algorithm. For duration larger then this limit michael@0: // we bypass using durations calculated from QPC when jitter is detected, michael@0: // but don't touch the sUseQPC flag. michael@0: // michael@0: // Value is in [ms]. michael@0: static const uint32_t kHardFailureLimit = 2000; michael@0: // Conversion to [mt] michael@0: static LONGLONG sHardFailureLimit; michael@0: michael@0: // Conversion of kFailureFreeInterval and kFailureThreshold to [mt] michael@0: static LONGLONG sFailureFreeInterval; michael@0: static LONGLONG sFailureThreshold; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Systemm status flags michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: // Flag for stable TSC that indicates platform where QPC is stable. michael@0: static bool sHasStableTSC = false; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Global state variables, changing at runtime michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: // Initially true, set to false when QPC is found unstable and never michael@0: // returns back to true since that time. michael@0: static bool volatile sUseQPC = true; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Global lock michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: // Thread spin count before entering the full wait state for sTimeStampLock. michael@0: // Inspired by Rob Arnold's work on PRMJ_Now(). michael@0: static const DWORD kLockSpinCount = 4096; michael@0: michael@0: // Common mutex (thanks the relative complexity of the logic, this is better michael@0: // then using CMPXCHG8B.) michael@0: // It is protecting the globals bellow. michael@0: static CRITICAL_SECTION sTimeStampLock; michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Global lock protected variables michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: // Timestamp in future until QPC must behave correctly. michael@0: // Set to now + kFailureFreeInterval on first QPC failure detection. michael@0: // Set to now + E * kFailureFreeInterval on following errors, michael@0: // where E is number of errors detected during last kFailureFreeInterval michael@0: // milliseconds, calculated simply as: michael@0: // E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. michael@0: // When E > kMaxFailuresPerInterval -> disable QPC. michael@0: // michael@0: // Kept in [mt] michael@0: static ULONGLONG sFaultIntoleranceCheckpoint = 0; michael@0: michael@0: // Used only when GetTickCount64 is not available on the platform. michael@0: // Last result of GetTickCount call. michael@0: // michael@0: // Kept in [ms] michael@0: static DWORD sLastGTCResult = 0; michael@0: michael@0: // Higher part of the 64-bit value of MozGetTickCount64, michael@0: // incremented atomically. michael@0: static DWORD sLastGTCRollover = 0; michael@0: michael@0: namespace mozilla { michael@0: michael@0: typedef ULONGLONG (WINAPI* GetTickCount64_t)(); michael@0: static GetTickCount64_t sGetTickCount64 = nullptr; michael@0: michael@0: // Function protecting GetTickCount result from rolling over, michael@0: // result is in [ms] michael@0: static ULONGLONG WINAPI michael@0: MozGetTickCount64() michael@0: { michael@0: DWORD GTC = ::GetTickCount(); michael@0: michael@0: // Cheaper then CMPXCHG8B michael@0: AutoCriticalSection lock(&sTimeStampLock); michael@0: michael@0: // Pull the rollover counter forward only if new value of GTC goes way michael@0: // down under the last saved result michael@0: if ((sLastGTCResult > GTC) && ((sLastGTCResult - GTC) > (1UL << 30))) michael@0: ++sLastGTCRollover; michael@0: michael@0: sLastGTCResult = GTC; michael@0: return ULONGLONG(sLastGTCRollover) << 32 | sLastGTCResult; michael@0: } michael@0: michael@0: // Result is in [mt] michael@0: static inline ULONGLONG michael@0: PerformanceCounter() michael@0: { michael@0: LARGE_INTEGER pc; michael@0: ::QueryPerformanceCounter(&pc); michael@0: return pc.QuadPart * 1000ULL; michael@0: } michael@0: michael@0: static void michael@0: InitThresholds() michael@0: { michael@0: DWORD timeAdjustment = 0, timeIncrement = 0; michael@0: BOOL timeAdjustmentDisabled; michael@0: GetSystemTimeAdjustment(&timeAdjustment, michael@0: &timeIncrement, michael@0: &timeAdjustmentDisabled); michael@0: michael@0: LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); michael@0: michael@0: if (!timeIncrement) michael@0: timeIncrement = kDefaultTimeIncrement; michael@0: michael@0: // Ceiling to a millisecond michael@0: // Example values: 156001, 210000 michael@0: DWORD timeIncrementCeil = timeIncrement; michael@0: // Don't want to round up if already rounded, values will be: 156000, 209999 michael@0: timeIncrementCeil -= 1; michael@0: // Convert to ms, values will be: 15, 20 michael@0: timeIncrementCeil /= 10000; michael@0: // Round up, values will be: 16, 21 michael@0: timeIncrementCeil += 1; michael@0: // Convert back to 100ns, values will be: 160000, 210000 michael@0: timeIncrementCeil *= 10000; michael@0: michael@0: // How many milli-ticks has the interval rounded up michael@0: LONGLONG ticksPerGetTickCountResolutionCeiling = michael@0: (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; michael@0: michael@0: // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. michael@0: sGTCResulutionThreshold = michael@0: LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); michael@0: michael@0: sHardFailureLimit = ms2mt(kHardFailureLimit); michael@0: sFailureFreeInterval = ms2mt(kFailureFreeInterval); michael@0: sFailureThreshold = ms2mt(kFailureThreshold); michael@0: } michael@0: michael@0: static void michael@0: InitResolution() michael@0: { michael@0: // 10 total trials is arbitrary: what we're trying to avoid by michael@0: // looping is getting unlucky and being interrupted by a context michael@0: // switch or signal, or being bitten by paging/cache effects michael@0: michael@0: ULONGLONG minres = ~0ULL; michael@0: int loops = 10; michael@0: do { michael@0: ULONGLONG start = PerformanceCounter(); michael@0: ULONGLONG end = PerformanceCounter(); michael@0: michael@0: ULONGLONG candidate = (end - start); michael@0: if (candidate < minres) michael@0: minres = candidate; michael@0: } while (--loops && minres); michael@0: michael@0: if (0 == minres) { michael@0: minres = 1; michael@0: } michael@0: michael@0: // Converting minres that is in [mt] to nanosecods, multiplicating michael@0: // the argument to preserve resolution. michael@0: ULONGLONG result = mt2ms(minres * kNsPerMillisec); michael@0: if (0 == result) { michael@0: result = 1; michael@0: } michael@0: michael@0: sResolution = result; michael@0: michael@0: // find the number of significant digits in mResolution, for the michael@0: // sake of ToSecondsSigDigits() michael@0: ULONGLONG sigDigs; michael@0: for (sigDigs = 1; michael@0: !(sigDigs == result michael@0: || 10*sigDigs > result); michael@0: sigDigs *= 10); michael@0: michael@0: sResolutionSigDigs = sigDigs; michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TimeStampValue implementation michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: TimeStampValue::TimeStampValue(ULONGLONG aGTC, ULONGLONG aQPC, bool aHasQPC) michael@0: : mGTC(aGTC) michael@0: , mQPC(aQPC) michael@0: , mHasQPC(aHasQPC) michael@0: , mIsNull(false) michael@0: { michael@0: } michael@0: michael@0: TimeStampValue& michael@0: TimeStampValue::operator+=(const int64_t aOther) michael@0: { michael@0: mGTC += aOther; michael@0: mQPC += aOther; michael@0: return *this; michael@0: } michael@0: michael@0: TimeStampValue& michael@0: TimeStampValue::operator-=(const int64_t aOther) michael@0: { michael@0: mGTC -= aOther; michael@0: mQPC -= aOther; michael@0: return *this; michael@0: } michael@0: michael@0: // If the duration is less then two seconds, perform check of QPC stability michael@0: // by comparing both GTC and QPC calculated durations of this and aOther. michael@0: uint64_t michael@0: TimeStampValue::CheckQPC(const TimeStampValue &aOther) const michael@0: { michael@0: uint64_t deltaGTC = mGTC - aOther.mGTC; michael@0: michael@0: if (!mHasQPC || !aOther.mHasQPC) // Both not holding QPC michael@0: return deltaGTC; michael@0: michael@0: uint64_t deltaQPC = mQPC - aOther.mQPC; michael@0: michael@0: if (sHasStableTSC) // For stable TSC there is no need to check michael@0: return deltaQPC; michael@0: michael@0: if (!sUseQPC) // QPC globally disabled michael@0: return deltaGTC; michael@0: michael@0: // Check QPC is sane before using it. michael@0: int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); michael@0: if (diff <= sGTCResulutionThreshold) michael@0: return deltaQPC; michael@0: michael@0: // Treat absolutely for calibration purposes michael@0: int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); michael@0: int64_t overflow = diff - sGTCResulutionThreshold; michael@0: michael@0: LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", michael@0: mt2ms(duration), mt2ms_f(overflow))); michael@0: michael@0: if (overflow <= sFailureThreshold) // We are in the limit, let go. michael@0: return deltaQPC; // XXX Should we return GTC here? michael@0: michael@0: // QPC deviates, don't use it, since now this method may only return deltaGTC. michael@0: LOG(("TimeStamp: QPC jittered over failure threshold")); michael@0: michael@0: if (duration < sHardFailureLimit) { michael@0: // Interval between the two time stamps is very short, consider michael@0: // QPC as unstable and record a failure. michael@0: uint64_t now = ms2mt(sGetTickCount64()); michael@0: michael@0: AutoCriticalSection lock(&sTimeStampLock); michael@0: michael@0: if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { michael@0: // There's already been an error in the last fault intollerant interval. michael@0: // Time since now to the checkpoint actually holds information on how many michael@0: // failures there were in the failure free interval we have defined. michael@0: uint64_t failureCount = (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / michael@0: sFailureFreeInterval; michael@0: if (failureCount > kMaxFailuresPerInterval) { michael@0: sUseQPC = false; michael@0: LOG(("TimeStamp: QPC disabled")); michael@0: } michael@0: else { michael@0: // Move the fault intolerance checkpoint more to the future, prolong it michael@0: // to reflect the number of detected failures. michael@0: ++failureCount; michael@0: sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; michael@0: LOG(("TimeStamp: recording %dth QPC failure", failureCount)); michael@0: } michael@0: } michael@0: else { michael@0: // Setup fault intolerance checkpoint in the future for first detected error. michael@0: sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; michael@0: LOG(("TimeStamp: recording 1st QPC failure")); michael@0: } michael@0: } michael@0: michael@0: return deltaGTC; michael@0: } michael@0: michael@0: uint64_t michael@0: TimeStampValue::operator-(const TimeStampValue &aOther) const michael@0: { michael@0: if (mIsNull && aOther.mIsNull) michael@0: return uint64_t(0); michael@0: michael@0: return CheckQPC(aOther); michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TimeDuration and TimeStamp implementation michael@0: // ---------------------------------------------------------------------------- michael@0: michael@0: double michael@0: TimeDuration::ToSeconds() const michael@0: { michael@0: // Converting before arithmetic avoids blocked store forward michael@0: return double(mValue) / (double(sFrequencyPerSec) * 1000.0); michael@0: } michael@0: michael@0: double michael@0: TimeDuration::ToSecondsSigDigits() const michael@0: { michael@0: // don't report a value < mResolution ... michael@0: LONGLONG resolution = sResolution; michael@0: LONGLONG resolutionSigDigs = sResolutionSigDigs; michael@0: LONGLONG valueSigDigs = resolution * (mValue / resolution); michael@0: // and chop off insignificant digits michael@0: valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); michael@0: return double(valueSigDigs) / kNsPerSecd; michael@0: } michael@0: michael@0: TimeDuration michael@0: TimeDuration::FromMilliseconds(double aMilliseconds) michael@0: { michael@0: return TimeDuration::FromTicks(int64_t(ms2mt(aMilliseconds))); michael@0: } michael@0: michael@0: TimeDuration michael@0: TimeDuration::Resolution() michael@0: { michael@0: return TimeDuration::FromTicks(int64_t(sResolution)); michael@0: } michael@0: michael@0: static bool michael@0: HasStableTSC() michael@0: { michael@0: union { michael@0: int regs[4]; michael@0: struct { michael@0: int nIds; michael@0: char cpuString[12]; michael@0: }; michael@0: } cpuInfo; michael@0: michael@0: __cpuid(cpuInfo.regs, 0); michael@0: // Only allow Intel CPUs for now michael@0: // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the michael@0: // string so that we can compare in one go. michael@0: if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString))) michael@0: return false; michael@0: michael@0: int regs[4]; michael@0: michael@0: // detect if the Advanced Power Management feature is supported michael@0: __cpuid(regs, 0x80000000); michael@0: if (regs[0] < 0x80000007) michael@0: return false; michael@0: michael@0: __cpuid(regs, 0x80000007); michael@0: // if bit 8 is set than TSC will run at a constant rate michael@0: // in all ACPI P-state, C-states and T-states michael@0: return regs[3] & (1 << 8); michael@0: } michael@0: michael@0: nsresult michael@0: TimeStamp::Startup() michael@0: { michael@0: // Decide which implementation to use for the high-performance timer. michael@0: michael@0: HMODULE kernelDLL = GetModuleHandleW(L"kernel32.dll"); michael@0: sGetTickCount64 = reinterpret_cast michael@0: (GetProcAddress(kernelDLL, "GetTickCount64")); michael@0: if (!sGetTickCount64) { michael@0: // If the platform does not support the GetTickCount64 (Windows XP doesn't), michael@0: // then use our fallback implementation based on GetTickCount. michael@0: sGetTickCount64 = MozGetTickCount64; michael@0: } michael@0: michael@0: InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); michael@0: michael@0: sHasStableTSC = HasStableTSC(); michael@0: LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); michael@0: michael@0: LARGE_INTEGER freq; michael@0: sUseQPC = ::QueryPerformanceFrequency(&freq); michael@0: if (!sUseQPC) { michael@0: // No Performance Counter. Fall back to use GetTickCount. michael@0: InitResolution(); michael@0: michael@0: LOG(("TimeStamp: using GetTickCount")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: sFrequencyPerSec = freq.QuadPart; michael@0: LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); michael@0: michael@0: InitThresholds(); michael@0: InitResolution(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: TimeStamp::Shutdown() michael@0: { michael@0: DeleteCriticalSection(&sTimeStampLock); michael@0: } michael@0: michael@0: TimeStamp michael@0: TimeStamp::Now(bool aHighResolution) michael@0: { michael@0: // sUseQPC is volatile michael@0: bool useQPC = (aHighResolution && sUseQPC); michael@0: michael@0: // Both values are in [mt] units. michael@0: ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); michael@0: ULONGLONG GTC = ms2mt(sGetTickCount64()); michael@0: return TimeStamp(TimeStampValue(GTC, QPC, useQPC)); michael@0: } michael@0: michael@0: // Computes and returns the process uptime in microseconds. michael@0: // Returns 0 if an error was encountered. michael@0: michael@0: uint64_t michael@0: TimeStamp::ComputeProcessUptime() michael@0: { michael@0: SYSTEMTIME nowSys; michael@0: GetSystemTime(&nowSys); michael@0: michael@0: FILETIME now; michael@0: bool success = SystemTimeToFileTime(&nowSys, &now); michael@0: michael@0: if (!success) michael@0: return 0; michael@0: michael@0: FILETIME start, foo, bar, baz; michael@0: success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); michael@0: michael@0: if (!success) michael@0: return 0; michael@0: michael@0: ULARGE_INTEGER startUsec = { michael@0: start.dwLowDateTime, michael@0: start.dwHighDateTime michael@0: }; michael@0: ULARGE_INTEGER nowUsec = { michael@0: now.dwLowDateTime, michael@0: now.dwHighDateTime michael@0: }; michael@0: michael@0: return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; michael@0: } michael@0: michael@0: } // namespace mozilla