src/cxx_supportlib/Utils/SystemTime.h in passenger-5.0.26 vs src/cxx_supportlib/Utils/SystemTime.h in passenger-5.0.27

- old
+ new

@@ -1,8 +1,8 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2010 Phusion Holding B.V. + * Copyright (c) 2010-2016 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,47 +25,234 @@ */ #ifndef _PASSENGER_SYSTEM_TIME_H_ #define _PASSENGER_SYSTEM_TIME_H_ #include <boost/thread.hpp> +#include <boost/predef.h> +#include <oxt/macros.hpp> #include <oxt/system_calls.hpp> -#include "../Exceptions.h" +#include <sys/time.h> +#include <cerrno> +#include <time.h> +#include <unistd.h> +#include <Exceptions.h> +#if BOOST_OS_MACOS + #include <mach/mach.h> + #include <mach/mach_time.h> + #include <cstring> +#elif BOOST_OS_AIX + #include <sys/systemcfg.h> +#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC) + #define SYSTEM_TIME_HAVE_MONOTONIC_CLOCK + #ifdef CLOCK_MONOTONIC_COARSE + #define SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_COARSE + #endif + #ifdef CLOCK_MONOTONIC_FAST + #define SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_FAST + #endif +#endif + namespace Passenger { +using namespace std; using namespace oxt; namespace SystemTimeData { + extern bool initialized; extern bool hasForcedValue; extern time_t forcedValue; - extern bool hasForcedMsecValue; - extern unsigned long long forcedMsecValue; extern bool hasForcedUsecValue; extern unsigned long long forcedUsecValue; + + #if BOOST_OS_MACOS + extern mach_timebase_info_data_t timeInfo; + #elif defined(SYSTEM_TIME_HAVE_MONOTONIC_CLOCK) + #ifdef SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_COARSE + extern unsigned long long monotonicCoarseResolutionNs; + #endif + #ifdef SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_FAST + extern unsigned long long monotonicFastResolutionNs; + #endif + extern unsigned long long monotonicResolutionNs; + #endif } +typedef unsigned long long MonotonicTimeUsec; + /** * This class allows one to obtain the system time, similar to time() and * gettimeofday(). Unlike time(), it is possible to force a certain time * to be returned, which is useful for testing code that depends on the * system time. * - * get() provides seconds resolution while getMsec() provides milliseconds + * get() provides seconds resolution while getUsec() provides microseconds * resolution. Both clocks can be independently forced to a certain value - * through force() and forceMsec(). + * through force() and forceUsec(). + * + * In addition, getMonotonicUsec() returns the monotonic clock in + * microseconds. This can also be forced to a certain value using forceUsec(). + * + * Before using any SystemTime functions, you should call + * SystemTime::initialize(). If you don't do that, then initialize() will be + * called for you, but since initialize() isn't thread-safe you should + * call it at the beginning of your program. */ class SystemTime { public: + enum Granularity { + GRAN_1SEC = 1000000000, // 1 millisecond granularity + GRAN_10MSEC = 10000000, // 10 milliseconds granularity + GRAN_1MSEC = 1000000, // 1 millisecond granularity + GRAN_1USEC = 1000 // 1 microsecond granularty + }; + +private: + static void initializeIfNeeded() { + if (OXT_UNLIKELY(!SystemTimeData::initialized)) { + initialize(); + } + } + + template<Granularity granularityNs> + static MonotonicTimeUsec _getMonotonicUsec() { + if (OXT_UNLIKELY(SystemTimeData::hasForcedUsecValue)) { + return SystemTimeData::hasForcedValue; + } + + #if BOOST_OS_MACOS + initializeIfNeeded(); + if (SystemTimeData::timeInfo.numer == 0 + && SystemTimeData::timeInfo.denom == 0) + { + return getUsec(); + } else { + return mach_absolute_time() + * SystemTimeData::timeInfo.numer + / SystemTimeData::timeInfo.denom + / 1000; + } + + #elif BOOST_OS_SOLARIS + return gethrtime() / 1000ull; + + #elif BOOST_OS_AIX + timebasestruct_t t; + read_wall_time(&t, TIMEBASE_SZ); + time_base_to_time(&t, TIMEBASE_SZ); + return t.tb_high * 1000000ull + t.tb_low / 1000ull; + + #elif defined(SYSTEM_TIME_HAVE_MONOTONIC_CLOCK) + clockid_t clockId = (clockid_t) -1; + struct timespec ts; + int ret; + + initializeIfNeeded(); + + // We choose a different monotonic clock + // based on the resolution we need. In general, + // coarser resolutions are faster, for example + // because (on Linux) they are implemented + // through VDSOs instead of system calls. + // + // Benchmarks and properties as of 10 March 2016: + // + // FreeBSD 10.2 (200000 iterations): + // CLOCK_MONOTONIC 1m 9s 11 nanosec resolution + // CLOCK_MONOTONIC_PRECISE 1m 9s 11 nanosec resolution + // CLOCK_MONOTONIC_FAST 2s 11 nanosec resolution + // gettimeofday 1m 9s + // + // Linux 3.13.0 (100000000 iterations): + // CLOCK_MONOTONIC 1.5s 1 nanosec resolution + // CLOCK_MONOTONIC_COARSE 0.45s 4 millisec resolution + // gettimeofday 1.5s + + #ifdef SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_COARSE + if (clockId == -1 + && SystemTimeData::monotonicCoarseResolutionNs != 0 + && SystemTimeData::monotonicCoarseResolutionNs <= granularityNs) + { + clockId = CLOCK_MONOTONIC_COARSE; + } + #endif + #ifdef SYSTEM_TIME_HAVE_CLOCK_MONOTONIC_FAST + if (clockId == -1 + && SystemTimeData::monotonicFastResolutionNs != 0 + && SystemTimeData::monotonicFastResolutionNs <= granularityNs) + { + clockId = CLOCK_MONOTONIC_FAST; + } + #endif + if (clockId == -1 + && SystemTimeData::monotonicResolutionNs != 0 + && SystemTimeData::monotonicResolutionNs <= granularityNs) + { + clockId = CLOCK_MONOTONIC; + } + + if (clockId == (clockid_t) -1) { + return getUsec(); + } else { + do { + ret = clock_gettime(clockId, &ts); + } while (ret == -1 && errno == EINTR); + if (ret == -1) { + int e = errno; + throw TimeRetrievalException( + "Unable to retrieve the system time", + e); + } + return ts.tv_sec * 1000000ull + ts.tv_nsec / 1000ull; + } + + #else + return getUsec(); + #endif + } + +public: + static void initialize() { + SystemTimeData::initialized = true; + #if BOOST_OS_MACOS + if (mach_timebase_info(&SystemTimeData::timeInfo) != KERN_SUCCESS) { + memset(&SystemTimeData::timeInfo, 0, sizeof(SystemTimeData::timeInfo)); + } + #elif defined(SYSTEM_TIME_HAVE_MONOTONIC_CLOCK) + struct timespec ts; + + #ifdef CLOCK_MONOTONIC_COARSE + if (clock_getres(CLOCK_MONOTONIC_COARSE, &ts) == 0) { + SystemTimeData::monotonicCoarseResolutionNs = + ts.tv_sec * 1000000000ull + + ts.tv_nsec; + } + #endif + #ifdef CLOCK_MONOTONIC_FAST + if (clock_getres(CLOCK_MONOTONIC_FAST, &ts) == 0) { + SystemTimeData::monotonicFastResolutionNs = + ts.tv_sec * 1000000000ull + + ts.tv_nsec; + } + #endif + if (clock_getres(CLOCK_MONOTONIC, &ts) == 0) { + SystemTimeData::monotonicResolutionNs = + ts.tv_sec * 1000000000ull + + ts.tv_nsec; + } + #endif + } + /** * Returns the time since the Epoch, measured in seconds. Or, if a time * was forced with force(), then the forced time is returned instead. * * @throws TimeRetrievalException Something went wrong while retrieving the time. * @throws boost::thread_interrupted */ static time_t get() { - if (SystemTimeData::hasForcedValue) { + if (OXT_UNLIKELY(SystemTimeData::hasForcedValue)) { return SystemTimeData::forcedValue; } else { time_t ret = syscalls::time(NULL); if (ret == -1) { int e = errno; @@ -76,20 +263,19 @@ return ret; } } /** - * Returns the time since the Epoch, measured in milliseconds. Or, if a - * time was forced with forceMsec(), then the forced time is returned instead. + * Returns the time since the Epoch, measured in microseconds. Or, if a + * time was forced with forceUsec(), then the forced time is returned instead. * - * @param real Whether to get the real time, even if a value was forced. * @throws TimeRetrievalException Something went wrong while retrieving the time. * @throws boost::thread_interrupted */ - static unsigned long long getMsec(bool real = false) { - if (SystemTimeData::hasForcedMsecValue && !real) { - return SystemTimeData::forcedMsecValue; + static unsigned long long getUsec() { + if (OXT_UNLIKELY(SystemTimeData::hasForcedUsecValue)) { + return SystemTimeData::forcedUsecValue; } else { struct timeval t; int ret; do { @@ -99,39 +285,45 @@ int e = errno; throw TimeRetrievalException( "Unable to retrieve the system time", e); } - return (unsigned long long) t.tv_sec * 1000 + t.tv_usec / 1000; + return (unsigned long long) t.tv_sec * 1000000 + t.tv_usec; } } /** - * Returns the time since the Epoch, measured in microseconds. Or, if a - * time was forced with forceUsec(), then the forced time is returned instead. + * Returns the time since an unspecified point in the last, measured in + * microseconds, using the monotonic clock. * + * The monotonic clock is not subject to clock drift, even if the user + * changes the wall clock time. It is ideal for measuring time between + * two intervals. + * + * The returned time is guaranteed to have a granularity of 1 microsecond + * or better. In general, querying with coarser granularities is faster. + * If you want a coarser granularity, use `getMonotonicUsecWithGranularity()` + * instead. + * + * If the monotonic clock is not available (e.g. because the operating + * system doesn't support it), then this function returns the regular + * wall clock time instead (using `getUsec()`). If the monotonic clock + * is available, but an error occurred querying it, then a + * `TimeRetrievalException` is thrown. + * + * If the time was forced with forceUsed(), then the forced time is returned + * instead. + * * @throws TimeRetrievalException Something went wrong while retrieving the time. - * @throws boost::thread_interrupted */ - static unsigned long long getUsec() { - if (SystemTimeData::hasForcedUsecValue) { - return SystemTimeData::forcedUsecValue; - } else { - struct timeval t; - int ret; + static MonotonicTimeUsec getMonotonicUsec() { + return _getMonotonicUsec<GRAN_1USEC>(); + } - do { - ret = gettimeofday(&t, NULL); - } while (ret == -1 && errno == EINTR); - if (ret == -1) { - int e = errno; - throw TimeRetrievalException( - "Unable to retrieve the system time", - e); - } - return (unsigned long long) t.tv_sec * 1000000 + t.tv_usec; - } + template<Granularity granularity> + static MonotonicTimeUsec getMonotonicUsecWithGranularity() { + return _getMonotonicUsec<granularity>(); } /** * Force get() to return the given value. */ @@ -139,28 +331,19 @@ SystemTimeData::hasForcedValue = true; SystemTimeData::forcedValue = value; } /** - * Force getMsec() to return the given value. - */ - static void forceMsec(unsigned long long value) { - SystemTimeData::hasForcedMsecValue = true; - SystemTimeData::forcedMsecValue = value; - } - - /** * Force getUsec() to return the given value. */ static void forceUsec(unsigned long long value) { SystemTimeData::hasForcedUsecValue = true; SystemTimeData::forcedUsecValue = value; } static void forceAll(unsigned long long usec) { force(usec / 1000000); - forceMsec(usec / 1000); forceUsec(usec); } /** * Release the previously forced seconds value, so that get() @@ -169,31 +352,22 @@ static void release() { SystemTimeData::hasForcedValue = false; } /** - * Release the previously forced msec value, so that getMsec() - * returns the system time once again. - */ - static void releaseMsec() { - SystemTimeData::hasForcedMsecValue = false; - } - - /** * Release the previously forced usec value, so that getUsec() * returns the system time once again. */ static void releaseUsec() { SystemTimeData::hasForcedUsecValue = false; } /** - * Release all previously forced values, so that get(), getMsec() - * and getUsec() return the system time once again. + * Release all previously forced values, so that get() and + * getUsec() return the system time once again. */ static void releaseAll() { SystemTimeData::hasForcedValue = false; - SystemTimeData::hasForcedMsecValue = false; SystemTimeData::hasForcedUsecValue = false; } }; } // namespace Passenger