Skip to content

Commit

Permalink
Refactor oshd_clock
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hoot-w00t committed Mar 5, 2024
1 parent 2f8dbae commit 0fabebe
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
40 changes: 37 additions & 3 deletions include/oshd_clock.h
Original file line number Diff line number Diff line change
@@ -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 <time.h>
#include <sys/time.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>

// 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) \
Expand Down
95 changes: 93 additions & 2 deletions src/oshd_clock.c
Original file line number Diff line number Diff line change
@@ -1,24 +1,115 @@
#include "oshd_clock.h"
#include "macros.h"
#include "macros_assert.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

__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;
}

0 comments on commit 0fabebe

Please sign in to comment.