blob: bb7a6729b5ab08ef12f832ff8294424f4efdc919 [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2023, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <zephyr/ztest.h>
#include <zephyr/logging/log.h>
#define SLEEP_SECONDS 1
#define CLOCK_INVALID -1
LOG_MODULE_REGISTER(clock_test, LOG_LEVEL_DBG);
/* Set a particular time. In this case, the output of: `date +%s -d 2018-01-01T15:45:01Z` */
static const struct timespec ref_ts = {1514821501, NSEC_PER_SEC / 2U};
static const clockid_t clocks[] = {
CLOCK_MONOTONIC,
CLOCK_REALTIME,
};
static const bool settable[] = {
false,
true,
};
static inline int64_t ts_to_ns(const struct timespec *ts)
{
return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
}
static inline void tv_to_ts(const struct timeval *tv, struct timespec *ts)
{
ts->tv_sec = tv->tv_sec;
ts->tv_nsec = tv->tv_usec * NSEC_PER_USEC;
}
#define _tp_op(_a, _b, _op) (ts_to_ns(_a) _op ts_to_ns(_b))
#define _decl_op(_type, _name, _op) \
static inline _type _name(const struct timespec *_a, const struct timespec *_b) \
{ \
return _tp_op(_a, _b, _op); \
}
_decl_op(bool, tp_eq, ==); /* a == b */
_decl_op(bool, tp_lt, <); /* a < b */
_decl_op(bool, tp_gt, >); /* a > b */
_decl_op(bool, tp_le, <=); /* a <= b */
_decl_op(bool, tp_ge, >=); /* a >= b */
_decl_op(int64_t, tp_diff, -); /* a - b */
/* lo <= (a - b) < hi */
static inline bool tp_diff_in_range_ns(const struct timespec *a, const struct timespec *b,
int64_t lo, int64_t hi)
{
int64_t diff = tp_diff(a, b);
return diff >= lo && diff < hi;
}
ZTEST(clock, test_clock_gettime)
{
struct timespec ts;
/* ensure argument validation is performed */
errno = 0;
zassert_equal(clock_gettime(CLOCK_INVALID, &ts), -1);
zassert_equal(errno, EINVAL);
if (false) {
/* undefined behaviour */
errno = 0;
zassert_equal(clock_gettime(clocks[0], NULL), -1);
zassert_equal(errno, EINVAL);
}
/* verify that we can call clock_gettime() on supported clocks */
ARRAY_FOR_EACH(clocks, i)
{
ts = (struct timespec){-1, -1};
zassert_ok(clock_gettime(clocks[i], &ts));
zassert_not_equal(ts.tv_sec, -1);
zassert_not_equal(ts.tv_nsec, -1);
}
}
ZTEST(clock, test_gettimeofday)
{
struct timeval tv;
struct timespec ts;
struct timespec rts;
if (false) {
/* undefined behaviour */
errno = 0;
zassert_equal(gettimeofday(NULL, NULL), -1);
zassert_equal(errno, EINVAL);
}
/* Validate gettimeofday API */
zassert_ok(gettimeofday(&tv, NULL));
zassert_ok(clock_gettime(CLOCK_REALTIME, &rts));
/* TESTPOINT: Check if time obtained from
* gettimeofday is same or more than obtained
* from clock_gettime
*/
tv_to_ts(&tv, &ts);
zassert_true(tp_ge(&rts, &ts));
}
ZTEST(clock, test_clock_settime)
{
int64_t diff_ns;
struct timespec ts = {0};
BUILD_ASSERT(ARRAY_SIZE(settable) == ARRAY_SIZE(clocks));
/* ensure argument validation is performed */
errno = 0;
zassert_equal(clock_settime(CLOCK_INVALID, &ts), -1);
zassert_equal(errno, EINVAL);
if (false) {
/* undefined behaviour */
errno = 0;
zassert_equal(clock_settime(CLOCK_REALTIME, NULL), -1);
zassert_equal(errno, EINVAL);
}
/* verify nanoseconds */
errno = 0;
ts = (struct timespec){0, NSEC_PER_SEC};
zassert_equal(clock_settime(CLOCK_REALTIME, &ts), -1);
zassert_equal(errno, EINVAL);
errno = 0;
ts = (struct timespec){0, -1};
zassert_equal(clock_settime(CLOCK_REALTIME, &ts), -1);
zassert_equal(errno, EINVAL);
ARRAY_FOR_EACH(clocks, i)
{
if (!settable[i]) {
/* should fail attempting to set unsettable clocks */
errno = 0;
zassert_equal(clock_settime(clocks[i], &ts), -1);
zassert_equal(errno, EINVAL);
continue;
}
zassert_ok(clock_settime(clocks[i], &ref_ts));
/* read-back the time */
zassert_ok(clock_gettime(clocks[i], &ts));
/* dt should be >= 0, but definitely <= 1s */
diff_ns = tp_diff(&ts, &ref_ts);
zassert_true(diff_ns >= 0 && diff_ns <= NSEC_PER_SEC);
}
}
ZTEST(clock, test_realtime)
{
struct timespec then, now;
/*
* For calculating cumulative moving average
* Note: we do not want to assert any individual samples due to scheduler noise.
* The CMA filters out the noise so we can make an assertion (on average).
* https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average
*/
int64_t cma_prev = 0;
int64_t cma;
int64_t x_i;
/* lower and uppoer boundary for assertion */
int64_t lo = CONFIG_TEST_CLOCK_RT_SLEEP_MS;
int64_t hi = CONFIG_TEST_CLOCK_RT_SLEEP_MS + CONFIG_TEST_CLOCK_RT_ERROR_MS;
/* lower and upper watermark */
int64_t lo_wm = INT64_MAX;
int64_t hi_wm = INT64_MIN;
/* Loop n times, sleeping a little bit for each */
(void)clock_gettime(CLOCK_REALTIME, &then);
for (int i = 0; i < CONFIG_TEST_CLOCK_RT_ITERATIONS; ++i) {
zassert_ok(k_usleep(USEC_PER_MSEC * CONFIG_TEST_CLOCK_RT_SLEEP_MS));
(void)clock_gettime(CLOCK_REALTIME, &now);
/* Make the delta milliseconds. */
x_i = tp_diff(&now, &then) / NSEC_PER_MSEC;
then = now;
if (x_i < lo_wm) {
/* update low watermark */
lo_wm = x_i;
}
if (x_i > hi_wm) {
/* update high watermark */
hi_wm = x_i;
}
/* compute cumulative running average */
cma = (x_i + i * cma_prev) / (i + 1);
cma_prev = cma;
}
LOG_INF("n: %d, sleep: %d, margin: %d, lo: %lld, avg: %lld, hi: %lld",
CONFIG_TEST_CLOCK_RT_ITERATIONS, CONFIG_TEST_CLOCK_RT_SLEEP_MS,
CONFIG_TEST_CLOCK_RT_ERROR_MS, lo_wm, cma, hi_wm);
zassert_between_inclusive(cma, lo, hi);
}
ZTEST(clock, test_clock_getcpuclockid)
{
int ret = 0;
clockid_t clock_id = CLOCK_INVALID;
ret = clock_getcpuclockid((pid_t)0, &clock_id);
zassert_equal(ret, 0, "POSIX clock_getcpuclock id failed");
zassert_equal(clock_id, CLOCK_PROCESS_CPUTIME_ID, "POSIX clock_getcpuclock id failed");
ret = clock_getcpuclockid((pid_t)2482, &clock_id);
zassert_equal(ret, EPERM, "POSIX clock_getcpuclock id failed");
}
ZTEST(clock, test_clock_getres)
{
int ret;
struct timespec res;
const struct timespec one_ns = {
.tv_sec = 0,
.tv_nsec = 1,
};
struct arg {
clockid_t clock_id;
struct timespec *res;
int expect;
};
const struct arg args[] = {
/* permuting over "invalid" inputs */
{CLOCK_INVALID, NULL, -1},
{CLOCK_INVALID, &res, -1},
{CLOCK_REALTIME, NULL, 0},
{CLOCK_MONOTONIC, NULL, 0},
{CLOCK_PROCESS_CPUTIME_ID, NULL, 0},
/* all valid inputs */
{CLOCK_REALTIME, &res, 0},
{CLOCK_MONOTONIC, &res, 0},
{CLOCK_PROCESS_CPUTIME_ID, &res, 0},
};
ARRAY_FOR_EACH_PTR(args, arg) {
errno = 0;
res = (struct timespec){0};
ret = clock_getres(arg->clock_id, arg->res);
zassert_equal(ret, arg->expect);
if (ret != 0) {
zassert_equal(errno, EINVAL);
continue;
}
if (arg->res != NULL) {
zassert_true(tp_ge(arg->res, &one_ns));
}
}
}
ZTEST_SUITE(clock, NULL, NULL, NULL, NULL, NULL);