zig/lib/std/time.zig
Andrew Kelley 3deda15e21 std.os reorganization, avoiding usingnamespace
The main purpose of this branch is to explore avoiding the
`usingnamespace` feature of the zig language, specifically with regards
to `std.os` and related functionality.

If this experiment is successful, it will provide a data point on
whether or not it would be practical to entirely remove `usingnamespace`
from the language.

In this commit, `usingnamespace` has been completely eliminated from
the Linux x86_64 compilation path, aside from io_uring.

The behavior tests pass, however that's as far as this branch goes. It is
very breaking, and a lot more work is needed before it could be
considered mergeable. I wanted to put a pull requset up early so that
zig programmers have time to provide feedback.

This is progress towards closing #6600 since it clarifies where the
actual "owner" of each declaration is, and reduces the number of
different ways to import the same declarations.

One of the main organizational strategies used here is to do namespacing
with real namespaces (e.g. structs) rather than by having declarations
share a common prefix (the C strategy). It's no coincidence that
`usingnamespace` has similar semantics to `#include` and becomes much
less necessary when using proper namespaces.
2021-09-01 17:54:06 -07:00

290 lines
11 KiB
Zig

const std = @import("std.zig");
const builtin = std.builtin;
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");
/// Spurious wakeups are possible and no precision of timing is guaranteed.
pub fn sleep(nanoseconds: u64) void {
// TODO: opting out of async sleeping?
if (std.io.is_async)
return std.event.Loop.instance.?.sleep(nanoseconds);
if (is_windows) {
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);
return;
}
if (builtin.os.tag == .wasi) {
const w = std.os.wasi;
const userdata: w.userdata_t = 0x0123_45678;
const clock = w.subscription_clock_t{
.id = w.CLOCK.MONOTONIC,
.timeout = nanoseconds,
.precision = 0,
.flags = 0,
};
const in = w.subscription_t{
.userdata = userdata,
.u = w.subscription_u_t{
.tag = w.EVENTTYPE_CLOCK,
.u = w.subscription_u_u_t{
.clock = clock,
},
},
};
var event: w.event_t = undefined;
var nevents: usize = undefined;
_ = w.poll_oneoff(&in, &event, 1, &nevents);
return;
}
const s = nanoseconds / ns_per_s;
const ns = nanoseconds % ns_per_s;
std.os.nanosleep(s, ns);
}
/// 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(), ms_per_s);
}
/// 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));
}
/// 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,
// 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 ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
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;
const err = os.wasi.clock_time_get(os.wasi.CLOCK_REALTIME, 1, &ns);
assert(err == .SUCCESS);
return ns;
}
var ts: os.timespec = undefined;
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;
}
// 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 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 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;
// 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;
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.
/// .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.
pub const Timer = struct {
///if we used resolution's value when performing the
/// performance counter calc on windows/darwin, it would
/// be less precise
frequency: switch (builtin.os.tag) {
.windows => u64,
.macos, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data,
else => void,
},
resolution: u64,
start_time: u64,
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
const monotonic_clock_id = os.CLOCK.MONOTONIC;
/// Initialize the timer structure.
/// 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 {
// 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) {
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()) {
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;
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;
}
/// Reads the timer value since start or the last reset in nanoseconds
pub fn read(self: Timer) u64 {
var clock = clockNative() - self.start_time;
return self.nativeDurationToNanos(clock);
}
/// Resets the timer value to 0/now.
pub fn reset(self: *Timer) void {
self.start_time = clockNative();
}
/// Returns the current value of the timer in nanoseconds, then resets it
pub fn lap(self: *Timer) u64 {
var now = clockNative();
var lap_time = self.nativeDurationToNanos(now - self.start_time);
self.start_time = now;
return lap_time;
}
fn clockNative() u64 {
if (is_windows) {
return os.windows.QueryPerformanceCounter();
}
if (comptime std.Target.current.isDarwin()) {
return os.darwin.mach_absolute_time();
}
var ts: os.timespec = undefined;
os.clock_gettime(monotonic_clock_id, &ts) catch unreachable;
return @intCast(u64, ts.tv_sec) * @as(u64, ns_per_s) + @intCast(u64, ts.tv_nsec);
}
fn nativeDurationToNanos(self: Timer, duration: u64) u64 {
if (is_windows) {
return safeMulDiv(duration, ns_per_s, self.frequency);
}
if (comptime std.Target.current.isDarwin()) {
return safeMulDiv(duration, self.frequency.numer, self.frequency.denom);
}
return duration;
}
};
// Calculate (a * b) / c without risk of overflowing too early because of the
// multiplication.
fn safeMulDiv(a: u64, b: u64, c: u64) u64 {
const q = a / c;
const r = a % c;
// (a * b) / c == (a / c) * b + ((a % c) * b) / c
return (q * b) + (r * b) / c;
}
test "sleep" {
sleep(1);
}
test "timestamp" {
const margin = ns_per_ms * 50;
const time_0 = milliTimestamp();
sleep(ns_per_ms);
const time_1 = milliTimestamp();
const interval = time_1 - time_0;
try testing.expect(interval > 0);
// Tests should not depend on timings: skip test if outside margin.
if (!(interval < margin)) return error.SkipZigTest;
}
test "Timer" {
const margin = ns_per_ms * 150;
var timer = try Timer.start();
sleep(10 * ns_per_ms);
const time_0 = timer.read();
try testing.expect(time_0 > 0);
// Tests should not depend on timings: skip test if outside margin.
if (!(time_0 < margin)) return error.SkipZigTest;
const time_1 = timer.lap();
try testing.expect(time_1 >= time_0);
timer.reset();
try testing.expect(timer.read() < time_1);
}