authorgravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2018-04-18 13:52:25-05:00
committergravatar for tgschultz@gmail.comtgschultz <tgschultz@gmail.com> 2018-04-18 13:52:25-05:00
logc90f936eef81fce3355231c4d79ecfe40df84f7e
treeee56d95abc9d03a4610dbde3d39a113c959700c0
parent7186e92c86982950d0aa7c0c2deef9ef96bc1264

Added timestamp, high-perf. timer functions.


2 files changed, 288 insertions(+), 0 deletions(-)

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