| ... | ... | @@ -0,0 +1,262 @@ |
| 1 | const std = @import("../index.zig"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const Os = builtin.Os; |
| 4 | const debug = std.debug; |
| 5 | |
| 6 | const windows = std.os.windows; |
| 7 | const darwin = std.os.darwin; |
| 8 | const posix = std.os.posix; |
| 9 | |
| 10 | pub const epoch = @import("epoch.zig"); |
| 11 | |
| 12 | /// Sleep for the specified duration |
| 13 | pub fn sleep(seconds: usize, nanoseconds: usize) void { |
| 14 | switch(builtin.os) { |
| 15 | Os.linux, Os.macosx, Os.ios => { |
| 16 | posixSleep(u63(seconds), u63(nanoseconds)); |
| 17 | }, |
| 18 | Os.windows => { |
| 19 | const milliseconds = seconds * ms_per_s + nanoseconds / (ns_per_s / ms_per_s); |
| 20 | windows.Sleep(windows.DWORD(milliseconds)); |
| 21 | }, |
| 22 | else => @compileError("Unsupported OS"), |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | const u63 = @IntType(false, 63); |
| 27 | pub fn posixSleep(seconds: u63, nanoseconds: u63) void { |
| 28 | var req = posix.timespec { |
| 29 | .tv_sec = seconds, |
| 30 | .tv_nsec = nanoseconds, |
| 31 | }; |
| 32 | var rem: posix.timespec = undefined; |
| 33 | while (true) { |
| 34 | const ret_val = posix.nanosleep(&req, &rem); |
| 35 | const err = posix.getErrno(ret_val); |
| 36 | if (err == 0) return; |
| 37 | switch (err) { |
| 38 | posix.EFAULT => unreachable, |
| 39 | posix.EINVAL => { |
| 40 | // Sometimes Darwin returns EINVAL for no reason. |
| 41 | // We treat it as a spurious wakeup. |
| 42 | return; |
| 43 | }, |
| 44 | posix.EINTR => { |
| 45 | req = rem; |
| 46 | continue; |
| 47 | }, |
| 48 | else => return, |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | /// Get the posix timestamp, UTC, in seconds |
| 54 | pub fn timestamp() u64 { |
| 55 | return @divFloor(miliTimestamp(), ms_per_s); |
| 56 | } |
| 57 | |
| 58 | /// Get the posix timestamp, UTC, in nanoseconds |
| 59 | pub const miliTimestamp = switch(builtin.os) { |
| 60 | Os.windows => miliTimestampWindows, |
| 61 | Os.linux => miliTimestampPosix, |
| 62 | Os.macosx, Os.ios => miliTimestampDarwin, |
| 63 | else => @compileError("Unsupported OS"), |
| 64 | }; |
| 65 | |
| 66 | fn miliTimestampWindows() u64 { |
| 67 | //FileTime has a granularity of 100 nanoseconds |
| 68 | // and uses the NTFS/Windows epoch |
| 69 | var ft: i64 = undefined; |
| 70 | windows.GetSystemTimeAsFileTime(&ft); |
| 71 | const hns_per_ms = (ns_per_s / 100) / ms_per_s; |
| 72 | const epoch_adj = epoch.windows * ms_per_s; |
| 73 | return u64(@divFloor(ft, hns_per_ms) + epoch_adj); |
| 74 | } |
| 75 | |
| 76 | fn miliTimestampDarwin() u64 { |
| 77 | //Sources suggest MacOS 10.12 has support for |
| 78 | // posix clock_gettime. |
| 79 | var tv: darwin.timeval = undefined; |
| 80 | var err = darwin.gettimeofday(&tv, null); |
| 81 | debug.assert(err == 0); |
| 82 | return tv.tv_sec * ms_per_s + ts.tv_usec |
| 83 | * (us_per_s / ms_per_s); |
| 84 | } |
| 85 | |
| 86 | fn miliTimestampPosix() u64 { |
| 87 | //From what I can tell there's no reason clock_gettime |
| 88 | // should ever fail for us with CLOCK_REALTIME |
| 89 | var ts: posix.timespec = undefined; |
| 90 | const err = posix.clock_gettime(posix.CLOCK_REALTIME, &ts); |
| 91 | debug.assert(err == 0); |
| 92 | const sec_ms = ts.tv_sec * ms_per_s; |
| 93 | const nsec_ms = @divFloor(ts.tv_nsec, ns_per_s / ms_per_s); |
| 94 | return u64(sec_ms) + u64(nsec_ms); |
| 95 | } |
| 96 | |
| 97 | /// Divisions of a second |
| 98 | pub const ns_per_s = 1000000000; |
| 99 | pub const us_per_s = 1000000; |
| 100 | pub const ms_per_s = 1000; |
| 101 | pub const cs_per_s = 100; |
| 102 | |
| 103 | /// Common time divisions |
| 104 | pub const s_per_min = 60; |
| 105 | pub const s_per_hour = s_per_min * 60; |
| 106 | pub const s_per_day = s_per_hour * 24; |
| 107 | pub const s_per_week = s_per_day * 7; |
| 108 | |
| 109 | |
| 110 | /// A monotonic high-performance timer. |
| 111 | /// Timer.start() must be called to initialize the struct, which captures |
| 112 | /// the counter frequency on windows and darwin, records the resolution, |
| 113 | /// and gives the user an oportunity to check for the existnece of |
| 114 | /// monotonic clocks without forcing them to check for error on each read. |
| 115 | /// .resolution is in nanoseconds on all platforms but .start_time's meaning |
| 116 | /// depends on the OS. On Windows and Darwin it is a hardware counter |
| 117 | /// value that requires calculation to convert to a meaninful unit. |
| 118 | pub const Timer = struct { |
| 119 | |
| 120 | //if we used resolution's value when performing the |
| 121 | // performance counter calc on windows, it would be |
| 122 | // less precise |
| 123 | frequency: switch(builtin.os) { |
| 124 | Os.windows => u64, |
| 125 | Os.macosx, Os.ios => darwin.mach_timebase_info_data, |
| 126 | else => void, |
| 127 | }, |
| 128 | resolution: u64, |
| 129 | start_time: u64, |
| 130 | |
| 131 | //Initialize the timer structure. |
| 132 | //This gives us an oportunity to grab the counter frequency in windows. |
| 133 | //On Windows: QueryPerformanceCounter will succeed on anything > XP. |
| 134 | //On Posix: CLOCK_MONOTONIC will only fail if the monotonic counter is not |
| 135 | // supported, or if the timespec pointer is out of bounds, which should be |
| 136 | // impossible here barring cosmic rays or other such occurances of |
| 137 | // incredibly bad luck. |
| 138 | //On Darwin: This cannot fail, as far as I am able to tell. |
| 139 | pub fn start() !Timer { |
| 140 | var self: Timer = undefined; |
| 141 | |
| 142 | switch(builtin.os) { |
| 143 | Os.windows => { |
| 144 | var freq: i64 = undefined; |
| 145 | var err = windows.QueryPerformanceFrequency(&freq); |
| 146 | if(err == 0) return error.TimerUnsupported; |
| 147 | self.frequency = u64(freq); |
| 148 | self.resolution = @divFloor(ns_per_s, self.frequency); |
| 149 | var start_time: i64 = undefined; |
| 150 | _ = windows.QueryPerformanceCounter(&start_time); |
| 151 | self.start_time = u64(start_time); |
| 152 | }, |
| 153 | Os.linux => { |
| 154 | var ts: posix.timespec = undefined; |
| 155 | var result = posix.clock_getres(posix.CLOCK_MONOTONIC, &ts); |
| 156 | switch(posix.getErrno(result)) { |
| 157 | 0 => {}, |
| 158 | posix.EINVAL => return error.TimerUnsupported, |
| 159 | else => unreachable, |
| 160 | } |
| 161 | self.resolution = u64(ts.tv_sec * ns_per_s + ts.tv_nsec); |
| 162 | _ = posix.clock_gettime(posix.CLOCK_MONOTONIC, &ts); |
| 163 | self.start_time = u64(ts.tv_sec * ns_per_s + ts.tv_nsec); |
| 164 | }, |
| 165 | Os.macosx, Os.ios => { |
| 166 | darwin.c.mach_timebase_info(&self.frequency); |
| 167 | self.resolution = @divFloor(self.frequency.numer, self.denom); |
| 168 | self.start_time = darwin.c.mach_absolute_time(); |
| 169 | }, |
| 170 | else => @compileError("Unsupported OS"), |
| 171 | } |
| 172 | return self; |
| 173 | } |
| 174 | |
| 175 | /// Reads the timer value since start or the last reset in nanoseconds |
| 176 | pub fn read(self: &Timer) u64 { |
| 177 | var clock = clockNative() - self.start_time; |
| 178 | return switch(builtin.os) { |
| 179 | Os.windows => @divFloor(clock * ns_per_s, self.frequency), |
| 180 | Os.linux => clock, |
| 181 | Os.macosx, Os.ios => @divFloor(clock * self.frequency.numer, self.frequency.denom), |
| 182 | else => @compileError("Unsupported OS"), |
| 183 | }; |
| 184 | } |
| 185 | |
| 186 | /// Resets the timer value to 0/now. |
| 187 | pub fn reset(self: &Timer) void |
| 188 | { |
| 189 | self.start_time = clockNative(); |
| 190 | } |
| 191 | |
| 192 | /// Returns the current value of the timer in nanoseconds, then resets it |
| 193 | pub fn lap(self: &Timer) u64 { |
| 194 | var now = clockNative(); |
| 195 | var lap_time = self.read(); |
| 196 | self.start_time = now; |
| 197 | return lap_time; |
| 198 | } |
| 199 | |
| 200 | |
| 201 | const clockNative = switch(builtin.os) { |
| 202 | Os.windows => clockWindows, |
| 203 | Os.linux => clockLinux, |
| 204 | Os.macosx, Os.ios => clockDarwin, |
| 205 | else => @compileError("Unsupported OS"), |
| 206 | }; |
| 207 | |
| 208 | fn clockWindows() u64 { |
| 209 | var result: i64 = undefined; |
| 210 | var err = windows.QueryPerformanceCounter(&result); |
| 211 | debug.assert(err != 0); |
| 212 | return u64(result); |
| 213 | } |
| 214 | |
| 215 | fn clockDarwin() u64 { |
| 216 | var result: u64 = undefined; |
| 217 | darwin.c.mach_absolute_time(&result); |
| 218 | return result; |
| 219 | } |
| 220 | |
| 221 | fn clockLinux() u64 { |
| 222 | var ts: posix.timespec = undefined; |
| 223 | var result = posix.clock_gettime(posix.CLOCK_MONOTONIC, &ts); |
| 224 | debug.assert(posix.getErrno(result) == 0); |
| 225 | return u64(ts.tv_sec * ns_per_s + ts.tv_nsec); |
| 226 | } |
| 227 | }; |
| 228 | |
| 229 | |
| 230 | |
| 231 | |
| 232 | |
| 233 | test "os.time.sleep" { |
| 234 | sleep(0, 1); |
| 235 | } |
| 236 | |
| 237 | test "os.time.timestamp" { |
| 238 | const ns_per_ms = (ns_per_s / ms_per_s); |
| 239 | const margin = 50; |
| 240 | |
| 241 | const time_0 = miliTimestamp(); |
| 242 | sleep(0, ns_per_ms); |
| 243 | const time_1 = miliTimestamp(); |
| 244 | const interval = time_1 - time_0; |
| 245 | debug.assert(interval > 0 and interval < margin); |
| 246 | } |
| 247 | |
| 248 | test "os.time.Timer" { |
| 249 | const ns_per_ms = (ns_per_s / ms_per_s); |
| 250 | const margin = ns_per_ms * 50; |
| 251 | |
| 252 | var timer = try Timer.start(); |
| 253 | sleep(0, 10 * ns_per_ms); |
| 254 | const time_0 = timer.read(); |
| 255 | debug.assert(time_0 > 0 and time_0 < margin); |
| 256 | |
| 257 | const time_1 = timer.lap(); |
| 258 | debug.assert(time_1 > time_0); |
| 259 | |
| 260 | timer.reset(); |
| 261 | debug.assert(timer.read() < time_1); |
| 262 | } |
| | \ No newline at end of file |