From 0fabebed0bcad04f319e32d8bc13cce08ab2e976 Mon Sep 17 00:00:00 2001 From: hoot-w00t <42416556+hoot-w00t@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:47:26 +0100 Subject: [PATCH] Refactor oshd_clock Add oshd_getepoch() Don't define OSHD_CLOCK_MONOTONIC if underlying macro isn't defined Use gettimeofday() if clock_gettime() isn't available Add TAI64N struct Add timespecadd() macro --- CMakeLists.txt | 2 + include/oshd_clock.h | 40 +++++++++++++++++-- src/oshd_clock.c | 95 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c19bf7..727e2ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -411,6 +411,8 @@ endif() add_have_symbol(CMAKE_C_FLAGS "memset_s" "string.h" HAVE_MEMSET_S) add_have_symbol(CMAKE_C_FLAGS "memset_explicit" "string.h" HAVE_MEMSET_EXPLICIT) add_have_symbol(CMAKE_C_FLAGS "explicit_bzero" "string.h" HAVE_EXPLICIT_BZERO) +add_have_symbol(CMAKE_C_FLAGS "gettimeofday" "sys/time.h" HAVE_GETTIMEOFDAY) +add_have_symbol(CMAKE_C_FLAGS "clock_gettime" "time.h" HAVE_CLOCK_GETTIME) # If no build type was specified we will use the default set(default_build_type "Debug") diff --git a/include/oshd_clock.h b/include/oshd_clock.h index 63e014e..be989bf 100644 --- a/include/oshd_clock.h +++ b/include/oshd_clock.h @@ -1,23 +1,57 @@ -#ifndef _OSH_OSHD_CLOCK_H -#define _OSH_OSHD_CLOCK_H +#ifndef OSH_OSHD_CLOCK_H_ +#define OSH_OSHD_CLOCK_H_ #include #include #include +#include +#include // Used to cast time_t when printing it using printf() typedef int64_t pri_time_t; #define PRI_TIME_T PRId64 +// https://cr.yp.to/libtai/tai64.html +#ifndef NSEC_MAX + // Nanoseconds in a second + #define NSEC_MAX (1000000000) +#endif + +#define TAI64_SEC_ZERO UINT64_C(0x4000000000000000) +#define NSEC_VALID(tv_nsec) (tv_nsec >= 0 && tv_nsec < NSEC_MAX) + +// Portable TAI64N 96-bit timestamp +struct __attribute__((packed)) tai64n { + uint64_t tv_sec; + uint32_t tv_nsec; +}; + #if defined(CLOCK_BOOTTIME) #define OSHD_CLOCK_MONOTONIC CLOCK_BOOTTIME -#else +#elif defined(CLOCK_MONOTONIC) #define OSHD_CLOCK_MONOTONIC CLOCK_MONOTONIC #endif +void oshd_getepoch(struct timespec *ts); + void oshd_gettime(struct timespec *ts); void oshd_gettime_delay(struct timespec *ts, time_t delay_s); +bool timespec_to_tai64n(struct tai64n *tai64n, const struct timespec *ts); + +#ifndef timespecadd +// timeradd for timespec structures +#define timespecadd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec + (b)->tv_nsec; \ + if ((result)->tv_nsec >= 1000000000) { \ + ++(result)->tv_sec; \ + (result)->tv_nsec -= 1000000000; \ + } \ + } while (0) +#endif // timespecadd + #ifndef timespecsub // timersub for timespec structures #define timespecsub(a, b, result) \ diff --git a/src/oshd_clock.c b/src/oshd_clock.c index 02cb637..bc30edc 100644 --- a/src/oshd_clock.c +++ b/src/oshd_clock.c @@ -1,24 +1,115 @@ #include "oshd_clock.h" +#include "macros.h" +#include "macros_assert.h" #include #include #include #include #include +#include + +__attribute__((unused)) +static void getepoch_nofail(struct timespec *ts) +{ +#if defined(HAVE_GETTIMEOFDAY) + struct timeval tv; + + gettimeofday(&tv, NULL); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; +#else + #error "getepoch_nofail() needs gettimeofday()" +#endif +} + +// Get current time since epoch +void oshd_getepoch(struct timespec *ts) +{ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) + if (clock_gettime(CLOCK_REALTIME, ts) != 0) { + fprintf(stderr, "%s:%i:%s: %s\n", __FILE__, __LINE__, __func__, strerror(errno)); + getepoch_nofail(ts); + } +#else + getepoch_nofail(ts); +#endif +} + +__attribute__((unused)) +static void gettime_monotonic_from_epoch(struct timespec *ts) +{ + static pthread_mutex_t static_lock = PTHREAD_MUTEX_INITIALIZER; + static bool initialized = false; + static struct timespec last_epoch = {0}; + static struct timespec monotonic = {0}; + struct timespec now; + struct timespec diff; + + pthread_mutex_lock(&static_lock); + oshd_getepoch(&now); + + // Initialize last timestamp with the first one we get + if (!initialized) { + initialized = true; + last_epoch = now; + } + + // Calculate how much time has elapsed + timespecsub(&now, &last_epoch, &diff); + if (diff.tv_sec < 0) { + // The epoch went back in time, leave the monotonic timestamp unchanged + fprintf(stderr, + "%s:%i:%s: epoch rolled back (%" PRI_TIME_T ".%09" PRI_TIME_T " seconds)\n", + __FILE__, __LINE__, __func__, (pri_time_t) diff.tv_sec, (pri_time_t) diff.tv_nsec); + } else { + // Increase the monotonic timestamp by the elapsed time + timespecadd(&monotonic, &diff, &monotonic); + } + + // Always remember the current epoch to measure elapsed time since last call + last_epoch = now; + *ts = monotonic; + + pthread_mutex_unlock(&static_lock); +} // Stores the current elapsed time in *ts -// Aborts on error +// Can abort on error void oshd_gettime(struct timespec *ts) { + // Different monotonic sources return incompatible timestamps as they track + // time differently (only one can be used, can't use a fallback if clock_gettime() + // fails) + +#if defined(HAVE_CLOCK_GETTIME) && defined(OSHD_CLOCK_MONOTONIC) if (clock_gettime(OSHD_CLOCK_MONOTONIC, ts) != 0) { fprintf(stderr, "%s:%i:%s: %s\n", __FILE__, __LINE__, __func__, strerror(errno)); abort(); } +#else + gettime_monotonic_from_epoch(ts); +#endif + + assert(ts->tv_sec >= 0); + assert(NSEC_VALID(ts->tv_nsec)); } // Stores the current elapsed time + delay (in seconds) in *ts -// Aborts on error +// Can abort on error void oshd_gettime_delay(struct timespec *ts, time_t delay_s) { oshd_gettime(ts); ts->tv_sec += delay_s; } + +// Returns false if timespec value is invalid +// https://cr.yp.to/libtai/tai64.html +bool timespec_to_tai64n(struct tai64n *tai64n, const struct timespec *ts) +{ + if (!NSEC_VALID(ts->tv_nsec)) + return false; + + tai64n->tv_sec = TAI64_SEC_ZERO + ts->tv_sec; + tai64n->tv_nsec = ts->tv_nsec; + return true; +}