| ... | ... | @@ -4,22 +4,23 @@ const assert = std.debug.assert; |
| 4 | 4 | const testing = std.testing; |
| 5 | 5 | const os = std.os; |
| 6 | 6 | const math = std.math; |
| 7 | | const is_windows = builtin.os.tag == .windows; |
| 8 | 7 | |
| 9 | 8 | pub const epoch = @import("time/epoch.zig"); |
| 10 | 9 | |
| 11 | 10 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 12 | 11 | pub fn sleep(nanoseconds: u64) void { |
| 13 | 12 | // TODO: opting out of async sleeping? |
| 14 | | if (std.io.is_async) |
| 13 | if (std.io.is_async) { |
| 15 | 14 | return std.event.Loop.instance.?.sleep(nanoseconds); |
| 15 | } |
| 16 | 16 | |
| 17 | | if (is_windows) { |
| 17 | if (builtin.os.tag == .windows) { |
| 18 | 18 | const big_ms_from_ns = nanoseconds / ns_per_ms; |
| 19 | 19 | const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); |
| 20 | 20 | os.windows.kernel32.Sleep(ms); |
| 21 | 21 | return; |
| 22 | 22 | } |
| 23 | |
| 23 | 24 | if (builtin.os.tag == .wasi) { |
| 24 | 25 | const w = std.os.wasi; |
| 25 | 26 | const userdata: w.userdata_t = 0x0123_45678; |
| ... | ... | @@ -50,6 +51,10 @@ pub fn sleep(nanoseconds: u64) void { |
| 50 | 51 | std.os.nanosleep(s, ns); |
| 51 | 52 | } |
| 52 | 53 | |
| 54 | test "sleep" { |
| 55 | sleep(1); |
| 56 | } |
| 57 | |
| 53 | 58 | /// Get a calendar timestamp, in seconds, relative to UTC 1970-01-01. |
| 54 | 59 | /// Precision of timing depends on the hardware and operating system. |
| 55 | 60 | /// The return value is signed because it is possible to have a date that is |
| ... | ... | @@ -75,7 +80,7 @@ pub fn milliTimestamp() i64 { |
| 75 | 80 | /// before the epoch. |
| 76 | 81 | /// See `std.os.clock_gettime` for a POSIX timestamp. |
| 77 | 82 | pub fn nanoTimestamp() i128 { |
| 78 | | if (is_windows) { |
| 83 | if (builtin.os.tag == .windows) { |
| 79 | 84 | // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch, |
| 80 | 85 | // which is 1601-01-01. |
| 81 | 86 | const epoch_adj = epoch.windows * (ns_per_s / 100); |
| ... | ... | @@ -84,12 +89,14 @@ pub fn nanoTimestamp() i128 { |
| 84 | 89 | const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; |
| 85 | 90 | return @as(i128, @bitCast(i64, ft64) + epoch_adj) * 100; |
| 86 | 91 | } |
| 92 | |
| 87 | 93 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 88 | 94 | var ns: os.wasi.timestamp_t = undefined; |
| 89 | 95 | const err = os.wasi.clock_time_get(os.wasi.CLOCK.REALTIME, 1, &ns); |
| 90 | 96 | assert(err == .SUCCESS); |
| 91 | 97 | return ns; |
| 92 | 98 | } |
| 99 | |
| 93 | 100 | var ts: os.timespec = undefined; |
| 94 | 101 | os.clock_gettime(os.CLOCK.REALTIME, &ts) catch |err| switch (err) { |
| 95 | 102 | error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS". |
| ... | ... | @@ -97,6 +104,18 @@ pub fn nanoTimestamp() i128 { |
| 97 | 104 | return (@as(i128, ts.tv_sec) * ns_per_s) + ts.tv_nsec; |
| 98 | 105 | } |
| 99 | 106 | |
| 107 | test "timestamp" { |
| 108 | const margin = ns_per_ms * 50; |
| 109 | |
| 110 | const time_0 = milliTimestamp(); |
| 111 | sleep(ns_per_ms); |
| 112 | const time_1 = milliTimestamp(); |
| 113 | const interval = time_1 - time_0; |
| 114 | try testing.expect(interval > 0); |
| 115 | // Tests should not depend on timings: skip test if outside margin. |
| 116 | if (!(interval < margin)) return error.SkipZigTest; |
| 117 | } |
| 118 | |
| 100 | 119 | // Divisions of a nanosecond. |
| 101 | 120 | pub const ns_per_us = 1000; |
| 102 | 121 | pub const ns_per_ms = 1000 * ns_per_us; |
| ... | ... | @@ -127,149 +146,162 @@ pub const s_per_hour = s_per_min * 60; |
| 127 | 146 | pub const s_per_day = s_per_hour * 24; |
| 128 | 147 | pub const s_per_week = s_per_day * 7; |
| 129 | 148 | |
| 130 | | /// A monotonic high-performance timer. |
| 131 | | /// Timer.start() must be called to initialize the struct, which captures |
| 132 | | /// the counter frequency on windows and darwin, records the resolution, |
| 133 | | /// and gives the user an opportunity to check for the existnece of |
| 134 | | /// monotonic clocks without forcing them to check for error on each read. |
| 135 | | /// .resolution is in nanoseconds on all platforms but .start_time's meaning |
| 136 | | /// depends on the OS. On Windows and Darwin it is a hardware counter |
| 137 | | /// value that requires calculation to convert to a meaninful unit. |
| 138 | | pub const Timer = struct { |
| 139 | | ///if we used resolution's value when performing the |
| 140 | | /// performance counter calc on windows/darwin, it would |
| 141 | | /// be less precise |
| 142 | | frequency: switch (builtin.os.tag) { |
| 143 | | .windows => u64, |
| 144 | | .macos, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data, |
| 145 | | else => void, |
| 146 | | }, |
| 147 | | resolution: u64, |
| 148 | | start_time: u64, |
| 149 | | |
| 150 | | pub const Error = error{TimerUnsupported}; |
| 151 | | |
| 152 | | /// At some point we may change our minds on RAW, but for now we're |
| 153 | | /// sticking with posix standard MONOTONIC. For more information, see: |
| 154 | | /// https://github.com/ziglang/zig/pull/933 |
| 155 | | const monotonic_clock_id = os.CLOCK.MONOTONIC; |
| 149 | /// An Instant represents a timestamp with respect to the currently |
| 150 | /// executing program that ticks during suspend and can be used to |
| 151 | /// record elapsed time unlike `nanoTimestamp`. |
| 152 | /// |
| 153 | /// It tries to sample the system's fastest and most precise timer available. |
| 154 | /// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs. |
| 155 | /// If you need monotonic readings for elapsed time, consider `Timer` instead. |
| 156 | pub const Instant = struct { |
| 157 | timestamp: if (is_posix) os.timespec else u64, |
| 158 | |
| 159 | // true if we should use clock_gettime() |
| 160 | const is_posix = switch (builtin.os.tag) { |
| 161 | .wasi => builtin.link_libc, |
| 162 | .windows => false, |
| 163 | else => true, |
| 164 | }; |
| 156 | 165 | |
| 157 | | /// Initialize the timer structure. |
| 158 | | /// Can only fail when running in a hostile environment that intentionally injects |
| 159 | | /// error values into syscalls, such as using seccomp on Linux to intercept |
| 160 | | /// `clock_gettime`. |
| 161 | | pub fn start() Error!Timer { |
| 162 | | // This gives us an opportunity to grab the counter frequency in windows. |
| 163 | | // On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000. |
| 164 | | // On Posix: CLOCK.MONOTONIC will only fail if the monotonic counter is not |
| 165 | | // supported, or if the timespec pointer is out of bounds, which should be |
| 166 | | // impossible here barring cosmic rays or other such occurrences of |
| 167 | | // incredibly bad luck. |
| 168 | | // On Darwin: This cannot fail, as far as I am able to tell. |
| 169 | | if (is_windows) { |
| 170 | | const freq = os.windows.QueryPerformanceFrequency(); |
| 171 | | return Timer{ |
| 172 | | .frequency = freq, |
| 173 | | .resolution = @divFloor(ns_per_s, freq), |
| 174 | | .start_time = os.windows.QueryPerformanceCounter(), |
| 175 | | }; |
| 176 | | } else if (comptime builtin.target.isDarwin()) { |
| 177 | | var freq: os.darwin.mach_timebase_info_data = undefined; |
| 178 | | os.darwin.mach_timebase_info(&freq); |
| 179 | | |
| 180 | | return Timer{ |
| 181 | | .frequency = freq, |
| 182 | | .resolution = @divFloor(freq.numer, freq.denom), |
| 183 | | .start_time = os.darwin.mach_absolute_time(), |
| 184 | | }; |
| 185 | | } else { |
| 186 | | // On Linux, seccomp can do arbitrary things to our ability to call |
| 187 | | // syscalls, including return any errno value it wants and |
| 188 | | // inconsistently throwing errors. Since we can't account for |
| 189 | | // abuses of seccomp in a reasonable way, we'll assume that if |
| 190 | | // seccomp is going to block us it will at least do so consistently |
| 191 | | var res: os.timespec = undefined; |
| 192 | | os.clock_getres(monotonic_clock_id, &res) catch return error.TimerUnsupported; |
| 193 | | |
| 194 | | var ts: os.timespec = undefined; |
| 195 | | os.clock_gettime(monotonic_clock_id, &ts) catch return error.TimerUnsupported; |
| 196 | | |
| 197 | | return Timer{ |
| 198 | | .resolution = @intCast(u64, res.tv_sec) * ns_per_s + @intCast(u64, res.tv_nsec), |
| 199 | | .start_time = @intCast(u64, ts.tv_sec) * ns_per_s + @intCast(u64, ts.tv_nsec), |
| 200 | | .frequency = {}, |
| 201 | | }; |
| 166 | /// Queries the system for the current moment of time as an Instant. |
| 167 | /// This is not guaranteed to be monotonic or steadily increasing, but for most implementations it is. |
| 168 | /// Returns `error.Unsupported` when a suitable clock is not detected. |
| 169 | pub fn now() error{Unsupported}!Instant { |
| 170 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 171 | if (builtin.os.tag == .windows) { |
| 172 | return Instant{ .timestamp = os.windows.QueryPerformanceCounter() }; |
| 202 | 173 | } |
| 203 | | } |
| 204 | 174 | |
| 205 | | /// Reads the timer value since start or the last reset in nanoseconds |
| 206 | | pub fn read(self: Timer) u64 { |
| 207 | | var clock = clockNative() - self.start_time; |
| 208 | | return self.nativeDurationToNanos(clock); |
| 209 | | } |
| 175 | // On WASI without libc, use clock_time_get directly. |
| 176 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 177 | var ns: os.wasi.timestamp_t = undefined; |
| 178 | const rc = os.wasi.clock_time_get(os.wasi.CLOCK.MONOTONIC, 1, &ns); |
| 179 | if (rc != .SUCCESS) return error.Unsupported; |
| 180 | return Instant{ .timestamp = ns }; |
| 181 | } |
| 210 | 182 | |
| 211 | | /// Resets the timer value to 0/now. |
| 212 | | pub fn reset(self: *Timer) void { |
| 213 | | self.start_time = clockNative(); |
| 214 | | } |
| 183 | // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while suspended. |
| 184 | // On linux, use BOOTTIME instead of MONOTONIC as it ticks while suspended. |
| 185 | // On freebsd derivatives, use MONOTONIC_FAST as currently there's no precision tradeoff. |
| 186 | // On other posix systems, MONOTONIC is generally the fastest and ticks while suspended. |
| 187 | const clock_id = switch (builtin.os.tag) { |
| 188 | .macos, .ios, .tvos, .watchos => os.CLOCK.UPTIME_RAW, |
| 189 | .freebsd, .dragonfly => os.CLOCK.MONOTONIC_FAST, |
| 190 | .linux => os.CLOCK.BOOTTIME, |
| 191 | else => os.CLOCK.MONOTONIC, |
| 192 | }; |
| 215 | 193 | |
| 216 | | /// Returns the current value of the timer in nanoseconds, then resets it |
| 217 | | pub fn lap(self: *Timer) u64 { |
| 218 | | var now = clockNative(); |
| 219 | | var lap_time = self.nativeDurationToNanos(now - self.start_time); |
| 220 | | self.start_time = now; |
| 221 | | return lap_time; |
| 194 | var ts: os.timespec = undefined; |
| 195 | os.clock_gettime(clock_id, &ts) catch return error.Unsupported; |
| 196 | return Instant{ .timestamp = ts }; |
| 222 | 197 | } |
| 223 | 198 | |
| 224 | | fn clockNative() u64 { |
| 225 | | if (is_windows) { |
| 226 | | return os.windows.QueryPerformanceCounter(); |
| 199 | /// Quickly compares two instances between each other. |
| 200 | pub fn order(self: Instant, other: Instant) std.math.Order { |
| 201 | // windows and wasi timestamps are in u64 which is easily comparible |
| 202 | if (!is_posix) { |
| 203 | return std.math.order(self.timestamp, other.timestamp); |
| 227 | 204 | } |
| 228 | | if (comptime builtin.target.isDarwin()) { |
| 229 | | return os.darwin.mach_absolute_time(); |
| 205 | |
| 206 | var ord = std.math.order(self.timestamp.tv_sec, other.timestamp.tv_sec); |
| 207 | if (ord == .eq) { |
| 208 | ord = std.math.order(self.timestamp.tv_nsec, other.timestamp.tv_nsec); |
| 230 | 209 | } |
| 231 | | var ts: os.timespec = undefined; |
| 232 | | os.clock_gettime(monotonic_clock_id, &ts) catch unreachable; |
| 233 | | return @intCast(u64, ts.tv_sec) * @as(u64, ns_per_s) + @intCast(u64, ts.tv_nsec); |
| 210 | return ord; |
| 234 | 211 | } |
| 235 | 212 | |
| 236 | | fn nativeDurationToNanos(self: Timer, duration: u64) u64 { |
| 237 | | if (is_windows) { |
| 238 | | return safeMulDiv(duration, ns_per_s, self.frequency); |
| 213 | /// Returns elapsed time in nanoseconds since the `earlier` Instant. |
| 214 | /// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`. |
| 215 | /// This also assumes that the time that has passed between both Instants fits inside a u64 (~585 yrs). |
| 216 | pub fn since(self: Instant, earlier: Instant) u64 { |
| 217 | if (builtin.os.tag == .windows) { |
| 218 | // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA |
| 219 | // (a read-only page of info updated and mapped by the kernel to all processes): |
| 220 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data |
| 221 | // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm |
| 222 | const qpc = self.timestamp - earlier.timestamp; |
| 223 | const qpf = os.windows.QueryPerformanceFrequency(); |
| 224 | |
| 225 | // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. |
| 226 | // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 |
| 227 | const common_qpf = 10_000_000; |
| 228 | if (qpf == common_qpf) { |
| 229 | return qpc * (ns_per_s / common_qpf); |
| 230 | } |
| 231 | |
| 232 | // Convert to ns using fixed point. |
| 233 | const scale = @as(u64, std.time.ns_per_s << 32) / @intCast(u32, qpf); |
| 234 | const result = (@as(u96, qpc) * scale) >> 32; |
| 235 | return @truncate(u64, result); |
| 239 | 236 | } |
| 240 | | if (comptime builtin.target.isDarwin()) { |
| 241 | | return safeMulDiv(duration, self.frequency.numer, self.frequency.denom); |
| 237 | |
| 238 | // WASI timestamps are directly in nanoseconds |
| 239 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 240 | return self.timestamp - earlier.timestamp; |
| 242 | 241 | } |
| 243 | | return duration; |
| 242 | |
| 243 | // Convert timespec diff to ns |
| 244 | const seconds = @intCast(u64, self.timestamp.tv_sec - earlier.timestamp.tv_sec); |
| 245 | const elapsed = (seconds * ns_per_s) + @intCast(u32, self.timestamp.tv_nsec); |
| 246 | return elapsed - @intCast(u32, earlier.timestamp.tv_nsec); |
| 244 | 247 | } |
| 245 | 248 | }; |
| 246 | 249 | |
| 247 | | // Calculate (a * b) / c without risk of overflowing too early because of the |
| 248 | | // multiplication. |
| 249 | | fn safeMulDiv(a: u64, b: u64, c: u64) u64 { |
| 250 | | const q = a / c; |
| 251 | | const r = a % c; |
| 252 | | // (a * b) / c == (a / c) * b + ((a % c) * b) / c |
| 253 | | return (q * b) + (r * b) / c; |
| 254 | | } |
| 250 | /// A monotonic, high performance timer. |
| 251 | /// |
| 252 | /// Timer.start() is used to initalize the timer |
| 253 | /// and gives the caller an opportunity to check for the existence of a supported clock. |
| 254 | /// Once a supported clock is discovered, |
| 255 | /// it is assumed that it will be available for the duration of the Timer's use. |
| 256 | /// |
| 257 | /// Monotonicity is ensured by saturating on the most previous sample. |
| 258 | /// This means that while timings reported are monotonic, |
| 259 | /// they're not guaranteed to tick at a steady rate as this is up to the underlying system. |
| 260 | pub const Timer = struct { |
| 261 | started: Instant, |
| 262 | previous: Instant, |
| 255 | 263 | |
| 256 | | test "sleep" { |
| 257 | | sleep(1); |
| 258 | | } |
| 264 | pub const Error = error{TimerUnsupported}; |
| 259 | 265 | |
| 260 | | test "timestamp" { |
| 261 | | const margin = ns_per_ms * 50; |
| 266 | /// Initialize the timer by querying for a supported clock. |
| 267 | /// Returns `error.TimerUnsupported` when such a clock is unavailable. |
| 268 | /// This should only fail in hostile environments such as linux seccomp misuse. |
| 269 | pub fn start() Error!Timer { |
| 270 | const current = Instant.now() catch return error.TimerUnsupported; |
| 271 | return Timer{ .started = current, .previous = current }; |
| 272 | } |
| 262 | 273 | |
| 263 | | const time_0 = milliTimestamp(); |
| 264 | | sleep(ns_per_ms); |
| 265 | | const time_1 = milliTimestamp(); |
| 266 | | const interval = time_1 - time_0; |
| 267 | | try testing.expect(interval > 0); |
| 268 | | // Tests should not depend on timings: skip test if outside margin. |
| 269 | | if (!(interval < margin)) return error.SkipZigTest; |
| 270 | | } |
| 274 | /// Reads the timer value since start or the last reset in nanoseconds. |
| 275 | pub fn read(self: *Timer) u64 { |
| 276 | const current = self.sample(); |
| 277 | return current.since(self.started); |
| 278 | } |
| 279 | |
| 280 | /// Resets the timer value to 0/now. |
| 281 | pub fn reset(self: *Timer) void { |
| 282 | const current = self.sample(); |
| 283 | self.started = current; |
| 284 | } |
| 285 | |
| 286 | /// Returns the current value of the timer in nanoseconds, then resets it. |
| 287 | pub fn lap(self: *Timer) u64 { |
| 288 | const current = self.sample(); |
| 289 | defer self.started = current; |
| 290 | return current.since(self.started); |
| 291 | } |
| 292 | |
| 293 | /// Returns an Instant sampled at the callsite that is |
| 294 | /// guaranteed to be monotonic with respect to the timer's starting point. |
| 295 | fn sample(self: *Timer) Instant { |
| 296 | const current = Instant.now() catch unreachable; |
| 297 | if (current.order(self.previous) == .gt) { |
| 298 | self.previous = current; |
| 299 | } |
| 300 | return self.previous; |
| 301 | } |
| 302 | }; |
| 271 | 303 | |
| 272 | | test "Timer" { |
| 304 | test "Timer + Instant" { |
| 273 | 305 | const margin = ns_per_ms * 150; |
| 274 | 306 | |
| 275 | 307 | var timer = try Timer.start(); |