mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-10 01:07:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
576
mozglue/misc/TimeStamp_windows.cpp
Normal file
576
mozglue/misc/TimeStamp_windows.cpp
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue