
// Program to check how fast clock_gettime runs, and what resolution it gives.
// Morten Hauke Solvang, 2024

// For testing on my native architecture (x86_64):
//   clang measure_gettime.c -o measure_gettime -O2

// For testing on aarch64:
//   zig cc measure_gettime.c -o measure_gettime -target aarch64-linux-musl -O2
// Ref: https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html

#include <time.h>
#include <stdio.h>
#include <stdint.h>

#define alen(xs) (sizeof(xs)/sizeof(xs[0]))

static uint64_t
timespec_to_ns(struct timespec timespec)
{
    return (uint64_t)timespec.tv_sec*1000000000 + (uint64_t)timespec.tv_nsec;
}

static uint64_t
read_clock_ns(clockid_t id)
{
    struct timespec timespec = {0};
    clock_gettime(id, &timespec);
    return timespec_to_ns(timespec);
}

static uint64_t
read_resolution_ns(clockid_t id)
{
    struct timespec timespec = {0};
    clock_getres(id, &timespec);
    return timespec_to_ns(timespec);
}

static uint64_t
read_hardware_counter()
{
    uint64_t result;
    #if defined(__x86_64__)
        #define HARDWARE_COUNTER_NAME "rdtsc"
        result = __rdtsc();
    #elif defined(__aarch64__)
        #define HARDWARE_COUNTER_NAME "CNTVCT"
        asm volatile("mrs %0, cntvct_el0" : "=r"(result) : : "memory");
    #else
        #define HARDWARE_COUNTER_NAME "<no hw counter>"
        result = 0;
    #endif
    return result;
}

static void
call_gettime_in_loop(clockid_t id, int iterations)
{
    if (id == -1) {
        while (iterations--) {    
            read_hardware_counter();
        }
    } else {
        struct timespec timespec;
        while (iterations--) {
            clock_gettime(id, &timespec);
        }
    }
}

static uint64_t
find_smallest_step(clockid_t id, int iterations)
{
    uint64_t min = UINT64_MAX;
    while (iterations--) {
        int64_t step;
        if (id == -1) {
            uint64_t a, b;
            a = read_hardware_counter();
            b = read_hardware_counter();
            step = (int64_t)(b - a);
        } else {
            struct timespec a, b;
            clock_gettime(id, &a);
            clock_gettime(id, &b);
            step = (int64_t)(timespec_to_ns(b) - timespec_to_ns(a));
        }
        if (step > 0 && step < min) min = step;
    }
    return min;
}


int main()
{
    int iterations_per_test = 1000;

    struct Clock
    {
        char *name;
        clockid_t id;
        double best_time_per_call_ns;
        uint64_t smallest_step_ns;
    };
    
    struct Clock clocks[] = {
        { HARDWARE_COUNTER_NAME, -1 },
        { "REALTIME", CLOCK_REALTIME },
        { "REALTIME_ALARM", CLOCK_REALTIME_ALARM },
        { "REALTIME_COARSE", CLOCK_REALTIME_COARSE },
        { "TAI", CLOCK_TAI },
        { "MONOTONIC", CLOCK_MONOTONIC },
        { "MONOTONIC_COARSE", CLOCK_MONOTONIC_COARSE },
        { "MONOTONIC_RAW", CLOCK_MONOTONIC_RAW },
        { "BOOTTIME", CLOCK_BOOTTIME },
        { "BOOTTIME_ALARM", CLOCK_BOOTTIME_ALARM },
        { "PROCESS_CPUTIME_ID", CLOCK_PROCESS_CPUTIME_ID },
        { "THREAD_CPUTIME_ID", CLOCK_THREAD_CPUTIME_ID },
    };
    for (int i = 0; i < alen(clocks); ++i) {
        clocks[i].best_time_per_call_ns = 1e9;
        clocks[i].smallest_step_ns = UINT64_MAX;
    }

    int run_count = 0;
    while (1) {
        ++run_count;

        int any_improved = 0;
        for (int i = 0; i < alen(clocks); ++i) {
            // Figure out time_per_call
            uint64_t t0_ns = read_clock_ns(CLOCK_BOOTTIME);
            call_gettime_in_loop(clocks[i].id, iterations_per_test);
            uint64_t t1_ns = read_clock_ns(CLOCK_BOOTTIME);
            uint64_t time_for_test_ns = t1_ns - t0_ns;
            double time_per_call_ns = (double)time_for_test_ns / (double)iterations_per_test;

            // Figure out smallest step
            uint64_t smallest_step_ns = find_smallest_step(clocks[i].id, iterations_per_test);

            if (time_per_call_ns < clocks[i].best_time_per_call_ns) {
                any_improved = 1;
                clocks[i].best_time_per_call_ns = time_per_call_ns;
            }
            if (smallest_step_ns < clocks[i].smallest_step_ns) {
                any_improved = 1;
                clocks[i].smallest_step_ns = smallest_step_ns;
            }
        }

        if (any_improved) {
            printf("Run %i\n", run_count);
            printf("%30s  %30s %30s %30s\n", "Clock", "Time per clock_gettime (ns)", "Claimed resolution (ns)", "Smallest step (ns)");
            for (int i = 0; i < alen(clocks); ++i) {
                uint64_t claimed_resolution = read_resolution_ns(clocks[i].id);
                printf("%30s  %30f %30lu %30lu", clocks[i].name, clocks[i].best_time_per_call_ns, claimed_resolution, clocks[i].smallest_step_ns);
                if (clocks[i].id == -1) {
                    printf("  (smallest step for %s is in cycle counts, not ns)", HARDWARE_COUNTER_NAME);
                }
                printf("\n");
            }
            printf("\n");
        }
    }

    return 0;
}

 