From bb4689411563d0f4d0327aefdf36074c54bf7f83 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Thu, 30 Apr 2020 20:44:22 -0600 Subject: [PATCH 1/6] Add `std.time.nanoTimestamp` function --- lib/std/time.zig | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index dd9a521543..fcbc2e0e0b 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -58,16 +58,25 @@ pub fn timestamp() u64 { /// Get the posix timestamp, UTC, in milliseconds /// TODO audit this function. is it possible to return an error? pub fn milliTimestamp() u64 { + return @divFloor(nanoTimestamp(), millisecond); +} + +/// Get the posix timestamp, UTC, in nanoseconds +/// +/// On windows this only has a granularity of 100 nanoseconds. +/// +/// TODO audit this function. is it possible to return an error? +pub fn nanoTimestamp() u64 { if (is_windows) { //FileTime has a granularity of 100 nanoseconds // and uses the NTFS/Windows epoch var ft: os.windows.FILETIME = undefined; os.windows.kernel32.GetSystemTimeAsFileTime(&ft); - const hns_per_ms = (ns_per_s / 100) / ms_per_s; - const epoch_adj = epoch.windows * ms_per_s; + const ns_per_hns = 100; + const epoch_adj = epoch.windows * ns_per_s; const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - return @divFloor(ft64, hns_per_ms) - -epoch_adj; + return (ft64 * hns_per_ms) - -epoch_adj; } if (builtin.os.tag == .wasi and !builtin.link_libc) { var ns: os.wasi.timestamp_t = undefined; @@ -76,25 +85,22 @@ pub fn milliTimestamp() u64 { const err = os.wasi.clock_time_get(os.wasi.CLOCK_REALTIME, 1, &ns); assert(err == os.wasi.ESUCCESS); - const ns_per_ms = 1000; - return @divFloor(ns, ns_per_ms); + return ns; } if (comptime std.Target.current.isDarwin()) { - var tv: os.darwin.timeval = undefined; - var err = os.darwin.gettimeofday(&tv, null); + var mts: os.darwin.mach_timespec_t = undefined; + var err = os.darwin.clock_get_time(os.darwin.CALENDAR_CLOCK, &mts); assert(err == 0); - const sec_ms = tv.tv_sec * ms_per_s; - const usec_ms = @divFloor(tv.tv_usec, us_per_s / ms_per_s); - return @intCast(u64, sec_ms + usec_ms); + const sec_ns = @as(u64, mts.tv_sec * ns_per_s); + return sec_ns + @intCast(u64, mts.tv_nsec); } var ts: os.timespec = undefined; //From what I can tell there's no reason clock_gettime // should ever fail for us with CLOCK_REALTIME, // seccomp aside. os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; - const sec_ms = @intCast(u64, ts.tv_sec) * ms_per_s; - const nsec_ms = @divFloor(@intCast(u64, ts.tv_nsec), ns_per_s / ms_per_s); - return sec_ms + nsec_ms; + const sec_ns = @intCast(u64, ts.tv_sec) * ns_per_s; + return sec_ns + @intCast(u64, ts.tv_nsec); } /// Multiples of a base unit (nanoseconds) From 7586ce0023e309fa5c86b67fe13d476f04a290f0 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Thu, 30 Apr 2020 22:45:54 -0600 Subject: [PATCH 2/6] Implement a faster way to get nanoseconds on darwin --- lib/std/time.zig | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index fcbc2e0e0b..027db463ed 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -61,6 +61,32 @@ pub fn milliTimestamp() u64 { return @divFloor(nanoTimestamp(), millisecond); } +const DarwinTimeStart = struct { + timebase: os.darwin.mach_timebase_info_data, + inittime: os.darwin.timespec, + initclock: u64, +}; + +var _timestart: ?DarwinTimeStart = null; + +pub fn _init_time_for_darwin() DarwinTimeStart { + var micro: os.darwin.timeval = undefined; + var timestart: DarwinTimeStart = undefined; + + var err = os.darwin.mach_timebase_info(×tart.timebase); + assert(err == 0); + + err = os.darwin.gettimeofday(µ, null); + assert(err == 0); + + timestart.initclock = os.darwin.mach_absolute_time(); + timestart.inittime.tv_sec = micro.tv_sec; + timestart.inittime.tv_nsec = micro.tv_usec * 1000; + + _timestart = timestart; + return timestart; +} + /// Get the posix timestamp, UTC, in nanoseconds /// /// On windows this only has a granularity of 100 nanoseconds. @@ -76,7 +102,7 @@ pub fn nanoTimestamp() u64 { const epoch_adj = epoch.windows * ns_per_s; const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - return (ft64 * hns_per_ms) - -epoch_adj; + return (ft64 * ns_per_hns) - -epoch_adj; } if (builtin.os.tag == .wasi and !builtin.link_libc) { var ns: os.wasi.timestamp_t = undefined; @@ -88,11 +114,17 @@ pub fn nanoTimestamp() u64 { return ns; } if (comptime std.Target.current.isDarwin()) { - var mts: os.darwin.mach_timespec_t = undefined; - var err = os.darwin.clock_get_time(os.darwin.CALENDAR_CLOCK, &mts); - assert(err == 0); - const sec_ns = @as(u64, mts.tv_sec * ns_per_s); - return sec_ns + @intCast(u64, mts.tv_nsec); + // https://stackoverflow.com/a/21352348 + const timestart = if (_timestart) |timestart| timestart else _init_time_for_darwin(); + + const clock: u64 = os.darwin.mach_absolute_time() - timestart.initclock; + const nano = clock * @divFloor(@as(u64, timestart.timebase.number), @as(u64, timestart.timebase.denom)); + + var future = timestart.inittime; + const tv_sec_nsec = @intCast(u64, timestart.inittime.tv_sec) * ns_per_s; + const tv_nsec = @intCast(u64, timestart.inittime.tv_nsec); + + return tv_sec_nsec + tv_nsec + nano; } var ts: os.timespec = undefined; //From what I can tell there's no reason clock_gettime From 2dfe798217c6348f4fc18b5fa9d86d9db06146b1 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Thu, 30 Apr 2020 23:16:54 -0600 Subject: [PATCH 3/6] Use `once` to init _timestart in thread safe way --- lib/std/time.zig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index 027db463ed..0d89118071 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -67,9 +67,10 @@ const DarwinTimeStart = struct { initclock: u64, }; -var _timestart: ?DarwinTimeStart = null; +var _timestart: DarwinTimeStart = undefined; +var _init_timestart_once = std.once(_init_timestart); -pub fn _init_time_for_darwin() DarwinTimeStart { +pub fn _init_timestart() void { var micro: os.darwin.timeval = undefined; var timestart: DarwinTimeStart = undefined; @@ -84,7 +85,6 @@ pub fn _init_time_for_darwin() DarwinTimeStart { timestart.inittime.tv_nsec = micro.tv_usec * 1000; _timestart = timestart; - return timestart; } /// Get the posix timestamp, UTC, in nanoseconds @@ -115,14 +115,14 @@ pub fn nanoTimestamp() u64 { } if (comptime std.Target.current.isDarwin()) { // https://stackoverflow.com/a/21352348 - const timestart = if (_timestart) |timestart| timestart else _init_time_for_darwin(); - const clock: u64 = os.darwin.mach_absolute_time() - timestart.initclock; - const nano = clock * @divFloor(@as(u64, timestart.timebase.number), @as(u64, timestart.timebase.denom)); + _init_timestart_once.call(); - var future = timestart.inittime; - const tv_sec_nsec = @intCast(u64, timestart.inittime.tv_sec) * ns_per_s; - const tv_nsec = @intCast(u64, timestart.inittime.tv_nsec); + const clock: u64 = os.darwin.mach_absolute_time() - _timestart.initclock; + const nano = clock * @divFloor(@as(u64, _timestart.timebase.number), @as(u64, _timestart.timebase.denom)); + + const tv_sec_nsec = @intCast(u64, _timestart.inittime.tv_sec) * ns_per_s; + const tv_nsec = @intCast(u64, _timestart.inittime.tv_nsec); return tv_sec_nsec + tv_nsec + nano; } From b85a191398b45eb4d8d2be7f1eaa1d871459a7db Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Fri, 1 May 2020 09:08:32 -0600 Subject: [PATCH 4/6] Fix compile errors for MacOS --- lib/std/time.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index 0d89118071..abdf90a3e1 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -74,10 +74,9 @@ pub fn _init_timestart() void { var micro: os.darwin.timeval = undefined; var timestart: DarwinTimeStart = undefined; - var err = os.darwin.mach_timebase_info(×tart.timebase); - assert(err == 0); + os.darwin.mach_timebase_info(×tart.timebase); - err = os.darwin.gettimeofday(µ, null); + const err = os.darwin.gettimeofday(µ, null); assert(err == 0); timestart.initclock = os.darwin.mach_absolute_time(); @@ -119,7 +118,7 @@ pub fn nanoTimestamp() u64 { _init_timestart_once.call(); const clock: u64 = os.darwin.mach_absolute_time() - _timestart.initclock; - const nano = clock * @divFloor(@as(u64, _timestart.timebase.number), @as(u64, _timestart.timebase.denom)); + const nano = @divFloor(clock * @as(u64, _timestart.timebase.numer), @as(u64, _timestart.timebase.denom)); const tv_sec_nsec = @intCast(u64, _timestart.inittime.tv_sec) * ns_per_s; const tv_nsec = @intCast(u64, _timestart.inittime.tv_nsec); From c6e7d0fcfdf83531c5c931433528c540eee62e56 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Fri, 1 May 2020 18:12:26 -0600 Subject: [PATCH 5/6] Use better names for darwin timestart --- lib/std/time.zig | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index abdf90a3e1..4d4a487467 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -67,10 +67,10 @@ const DarwinTimeStart = struct { initclock: u64, }; -var _timestart: DarwinTimeStart = undefined; -var _init_timestart_once = std.once(_init_timestart); +var global_timestart: DarwinTimeStart = undefined; +var init_global_timestart_once = std.once(init_global_timestart); -pub fn _init_timestart() void { +pub fn init_global_timestart() void { var micro: os.darwin.timeval = undefined; var timestart: DarwinTimeStart = undefined; @@ -83,7 +83,7 @@ pub fn _init_timestart() void { timestart.inittime.tv_sec = micro.tv_sec; timestart.inittime.tv_nsec = micro.tv_usec * 1000; - _timestart = timestart; + global_timestart = timestart; } /// Get the posix timestamp, UTC, in nanoseconds @@ -114,14 +114,13 @@ pub fn nanoTimestamp() u64 { } if (comptime std.Target.current.isDarwin()) { // https://stackoverflow.com/a/21352348 + init_global_timestart_once.call(); - _init_timestart_once.call(); + const clock: u64 = os.darwin.mach_absolute_time() - global_timestart.initclock; + const nano = @divFloor(clock * @as(u64, global_timestart.timebase.numer), @as(u64, global_timestart.timebase.denom)); - const clock: u64 = os.darwin.mach_absolute_time() - _timestart.initclock; - const nano = @divFloor(clock * @as(u64, _timestart.timebase.numer), @as(u64, _timestart.timebase.denom)); - - const tv_sec_nsec = @intCast(u64, _timestart.inittime.tv_sec) * ns_per_s; - const tv_nsec = @intCast(u64, _timestart.inittime.tv_nsec); + const tv_sec_nsec = @intCast(u64, global_timestart.inittime.tv_sec) * ns_per_s; + const tv_nsec = @intCast(u64, global_timestart.inittime.tv_nsec); return tv_sec_nsec + tv_nsec + nano; } From 53d011fa1a7bfb2389e3677e1f6fcbe7a678e05f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 24 May 2020 20:06:56 -0400 Subject: [PATCH 6/6] (breaking) std.time fixups and API changes Remove the constants that assume a base unit in favor of explicit x_per_y constants. nanosecond calendar timestamps now use i128 for the type. This affects fs.File.Stat, std.time.nanoTimestamp, and fs.File.updateTimes. calendar timestamps are now signed, because the value can be less than the epoch (the user can set their computer time to whatever they wish). implement std.os.clock_gettime for Windows when clock id is CLOCK_CALENDAR. --- lib/std/event/batch.zig | 2 +- lib/std/event/group.zig | 2 +- lib/std/event/loop.zig | 2 +- lib/std/fs/file.zig | 24 ++-- lib/std/fs/test.zig | 6 +- lib/std/net.zig | 4 +- lib/std/os.zig | 19 ++++ lib/std/os/bits/darwin.zig | 9 ++ lib/std/os/windows.zig | 14 +-- lib/std/progress.zig | 16 +-- lib/std/reset_event.zig | 10 +- lib/std/time.zig | 218 +++++++++++++++++-------------------- lib/std/time/epoch.zig | 35 ++++-- 13 files changed, 191 insertions(+), 170 deletions(-) diff --git a/lib/std/event/batch.zig b/lib/std/event/batch.zig index 9c424fcd2c..8f2954d85b 100644 --- a/lib/std/event/batch.zig +++ b/lib/std/event/batch.zig @@ -122,7 +122,7 @@ test "std.event.Batch" { } fn sleepALittle(count: *usize) void { - std.time.sleep(1 * std.time.millisecond); + std.time.sleep(1 * std.time.ns_per_ms); _ = @atomicRmw(usize, count, .Add, 1, .SeqCst); } diff --git a/lib/std/event/group.zig b/lib/std/event/group.zig index 155a9486b7..61130b32cb 100644 --- a/lib/std/event/group.zig +++ b/lib/std/event/group.zig @@ -145,7 +145,7 @@ fn testGroup(allocator: *Allocator) callconv(.Async) void { testing.expectError(error.ItBroke, another.wait()); } fn sleepALittle(count: *usize) callconv(.Async) void { - std.time.sleep(1 * std.time.millisecond); + std.time.sleep(1 * std.time.ns_per_ms); _ = @atomicRmw(usize, count, .Add, 1, .SeqCst); } fn increaseByTen(count: *usize) callconv(.Async) void { diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index 607e2f24ca..0abcad32e1 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -457,7 +457,7 @@ pub const Loop = struct { => { // Even poll() didn't work. The best we can do now is sleep for a // small duration and then hope that something changed. - std.time.sleep(1 * std.time.millisecond); + std.time.sleep(1 * std.time.ns_per_ms); }, }; resume @frame(); diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index f8cf69cbb8..a9ee996e4b 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -227,14 +227,12 @@ pub const File = struct { size: u64, mode: Mode, - /// access time in nanoseconds - atime: i64, - - /// last modification time in nanoseconds - mtime: i64, - - /// creation time in nanoseconds - ctime: i64, + /// Access time in nanoseconds, relative to UTC 1970-01-01. + atime: i128, + /// Last modification time in nanoseconds, relative to UTC 1970-01-01. + mtime: i128, + /// Creation time in nanoseconds, relative to UTC 1970-01-01. + ctime: i128, }; pub const StatError = os.FStatError; @@ -270,9 +268,9 @@ pub const File = struct { .inode = st.ino, .size = @bitCast(u64, st.size), .mode = st.mode, - .atime = @as(i64, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, - .mtime = @as(i64, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, - .ctime = @as(i64, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, + .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, + .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, + .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, }; } @@ -286,9 +284,9 @@ pub const File = struct { pub fn updateTimes( self: File, /// access timestamp in nanoseconds - atime: i64, + atime: i128, /// last modification timestamp in nanoseconds - mtime: i64, + mtime: i128, ) UpdateTimesError!void { if (builtin.os.tag == .windows) { const atime_ft = windows.nanoSecondsToFileTime(atime); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a857c3a7f8..2d979f5690 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -10,7 +10,7 @@ test "openSelfExe" { self_exe_file.close(); } -const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; +const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms; test "open file with exclusive nonblocking lock twice" { if (builtin.os.tag == .wasi) return error.SkipZigTest; @@ -142,8 +142,8 @@ const FileLockTestContext = struct { // Output variables err: ?(File.OpenError || std.os.ReadError) = null, - start_time: u64 = 0, - end_time: u64 = 0, + start_time: i64 = 0, + end_time: i64 = 0, bytes_read: ?usize = null, fn overlaps(self: *const @This(), other: *const @This()) bool { diff --git a/lib/std/net.zig b/lib/std/net.zig index 96c95fc497..067c9026b0 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1135,13 +1135,13 @@ fn resMSendRc( }}; const retry_interval = timeout / attempts; var next: u32 = 0; - var t2: u64 = std.time.milliTimestamp(); + var t2: u64 = @bitCast(u64, std.time.milliTimestamp()); var t0 = t2; var t1 = t2 - retry_interval; var servfail_retry: usize = undefined; - outer: while (t2 - t0 < timeout) : (t2 = std.time.milliTimestamp()) { + outer: while (t2 - t0 < timeout) : (t2 = @bitCast(u64, std.time.milliTimestamp())) { if (t2 - t1 >= retry_interval) { // Query all configured nameservers in parallel var i: usize = 0; diff --git a/lib/std/os.zig b/lib/std/os.zig index bc8803e3b0..22c884fce8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -3880,6 +3880,8 @@ pub fn dl_iterate_phdr( pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; +/// TODO: change this to return the timespec as a return value +/// TODO: look into making clk_id an enum pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { if (std.Target.current.os.tag == .wasi) { var ts: timestamp_t = undefined; @@ -3895,6 +3897,23 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { } return; } + if (std.Target.current.os.tag == .windows) { + if (clk_id == CLOCK_REALTIME) { + var ft: windows.FILETIME = undefined; + windows.kernel32.GetSystemTimeAsFileTime(&ft); + // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch. + const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + const ft_per_s = std.time.ns_per_s / 100; + tp.* = .{ + .tv_sec = @intCast(i64, ft64 / ft_per_s) + std.time.epoch.windows, + .tv_nsec = @intCast(c_long, ft64 % ft_per_s) * 100, + }; + return; + } else { + // TODO POSIX implementation of CLOCK_MONOTONIC on Windows. + return error.UnsupportedClock; + } + } switch (errno(system.clock_gettime(clk_id, tp))) { 0 => return, diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index 24f20b70ea..4581490e6f 100644 --- a/lib/std/os/bits/darwin.zig +++ b/lib/std/os/bits/darwin.zig @@ -1456,3 +1456,12 @@ pub const POLLHUP = 0x010; pub const POLLNVAL = 0x020; pub const POLLSTANDARD = POLLIN | POLLPRI | POLLOUT | POLLRDNORM | POLLRDBAND | POLLWRBAND | POLLERR | POLLHUP | POLLNVAL; + +pub const CLOCK_REALTIME = 0; +pub const CLOCK_MONOTONIC = 6; +pub const CLOCK_MONOTONIC_RAW = 4; +pub const CLOCK_MONOTONIC_RAW_APPROX = 5; +pub const CLOCK_UPTIME_RAW = 8; +pub const CLOCK_UPTIME_RAW_APPROX = 9; +pub const CLOCK_PROCESS_CPUTIME_ID = 12; +pub const CLOCK_THREAD_CPUTIME_ID = 16; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index ff10c08865..e1d54743ab 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1193,23 +1193,23 @@ pub fn peb() *PEB { /// Universal Time (UTC). /// This function returns the number of nanoseconds since the canonical epoch, /// which is the POSIX one (Jan 01, 1970 AD). -pub fn fromSysTime(hns: i64) i64 { - const adjusted_epoch = hns + std.time.epoch.windows * (std.time.ns_per_s / 100); +pub fn fromSysTime(hns: i64) i128 { + const adjusted_epoch = @as(i128, hns + std.time.epoch.windows) * (std.time.ns_per_s / 100); return adjusted_epoch * 100; } -pub fn toSysTime(ns: i64) i64 { +pub fn toSysTime(ns: i128) i64 { const hns = @divFloor(ns, 100); - return hns - std.time.epoch.windows * (std.time.ns_per_s / 100); + return @intCast(i64, hns) - std.time.epoch.windows * (std.time.ns_per_s / 100); } -pub fn fileTimeToNanoSeconds(ft: FILETIME) i64 { - const hns = @bitCast(i64, (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime); +pub fn fileTimeToNanoSeconds(ft: FILETIME) i128 { + const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; return fromSysTime(hns); } /// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME. -pub fn nanoSecondsToFileTime(ns: i64) FILETIME { +pub fn nanoSecondsToFileTime(ns: i128) FILETIME { const adjusted = @bitCast(u64, toSysTime(ns)); return FILETIME{ .dwHighDateTime = @truncate(u32, adjusted >> 32), diff --git a/lib/std/progress.zig b/lib/std/progress.zig index d73f3ee139..d80f8c4423 100644 --- a/lib/std/progress.zig +++ b/lib/std/progress.zig @@ -31,10 +31,10 @@ pub const Progress = struct { output_buffer: [100]u8 = undefined, /// How many nanoseconds between writing updates to the terminal. - refresh_rate_ns: u64 = 50 * std.time.millisecond, + refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, /// How many nanoseconds to keep the output hidden - initial_delay_ns: u64 = 500 * std.time.millisecond, + initial_delay_ns: u64 = 500 * std.time.ns_per_ms, done: bool = true, @@ -282,24 +282,24 @@ test "basic functionality" { next_sub_task = (next_sub_task + 1) % sub_task_names.len; node.completeOne(); - std.time.sleep(5 * std.time.millisecond); + std.time.sleep(5 * std.time.ns_per_ms); node.completeOne(); node.completeOne(); - std.time.sleep(5 * std.time.millisecond); + std.time.sleep(5 * std.time.ns_per_ms); node.completeOne(); node.completeOne(); - std.time.sleep(5 * std.time.millisecond); + std.time.sleep(5 * std.time.ns_per_ms); node.end(); - std.time.sleep(5 * std.time.millisecond); + std.time.sleep(5 * std.time.ns_per_ms); } { var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null); node.activate(); - std.time.sleep(10 * std.time.millisecond); + std.time.sleep(10 * std.time.ns_per_ms); progress.refresh(); - std.time.sleep(10 * std.time.millisecond); + std.time.sleep(10 * std.time.ns_per_ms); node.end(); } } diff --git a/lib/std/reset_event.zig b/lib/std/reset_event.zig index 860f494f3f..6f520773d8 100644 --- a/lib/std/reset_event.zig +++ b/lib/std/reset_event.zig @@ -152,15 +152,15 @@ const PosixEvent = struct { if (comptime std.Target.current.isDarwin()) { var tv: os.darwin.timeval = undefined; assert(os.darwin.gettimeofday(&tv, null) == 0); - timeout_abs += @intCast(u64, tv.tv_sec) * time.second; - timeout_abs += @intCast(u64, tv.tv_usec) * time.microsecond; + timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s; + timeout_abs += @intCast(u64, tv.tv_usec) * time.us_per_s; } else { os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; - timeout_abs += @intCast(u64, ts.tv_sec) * time.second; + timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; timeout_abs += @intCast(u64, ts.tv_nsec); } - ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.second)); - ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.second)); + ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); + ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); } while (!self.is_set) { diff --git a/lib/std/time.zig b/lib/std/time.zig index 4d4a487467..8fb2b79fd2 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -4,16 +4,14 @@ const assert = std.debug.assert; const testing = std.testing; const os = std.os; const math = std.math; +const is_windows = std.Target.current.os.tag == .windows; pub const epoch = @import("time/epoch.zig"); -const is_windows = std.Target.current.os.tag == .windows; - /// Spurious wakeups are possible and no precision of timing is guaranteed. /// TODO integrate with evented I/O pub fn sleep(nanoseconds: u64) void { if (is_windows) { - const ns_per_ms = ns_per_s / ms_per_s; const big_ms_from_ns = nanoseconds / ns_per_ms; const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); os.windows.kernel32.Sleep(ms); @@ -49,105 +47,78 @@ pub fn sleep(nanoseconds: u64) void { std.os.nanosleep(s, ns); } -/// Get the posix timestamp, UTC, in seconds -/// TODO audit this function. is it possible to return an error? -pub fn timestamp() u64 { - return @divFloor(milliTimestamp(), ms_per_s); +/// Get a calendar timestamp, in seconds, relative to UTC 1970-01-01. +/// Precision of timing depends on the hardware and operating system. +/// The return value is signed because it is possible to have a date that is +/// before the epoch. +/// See `std.os.clock_gettime` for a POSIX timestamp. +pub fn timestamp() i64 { + return @divFloor(milliTimestamp(), ns_per_s); } -/// Get the posix timestamp, UTC, in milliseconds -/// TODO audit this function. is it possible to return an error? -pub fn milliTimestamp() u64 { - return @divFloor(nanoTimestamp(), millisecond); +/// Get a calendar timestamp, in milliseconds, relative to UTC 1970-01-01. +/// Precision of timing depends on the hardware and operating system. +/// The return value is signed because it is possible to have a date that is +/// before the epoch. +/// See `std.os.clock_gettime` for a POSIX timestamp. +pub fn milliTimestamp() i64 { + return @intCast(i64, @divFloor(nanoTimestamp(), ns_per_ms)); } -const DarwinTimeStart = struct { - timebase: os.darwin.mach_timebase_info_data, - inittime: os.darwin.timespec, - initclock: u64, -}; - -var global_timestart: DarwinTimeStart = undefined; -var init_global_timestart_once = std.once(init_global_timestart); - -pub fn init_global_timestart() void { - var micro: os.darwin.timeval = undefined; - var timestart: DarwinTimeStart = undefined; - - os.darwin.mach_timebase_info(×tart.timebase); - - const err = os.darwin.gettimeofday(µ, null); - assert(err == 0); - - timestart.initclock = os.darwin.mach_absolute_time(); - timestart.inittime.tv_sec = micro.tv_sec; - timestart.inittime.tv_nsec = micro.tv_usec * 1000; - - global_timestart = timestart; -} - -/// Get the posix timestamp, UTC, in nanoseconds -/// -/// On windows this only has a granularity of 100 nanoseconds. -/// -/// TODO audit this function. is it possible to return an error? -pub fn nanoTimestamp() u64 { +/// Get a calendar timestamp, in nanoseconds, relative to UTC 1970-01-01. +/// Precision of timing depends on the hardware and operating system. +/// On Windows this has a maximum granularity of 100 nanoseconds. +/// The return value is signed because it is possible to have a date that is +/// before the epoch. +/// See `std.os.clock_gettime` for a POSIX timestamp. +pub fn nanoTimestamp() i128 { if (is_windows) { - //FileTime has a granularity of 100 nanoseconds - // and uses the NTFS/Windows epoch + // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch, + // which is 1601-01-01. + const epoch_adj = epoch.windows * (ns_per_s / 100); var ft: os.windows.FILETIME = undefined; os.windows.kernel32.GetSystemTimeAsFileTime(&ft); - const ns_per_hns = 100; - const epoch_adj = epoch.windows * ns_per_s; - const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - return (ft64 * ns_per_hns) - -epoch_adj; + return @as(i128, @bitCast(i64, ft64) + epoch_adj) * 100; } if (builtin.os.tag == .wasi and !builtin.link_libc) { var ns: os.wasi.timestamp_t = undefined; - - // TODO: Verify that precision is ignored const err = os.wasi.clock_time_get(os.wasi.CLOCK_REALTIME, 1, &ns); assert(err == os.wasi.ESUCCESS); - return ns; } - if (comptime std.Target.current.isDarwin()) { - // https://stackoverflow.com/a/21352348 - init_global_timestart_once.call(); - - const clock: u64 = os.darwin.mach_absolute_time() - global_timestart.initclock; - const nano = @divFloor(clock * @as(u64, global_timestart.timebase.numer), @as(u64, global_timestart.timebase.denom)); - - const tv_sec_nsec = @intCast(u64, global_timestart.inittime.tv_sec) * ns_per_s; - const tv_nsec = @intCast(u64, global_timestart.inittime.tv_nsec); - - return tv_sec_nsec + tv_nsec + nano; - } var ts: os.timespec = undefined; - //From what I can tell there's no reason clock_gettime - // should ever fail for us with CLOCK_REALTIME, - // seccomp aside. - os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; - const sec_ns = @intCast(u64, ts.tv_sec) * ns_per_s; - return sec_ns + @intCast(u64, ts.tv_nsec); + os.clock_gettime(os.CLOCK_REALTIME, &ts) catch |err| switch (err) { + error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS". + }; + return (@as(i128, ts.tv_sec) * ns_per_s) + ts.tv_nsec; } -/// Multiples of a base unit (nanoseconds) -pub const nanosecond = 1; -pub const microsecond = 1000 * nanosecond; -pub const millisecond = 1000 * microsecond; -pub const second = 1000 * millisecond; -pub const minute = 60 * second; -pub const hour = 60 * minute; +// Divisions of a nanosecond. +pub const ns_per_us = 1000; +pub const ns_per_ms = 1000 * ns_per_us; +pub const ns_per_s = 1000 * ns_per_ms; +pub const ns_per_min = 60 * ns_per_s; +pub const ns_per_hour = 60 * ns_per_min; +pub const ns_per_day = 24 * ns_per_hour; +pub const ns_per_week = 7 * ns_per_day; -/// Divisions of a second -pub const ns_per_s = 1000000000; -pub const us_per_s = 1000000; +// Divisions of a microsecond. +pub const us_per_ms = 1000; +pub const us_per_s = 1000 * us_per_ms; +pub const us_per_min = 60 * us_per_s; +pub const us_per_hour = 60 * us_per_min; +pub const us_per_day = 24 * us_per_hour; +pub const us_per_week = 7 * us_per_day; + +// Divisions of a millisecond. pub const ms_per_s = 1000; -pub const cs_per_s = 100; +pub const ms_per_min = 60 * ms_per_s; +pub const ms_per_hour = 60 * ms_per_min; +pub const ms_per_day = 24 * ms_per_hour; +pub const ms_per_week = 7 * ms_per_day; -/// Common time divisions +// Divisions of a second. pub const s_per_min = 60; pub const s_per_hour = s_per_min * 60; pub const s_per_day = s_per_hour * 24; @@ -155,12 +126,12 @@ pub const s_per_week = s_per_day * 7; /// A monotonic high-performance timer. /// Timer.start() must be called to initialize the struct, which captures -/// the counter frequency on windows and darwin, records the resolution, -/// and gives the user an opportunity to check for the existnece of -/// monotonic clocks without forcing them to check for error on each read. +/// the counter frequency on windows and darwin, records the resolution, +/// and gives the user an opportunity to check for the existnece of +/// monotonic clocks without forcing them to check for error on each read. /// .resolution is in nanoseconds on all platforms but .start_time's meaning -/// depends on the OS. On Windows and Darwin it is a hardware counter -/// value that requires calculation to convert to a meaninful unit. +/// depends on the OS. On Windows and Darwin it is a hardware counter +/// value that requires calculation to convert to a meaninful unit. pub const Timer = struct { ///if we used resolution's value when performing the /// performance counter calc on windows/darwin, it would @@ -173,43 +144,58 @@ pub const Timer = struct { resolution: u64, start_time: u64, - const Error = error{TimerUnsupported}; + pub const Error = error{TimerUnsupported}; - ///At some point we may change our minds on RAW, but for now we're - /// sticking with posix standard MONOTONIC. For more information, see: - /// https://github.com/ziglang/zig/pull/933 + /// At some point we may change our minds on RAW, but for now we're + /// sticking with posix standard MONOTONIC. For more information, see: + /// https://github.com/ziglang/zig/pull/933 const monotonic_clock_id = os.CLOCK_MONOTONIC; + /// Initialize the timer structure. - //This gives us an opportunity to grab the counter frequency in windows. - //On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000. - //On Posix: CLOCK_MONOTONIC will only fail if the monotonic counter is not - // supported, or if the timespec pointer is out of bounds, which should be - // impossible here barring cosmic rays or other such occurrences of - // incredibly bad luck. - //On Darwin: This cannot fail, as far as I am able to tell. + /// Can only fail when running in a hostile environment that intentionally injects + /// error values into syscalls, such as using seccomp on Linux to intercept + /// `clock_gettime`. pub fn start() Error!Timer { - var self: Timer = undefined; - + // This gives us an opportunity to grab the counter frequency in windows. + // On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000. + // On Posix: CLOCK_MONOTONIC will only fail if the monotonic counter is not + // supported, or if the timespec pointer is out of bounds, which should be + // impossible here barring cosmic rays or other such occurrences of + // incredibly bad luck. + // On Darwin: This cannot fail, as far as I am able to tell. if (is_windows) { - self.frequency = os.windows.QueryPerformanceFrequency(); - self.resolution = @divFloor(ns_per_s, self.frequency); - self.start_time = os.windows.QueryPerformanceCounter(); + const freq = os.windows.QueryPerformanceFrequency(); + return Timer{ + .frequency = freq, + .resolution = @divFloor(ns_per_s, freq), + .start_time = os.windows.QueryPerformanceCounter(), + }; } else if (comptime std.Target.current.isDarwin()) { - os.darwin.mach_timebase_info(&self.frequency); - self.resolution = @divFloor(self.frequency.numer, self.frequency.denom); - self.start_time = os.darwin.mach_absolute_time(); - } else { - //On Linux, seccomp can do arbitrary things to our ability to call - // syscalls, including return any errno value it wants and - // inconsistently throwing errors. Since we can't account for - // abuses of seccomp in a reasonable way, we'll assume that if - // seccomp is going to block us it will at least do so consistently - var ts: os.timespec = undefined; - os.clock_getres(monotonic_clock_id, &ts) catch return error.TimerUnsupported; - self.resolution = @intCast(u64, ts.tv_sec) * @as(u64, ns_per_s) + @intCast(u64, ts.tv_nsec); + var freq: os.darwin.mach_timebase_info_data = undefined; + os.darwin.mach_timebase_info(&freq); + return Timer{ + .frequency = freq, + .resolution = @divFloor(freq.numer, freq.denom), + .start_time = os.darwin.mach_absolute_time(), + }; + } else { + // On Linux, seccomp can do arbitrary things to our ability to call + // syscalls, including return any errno value it wants and + // inconsistently throwing errors. Since we can't account for + // abuses of seccomp in a reasonable way, we'll assume that if + // seccomp is going to block us it will at least do so consistently + var res: os.timespec = undefined; + os.clock_getres(monotonic_clock_id, &res) catch return error.TimerUnsupported; + + var ts: os.timespec = undefined; os.clock_gettime(monotonic_clock_id, &ts) catch return error.TimerUnsupported; - self.start_time = @intCast(u64, ts.tv_sec) * @as(u64, ns_per_s) + @intCast(u64, ts.tv_nsec); + + return Timer{ + .resolution = @intCast(u64, res.tv_sec) * ns_per_s + @intCast(u64, res.tv_nsec), + .start_time = @intCast(u64, ts.tv_sec) * ns_per_s + @intCast(u64, ts.tv_nsec), + .frequency = {}, + }; } return self; @@ -262,7 +248,6 @@ test "sleep" { } test "timestamp" { - const ns_per_ms = (ns_per_s / ms_per_s); const margin = ns_per_ms * 50; const time_0 = milliTimestamp(); @@ -273,7 +258,6 @@ test "timestamp" { } test "Timer" { - const ns_per_ms = (ns_per_s / ms_per_s); const margin = ns_per_ms * 150; var timer = try Timer.start(); diff --git a/lib/std/time/epoch.zig b/lib/std/time/epoch.zig index fc031521a5..126c4fceb7 100644 --- a/lib/std/time/epoch.zig +++ b/lib/std/time/epoch.zig @@ -1,15 +1,26 @@ -/// Epoch reference times in terms of their difference from -/// posix epoch in seconds. -pub const posix = 0; //Jan 01, 1970 AD -pub const dos = 315532800; //Jan 01, 1980 AD -pub const ios = 978307200; //Jan 01, 2001 AD -pub const openvms = -3506716800; //Nov 17, 1858 AD -pub const zos = -2208988800; //Jan 01, 1900 AD -pub const windows = -11644473600; //Jan 01, 1601 AD -pub const amiga = 252460800; //Jan 01, 1978 AD -pub const pickos = -63244800; //Dec 31, 1967 AD -pub const gps = 315964800; //Jan 06, 1980 AD -pub const clr = -62135769600; //Jan 01, 0001 AD +//! Epoch reference times in terms of their difference from +//! UTC 1970-01-01 in seconds. + +/// Jan 01, 1970 AD +pub const posix = 0; +/// Jan 01, 1980 AD +pub const dos = 315532800; +/// Jan 01, 2001 AD +pub const ios = 978307200; +/// Nov 17, 1858 AD +pub const openvms = -3506716800; +/// Jan 01, 1900 AD +pub const zos = -2208988800; +/// Jan 01, 1601 AD +pub const windows = -11644473600; +/// Jan 01, 1978 AD +pub const amiga = 252460800; +/// Dec 31, 1967 AD +pub const pickos = -63244800; +/// Jan 06, 1980 AD +pub const gps = 315964800; +/// Jan 01, 0001 AD +pub const clr = -62135769600; pub const unix = posix; pub const android = posix;