summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/TimeStamp_windows.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/misc/TimeStamp_windows.cpp')
-rw-r--r--mozglue/misc/TimeStamp_windows.cpp576
1 files changed, 576 insertions, 0 deletions
diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp
new file mode 100644
index 000000000..cd519affd
--- /dev/null
+++ b/mozglue/misc/TimeStamp_windows.cpp
@@ -0,0 +1,576 @@
+/* -*- 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);
+}
+
+static bool gInitialized = false;
+
+MFBT_API void
+TimeStamp::Startup()
+{
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ // 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