1use crate::{builtins::PyModule, PyRef, VirtualMachine};
6
7pub use decl::time;
8
9pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
10 #[cfg(not(target_env = "msvc"))]
11 #[cfg(not(target_arch = "wasm32"))]
12 unsafe {
13 c_tzset()
14 };
15 decl::make_module(vm)
16}
17
18#[cfg(not(target_env = "msvc"))]
19#[cfg(not(target_arch = "wasm32"))]
20extern "C" {
21 #[link_name = "daylight"]
22 static c_daylight: std::ffi::c_int;
23 #[link_name = "timezone"]
25 static c_timezone: std::ffi::c_long;
26 #[link_name = "tzname"]
27 static c_tzname: [*const std::ffi::c_char; 2];
28 #[link_name = "tzset"]
29 fn c_tzset();
30}
31
32#[pymodule(name = "time", with(platform))]
33mod decl {
34 use crate::{
35 builtins::{PyStrRef, PyTypeRef},
36 function::{Either, FuncArgs, OptionalArg},
37 types::PyStructSequence,
38 PyObjectRef, PyResult, TryFromObject, VirtualMachine,
39 };
40 use chrono::{
41 naive::{NaiveDate, NaiveDateTime, NaiveTime},
42 DateTime, Datelike, Timelike,
43 };
44 use std::time::Duration;
45
46 #[allow(dead_code)]
47 pub(super) const SEC_TO_MS: i64 = 1000;
48 #[allow(dead_code)]
49 pub(super) const MS_TO_US: i64 = 1000;
50 #[allow(dead_code)]
51 pub(super) const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US;
52 #[allow(dead_code)]
53 pub(super) const US_TO_NS: i64 = 1000;
54 #[allow(dead_code)]
55 pub(super) const MS_TO_NS: i64 = MS_TO_US * US_TO_NS;
56 #[allow(dead_code)]
57 pub(super) const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS;
58 #[allow(dead_code)]
59 pub(super) const NS_TO_MS: i64 = 1000 * 1000;
60 #[allow(dead_code)]
61 pub(super) const NS_TO_US: i64 = 1000;
62
63 fn duration_since_system_now(vm: &VirtualMachine) -> PyResult<Duration> {
64 use std::time::{SystemTime, UNIX_EPOCH};
65
66 SystemTime::now()
67 .duration_since(UNIX_EPOCH)
68 .map_err(|e| vm.new_value_error(format!("Time error: {e:?}")))
69 }
70
71 #[cfg(not(any(unix, windows)))]
73 fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
74 duration_since_system_now(vm)
75 }
76
77 #[cfg(not(any(unix, windows)))]
79 fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
80 duration_since_system_now(vm)
81 }
82
83 #[cfg(not(unix))]
84 #[pyfunction]
85 fn sleep(dur: Duration) {
86 std::thread::sleep(dur);
87 }
88
89 #[cfg(not(target_os = "wasi"))]
90 #[pyfunction]
91 fn time_ns(vm: &VirtualMachine) -> PyResult<u64> {
92 Ok(duration_since_system_now(vm)?.as_nanos() as u64)
93 }
94
95 #[pyfunction]
96 pub fn time(vm: &VirtualMachine) -> PyResult<f64> {
97 _time(vm)
98 }
99
100 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
101 fn _time(vm: &VirtualMachine) -> PyResult<f64> {
102 Ok(duration_since_system_now(vm)?.as_secs_f64())
103 }
104
105 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
106 fn _time(_vm: &VirtualMachine) -> PyResult<f64> {
107 use wasm_bindgen::prelude::*;
108 #[wasm_bindgen]
109 extern "C" {
110 type Date;
111 #[wasm_bindgen(static_method_of = Date)]
112 fn now() -> f64;
113 }
114 Ok(Date::now() / 1000.0)
116 }
117
118 #[pyfunction]
119 fn monotonic(vm: &VirtualMachine) -> PyResult<f64> {
120 Ok(get_monotonic_time(vm)?.as_secs_f64())
121 }
122
123 #[pyfunction]
124 fn monotonic_ns(vm: &VirtualMachine) -> PyResult<u128> {
125 Ok(get_monotonic_time(vm)?.as_nanos())
126 }
127
128 #[pyfunction]
129 fn perf_counter(vm: &VirtualMachine) -> PyResult<f64> {
130 Ok(get_perf_time(vm)?.as_secs_f64())
131 }
132
133 #[pyfunction]
134 fn perf_counter_ns(vm: &VirtualMachine) -> PyResult<u128> {
135 Ok(get_perf_time(vm)?.as_nanos())
136 }
137
138 #[cfg(not(target_env = "msvc"))]
144 #[cfg(not(target_arch = "wasm32"))]
145 #[pyattr]
146 fn timezone(_vm: &VirtualMachine) -> std::ffi::c_long {
147 unsafe { super::c_timezone }
148 }
149
150 #[cfg(not(target_env = "msvc"))]
151 #[cfg(not(target_arch = "wasm32"))]
152 #[pyattr]
153 fn daylight(_vm: &VirtualMachine) -> std::ffi::c_int {
154 unsafe { super::c_daylight }
155 }
156
157 #[cfg(not(target_env = "msvc"))]
158 #[cfg(not(target_arch = "wasm32"))]
159 #[pyattr]
160 fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
161 use crate::builtins::tuple::IntoPyTuple;
162
163 unsafe fn to_str(s: *const std::ffi::c_char) -> String {
164 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
165 }
166 unsafe { (to_str(super::c_tzname[0]), to_str(super::c_tzname[1])) }.into_pytuple(vm)
167 }
168
169 fn pyobj_to_date_time(
170 value: Either<f64, i64>,
171 vm: &VirtualMachine,
172 ) -> PyResult<DateTime<chrono::offset::Utc>> {
173 let timestamp = match value {
174 Either::A(float) => {
175 let secs = float.trunc() as i64;
176 let nsecs = (float.fract() * 1e9) as u32;
177 DateTime::<chrono::offset::Utc>::from_timestamp(secs, nsecs)
178 }
179 Either::B(int) => DateTime::<chrono::offset::Utc>::from_timestamp(int, 0),
180 };
181 timestamp.ok_or_else(|| {
182 vm.new_overflow_error("timestamp out of range for platform time_t".to_owned())
183 })
184 }
185
186 impl OptionalArg<Either<f64, i64>> {
187 fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
189 Ok(match self {
190 OptionalArg::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(),
191 OptionalArg::Missing => chrono::offset::Local::now().naive_local(),
192 })
193 }
194
195 fn naive_or_utc(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
196 Ok(match self {
197 OptionalArg::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(),
198 OptionalArg::Missing => chrono::offset::Utc::now().naive_utc(),
199 })
200 }
201 }
202
203 impl OptionalArg<PyStructTime> {
204 fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
205 Ok(match self {
206 OptionalArg::Present(t) => t.to_date_time(vm)?,
207 OptionalArg::Missing => chrono::offset::Local::now().naive_local(),
208 })
209 }
210 }
211
212 #[pyfunction]
214 fn gmtime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<PyStructTime> {
215 let instant = secs.naive_or_utc(vm)?;
216 Ok(PyStructTime::new(vm, instant, 0))
217 }
218
219 #[pyfunction]
220 fn localtime(
221 secs: OptionalArg<Either<f64, i64>>,
222 vm: &VirtualMachine,
223 ) -> PyResult<PyStructTime> {
224 let instant = secs.naive_or_local(vm)?;
225 Ok(PyStructTime::new(vm, instant, -1))
228 }
229
230 #[pyfunction]
231 fn mktime(t: PyStructTime, vm: &VirtualMachine) -> PyResult<f64> {
232 let datetime = t.to_date_time(vm)?;
233 let seconds_since_epoch = datetime.and_utc().timestamp() as f64;
234 Ok(seconds_since_epoch)
235 }
236
237 const CFMT: &str = "%a %b %e %H:%M:%S %Y";
238
239 #[pyfunction]
240 fn asctime(t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult {
241 let instant = t.naive_or_local(vm)?;
242 let formatted_time = instant.format(CFMT).to_string();
243 Ok(vm.ctx.new_str(formatted_time).into())
244 }
245
246 #[pyfunction]
247 fn ctime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<String> {
248 let instant = secs.naive_or_local(vm)?;
249 Ok(instant.format(CFMT).to_string())
250 }
251
252 #[pyfunction]
253 fn strftime(format: PyStrRef, t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult {
254 use std::fmt::Write;
255
256 let instant = t.naive_or_local(vm)?;
257 let mut formatted_time = String::new();
258
259 write!(&mut formatted_time, "{}", instant.format(format.as_str()))
265 .unwrap_or_else(|_| formatted_time = format.to_string());
266 Ok(vm.ctx.new_str(formatted_time).into())
267 }
268
269 #[pyfunction]
270 fn strptime(
271 string: PyStrRef,
272 format: OptionalArg<PyStrRef>,
273 vm: &VirtualMachine,
274 ) -> PyResult<PyStructTime> {
275 let format = format.as_ref().map_or("%a %b %H:%M:%S %Y", |s| s.as_str());
276 let instant = NaiveDateTime::parse_from_str(string.as_str(), format)
277 .map_err(|e| vm.new_value_error(format!("Parse error: {e:?}")))?;
278 Ok(PyStructTime::new(vm, instant, -1))
279 }
280
281 #[cfg(not(any(
282 windows,
283 target_vendor = "apple",
284 target_os = "android",
285 target_os = "dragonfly",
286 target_os = "freebsd",
287 target_os = "linux",
288 target_os = "fuchsia",
289 target_os = "emscripten",
290 )))]
291 fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
292 Err(vm.new_not_implemented_error("thread time unsupported in this system".to_owned()))
293 }
294
295 #[pyfunction]
296 fn thread_time(vm: &VirtualMachine) -> PyResult<f64> {
297 Ok(get_thread_time(vm)?.as_secs_f64())
298 }
299
300 #[pyfunction]
301 fn thread_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
302 Ok(get_thread_time(vm)?.as_nanos() as u64)
303 }
304
305 #[cfg(any(windows, all(target_arch = "wasm32", target_os = "emscripten")))]
306 pub(super) fn time_muldiv(ticks: i64, mul: i64, div: i64) -> u64 {
307 let intpart = ticks / div;
308 let ticks = ticks % div;
309 let remaining = (ticks * mul) / div;
310 (intpart * mul + remaining) as u64
311 }
312
313 #[cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
314 fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
315 let t: libc::tms = unsafe {
316 let mut t = std::mem::MaybeUninit::uninit();
317 if libc::times(t.as_mut_ptr()) == -1 {
318 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
319 }
320 t.assume_init()
321 };
322 let freq = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
323
324 Ok(Duration::from_nanos(
325 time_muldiv(t.tms_utime, SEC_TO_NS, freq) + time_muldiv(t.tms_stime, SEC_TO_NS, freq),
326 ))
327 }
328
329 #[cfg(all(target_arch = "wasm32", target_os = "wasi"))]
331 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
332 let time: libc::timespec = unsafe {
333 let mut time = std::mem::MaybeUninit::uninit();
334 if libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, time.as_mut_ptr()) == -1 {
335 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
336 }
337 time.assume_init()
338 };
339 Ok(Duration::new(time.tv_sec as u64, time.tv_nsec as u32))
340 }
341
342 #[cfg(not(any(
343 windows,
344 target_os = "macos",
345 target_os = "android",
346 target_os = "dragonfly",
347 target_os = "freebsd",
348 target_os = "linux",
349 target_os = "illumos",
350 target_os = "netbsd",
351 target_os = "solaris",
352 target_os = "openbsd",
353 target_os = "redox",
354 all(target_arch = "wasm32", not(target_os = "unknown"))
355 )))]
356 fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
357 Err(vm.new_not_implemented_error("process time unsupported in this system".to_owned()))
358 }
359
360 #[pyfunction]
361 fn process_time(vm: &VirtualMachine) -> PyResult<f64> {
362 Ok(get_process_time(vm)?.as_secs_f64())
363 }
364
365 #[pyfunction]
366 fn process_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
367 Ok(get_process_time(vm)?.as_nanos() as u64)
368 }
369
370 #[pyattr]
371 #[pyclass(name = "struct_time")]
372 #[derive(PyStructSequence, TryIntoPyStructSequence)]
373 #[allow(dead_code)]
374 struct PyStructTime {
375 tm_year: PyObjectRef,
376 tm_mon: PyObjectRef,
377 tm_mday: PyObjectRef,
378 tm_hour: PyObjectRef,
379 tm_min: PyObjectRef,
380 tm_sec: PyObjectRef,
381 tm_wday: PyObjectRef,
382 tm_yday: PyObjectRef,
383 tm_isdst: PyObjectRef,
384 }
385
386 impl std::fmt::Debug for PyStructTime {
387 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
388 write!(f, "struct_time()")
389 }
390 }
391
392 #[pyclass(with(PyStructSequence))]
393 impl PyStructTime {
394 fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
395 PyStructTime {
396 tm_year: vm.ctx.new_int(tm.year()).into(),
397 tm_mon: vm.ctx.new_int(tm.month()).into(),
398 tm_mday: vm.ctx.new_int(tm.day()).into(),
399 tm_hour: vm.ctx.new_int(tm.hour()).into(),
400 tm_min: vm.ctx.new_int(tm.minute()).into(),
401 tm_sec: vm.ctx.new_int(tm.second()).into(),
402 tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
403 tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
404 tm_isdst: vm.ctx.new_int(isdst).into(),
405 }
406 }
407
408 fn to_date_time(&self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
409 let invalid_overflow =
410 || vm.new_overflow_error("mktime argument out of range".to_owned());
411 let invalid_value = || vm.new_value_error("invalid struct_time parameter".to_owned());
412
413 macro_rules! field {
414 ($field:ident) => {
415 self.$field.clone().try_into_value(vm)?
416 };
417 }
418 let dt = NaiveDateTime::new(
419 NaiveDate::from_ymd_opt(field!(tm_year), field!(tm_mon), field!(tm_mday))
420 .ok_or_else(invalid_value)?,
421 NaiveTime::from_hms_opt(field!(tm_hour), field!(tm_min), field!(tm_sec))
422 .ok_or_else(invalid_overflow)?,
423 );
424 Ok(dt)
425 }
426
427 #[pyslot]
428 fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
429 let seq = args.bind(vm)?;
431 Ok(vm.new_pyobj(Self::try_from_object(vm, seq)?))
432 }
433 }
434
435 #[allow(unused_imports)]
436 use super::platform::*;
437}
438
439#[cfg(unix)]
440#[pymodule(sub)]
441mod platform {
442 #[allow(unused_imports)]
443 use super::decl::{SEC_TO_NS, US_TO_NS};
444 #[cfg_attr(target_os = "macos", allow(unused_imports))]
445 use crate::{
446 builtins::{PyNamespace, PyStrRef},
447 convert::IntoPyException,
448 PyObject, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine,
449 };
450 use nix::{sys::time::TimeSpec, time::ClockId};
451 use std::time::Duration;
452
453 #[cfg(target_os = "solaris")]
454 #[pyattr]
455 use libc::CLOCK_HIGHRES;
456 #[cfg(not(any(
457 target_os = "illumos",
458 target_os = "netbsd",
459 target_os = "solaris",
460 target_os = "openbsd",
461 )))]
462 #[pyattr]
463 use libc::CLOCK_PROCESS_CPUTIME_ID;
464 #[cfg(not(any(
465 target_os = "illumos",
466 target_os = "netbsd",
467 target_os = "solaris",
468 target_os = "openbsd",
469 target_os = "redox",
470 )))]
471 #[pyattr]
472 use libc::CLOCK_THREAD_CPUTIME_ID;
473 #[cfg(target_os = "linux")]
474 #[pyattr]
475 use libc::{CLOCK_BOOTTIME, CLOCK_MONOTONIC_RAW, CLOCK_TAI};
476 #[pyattr]
477 use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
478 #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly"))]
479 #[pyattr]
480 use libc::{CLOCK_PROF, CLOCK_UPTIME};
481
482 impl<'a> TryFromBorrowedObject<'a> for ClockId {
483 fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
484 obj.try_to_value(vm).map(ClockId::from_raw)
485 }
486 }
487
488 fn get_clock_time(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<Duration> {
489 let ts = nix::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm))?;
490 Ok(ts.into())
491 }
492
493 #[pyfunction]
494 fn clock_gettime(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
495 get_clock_time(clk_id, vm).map(|d| d.as_secs_f64())
496 }
497
498 #[pyfunction]
499 fn clock_gettime_ns(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<u128> {
500 get_clock_time(clk_id, vm).map(|d| d.as_nanos())
501 }
502
503 #[cfg(not(target_os = "redox"))]
504 #[pyfunction]
505 fn clock_getres(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
506 let ts = nix::time::clock_getres(clk_id).map_err(|e| e.into_pyexception(vm))?;
507 Ok(Duration::from(ts).as_secs_f64())
508 }
509
510 #[cfg(not(target_os = "redox"))]
511 #[cfg(not(target_vendor = "apple"))]
512 fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
513 nix::time::clock_settime(clk_id, timespec).map_err(|e| e.into_pyexception(vm))
514 }
515
516 #[cfg(not(target_os = "redox"))]
517 #[cfg(target_os = "macos")]
518 fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
519 let ret = unsafe { libc::clock_settime(clk_id.as_raw(), timespec.as_ref()) };
521 nix::Error::result(ret)
522 .map(drop)
523 .map_err(|e| e.into_pyexception(vm))
524 }
525
526 #[cfg(not(target_os = "redox"))]
527 #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
528 #[pyfunction]
529 fn clock_settime(clk_id: ClockId, time: Duration, vm: &VirtualMachine) -> PyResult<()> {
530 set_clock_time(clk_id, time.into(), vm)
531 }
532
533 #[cfg(not(target_os = "redox"))]
534 #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
535 #[pyfunction]
536 fn clock_settime_ns(clk_id: ClockId, time: libc::time_t, vm: &VirtualMachine) -> PyResult<()> {
537 let ts = Duration::from_nanos(time as _).into();
538 set_clock_time(clk_id, ts, vm)
539 }
540
541 #[cfg(any(
543 target_os = "macos",
544 target_os = "android",
545 target_os = "dragonfly",
546 target_os = "freebsd",
547 target_os = "fuchsia",
548 target_os = "emscripten",
549 target_os = "linux",
550 ))]
551 #[pyfunction]
552 fn get_clock_info(name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
553 let (adj, imp, mono, res) = match name.as_ref() {
554 "monotonic" | "perf_counter" => (
555 false,
556 "time.clock_gettime(CLOCK_MONOTONIC)",
557 true,
558 clock_getres(ClockId::CLOCK_MONOTONIC, vm)?,
559 ),
560 "process_time" => (
561 false,
562 "time.clock_gettime(CLOCK_PROCESS_CPUTIME_ID)",
563 true,
564 clock_getres(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)?,
565 ),
566 "thread_time" => (
567 false,
568 "time.clock_gettime(CLOCK_THREAD_CPUTIME_ID)",
569 true,
570 clock_getres(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)?,
571 ),
572 "time" => (
573 true,
574 "time.clock_gettime(CLOCK_REALTIME)",
575 false,
576 clock_getres(ClockId::CLOCK_REALTIME, vm)?,
577 ),
578 _ => return Err(vm.new_value_error("unknown clock".to_owned())),
579 };
580
581 Ok(py_namespace!(vm, {
582 "implementation" => vm.new_pyobj(imp),
583 "monotonic" => vm.ctx.new_bool(mono),
584 "adjustable" => vm.ctx.new_bool(adj),
585 "resolution" => vm.ctx.new_float(res),
586 }))
587 }
588
589 #[cfg(not(any(
590 target_os = "macos",
591 target_os = "android",
592 target_os = "dragonfly",
593 target_os = "freebsd",
594 target_os = "fuchsia",
595 target_os = "emscripten",
596 target_os = "linux",
597 )))]
598 #[pyfunction]
599 fn get_clock_info(_name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
600 Err(vm.new_not_implemented_error("get_clock_info unsupported on this system".to_owned()))
601 }
602
603 pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
604 get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
605 }
606
607 pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
608 get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
609 }
610
611 #[pyfunction]
612 fn sleep(dur: Duration, vm: &VirtualMachine) -> PyResult<()> {
613 let ts = TimeSpec::from(dur);
616 let res = unsafe { libc::nanosleep(ts.as_ref(), std::ptr::null_mut()) };
617 let interrupted = res == -1 && nix::errno::errno() == libc::EINTR;
618
619 if interrupted {
620 vm.check_signals()?;
621 }
622
623 Ok(())
624 }
625
626 #[cfg(not(any(
627 target_os = "illumos",
628 target_os = "netbsd",
629 target_os = "openbsd",
630 target_os = "redox"
631 )))]
632 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
633 get_clock_time(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)
634 }
635
636 #[cfg(target_os = "solaris")]
637 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
638 Ok(Duration::from_nanos(unsafe { libc::gethrvtime() }))
639 }
640
641 #[cfg(not(any(
642 target_os = "illumos",
643 target_os = "netbsd",
644 target_os = "solaris",
645 target_os = "openbsd",
646 )))]
647 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
648 get_clock_time(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)
649 }
650
651 #[cfg(any(
652 target_os = "illumos",
653 target_os = "netbsd",
654 target_os = "solaris",
655 target_os = "openbsd",
656 ))]
657 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
658 use nix::sys::resource::{getrusage, UsageWho};
659 fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult<i64> {
660 (|tv: libc::timeval| {
661 let t = tv.tv_sec.checked_mul(SEC_TO_NS)?;
662 let u = (tv.tv_usec as i64).checked_mul(US_TO_NS)?;
663 t.checked_add(u)
664 })(tv)
665 .ok_or_else(|| {
666 vm.new_overflow_error("timestamp too large to convert to i64".to_owned())
667 })
668 }
669 let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?;
670 let utime = from_timeval(ru.user_time().into(), vm)?;
671 let stime = from_timeval(ru.system_time().into(), vm)?;
672
673 Ok(Duration::from_nanos((utime + stime) as u64))
674 }
675}
676
677#[cfg(windows)]
678#[pymodule]
679mod platform {
680 use super::decl::{time_muldiv, MS_TO_NS, SEC_TO_NS};
681 use crate::{
682 builtins::{PyNamespace, PyStrRef},
683 stdlib::os::errno_err,
684 PyRef, PyResult, VirtualMachine,
685 };
686 use std::time::Duration;
687 use windows_sys::Win32::{
688 Foundation::FILETIME,
689 System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency},
690 System::SystemInformation::{GetSystemTimeAdjustment, GetTickCount64},
691 System::Threading::{GetCurrentProcess, GetCurrentThread, GetProcessTimes, GetThreadTimes},
692 };
693
694 fn u64_from_filetime(time: FILETIME) -> u64 {
695 let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime];
696 unsafe { std::mem::transmute(large) }
697 }
698
699 fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult<i64> {
700 let frequency = unsafe {
701 let mut freq = std::mem::MaybeUninit::uninit();
702 if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 {
703 return Err(errno_err(vm));
704 }
705 freq.assume_init()
706 };
707
708 if frequency < 1 {
709 Err(vm.new_runtime_error("invalid QueryPerformanceFrequency".to_owned()))
710 } else if frequency > i64::MAX / SEC_TO_NS {
711 Err(vm.new_overflow_error("QueryPerformanceFrequency is too large".to_owned()))
712 } else {
713 Ok(frequency)
714 }
715 }
716
717 fn global_frequency(vm: &VirtualMachine) -> PyResult<i64> {
718 rustpython_common::static_cell! {
719 static FREQUENCY: PyResult<i64>;
720 };
721 FREQUENCY
722 .get_or_init(|| win_perf_counter_frequency(vm))
723 .clone()
724 }
725
726 pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
727 let ticks = unsafe {
728 let mut performance_count = std::mem::MaybeUninit::uninit();
729 QueryPerformanceCounter(performance_count.as_mut_ptr());
730 performance_count.assume_init()
731 };
732
733 Ok(Duration::from_nanos(time_muldiv(
734 ticks,
735 SEC_TO_NS,
736 global_frequency(vm)?,
737 )))
738 }
739
740 fn get_system_time_adjustment(vm: &VirtualMachine) -> PyResult<u32> {
741 let mut _time_adjustment = std::mem::MaybeUninit::uninit();
742 let mut time_increment = std::mem::MaybeUninit::uninit();
743 let mut _is_time_adjustment_disabled = std::mem::MaybeUninit::uninit();
744 let time_increment = unsafe {
745 if GetSystemTimeAdjustment(
746 _time_adjustment.as_mut_ptr(),
747 time_increment.as_mut_ptr(),
748 _is_time_adjustment_disabled.as_mut_ptr(),
749 ) == 0
750 {
751 return Err(errno_err(vm));
752 }
753 time_increment.assume_init()
754 };
755 Ok(time_increment)
756 }
757
758 pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
759 let ticks = unsafe { GetTickCount64() };
760
761 Ok(Duration::from_nanos(
762 (ticks as i64).checked_mul(MS_TO_NS).ok_or_else(|| {
763 vm.new_overflow_error("timestamp too large to convert to i64".to_owned())
764 })? as u64,
765 ))
766 }
767
768 #[pyfunction]
769 fn get_clock_info(name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
770 let (adj, imp, mono, res) = match name.as_ref() {
771 "monotonic" => (
772 false,
773 "GetTickCount64()",
774 true,
775 get_system_time_adjustment(vm)? as f64 * 1e-7,
776 ),
777 "perf_counter" => (
778 false,
779 "QueryPerformanceCounter()",
780 true,
781 1.0 / (global_frequency(vm)? as f64),
782 ),
783 "process_time" => (false, "GetProcessTimes()", true, 1e-7),
784 "thread_time" => (false, "GetThreadTimes()", true, 1e-7),
785 "time" => (
786 true,
787 "GetSystemTimeAsFileTime()",
788 false,
789 get_system_time_adjustment(vm)? as f64 * 1e-7,
790 ),
791 _ => return Err(vm.new_value_error("unknown clock".to_owned())),
792 };
793
794 Ok(py_namespace!(vm, {
795 "implementation" => vm.new_pyobj(imp),
796 "monotonic" => vm.ctx.new_bool(mono),
797 "adjustable" => vm.ctx.new_bool(adj),
798 "resolution" => vm.ctx.new_float(res),
799 }))
800 }
801
802 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
803 let (kernel_time, user_time) = unsafe {
804 let mut _creation_time = std::mem::MaybeUninit::uninit();
805 let mut _exit_time = std::mem::MaybeUninit::uninit();
806 let mut kernel_time = std::mem::MaybeUninit::uninit();
807 let mut user_time = std::mem::MaybeUninit::uninit();
808
809 let thread = GetCurrentThread();
810 if GetThreadTimes(
811 thread,
812 _creation_time.as_mut_ptr(),
813 _exit_time.as_mut_ptr(),
814 kernel_time.as_mut_ptr(),
815 user_time.as_mut_ptr(),
816 ) == 0
817 {
818 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
819 }
820 (kernel_time.assume_init(), user_time.assume_init())
821 };
822 let k_time = u64_from_filetime(kernel_time);
823 let u_time = u64_from_filetime(user_time);
824 Ok(Duration::from_nanos((k_time + u_time) * 100))
825 }
826
827 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
828 let (kernel_time, user_time) = unsafe {
829 let mut _creation_time = std::mem::MaybeUninit::uninit();
830 let mut _exit_time = std::mem::MaybeUninit::uninit();
831 let mut kernel_time = std::mem::MaybeUninit::uninit();
832 let mut user_time = std::mem::MaybeUninit::uninit();
833
834 let process = GetCurrentProcess();
835 if GetProcessTimes(
836 process,
837 _creation_time.as_mut_ptr(),
838 _exit_time.as_mut_ptr(),
839 kernel_time.as_mut_ptr(),
840 user_time.as_mut_ptr(),
841 ) == 0
842 {
843 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
844 }
845 (kernel_time.assume_init(), user_time.assume_init())
846 };
847 let k_time = u64_from_filetime(kernel_time);
848 let u_time = u64_from_filetime(user_time);
849 Ok(Duration::from_nanos((k_time + u_time) * 100))
850 }
851}
852
853#[cfg(not(any(unix, windows)))]
855#[pymodule(sub)]
856mod platform {}