/* * Copyright (c) 2018 Intel Corporation * Copyright (c) 2023, Meta * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #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(posix_timers, 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(posix_timers, 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(posix_timers, 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(posix_timers, 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(posix_timers, 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)); } } }