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 dd9a521543fa09a20e76e71f681649fa1d0e38c0..fcbc2e0e0b978a1c75734b1170b2f4d9d8b73d1c 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) -- 2.54.0 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 fcbc2e0e0b978a1c75734b1170b2f4d9d8b73d1c..027db463ed1e3366726f496322e476133c472bc0 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 -- 2.54.0 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 027db463ed1e3366726f496322e476133c472bc0..0d8911807185f709738b4b0a9271510f6765ecc1 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; } -- 2.54.0 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 0d8911807185f709738b4b0a9271510f6765ecc1..abdf90a3e11e839d545effcd1cd6a5c79198b1e2 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); -- 2.54.0 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 abdf90a3e11e839d545effcd1cd6a5c79198b1e2..4d4a487467dea35c23a333756e646fe2b10d7435 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; } -- 2.54.0 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 9c424fcd2c60c845e7f74e852dd43365fde7a37d..8f2954d85bc205af7696341117cc76f0e4c7bf57 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 155a9486b7b1ee0c134ad78912e53a784d20cca7..61130b32cbac2d4aff7fd34f2119d93d75258911 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 607e2f24cae8f3c8d352c0ff66e6a3e4e86d5ca5..0abcad32e1d94ba1f88fcf46bb05facf06951674 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 f8cf69cbb8c623625435262d532a2adf3f5bab73..a9ee996e4b7cb0e9f365dfb5deb38818cdefb0ea 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 a857c3a7f89445519bd6a674462fc039e3cc8c6b..2d979f569006a0a163fd1963a3e57350e7764982 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 96c95fc497bfb7c3742cba2abdb43cbe839ee695..067c9026b0026f01b235e57748ec1c92e7b16d86 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 bc8803e3b03dc8b8bd032f01a4d6b8d10bd3510f..22c884fce8d787f798768047e012c9792aaa037b 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 24f20b70ea407d981af8771a8b12a17ba97233e4..4581490e6f0c574302801e40e483c9c9ea526a5b 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 ff10c0886565cbd019d60adc0e9e7e9b1c085036..e1d54743ab0a71722a00e8f6febb34d20827a1f0 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 d73f3ee139ede91485ccb1dabd1c7a4a84a06640..d80f8c4423049544e76c826037905e2bc36f5714 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 860f494f3f97eb82cbe6510324b2866aedda443e..6f520773d84ba4944d22e202d087db0356d467dd 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 4d4a487467dea35c23a333756e646fe2b10d7435..8fb2b79fd24a01ff313584197f471d6ffec43e1f 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; - -pub const epoch = @import("time/epoch.zig"); - const is_windows = std.Target.current.os.tag == .windows; +pub const epoch = @import("time/epoch.zig"); + /// 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(); + 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 + // 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_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); - 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 fc031521a518915c74320ef6234440281aa4aa15..126c4fceb7386266e28102644dd65ed53e84bcbf 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; -- 2.54.0