diff --git a/src/core/os.c b/src/core/os.c index 64a03470..bf5673b7 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1278,14 +1278,32 @@ JANET_CORE_FN(os_time, } JANET_CORE_FN(os_clock, - "(os/clock)", - "Return the number of whole + fractional seconds since some fixed point in time. The clock " - "is guaranteed to be non-decreasing in real time.") { + "(os/clock &opt source)", + "Return the number of whole + fractional seconds of the requested clock source.\n\n" + "The `source` argument selects the clock source to use, when not specified the default " + "is `:realtime`:\n" + "- :realtime: Return the real (i.e., wall-clock) time. This clock is affected by discontinuous " + " jumps in the system time\n" + "- :monotonic: Return the number of whole + fractional seconds since some fixed point in " + " time. The clock is guaranteed to be non-decreasing in real time.\n" + "- :cputime: Return the CPU time consumed by this process (i.e. all threads in the process)\n") { janet_sandbox_assert(JANET_SANDBOX_HRTIME); - janet_fixarity(argc, 0); - (void) argv; + janet_arity(argc, 0, 1); + enum JanetTimeSource source = JANET_TIME_REALTIME; + if (argc == 1) { + JanetKeyword sourcestr = janet_getkeyword(argv, 0); + if (janet_cstrcmp(sourcestr, "realtime") == 0) { + source = JANET_TIME_REALTIME; + } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { + source = JANET_TIME_MONOTONIC; + } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { + source = JANET_TIME_CPUTIME; + } else { + janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); + } + } struct timespec tv; - if (janet_gettime(&tv)) janet_panic("could not get time"); + if (janet_gettime(&tv, source)) janet_panic("could not get time"); double dtime = tv.tv_sec + (tv.tv_nsec / 1E9); return janet_wrap_number(dtime); } diff --git a/src/core/util.c b/src/core/util.c index 3c50bc94..a699965e 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -875,34 +875,73 @@ int32_t janet_sorted_keys(const JanetKV *dict, int32_t cap, int32_t *index_buffe /* Clock shims for various platforms */ #ifdef JANET_GETTIME #ifdef JANET_WINDOWS -int janet_gettime(struct timespec *spec) { - FILETIME ftime; - GetSystemTimeAsFileTime(&ftime); - int64_t wintime = (int64_t)(ftime.dwLowDateTime) | ((int64_t)(ftime.dwHighDateTime) << 32); - /* Windows epoch is January 1, 1601 apparently */ - wintime -= 116444736000000000LL; - spec->tv_sec = wintime / 10000000LL; - /* Resolution is 100 nanoseconds. */ - spec->tv_nsec = wintime % 10000000LL * 100; +#include +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + if (source == JANET_TIME_REALTIME) { + FILETIME ftime; + GetSystemTimeAsFileTime(&ftime); + int64_t wintime = (int64_t)(ftime.dwLowDateTime) | ((int64_t)(ftime.dwHighDateTime) << 32); + /* Windows epoch is January 1, 1601 apparently */ + wintime -= 116444736000000000LL; + spec->tv_sec = wintime / 10000000LL; + /* Resolution is 100 nanoseconds. */ + spec->tv_nsec = wintime % 10000000LL * 100; + } else if (source == JANET_TIME_MONOTONIC) { + LARGE_INTEGER count; + LARGE_INTEGER perf_freq; + QueryPerformanceCounter(&count); + QueryPerformanceFrequency(&perf_freq); + spec->tv_sec = count.QuadPart / perf_freq.QuadPart; + spec->tv_nsec = (long)((count.QuadPart % perf_freq.QuadPart) * 1000000000 / perf_freq.QuadPart); + } else if (source == JANET_TIME_CPUTIME) { + FILETIME creationTime, exitTime, kernelTime, userTime; + GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime); + int64_t tmp = ((int64_t)userTime.dwHighDateTime << 32) + userTime.dwLowDateTime; + spec->tv_sec = tmp / 10000000LL; + spec->tv_nsec = tmp % 10000000LL * 100; + } return 0; } /* clock_gettime() wasn't available on Mac until 10.12. */ #elif defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_12) #include #include -int janet_gettime(struct timespec *spec) { - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - spec->tv_sec = mts.tv_sec; - spec->tv_nsec = mts.tv_nsec; +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + if (source == JANET_TIME_REALTIME) { + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + spec->tv_sec = mts.tv_sec; + spec->tv_nsec = mts.tv_nsec; + } else if (source == JANET_TIME_MONOTONIC) { + clock_serv_t cclock; + int nsecs; + mach_msg_type_number_t count; + host_get_clock_service(mach_host_self(), clock, &cclock); + clock_get_attributes(cclock, CLOCK_GET_TIME_RES, (clock_attr_t)&nsecs, &count); + mach_port_deallocate(mach_task_self(), cclock); + clock_getres(CLOCK_MONOTONIC, spec); + } + if (source == JANET_TIME_CPUTIME) { + clock_t tmp = clock(); + spec->tv_sec = tmp; + spec->tv_nsec = (tmp - spec->tv_sec) * 1.0e9; + } return 0; } #else -int janet_gettime(struct timespec *spec) { - return clock_gettime(CLOCK_REALTIME, spec); +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + clockid_t cid = JANET_TIME_REALTIME; + if (source == JANET_TIME_REALTIME) { + cid = CLOCK_REALTIME; + } else if (source == JANET_TIME_MONOTONIC) { + cid = CLOCK_MONOTONIC; + } else if (source == JANET_TIME_CPUTIME) { + cid = CLOCK_PROCESS_CPUTIME_ID; + } + return clock_gettime(cid, spec); } #endif #endif diff --git a/src/core/util.h b/src/core/util.h index b8f9cc90..3fe7b858 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -126,7 +126,12 @@ void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetReg /* Clock gettime */ #ifdef JANET_GETTIME -int janet_gettime(struct timespec *spec); +enum JanetTimeSource { + JANET_TIME_REALTIME, + JANET_TIME_MONOTONIC, + JANET_TIME_CPUTIME +}; +int janet_gettime(struct timespec *spec, enum JanetTimeSource source); #endif /* strdup */ diff --git a/test/suite0007.janet b/test/suite0007.janet index c60a9678..e59b049c 100644 --- a/test/suite0007.janet +++ b/test/suite0007.janet @@ -333,4 +333,29 @@ (assert (pos? (length (gensym))) "gensym not empty, regression #753") + +# os/clock. These tests might prove fragile under CI because they +# rely on measured time. We'll see. + +(defmacro measure-time [clocks & body] + (def [t1 t2] [(gensym) (gensym)]) + ~(do + (def ,t1 (map |(os/clock $) ,clocks)) + ,;body + (def ,t2 (map |(os/clock $) ,clocks)) + (zipcoll ,clocks (map |(- ;$) (map tuple ,t2 ,t1)))) +) + +# Spin for 0.1 seconds +(def dt (measure-time [:realtime :monotonic :cputime] + (def t1 (os/clock :monotonic)) + (while (< (- (os/clock :monotonic) t1) 0.1) true))) +(assert (> (dt :monotonic) 0.10)) +(assert (> (dt :cputime) 0.05)) + +# Sleep for 0.1 seconds +(def dt (measure-time [:realtime :monotonic :cputime] (os/sleep 0.1))) +(assert (> (dt :monotonic) 0.10)) +(assert (< (dt :cputime) 0.05)) + (end-suite)