Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Windows Timer Primer
//
//
// The default windows timer, GetSystemTimeAsFileTime is not very precise.
// It is only good to ~15.5ms.
//
// QueryPerformanceCounter is the logical choice for a high-precision timer.
// However, it is known to be buggy on some hardware. Specifically, it can
// sometimes "jump". On laptops, QPC can also be very expensive to call.
// It's 3-4x slower than timeGetTime() on desktops, but can be 10x slower
// on laptops. A unittest exists which will show the relative cost of various
// timers on any system.
//
// The next logical choice is timeGetTime(). timeGetTime has a precision of
// 1ms, but only if you call APIs (timeBeginPeriod()) which affect all other
// applications on the system. By default, precision is only 15.5ms.
// Unfortunately, we don't want to call timeBeginPeriod because we don't
// want to affect other applications. Further, on mobile platforms, use of
// faster multimedia timers can hurt battery life. See the intel
// article about this here:
//
// To work around all this, we're going to generally use timeGetTime(). We
// will only increase the system-wide timer if we're not running on battery
// power. Using timeBeginPeriod(1) is a requirement in order to make our
// message loop waits have the same resolution that our time measurements
// do. Otherwise, WaitForSingleObject(..., 1) will no less than 15ms when
// there is nothing else to waken the Wait.
#include "base/time.h"
#ifndef __MINGW32__
# pragma comment(lib, "winmm.lib")
#endif
#include <windows.h>
#include <mmsystem.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "mozilla/Casting.h"
#include "mozilla/StaticMutex.h"
using base::Time;
using base::TimeDelta;
using base::TimeTicks;
using mozilla::BitwiseCast;
namespace {
// From MSDN, FILETIME "Contains a 64-bit value representing the number of
// 100-nanosecond intervals since January 1, 1601 (UTC)."
int64_t FileTimeToMicroseconds(const FILETIME& ft) {
// Need to BitwiseCast to fix alignment, then divide by 10 to convert
// 100-nanoseconds to milliseconds. This only works on little-endian
// machines.
return BitwiseCast<int64_t>(ft) / 10;
}
void MicrosecondsToFileTime(int64_t us, FILETIME* ft) {
DCHECK(us >= 0) << "Time is less than 0, negative values are not "
"representable in FILETIME";
// Multiply by 10 to convert milliseconds to 100-nanoseconds. BitwiseCast will
// handle alignment problems. This only works on little-endian machines.
*ft = BitwiseCast<FILETIME>(us * 10);
}
int64_t CurrentWallclockMicroseconds() {
FILETIME ft;
::GetSystemTimeAsFileTime(&ft);
return FileTimeToMicroseconds(ft);
}
// Time between resampling the un-granular clock for this API. 60 seconds.
const int kMaxMillisecondsToAvoidDrift = 60 * Time::kMillisecondsPerSecond;
int64_t initial_time = 0;
TimeTicks initial_ticks;
void InitializeClock() {
initial_ticks = TimeTicks::Now();
initial_time = CurrentWallclockMicroseconds();
}
} // namespace
// Time -----------------------------------------------------------------------
// The internal representation of Time uses FILETIME, whose epoch is 1601-01-01
// 00:00:00 UTC. ((1970-1601)*365+89)*24*60*60*1000*1000, where 89 is the
// number of leap year days between 1601 and 1970: (1970-1601)/4 excluding
// 1700, 1800, and 1900.
// static
const int64_t Time::kTimeTToMicrosecondsOffset = GG_INT64_C(11644473600000000);
// static
Time Time::Now() {
if (initial_time == 0) InitializeClock();
// We implement time using the high-resolution timers so that we can get
// timeouts which are smaller than 10-15ms. If we just used
// CurrentWallclockMicroseconds(), we'd have the less-granular timer.
//
// To make this work, we initialize the clock (initial_time) and the
// counter (initial_ctr). To compute the initial time, we can check
// the number of ticks that have elapsed, and compute the delta.
//
// To avoid any drift, we periodically resync the counters to the system
// clock.
while (true) {
TimeTicks ticks = TimeTicks::Now();
// Calculate the time elapsed since we started our timer
TimeDelta elapsed = ticks - initial_ticks;
// Check if enough time has elapsed that we need to resync the clock.
if (elapsed.InMilliseconds() > kMaxMillisecondsToAvoidDrift) {
InitializeClock();
continue;
}
return Time(elapsed + Time(initial_time));
}
}
// static
Time Time::NowFromSystemTime() {
// Force resync.
InitializeClock();
return Time(initial_time);
}
// static
Time Time::FromExploded(bool is_local, const Exploded& exploded) {
// Create the system struct representing our exploded time. It will either be
// in local time or UTC.
SYSTEMTIME st;
st.wYear = exploded.year;
st.wMonth = exploded.month;
st.wDayOfWeek = exploded.day_of_week;
st.wDay = exploded.day_of_month;
st.wHour = exploded.hour;
st.wMinute = exploded.minute;
st.wSecond = exploded.second;
st.wMilliseconds = exploded.millisecond;
// Convert to FILETIME.
FILETIME ft;
if (!SystemTimeToFileTime(&st, &ft)) {
NOTREACHED() << "Unable to convert time";
return Time(0);
}
// Ensure that it's in UTC.
if (is_local) {
FILETIME utc_ft;
LocalFileTimeToFileTime(&ft, &utc_ft);
return Time(FileTimeToMicroseconds(utc_ft));
}
return Time(FileTimeToMicroseconds(ft));
}
void Time::Explode(bool is_local, Exploded* exploded) const {
// FILETIME in UTC.
FILETIME utc_ft;
MicrosecondsToFileTime(us_, &utc_ft);
// FILETIME in local time if necessary.
BOOL success = TRUE;
FILETIME ft;
if (is_local)
success = FileTimeToLocalFileTime(&utc_ft, &ft);
else
ft = utc_ft;
// FILETIME in SYSTEMTIME (exploded).
SYSTEMTIME st;
if (!success || !FileTimeToSystemTime(&ft, &st)) {
NOTREACHED() << "Unable to convert time, don't know why";
ZeroMemory(exploded, sizeof(*exploded));
return;
}
exploded->year = st.wYear;
exploded->month = st.wMonth;
exploded->day_of_week = st.wDayOfWeek;
exploded->day_of_month = st.wDay;
exploded->hour = st.wHour;
exploded->minute = st.wMinute;
exploded->second = st.wSecond;
exploded->millisecond = st.wMilliseconds;
}
// TimeTicks ------------------------------------------------------------------
namespace {
// We define a wrapper to adapt between the __stdcall and __cdecl call of the
// mock function, and to avoid a static constructor. Assigning an import to a
// function pointer directly would require setup code to fetch from the IAT.
DWORD timeGetTimeWrapper() { return timeGetTime(); }
DWORD (*tick_function)(void) = &timeGetTimeWrapper;
// This setup is a little gross: the `now` instance lives until libxul is
// unloaded, but leak checking runs prior to that, and would see a Mutex
// instance contained in NowSingleton as still live. Said instance would
// be reported as a leak...but it's not, really. To avoid that, we need
// to use StaticMutex (which is not leak-checked), but StaticMutex can't
// be a member variable. So we have to have this separate static variable.
static mozilla::StaticMutex sNowSingletonLock;
// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic
// because it returns the number of milliseconds since Windows has started,
// which will roll over the 32-bit value every ~49 days. We try to track
// rollover ourselves, which works if TimeTicks::Now() is called at least every
// 49 days.
class NowSingleton {
public:
TimeDelta Now() {
mozilla::StaticMutexAutoLock locked(sNowSingletonLock);
// We should hold the lock while calling tick_function to make sure that
// we keep our last_seen_ stay correctly in sync.
DWORD now = tick_function();
if (now < last_seen_)
rollover_ +=
TimeDelta::FromMilliseconds(GG_LONGLONG(0x100000000)); // ~49.7 days.
last_seen_ = now;
return TimeDelta::FromMilliseconds(now) + rollover_;
}
static NowSingleton& instance() {
static NowSingleton now;
return now;
}
private:
explicit NowSingleton()
: rollover_(TimeDelta::FromMilliseconds(0)), last_seen_(0) {}
~NowSingleton() = default;
TimeDelta rollover_ MOZ_GUARDED_BY(
sNowSingletonLock); // Accumulation of time lost due to rollover.
DWORD last_seen_
MOZ_GUARDED_BY(sNowSingletonLock); // The last timeGetTime value we saw,
// to detect rollover.
DISALLOW_COPY_AND_ASSIGN(NowSingleton);
};
} // namespace
// static
TimeTicks TimeTicks::Now() {
return TimeTicks() + NowSingleton::instance().Now();
}