From 8e29e0621b074a8d368b4d7d344281adb7a91e54 Mon Sep 17 00:00:00 2001 From: Jonathan M Davis Date: Sat, 25 Apr 2015 23:43:01 -0700 Subject: [PATCH] Add ClockType enum to core.time for issue# 13433. This adds an enum for indicating which type of clock to use when it's appropriate for a time function to have multiple options for the source clock. In the case of MonoTime, to make that work cleanly, the implementation of MonoTime has become MonoTimeImpl, templated on ClockType, and MonoTime has become an alias to MonoTimeImpl!(ClockType.normal). In the case of SysTime (in a separate PR), that will a default template argument to Clock.currTime and SysTime will be unaffected (because in MonoTime's case, the clock that it came from is integral to the type, whereas in SysTime's case, it doesn't matter after the SysTime has been initialized). --- src/core/time.d | 684 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 563 insertions(+), 121 deletions(-) diff --git a/src/core/time.d b/src/core/time.d index cb335fde4d7..0f39f6a30c7 100644 --- a/src/core/time.d +++ b/src/core/time.d @@ -126,6 +126,209 @@ version(unittest) T copy(T)(T t) } +/++ + What type of clock to use with $(LREF MonoTime) / $(LREF MonoTimeImpl) or + $(D std.datetime.Clock.currTime). They default to $(D ClockType.normal), + and most programs do not need to ever deal with the others. + + The other $(D ClockType)s are provided so that other clocks provided by the + underlying C, system calls can be used with $(LREF MonoTimeImpl) or + $(D std.datetime.Clock.currTime) without having to use the C API directly. + + In the case of the monotonic time, $(LREF MonoTimeImpl) is templatized on + $(D ClockType), whereas with $(D std.datetime.Clock.currTime), its a runtime + argument, since in the case of the monotonic time, the type of the clock + affects the resolution of a $(LREF MonoTimeImpl) object, whereas with + $(D XREF datetime, SysTime), its resolution is always hecto-nanoseconds + regardless of the source of the time. + + $(D ClockType.normal), $(D ClockType.coarse), and $(D ClockType.precise) + work with both $(D Clock.currTime) and $(LREF MonoTimeImpl). + $(D ClockType.second) only works with $(D Clock.currTime). The others only + work with $(LREF MonoTimeImpl). + +/ +version(CoreDdoc) enum ClockType +{ + /++ + Use the normal clock. + +/ + normal = 0, + + /++ + $(BLUE Linux-Only) + + Uses $(D CLOCK_MONOTONIC_BOOTTIME). + +/ + bootTime = 1, + + /++ + Use the coarse clock, not the normal one (e.g. on Linux, that would be + $(D CLOCK_REALTIME_COARSE) instead of $(D CLOCK_REALTIME) for + $(D clock_gettime) if a function is using the realtime clock). It's + generally faster to get the time with the coarse clock than the normal + clock, but it's less precise (e.g. 1 msec instead of 1 usec or 1 nsec). + Howeover, it $(I is) guaranteed to still have sub-second precision + (just not as high as with $(D ClockType.normal)). + + On systems which do not support a coarser clock, + $(D MonoTimeImpl!(ClockType.coarse)) will internally use the same clock + as $(D Monotime) does, and $(D Clock.currTime!(ClockType.coarse)) will + use the same clock as $(D Clock.currTime). This is because the coarse + clock is doing the same thing as the normal clock (just at lower + precision), whereas some of the other clock types + (e.g. $(D ClockType.processCPUTime)) mean something fundamentally + different. So, treating those as $(D ClockType.normal) on systems where + they weren't natively supported would give misleading results. + + Most programs should not use the coarse clock, exactly because it's + less precise, and most programs don't need to get the time often + enough to care, but for those rare programs that need to get the time + extremely frequently (e.g. hundreds of thousands of times a second) but + don't care about high precision, the coarse clock might be appropriate. + + Currently, only Linux and FreeBSD support a coarser clock, and on other + platforms, it's treated as $(D ClockType.normal). + +/ + coarse = 2, + + /++ + Uses a more precise clock than the normal one (which is already very + precise), but it takes longer to get the time. Similarly to + $(D ClockType.coarse), if it's used on a system that does not support a + more precise clock than the normal one, it's treated as equivalent to + $(D ClockType.normal). + + Currently, only FreeBSD supports a more precise clock, where it uses + $(D CLOCK_MONOTONIC_PRECISE) for the monotonic time and + $(D CLOCK_REALTIME_PRECISE) for the wall clock time. + +/ + precise = 3, + + /++ + $(BLUE Linux-Only) + + Uses $(D CLOCK_PROCESS_CPUTIME_ID). + +/ + processCPUTime = 4, + + /++ + $(BLUE Linux-Only) + + Uses $(D CLOCK_MONOTONIC_RAW). + +/ + raw = 5, + + /++ + Uses a clock that has a precision of one second (contrast to the coarse + clock, which has sub-second precision like the normal clock does). + + FreeBSD is the only system which specifically has a clock set up for + this (it has $(D CLOCK_SECOND) to use with $(D clock_gettime) which + takes advantage of an in-kernel cached value), but on other systems, the + fastest function available will be used, and the resulting $(D SysTime) + will be rounded down to the second if the clock that was used gave the + time at a more precise resolution. So, it's guaranteed that the time + will be given at a precision of one second and it's likely the case that + will be faster than $(D ClockType.normal), since there tend to be + several options on a system to get the time at low resolutions, and they + tend to be faster than getting the time at high resolutions. + + So, the primary difference between $(D ClockType.coarse) and + $(D ClockType.second) is that $(D ClockType.coarse) sacrifices some + precision in order to get speed but is still fairly precise, whereas + $(D ClockType.second) tries to be as fast as possible at the expense of + all sub-second precision. + +/ + second = 6, + + /++ + $(BLUE Linux-Only) + + Uses $(D CLOCK_THREAD_CPUTIME_ID). + +/ + threadCPUTime = 7, + + /++ + $(BLUE FreeBSD-Only) + + Uses $(D CLOCK_UPTIME). + +/ + uptime = 8, + + /++ + $(BLUE FreeBSD-Only) + + Uses $(D CLOCK_UPTIME_FAST). + +/ + uptimeCoarse = 9, + + /++ + $(BLUE FreeBSD-Only) + + Uses $(D CLOCK_UPTIME_PRECISE). + +/ + uptimePrecise = 10, +} +else version(Windows) enum ClockType +{ + normal = 0, + coarse = 2, + precise = 3, + second = 6, +} +else version(OSX) enum ClockType +{ + normal = 0, + coarse = 2, + precise = 3, + second = 6, +} +else version(linux) enum ClockType +{ + normal = 0, + bootTime = 1, + coarse = 2, + precise = 3, + processCPUTime = 4, + raw = 5, + second = 6, + threadCPUTime = 7, +} +else version(FreeBSD) enum ClockType +{ + normal = 0, + coarse = 2, + precise = 3, + second = 6, + uptime = 8, + uptimeCoarse = 9, + uptimePrecise = 10, +} +else +{ + // It needs to be decided (and implemented in an appropriate version branch + // here) which clock types new platforms are going to support. At minimum, + // the ones _not_ marked with $(D Blue Foo-Only) should be supported. + static assert(0, "What are the clock types supported by this system?"); +} + +unittest +{ + // Make sure that the values are the same across platforms. + static if(is(typeof(ClockType.normal))) static assert(ClockType.normal == 0); + static if(is(typeof(ClockType.bootTime))) static assert(ClockType.bootTime == 1); + static if(is(typeof(ClockType.coarse))) static assert(ClockType.coarse == 2); + static if(is(typeof(ClockType.precise))) static assert(ClockType.precise == 3); + static if(is(typeof(ClockType.processCPUTime))) static assert(ClockType.processCPUTime == 4); + static if(is(typeof(ClockType.raw))) static assert(ClockType.raw == 5); + static if(is(typeof(ClockType.second))) static assert(ClockType.second == 6); + static if(is(typeof(ClockType.threadCPUTime))) static assert(ClockType.threadCPUTime == 7); + static if(is(typeof(ClockType.uptime))) static assert(ClockType.uptime == 8); + static if(is(typeof(ClockType.uptimeCoarse))) static assert(ClockType.uptimeCoarse == 9); + static if(is(typeof(ClockType.uptimePrecise))) static assert(ClockType.uptimePrecise == 10); +} + + /++ Represents a duration of time of weeks or less (kept internally as hnsecs). (e.g. 22 days or 700 seconds). @@ -1848,6 +2051,14 @@ unittest } +/++ + alias for $(D MonoTimeImpl) instantiated with $(D ClockType.normal). This is + what most programs should use. It's also what much of $(D MonoTimeImpl) uses + in its documentation (particularly in the examples), because that's what's + going to be used in most code. + +/ +alias MonoTime = MonoTimeImpl!(ClockType.normal); + /++ Represents a timestamp of the system's monotonic clock. @@ -1856,12 +2067,12 @@ unittest $(XREF datetime, SysTime)). The system's wall clock time can be adjusted by the user or by the system itself via services such as NTP, so it is unreliable to use the wall clock time for timing. Timers which use the wall - clock time could easily end up never going off due changes made to the wall - clock time or otherwise waiting for a different period of time than that - specified by the programmer. However, because the monotonic clock always - increases at a fixed rate and is not affected by adjustments to the wall - clock time, it is ideal for use with timers or anything which requires high - precision timing. + clock time could easily end up never going off due to changes made to the + wall clock time or otherwise waiting for a different period of time than + that specified by the programmer. However, because the monotonic clock + always increases at a fixed rate and is not affected by adjustments to the + wall clock time, it is ideal for use with timers or anything which requires + high precision timing. So, MonoTime should be used for anything involving timers and timing, whereas $(XREF datetime, SysTime) should be used when the wall clock time @@ -1877,19 +2088,108 @@ unittest to get a meaningful duration of time. Normally, MonoTime does these calculations for the programmer, but the $(D ticks) and $(D ticksPerSecond) properties are provided for those who require direct access to the system - ticks. However, the normal way that MonoTime would be used is + ticks. The normal way that MonoTime would be used is -------------------- - MonoTime before = MonoTime.currTime; - // do stuff... - MonoTime after = MonoTime.currTime; - Duration timeElapsed = after - before; + MonoTime before = MonoTime.currTime; + // do stuff... + MonoTime after = MonoTime.currTime; + Duration timeElapsed = after - before; -------------------- + + $(LREF MonoTime) is an alias to $(D MonoTimeImpl!(ClockType.normal)) and is + what most programs should use for the monotonic clock, so that's what is + used in most of $(D MonoTimeImpl)'s documentation. But $(D MonoTimeImpl) + can be instantiated with other clock types for those rare programs that need + it. + + See_Also: + $(LREF ClockType) +/ -struct MonoTime +struct MonoTimeImpl(ClockType clockType) { + // need this helper to get the index of the clock type in the array below + private static size_t _clockNameIndex() + { + foreach(i, strname; __traits(allMembers, ClockType)) + { + mixin("alias val = ClockType." ~ strname ~ ";"); + if(val == clockType) + return i; + } + assert(0); + } + + private enum _clockIdx = _clockNameIndex(); + + unittest + { + mixin("alias val = ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ ";"); + assert(val == clockType); + } + @safe: + version(Windows) + { + static if(clockType != ClockType.coarse && + clockType != ClockType.normal && + clockType != ClockType.precise) + { + static assert(0, "ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ + " is not supported by MonoTimeImpl on this system."); + } + } + else version(OSX) + { + static if(clockType != ClockType.coarse && + clockType != ClockType.normal && + clockType != ClockType.precise) + { + static assert(0, "ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ + " is not supported by MonoTimeImpl on this system."); + } + } + else version(Posix) + { + version(linux) + { + import core.sys.linux.time; + static if(clockType == ClockType.bootTime) private alias clockArg = CLOCK_BOOTTIME; + else static if(clockType == ClockType.coarse) private alias clockArg = CLOCK_MONOTONIC_COARSE; + else static if(clockType == ClockType.normal) private alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.precise) private alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.processCPUTime) private alias clockArg = CLOCK_PROCESS_CPUTIME_ID; + else static if(clockType == ClockType.raw) private alias clockArg = CLOCK_MONOTONIC_RAW; + else static if(clockType == ClockType.threadCPUTime) private alias clockArg = CLOCK_THREAD_CPUTIME_ID; + else static assert(0, "ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ + " is not supported by MonoTimeImpl on this system."); + } + else version(FreeBSD) + { + import core.sys.freebsd.time; + static if(clockType == ClockType.coarse) private alias clockArg = CLOCK_MONOTONIC_FAST; + else static if(clockType == ClockType.normal) private alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.precise) private alias clockArg = CLOCK_MONOTONIC_PRECISE; + else static if(clockType == ClockType.uptime) private alias clockArg = CLOCK_UPTIME; + else static if(clockType == ClockType.uptimeCoarse) private alias clockArg = CLOCK_UPTIME_FAST; + else static if(clockType == ClockType.uptimePrecise) private alias clockArg = CLOCK_UPTIME_PRECISE; + else static assert(0, "ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ + " is not supported by MonoTimeImpl on this system."); + } + else + { + // It needs to be decided (and implemented in an appropriate + // version branch here) which clock types new platforms are + // going to support. Also, ClockType's documentation should be + // updated to mention it if a new platform uses anything that's + // not supported on all platforms.. + static assert(0, "What are the monotonic clock types supported by this system?"); + } + } + else + static assert(0, "Unsupported platform"); + /++ The current time of the system's monotonic clock. This has no relation to the wall clock time, as the wall clock time can be adjusted (e.g. @@ -1905,12 +2205,15 @@ struct MonoTime monotonic clock may indicate less time than has actually passed if that occurs. This is known to happen on Mac OS X. It has not been tested whether it occurs on - either Windows or on Linux. + either Windows or Linux. +/ - static @property MonoTime currTime() @trusted nothrow @nogc + static @property MonoTimeImpl currTime() @trusted nothrow @nogc { - if(_ticksPerSecond == 0) - assert(0, "MonoTime failed to get the frequency of the system's monotonic clock."); + if(ticksPerSecond == 0) + { + assert(0, "MonoTimeImpl!(ClockType." ~ __traits(allMembers, ClockType)[_clockIdx] ~ + ") failed to get the frequency of the system's monotonic clock."); + } version(Windows) { @@ -1920,19 +2223,19 @@ struct MonoTime // This probably cannot happen on Windows 95 or later assert(0, "Call to QueryPerformanceCounter failed."); } - return MonoTime(ticks); + return MonoTimeImpl(ticks); } else version(OSX) - return MonoTime(mach_absolute_time()); + return MonoTimeImpl(mach_absolute_time()); else version(Posix) { timespec ts; - if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + if(clock_gettime(clockArg, &ts) != 0) assert(0, "Call to clock_gettime failed."); - return MonoTime(convClockFreq(ts.tv_sec * 1_000_000_000L + ts.tv_nsec, - 1_000_000_000L, - _ticksPerSecond)); + return MonoTimeImpl(convClockFreq(ts.tv_sec * 1_000_000_000L + ts.tv_nsec, + 1_000_000_000L, + ticksPerSecond)); } } @@ -1943,27 +2246,27 @@ struct MonoTime A $(D MonoTime) of $(D 0) ticks. It's provided to be consistent with $(D Duration.zero), and it's more explicit than $(D MonoTime.init). +/ - MonoTime zero() { return MonoTime(0); } + MonoTimeImpl zero() { return MonoTimeImpl(0); } /++ Largest $(D MonoTime) possible. +/ - MonoTime max() { return MonoTime(long.max); } + MonoTimeImpl max() { return MonoTimeImpl(long.max); } /++ Most negative $(D MonoTime) possible. +/ - MonoTime min() { return MonoTime(long.min); } + MonoTimeImpl min() { return MonoTimeImpl(long.min); } } unittest { - assert(zero == MonoTime(0)); - assert(MonoTime.max == MonoTime(long.max)); - assert(MonoTime.min == MonoTime(long.min)); - assert(MonoTime.min < MonoTime.zero); - assert(MonoTime.zero < MonoTime.max); - assert(MonoTime.min < MonoTime.max); + assert(MonoTimeImpl.zero == MonoTimeImpl(0)); + assert(MonoTimeImpl.max == MonoTimeImpl(long.max)); + assert(MonoTimeImpl.min == MonoTimeImpl(long.min)); + assert(MonoTimeImpl.min < MonoTimeImpl.zero); + assert(MonoTimeImpl.zero < MonoTimeImpl.max); + assert(MonoTimeImpl.min < MonoTimeImpl.max); } @@ -1977,7 +2280,7 @@ struct MonoTime $(TR $(TD this > rhs) $(TD > 0)) ) +/ - int opCmp(MonoTime rhs) const pure nothrow @nogc + int opCmp(MonoTimeImpl rhs) const pure nothrow @nogc { if(_ticks < rhs._ticks) return -1; @@ -1986,11 +2289,11 @@ struct MonoTime unittest { - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - foreach(U; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(U; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - T t = MonoTime.currTime; + T t = MonoTimeImpl.currTime; U u = t; assert(t == u); assert(copy(t) == u); @@ -1998,11 +2301,11 @@ struct MonoTime } } - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - foreach(U; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(U; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - T before = MonoTime.currTime; + T before = MonoTimeImpl.currTime; auto after = U(before._ticks + 42); assert(before < after); assert(before <= before); @@ -2021,20 +2324,20 @@ struct MonoTime } } - immutable currTime = MonoTime.currTime; - assert(MonoTime(long.max) > MonoTime(0)); - assert(MonoTime(0) > MonoTime(long.min)); - assert(MonoTime(long.max) > currTime); - assert(currTime > MonoTime(0)); - assert(MonoTime(0) < currTime); - assert(MonoTime(0) < MonoTime(long.max)); - assert(MonoTime(long.min) < MonoTime(0)); + immutable currTime = MonoTimeImpl.currTime; + assert(MonoTimeImpl(long.max) > MonoTimeImpl(0)); + assert(MonoTimeImpl(0) > MonoTimeImpl(long.min)); + assert(MonoTimeImpl(long.max) > currTime); + assert(currTime > MonoTimeImpl(0)); + assert(MonoTimeImpl(0) < currTime); + assert(MonoTimeImpl(0) < MonoTimeImpl(long.max)); + assert(MonoTimeImpl(long.min) < MonoTimeImpl(0)); } /++ - Subtracting two MonoTimes results in a $(LREF Duration) representing the - amount of time which elapsed between them. + Subtracting two MonoTimes results in a $(LREF Duration) representing + the amount of time which elapsed between them. The primary way that programs should time how long something takes is to do @@ -2066,7 +2369,7 @@ assert(before + timeElapsed == after). MonoTime's $(D ticks) property and keep all calculations in ticks rather than using $(LREF Duration). +/ - Duration opBinary(string op)(MonoTime rhs) const pure nothrow @nogc + Duration opBinary(string op)(MonoTimeImpl rhs) const pure nothrow @nogc if(op == "-") { immutable diff = _ticks - rhs._ticks; @@ -2075,11 +2378,11 @@ assert(before + timeElapsed == after). unittest { - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - foreach(U; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(U; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - T t = MonoTime.currTime; + T t = MonoTimeImpl.currTime; U u = t; assert(u - t == Duration.zero); assert(copy(t) - u == Duration.zero); @@ -2087,9 +2390,9 @@ assert(before + timeElapsed == after). } } - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - foreach(U; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(U; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { static void test()(T before, U after, Duration min, size_t line = __LINE__) @trusted { @@ -2107,9 +2410,9 @@ assert(before + timeElapsed == after). if(before - after == -diff) {} else throw new AssertError("unittest failure 2", __FILE__, line); } - T before = MonoTime.currTime; - test(before, MonoTime(before._ticks + 4202), Duration.zero); - test(before, MonoTime.currTime, Duration.zero); + T before = MonoTimeImpl.currTime; + test(before, MonoTimeImpl(before._ticks + 4202), Duration.zero); + test(before, MonoTimeImpl.currTime, Duration.zero); auto durLargerUnits = dur!"minutes"(7) + dur!"seconds"(22); test(before, before + durLargerUnits + dur!"msecs"(33) + dur!"hnsecs"(571), durLargerUnits); @@ -2122,22 +2425,22 @@ assert(before + timeElapsed == after). Adding or subtracting a $(LREF Duration) to/from a MonoTime results in a MonoTime which is adjusted by that amount. +/ - MonoTime opBinary(string op)(Duration rhs) const pure nothrow @nogc + MonoTimeImpl opBinary(string op)(Duration rhs) const pure nothrow @nogc if(op == "+" || op == "-") { immutable rhsConverted = convClockFreq(rhs._hnsecs, hnsecsPer!"seconds", ticksPerSecond); - mixin("return MonoTime(_ticks " ~ op ~ " rhsConverted);"); + mixin("return MonoTimeImpl(_ticks " ~ op ~ " rhsConverted);"); } unittest { - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { - foreach(U; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(U; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { foreach(V; _TypeTuple!(Duration, const Duration, immutable Duration)) { - T t = MonoTime.currTime; + T t = MonoTimeImpl.currTime; U u1 = t + V(0); U u2 = t - V(0); assert(t == u1); @@ -2146,29 +2449,29 @@ assert(before + timeElapsed == after). } } - foreach(T; _TypeTuple!(MonoTime, const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(MonoTimeImpl, const MonoTimeImpl, immutable MonoTimeImpl)) { foreach(U; _TypeTuple!(Duration, const Duration, immutable Duration)) { - T t = MonoTime.currTime; + T t = MonoTimeImpl.currTime; // We reassign ticks in order to get the same rounding errors - // that we should be getting with Duration (e.g. MonoTime may be + // that we should be getting with Duration (e.g. MonoTimeImpl may be // at a higher precision than hnsecs, meaning that 7333 would be // truncated when converting to hnsecs). long ticks = 7333; - auto hnsecs = convClockFreq(ticks, MonoTime.ticksPerSecond, hnsecsPer!"seconds"); - ticks = convClockFreq(hnsecs, hnsecsPer!"seconds", MonoTime.ticksPerSecond); + auto hnsecs = convClockFreq(ticks, ticksPerSecond, hnsecsPer!"seconds"); + ticks = convClockFreq(hnsecs, hnsecsPer!"seconds", ticksPerSecond); - assert(t - Duration(hnsecs) == MonoTime(t._ticks - ticks)); - assert(t + Duration(hnsecs) == MonoTime(t._ticks + ticks)); + assert(t - Duration(hnsecs) == MonoTimeImpl(t._ticks - ticks)); + assert(t + Duration(hnsecs) == MonoTimeImpl(t._ticks + ticks)); } } } /++ Ditto +/ - ref MonoTime opOpAssign(string op)(Duration rhs) pure nothrow @nogc + ref MonoTimeImpl opOpAssign(string op)(Duration rhs) pure nothrow @nogc if(op == "+" || op == "-") { immutable rhsConverted = convClockFreq(rhs._hnsecs, hnsecsPer!"seconds", ticksPerSecond); @@ -2178,16 +2481,16 @@ assert(before + timeElapsed == after). unittest { - foreach(T; _TypeTuple!(const MonoTime, immutable MonoTime)) + foreach(T; _TypeTuple!(const MonoTimeImpl, immutable MonoTimeImpl)) { - T t = MonoTime.currTime; + T t = MonoTimeImpl.currTime; static assert(!is(typeof(t += Duration.zero))); static assert(!is(typeof(t -= Duration.zero))); } foreach(T; _TypeTuple!(Duration, const Duration, immutable Duration)) { - auto mt = MonoTime.currTime; + auto mt = MonoTimeImpl.currTime; auto initial = mt; mt += T(0); assert(mt == initial); @@ -2195,13 +2498,13 @@ assert(before + timeElapsed == after). assert(mt == initial); // We reassign ticks in order to get the same rounding errors - // that we should be getting with Duration (e.g. MonoTime may be + // that we should be getting with Duration (e.g. MonoTimeImpl may be // at a higher precision than hnsecs, meaning that 7333 would be // truncated when converting to hnsecs). long ticks = 7333; - auto hnsecs = convClockFreq(ticks, MonoTime.ticksPerSecond, hnsecsPer!"seconds"); - ticks = convClockFreq(hnsecs, hnsecsPer!"seconds", MonoTime.ticksPerSecond); - auto before = MonoTime(initial._ticks - ticks); + auto hnsecs = convClockFreq(ticks, ticksPerSecond, hnsecsPer!"seconds"); + ticks = convClockFreq(hnsecs, hnsecsPer!"seconds", ticksPerSecond); + auto before = MonoTimeImpl(initial._ticks - ticks); assert((mt -= Duration(hnsecs)) == before); assert(mt == before); @@ -2229,7 +2532,7 @@ assert(before + timeElapsed == after). unittest { - auto mt = MonoTime.currTime; + auto mt = MonoTimeImpl.currTime; assert(mt.ticks == mt._ticks); } @@ -2243,19 +2546,26 @@ assert(before + timeElapsed == after). +/ static @property long ticksPerSecond() pure nothrow @nogc { - return _ticksPerSecond; + return _ticksPerSecond[_clockIdx]; } unittest { - assert(MonoTime.ticksPerSecond == MonoTime._ticksPerSecond); + assert(MonoTimeImpl.ticksPerSecond == _ticksPerSecond[_clockIdx]); } /// string toString() const pure nothrow { - return "MonoTime(" ~ numToString(_ticks) ~ " ticks, " ~ numToString(_ticksPerSecond) ~ " ticks per second)"; + static if(clockType == ClockType.normal) + return "MonoTime(" ~ numToString(_ticks) ~ " ticks, " ~ numToString(ticksPerSecond) ~ " ticks per second)"; + else + { + enum name = __traits(allMembers, ClockType)[_clockIdx]; + return "MonoTimeImpl!(ClockType." ~ name ~ ")(" ~ numToString(_ticks) ~ " ticks, " ~ + numToString(ticksPerSecond) ~ " ticks per second)"; + } } unittest @@ -2270,10 +2580,38 @@ assert(before + timeElapsed == after). throw new AssertError("unittest failure", __FILE__, line); } - immutable mt = MonoTime.currTime; + immutable mt = MonoTimeImpl.currTime; auto str = mt.toString(); - assert(str[0 .. "MonoTime(".length] == "MonoTime("); - str = str["MonoTime(".length .. $]; + static if(is(typeof(this) == MonoTime)) + { + assert(str[0 .. "MonoTime(".length] == "MonoTime("); + str = str["MonoTime(".length .. $]; + } + else + { + enum len1 = "MonoTimeImpl!(ClockType.".length; + assert(str[0 .. len1] == "MonoTimeImpl!(ClockType."); + + string name; + final switch(clockType) + { + foreach(val; __traits(allMembers, ClockType)) + { + mixin("case ClockType." ~ val ~ ":"); + { + name = val; + break; + } + } + } + assert(name.length > 0); + + auto len2 = len1 + name.length; + assert(str[len1 .. len2] == name); + assert(str[len2 .. len2 + 2] == ")("); + str = str[len2 + 2 .. $]; + } + immutable space1 = findSpace(str); immutable ticksStr = str[0 .. space1]; assert(ticksStr == numToString(mt._ticks)); @@ -2282,52 +2620,155 @@ assert(before + timeElapsed == after). str = str["ticks, ".length .. $]; immutable space2 = findSpace(str); immutable ticksPerSecondStr = str[0 .. space2]; - assert(ticksPerSecondStr == numToString(MonoTime.ticksPerSecond)); + assert(ticksPerSecondStr == numToString(MonoTimeImpl.ticksPerSecond)); str = str[space2 + 1 .. $]; assert(str == "ticks per second)"); } private: - static immutable long _ticksPerSecond; + // static immutable long _ticksPerSecond; - @trusted shared static this() + unittest { - version(Windows) + assert(_ticksPerSecond[_clockIdx]); + } + + + long _ticks; +} + +// This is supposed to be a static variable in MonoTimeImpl with the static +// constructor being in there, but https://issues.dlang.org/show_bug.cgi?id=14517 +// prevents that from working. This is the workaround that Steven Schveighoffer +// came up with. Once issue# 14517 has been fixed, this should be changed so +// that _ticksPerSecond is a static long in MonoTimeImple rather than a static +// array outside of it. +private immutable long[__traits(allMembers, ClockType).length] _ticksPerSecond; + +@trusted shared static this() +{ + version(Windows) + { + long ticksPerSecond; + if(QueryPerformanceFrequency(&ticksPerSecond) != 0) { - long ticksPerSecond; - if(QueryPerformanceFrequency(&ticksPerSecond) != 0) - _ticksPerSecond = ticksPerSecond; + foreach(i, typeStr; __traits(allMembers, ClockType)) + _ticksPerSecond[i] = ticksPerSecond; } - else version(OSX) + else + assert(0, "Failed to get the frequency of the system's monotonic clock."); + } + else version(OSX) + { + mach_timebase_info_data_t info; + if(mach_timebase_info(&info) == 0) { - mach_timebase_info_data_t info; - if(mach_timebase_info(&info) == 0) - _ticksPerSecond = (1_000_000_000L * info.denom) / info.numer; + long ticksPerSecond = 1_000_000_000L * info.numer / info.denom; + foreach(i, typeStr; __traits(allMembers, ClockType)) + _ticksPerSecond[i] = ticksPerSecond; } - else version(Posix) + else + assert(0, "Failed to get the frequency of the system's monotonic clock."); + } + else version(Posix) + { + timespec ts; + foreach(i, typeStr; __traits(allMembers, ClockType)) { - timespec ts; - if(clock_getres(CLOCK_MONOTONIC, &ts) == 0) + mixin("enum clockType = ClockType." ~ typeStr ~ ";"); + static if(clockType != ClockType.second) { - // For some reason, on some systems, clock_getres returns - // a resolution which is clearly wrong (it's a millisecond - // or worse, but the time is updated much more frequently - // than that). In such cases, we'll just use nanosecond - // resolution. - _ticksPerSecond = ts.tv_nsec >= 1000 ? 1_000_000_000L - : 1_000_000_000L / ts.tv_nsec; + // This needs to be kept in sync with the corresponding code at the + // top of MonoTimeImpl, since as long as we're forced to put the + // static constructor outside of MonoTimeImpl, we don't have access + // to clockArg. + version(linux) + { + import core.sys.linux.time; + static if(clockType == ClockType.bootTime) alias clockArg = CLOCK_BOOTTIME; + else static if(clockType == ClockType.coarse) alias clockArg = CLOCK_MONOTONIC_COARSE; + else static if(clockType == ClockType.normal) alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.precise) alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.processCPUTime) alias clockArg = CLOCK_PROCESS_CPUTIME_ID; + else static if(clockType == ClockType.raw) alias clockArg = CLOCK_MONOTONIC_RAW; + else static if(clockType == ClockType.threadCPUTime) alias clockArg = CLOCK_THREAD_CPUTIME_ID; + else static assert(0, "ClockType." ~ typeStr ~ " is not supported by MonoTimeImpl on this system."); + } + else version(FreeBSD) + { + import core.sys.freebsd.time; + static if(clockType == ClockType.coarse) alias clockArg = CLOCK_MONOTONIC_FAST; + else static if(clockType == ClockType.normal) alias clockArg = CLOCK_MONOTONIC; + else static if(clockType == ClockType.precise) alias clockArg = CLOCK_MONOTONIC_PRECISE; + else static if(clockType == ClockType.uptime) alias clockArg = CLOCK_UPTIME; + else static if(clockType == ClockType.uptimeCoarse) alias clockArg = CLOCK_UPTIME_FAST; + else static if(clockType == ClockType.uptimePrecise) alias clockArg = CLOCK_UPTIME_PRECISE; + else static assert(0, "ClockType." ~ typeStr ~ " is not supported by MonoTimeImpl on this system."); + } + else + static assert(0, "What are the monotonic clock types supported by this system?"); + + if(clock_getres(clockArg, &ts) == 0) + { + // For some reason, on some systems, clock_getres returns + // a resolution which is clearly wrong (it's a millisecond + // or worse, but the time is updated much more frequently + // than that). In such cases, we'll just use nanosecond + // resolution. + _ticksPerSecond[i] = ts.tv_nsec >= 1000 ? 1_000_000_000L + : 1_000_000_000L / ts.tv_nsec; + } + else + assert(0, "Failed to get the frequency for of the system's monotonic clock: ClockType." ~ typeStr); } } } +} - unittest - { - assert(_ticksPerSecond); - } +// Tests for MonoTimeImpl.currTime. It has to be outside, because MonoTimeImpl +// is a template. This unittest block also makes sure that MonoTimeImpl actually +// is instantiated with all of the various ClockTypes so that those types and +// their tests are compiled and run. +unittest +{ + // This test is separate so that it can be tested with MonoTime and not just + // MonoTimeImpl. + auto norm1 = MonoTime.currTime; + auto norm2 = MonoTimeImpl!(ClockType.normal).currTime; + assert(norm1 <= norm2); - long _ticks; + foreach(typeStr; __traits(allMembers, ClockType)) + { + // ClockType.second is currently the only clock type that doesn't work + // with MonoTimeImpl. + static if(typeStr != "second") + { + mixin("alias type = ClockType." ~ typeStr ~ ";"); + auto v1 = MonoTimeImpl!type.currTime; + auto v2 = MonoTimeImpl!type.currTime; + scope(failure) + { + printf("%s: v1 %s, v2 %s, tps %s\n", + (type.stringof ~ "\0").ptr, + numToStringz(v1._ticks), + numToStringz(v2._ticks), + numToStringz(typeof(v1).ticksPerSecond)); + } + assert(v1 <= v2); + + foreach(otherStr; __traits(allMembers, ClockType)) + { + static if(otherStr != "second") + { + mixin("alias other = ClockType." ~ otherStr ~ ";"); + static assert(is(typeof({auto o1 = MonTimeImpl!other.currTime; auto b = v1 <= o1;})) == + is(type == other)); + } + } + } + } } @@ -4329,7 +4770,7 @@ string numToString(long value) @safe pure nothrow assert(0, "Something threw when nothing can throw."); } -version(unittest) const(char)* numToStringz(long value) @safe pure nothrow +version(unittest) const(char)* numToStringz()(long value) @safe pure nothrow { return (numToString(value) ~ "\0").ptr; } @@ -4466,20 +4907,21 @@ version(unittest) void assertApprox(D, E)(D actual, } } -version(unittest) void assertApprox(MonoTime actual, - MonoTime lower, - MonoTime upper, - string msg = "unittest failure", - size_t line = __LINE__) +version(unittest) void assertApprox(MT)(MT actual, + MT lower, + MT upper, + string msg = "unittest failure", + size_t line = __LINE__) + if(is(MT == MonoTimeImpl!type, ClockType type)) { assertApprox(actual._ticks, lower._ticks, upper._ticks, msg, line); } -version(unittest) void assertApprox(long actual, - long lower, - long upper, - string msg = "unittest failure", - size_t line = __LINE__) +version(unittest) void assertApprox()(long actual, + long lower, + long upper, + string msg = "unittest failure", + size_t line = __LINE__) { if(actual < lower) throw new AssertError(msg ~ ": lower: " ~ numToString(actual), __FILE__, line);