| ... | @@ -4,22 +4,23 @@ const assert = std.debug.assert; | ... | @@ -4,22 +4,23 @@ const assert = std.debug.assert; |
| 4 | const testing = std.testing; | 4 | const testing = std.testing; |
| 5 | const os = std.os; | 5 | const os = std.os; |
| 6 | const math = std.math; | 6 | const math = std.math; |
| 7 | const is_windows = builtin.os.tag == .windows; | | |
| 8 | | 7 | |
| 9 | pub const epoch = @import("time/epoch.zig"); | 8 | pub const epoch = @import("time/epoch.zig"); |
| 10 | | 9 | |
| 11 | /// Spurious wakeups are possible and no precision of timing is guaranteed. | 10 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 12 | pub fn sleep(nanoseconds: u64) void { | 11 | pub fn sleep(nanoseconds: u64) void { |
| 13 | // TODO: opting out of async sleeping? | 12 | // TODO: opting out of async sleeping? |
| 14 | if (std.io.is_async) | 13 | if (std.io.is_async) { |
| 15 | return std.event.Loop.instance.?.sleep(nanoseconds); | 14 | return std.event.Loop.instance.?.sleep(nanoseconds); |
| | 15 | } |
| 16 | | 16 | |
| 17 | if (is_windows) { | 17 | if (builtin.os.tag == .windows) { |
| 18 | const big_ms_from_ns = nanoseconds / ns_per_ms; | 18 | const big_ms_from_ns = nanoseconds / ns_per_ms; |
| 19 | const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); | 19 | const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); |
| 20 | os.windows.kernel32.Sleep(ms); | 20 | os.windows.kernel32.Sleep(ms); |
| 21 | return; | 21 | return; |
| 22 | } | 22 | } |
| | 23 | |
| 23 | if (builtin.os.tag == .wasi) { | 24 | if (builtin.os.tag == .wasi) { |
| 24 | const w = std.os.wasi; | 25 | const w = std.os.wasi; |
| 25 | const userdata: w.userdata_t = 0x0123_45678; | 26 | const userdata: w.userdata_t = 0x0123_45678; |
| ... | @@ -50,6 +51,10 @@ pub fn sleep(nanoseconds: u64) void { | ... | @@ -50,6 +51,10 @@ pub fn sleep(nanoseconds: u64) void { |
| 50 | std.os.nanosleep(s, ns); | 51 | std.os.nanosleep(s, ns); |
| 51 | } | 52 | } |
| 52 | | 53 | |
| | 54 | test "sleep" { |
| | 55 | sleep(1); |
| | 56 | } |
| | 57 | |
| 53 | /// Get a calendar timestamp, in seconds, relative to UTC 1970-01-01. | 58 | /// Get a calendar timestamp, in seconds, relative to UTC 1970-01-01. |
| 54 | /// Precision of timing depends on the hardware and operating system. | 59 | /// Precision of timing depends on the hardware and operating system. |
| 55 | /// The return value is signed because it is possible to have a date that is | 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,7 +80,7 @@ pub fn milliTimestamp() i64 { |
| 75 | /// before the epoch. | 80 | /// before the epoch. |
| 76 | /// See `std.os.clock_gettime` for a POSIX timestamp. | 81 | /// See `std.os.clock_gettime` for a POSIX timestamp. |
| 77 | pub fn nanoTimestamp() i128 { | 82 | pub fn nanoTimestamp() i128 { |
| 78 | if (is_windows) { | 83 | if (builtin.os.tag == .windows) { |
| 79 | // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch, | 84 | // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch, |
| 80 | // which is 1601-01-01. | 85 | // which is 1601-01-01. |
| 81 | const epoch_adj = epoch.windows * (ns_per_s / 100); | 86 | const epoch_adj = epoch.windows * (ns_per_s / 100); |
| ... | @@ -84,12 +89,14 @@ pub fn nanoTimestamp() i128 { | ... | @@ -84,12 +89,14 @@ pub fn nanoTimestamp() i128 { |
| 84 | const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; | 89 | const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; |
| 85 | return @as(i128, @bitCast(i64, ft64) + epoch_adj) * 100; | 90 | return @as(i128, @bitCast(i64, ft64) + epoch_adj) * 100; |
| 86 | } | 91 | } |
| | 92 | |
| 87 | if (builtin.os.tag == .wasi and !builtin.link_libc) { | 93 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 88 | var ns: os.wasi.timestamp_t = undefined; | 94 | var ns: os.wasi.timestamp_t = undefined; |
| 89 | const err = os.wasi.clock_time_get(os.wasi.CLOCK.REALTIME, 1, &ns); | 95 | const err = os.wasi.clock_time_get(os.wasi.CLOCK.REALTIME, 1, &ns); |
| 90 | assert(err == .SUCCESS); | 96 | assert(err == .SUCCESS); |
| 91 | return ns; | 97 | return ns; |
| 92 | } | 98 | } |
| | 99 | |
| 93 | var ts: os.timespec = undefined; | 100 | var ts: os.timespec = undefined; |
| 94 | os.clock_gettime(os.CLOCK.REALTIME, &ts) catch |err| switch (err) { | 101 | os.clock_gettime(os.CLOCK.REALTIME, &ts) catch |err| switch (err) { |
| 95 | error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS". | 102 | error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS". |
| ... | @@ -97,6 +104,18 @@ pub fn nanoTimestamp() i128 { | ... | @@ -97,6 +104,18 @@ pub fn nanoTimestamp() i128 { |
| 97 | return (@as(i128, ts.tv_sec) * ns_per_s) + ts.tv_nsec; | 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 | // Divisions of a nanosecond. | 119 | // Divisions of a nanosecond. |
| 101 | pub const ns_per_us = 1000; | 120 | pub const ns_per_us = 1000; |
| 102 | pub const ns_per_ms = 1000 * ns_per_us; | 121 | pub const ns_per_ms = 1000 * ns_per_us; |
| ... | @@ -127,149 +146,162 @@ pub const s_per_hour = s_per_min * 60; | ... | @@ -127,149 +146,162 @@ pub const s_per_hour = s_per_min * 60; |
| 127 | pub const s_per_day = s_per_hour * 24; | 146 | pub const s_per_day = s_per_hour * 24; |
| 128 | pub const s_per_week = s_per_day * 7; | 147 | pub const s_per_week = s_per_day * 7; |
| 129 | | 148 | |
| 130 | /// A monotonic high-performance timer. | 149 | /// An Instant represents a timestamp with respect to the currently |
| 131 | /// Timer.start() must be called to initialize the struct, which captures | 150 | /// executing program that ticks during suspend and can be used to |
| 132 | /// the counter frequency on windows and darwin, records the resolution, | 151 | /// record elapsed time unlike `nanoTimestamp`. |
| 133 | /// and gives the user an opportunity to check for the existnece of | 152 | /// |
| 134 | /// monotonic clocks without forcing them to check for error on each read. | 153 | /// It tries to sample the system's fastest and most precise timer available. |
| 135 | /// .resolution is in nanoseconds on all platforms but .start_time's meaning | 154 | /// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs. |
| 136 | /// depends on the OS. On Windows and Darwin it is a hardware counter | 155 | /// If you need monotonic readings for elapsed time, consider `Timer` instead. |
| 137 | /// value that requires calculation to convert to a meaninful unit. | 156 | pub const Instant = struct { |
| 138 | pub const Timer = struct { | 157 | timestamp: if (is_posix) os.timespec else u64, |
| 139 | ///if we used resolution's value when performing the | 158 | |
| 140 | /// performance counter calc on windows/darwin, it would | 159 | // true if we should use clock_gettime() |
| 141 | /// be less precise | 160 | const is_posix = switch (builtin.os.tag) { |
| 142 | frequency: switch (builtin.os.tag) { | 161 | .wasi => builtin.link_libc, |
| 143 | .windows => u64, | 162 | .windows => false, |
| 144 | .macos, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data, | 163 | else => true, |
| 145 | else => void, | 164 | }; |
| 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; | | |
| 156 | | 165 | |
| 157 | /// Initialize the timer structure. | 166 | /// Queries the system for the current moment of time as an Instant. |
| 158 | /// Can only fail when running in a hostile environment that intentionally injects | 167 | /// This is not guaranteed to be monotonic or steadily increasing, but for most implementations it is. |
| 159 | /// error values into syscalls, such as using seccomp on Linux to intercept | 168 | /// Returns `error.Unsupported` when a suitable clock is not detected. |
| 160 | /// `clock_gettime`. | 169 | pub fn now() error{Unsupported}!Instant { |
| 161 | pub fn start() Error!Timer { | 170 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 162 | // This gives us an opportunity to grab the counter frequency in windows. | 171 | if (builtin.os.tag == .windows) { |
| 163 | // On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000. | 172 | return Instant{ .timestamp = os.windows.QueryPerformanceCounter() }; |
| 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 | }; | | |
| 202 | } | 173 | } |
| 203 | } | | |
| 204 | | 174 | |
| 205 | /// Reads the timer value since start or the last reset in nanoseconds | 175 | // On WASI without libc, use clock_time_get directly. |
| 206 | pub fn read(self: Timer) u64 { | 176 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 207 | var clock = clockNative() - self.start_time; | 177 | var ns: os.wasi.timestamp_t = undefined; |
| 208 | return self.nativeDurationToNanos(clock); | 178 | const rc = os.wasi.clock_time_get(os.wasi.CLOCK.MONOTONIC, 1, &ns); |
| 209 | } | 179 | if (rc != .SUCCESS) return error.Unsupported; |
| | 180 | return Instant{ .timestamp = ns }; |
| | 181 | } |
| 210 | | 182 | |
| 211 | /// Resets the timer value to 0/now. | 183 | // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while suspended. |
| 212 | pub fn reset(self: *Timer) void { | 184 | // On linux, use BOOTTIME instead of MONOTONIC as it ticks while suspended. |
| 213 | self.start_time = clockNative(); | 185 | // On freebsd derivatives, use MONOTONIC_FAST as currently there's no precision tradeoff. |
| 214 | } | 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 | 194 | var ts: os.timespec = undefined; |
| 217 | pub fn lap(self: *Timer) u64 { | 195 | os.clock_gettime(clock_id, &ts) catch return error.Unsupported; |
| 218 | var now = clockNative(); | 196 | return Instant{ .timestamp = ts }; |
| 219 | var lap_time = self.nativeDurationToNanos(now - self.start_time); | | |
| 220 | self.start_time = now; | | |
| 221 | return lap_time; | | |
| 222 | } | 197 | } |
| 223 | | 198 | |
| 224 | fn clockNative() u64 { | 199 | /// Quickly compares two instances between each other. |
| 225 | if (is_windows) { | 200 | pub fn order(self: Instant, other: Instant) std.math.Order { |
| 226 | return os.windows.QueryPerformanceCounter(); | 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()) { | 205 | |
| 229 | return os.darwin.mach_absolute_time(); | 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; | 210 | return ord; |
| 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); | | |
| 234 | } | 211 | } |
| 235 | | 212 | |
| 236 | fn nativeDurationToNanos(self: Timer, duration: u64) u64 { | 213 | /// Returns elapsed time in nanoseconds since the `earlier` Instant. |
| 237 | if (is_windows) { | 214 | /// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`. |
| 238 | return safeMulDiv(duration, ns_per_s, self.frequency); | 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()) { | 237 | |
| 241 | return safeMulDiv(duration, self.frequency.numer, self.frequency.denom); | 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 | 250 | /// A monotonic, high performance timer. |
| 248 | // multiplication. | 251 | /// |
| 249 | fn safeMulDiv(a: u64, b: u64, c: u64) u64 { | 252 | /// Timer.start() is used to initalize the timer |
| 250 | const q = a / c; | 253 | /// and gives the caller an opportunity to check for the existence of a supported clock. |
| 251 | const r = a % c; | 254 | /// Once a supported clock is discovered, |
| 252 | // (a * b) / c == (a / c) * b + ((a % c) * b) / c | 255 | /// it is assumed that it will be available for the duration of the Timer's use. |
| 253 | return (q * b) + (r * b) / c; | 256 | /// |
| 254 | } | 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" { | 264 | pub const Error = error{TimerUnsupported}; |
| 257 | sleep(1); | | |
| 258 | } | | |
| 259 | | 265 | |
| 260 | test "timestamp" { | 266 | /// Initialize the timer by querying for a supported clock. |
| 261 | const margin = ns_per_ms * 50; | 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(); | 274 | /// Reads the timer value since start or the last reset in nanoseconds. |
| 264 | sleep(ns_per_ms); | 275 | pub fn read(self: *Timer) u64 { |
| 265 | const time_1 = milliTimestamp(); | 276 | const current = self.sample(); |
| 266 | const interval = time_1 - time_0; | 277 | return current.since(self.started); |
| 267 | try testing.expect(interval > 0); | 278 | } |
| 268 | // Tests should not depend on timings: skip test if outside margin. | 279 | |
| 269 | if (!(interval < margin)) return error.SkipZigTest; | 280 | /// Resets the timer value to 0/now. |
| 270 | } | 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 | const margin = ns_per_ms * 150; | 305 | const margin = ns_per_ms * 150; |
| 274 | | 306 | |
| 275 | var timer = try Timer.start(); | 307 | var timer = try Timer.start(); |