aboutsummaryrefslogtreecommitdiff
path: root/std/os/time.zig
blob: 42f32f8fee230c35dc1d6eed40ed01c32640a6ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
const std = @import("../index.zig");
const builtin = @import("builtin");
const Os = builtin.Os;
const debug = std.debug;

const windows = std.os.windows;
const linux = std.os.linux;
const darwin = std.os.darwin;
const posix = std.os.posix;

pub const epoch = @import("epoch.zig");

/// Sleep for the specified duration
pub fn sleep(nanoseconds: u64) void {
    switch (builtin.os) {
        Os.linux, Os.macosx, Os.ios, Os.freebsd => {
            const s = nanoseconds / ns_per_s;
            const ns = nanoseconds % ns_per_s;
            posixSleep(@intCast(u63, s), @intCast(u63, ns));
        },
        Os.windows => {
            const ns_per_ms = ns_per_s / ms_per_s;
            const milliseconds = nanoseconds / ns_per_ms;
            windows.Sleep(@intCast(windows.DWORD, milliseconds));
        },
        else => @compileError("Unsupported OS"),
    }
}

pub fn posixSleep(seconds: u63, nanoseconds: u63) void {
    var req = posix.timespec{
        .tv_sec = seconds,
        .tv_nsec = nanoseconds,
    };
    var rem: posix.timespec = undefined;
    while (true) {
        const ret_val = posix.nanosleep(&req, &rem);
        const err = posix.getErrno(ret_val);
        if (err == 0) return;
        switch (err) {
            posix.EFAULT => unreachable,
            posix.EINVAL => {
                // Sometimes Darwin returns EINVAL for no reason.
                // We treat it as a spurious wakeup.
                return;
            },
            posix.EINTR => {
                req = rem;
                continue;
            },
            else => return,
        }
    }
}

/// Get the posix timestamp, UTC, in seconds
pub fn timestamp() u64 {
    return @divFloor(milliTimestamp(), ms_per_s);
}

/// Get the posix timestamp, UTC, in milliseconds
pub const milliTimestamp = switch (builtin.os) {
    Os.windows => milliTimestampWindows,
    Os.linux, Os.freebsd => milliTimestampPosix,
    Os.macosx, Os.ios => milliTimestampDarwin,
    else => @compileError("Unsupported OS"),
};

fn milliTimestampWindows() u64 {
    //FileTime has a granularity of 100 nanoseconds
    //  and uses the NTFS/Windows epoch
    var ft: windows.FILETIME = undefined;
    windows.GetSystemTimeAsFileTime(&ft);
    const hns_per_ms = (ns_per_s / 100) / ms_per_s;
    const epoch_adj = epoch.windows * ms_per_s;

    const ft64 = (u64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
    return @divFloor(ft64, hns_per_ms) - -epoch_adj;
}

fn milliTimestampDarwin() u64 {
    //Sources suggest MacOS 10.12 has support for
    //  posix clock_gettime.
    var tv: darwin.timeval = undefined;
    var err = darwin.gettimeofday(&tv, null);
    debug.assert(err == 0);
    const sec_ms = @intCast(u64, tv.tv_sec) * ms_per_s;
    const usec_ms = @divFloor(@intCast(u64, tv.tv_usec), us_per_s / ms_per_s);
    return u64(sec_ms) + u64(usec_ms);
}

fn milliTimestampPosix() u64 {
    //From what I can tell there's no reason clock_gettime
    //  should ever fail for us with CLOCK_REALTIME,
    //  seccomp aside.
    var ts: posix.timespec = undefined;
    const err = posix.clock_gettime(posix.CLOCK_REALTIME, &ts);
    debug.assert(err == 0);
    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;
}

/// 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 second
pub const ns_per_s = 1000000000;
pub const us_per_s = 1000000;
pub const ms_per_s = 1000;
pub const cs_per_s = 100;

/// Common time divisions
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) {
        Os.windows => u64,
        Os.macosx, Os.ios => darwin.mach_timebase_info_data,
        else => void,
    },
    resolution: u64,
    start_time: u64,

    //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 = switch(builtin.os) {
    //    Os.linux => linux.CLOCK_MONOTONIC_RAW,
    //    else => posix.CLOCK_MONOTONIC,
    //};
    const monotonic_clock_id = posix.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.
    const TimerError = error{
        TimerUnsupported,
        Unexpected,
    };
    pub fn start() TimerError!Timer {
        var self: Timer = undefined;

        switch (builtin.os) {
            Os.windows => {
                var freq: i64 = undefined;
                var err = windows.QueryPerformanceFrequency(&freq);
                if (err == windows.FALSE) return error.TimerUnsupported;
                self.frequency = @intCast(u64, freq);
                self.resolution = @divFloor(ns_per_s, self.frequency);

                var start_time: i64 = undefined;
                err = windows.QueryPerformanceCounter(&start_time);
                debug.assert(err != windows.FALSE);
                self.start_time = @intCast(u64, start_time);
            },
            Os.linux, Os.freebsd => {
                //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: posix.timespec = undefined;
                var result = posix.clock_getres(monotonic_clock_id, &ts);
                var errno = posix.getErrno(result);
                switch (errno) {
                    0 => {},
                    posix.EINVAL => return error.TimerUnsupported,
                    else => return std.os.unexpectedErrorPosix(errno),
                }
                self.resolution = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);

                result = posix.clock_gettime(monotonic_clock_id, &ts);
                errno = posix.getErrno(result);
                if (errno != 0) return std.os.unexpectedErrorPosix(errno);
                self.start_time = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
            },
            Os.macosx, Os.ios => {
                darwin.mach_timebase_info(&self.frequency);
                self.resolution = @divFloor(self.frequency.numer, self.frequency.denom);
                self.start_time = darwin.mach_absolute_time();
            },
            else => @compileError("Unsupported OS"),
        }
        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 switch (builtin.os) {
            Os.windows => @divFloor(clock * ns_per_s, self.frequency),
            Os.linux, Os.freebsd => clock,
            Os.macosx, Os.ios => @divFloor(clock * self.frequency.numer, self.frequency.denom),
            else => @compileError("Unsupported OS"),
        };
    }

    /// 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.read();
        self.start_time = now;
        return lap_time;
    }

    const clockNative = switch (builtin.os) {
        Os.windows => clockWindows,
        Os.linux, Os.freebsd => clockLinux,
        Os.macosx, Os.ios => clockDarwin,
        else => @compileError("Unsupported OS"),
    };

    fn clockWindows() u64 {
        var result: i64 = undefined;
        var err = windows.QueryPerformanceCounter(&result);
        debug.assert(err != windows.FALSE);
        return @intCast(u64, result);
    }

    fn clockDarwin() u64 {
        return darwin.mach_absolute_time();
    }

    fn clockLinux() u64 {
        var ts: posix.timespec = undefined;
        var result = posix.clock_gettime(monotonic_clock_id, &ts);
        debug.assert(posix.getErrno(result) == 0);
        return @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
    }
};

test "os.time.sleep" {
    sleep(1);
}

test "os.time.timestamp" {
    const ns_per_ms = (ns_per_s / ms_per_s);
    const margin = 50;

    const time_0 = milliTimestamp();
    sleep(ns_per_ms);
    const time_1 = milliTimestamp();
    const interval = time_1 - time_0;
    debug.assert(interval > 0 and interval < margin);
}

test "os.time.Timer" {
    const ns_per_ms = (ns_per_s / ms_per_s);
    const margin = ns_per_ms * 150;

    var timer = try Timer.start();
    sleep(10 * ns_per_ms);
    const time_0 = timer.read();
    debug.assert(time_0 > 0 and time_0 < margin);

    const time_1 = timer.lap();
    debug.assert(time_1 >= time_0);

    timer.reset();
    debug.assert(timer.read() < time_1);
}