1 /*
2  * Copyright (c) 2018 Intel Corporation
3  * Copyright (c) 2023, Meta
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /* for tp_ge(), tp_diff() */
9 #include "posix_clock.h"
10 
11 #include <sys/time.h>
12 #include <time.h>
13 #include <unistd.h>
14 
15 #include <zephyr/ztest.h>
16 #include <zephyr/logging/log.h>
17 
18 #define SLEEP_SECONDS 1
19 #define CLOCK_INVALID -1
20 
21 LOG_MODULE_REGISTER(clock_test, LOG_LEVEL_DBG);
22 
23 /* Set a particular time.  In this case, the output of: `date +%s -d 2018-01-01T15:45:01Z` */
24 static const struct timespec ref_ts = {1514821501, NSEC_PER_SEC / 2U};
25 
26 static const clockid_t clocks[] = {
27 	CLOCK_MONOTONIC,
28 	CLOCK_REALTIME,
29 };
30 
31 static const bool settable[] = {
32 	false,
33 	true,
34 };
35 
ZTEST(posix_timers,test_clock_gettime)36 ZTEST(posix_timers, test_clock_gettime)
37 {
38 	struct timespec ts;
39 
40 	/* ensure argument validation is performed */
41 	errno = 0;
42 	zassert_equal(clock_gettime(CLOCK_INVALID, &ts), -1);
43 	zassert_equal(errno, EINVAL);
44 
45 	if (false) {
46 		/* undefined behaviour */
47 		errno = 0;
48 		zassert_equal(clock_gettime(clocks[0], NULL), -1);
49 		zassert_equal(errno, EINVAL);
50 	}
51 
52 	/* verify that we can call clock_gettime() on supported clocks */
53 	ARRAY_FOR_EACH(clocks, i) {
54 		ts = (struct timespec){-1, -1};
55 		zassert_ok(clock_gettime(clocks[i], &ts));
56 		zassert_not_equal(ts.tv_sec, -1);
57 		zassert_not_equal(ts.tv_nsec, -1);
58 	}
59 }
60 
ZTEST(posix_timers,test_clock_settime)61 ZTEST(posix_timers, test_clock_settime)
62 {
63 	int64_t diff_ns;
64 	struct timespec ts = {0};
65 
66 	BUILD_ASSERT(ARRAY_SIZE(settable) == ARRAY_SIZE(clocks));
67 
68 	/* ensure argument validation is performed */
69 	errno = 0;
70 	zassert_equal(clock_settime(CLOCK_INVALID, &ts), -1);
71 	zassert_equal(errno, EINVAL);
72 
73 	if (false) {
74 		/* undefined behaviour */
75 		errno = 0;
76 		zassert_equal(clock_settime(CLOCK_REALTIME, NULL), -1);
77 		zassert_equal(errno, EINVAL);
78 	}
79 
80 	/* verify nanoseconds */
81 	errno = 0;
82 	ts = (struct timespec){0, NSEC_PER_SEC};
83 	zassert_equal(clock_settime(CLOCK_REALTIME, &ts), -1);
84 	zassert_equal(errno, EINVAL);
85 	errno = 0;
86 	ts = (struct timespec){0, -1};
87 	zassert_equal(clock_settime(CLOCK_REALTIME, &ts), -1);
88 	zassert_equal(errno, EINVAL);
89 
90 	ARRAY_FOR_EACH(clocks, i) {
91 		if (!settable[i]) {
92 			/* should fail attempting to set unsettable clocks */
93 			errno = 0;
94 			zassert_equal(clock_settime(clocks[i], &ts), -1);
95 			zassert_equal(errno, EINVAL);
96 			continue;
97 		}
98 
99 		zassert_ok(clock_settime(clocks[i], &ref_ts));
100 
101 		/* read-back the time */
102 		zassert_ok(clock_gettime(clocks[i], &ts));
103 		/* dt should be >= 0, but definitely <= 1s */
104 		diff_ns = tp_diff(&ts, &ref_ts);
105 		zassert_true(diff_ns >= 0 && diff_ns <= NSEC_PER_SEC);
106 	}
107 }
108 
ZTEST(posix_timers,test_realtime)109 ZTEST(posix_timers, test_realtime)
110 {
111 	struct timespec then, now;
112 	/*
113 	 * For calculating cumulative moving average
114 	 * Note: we do not want to assert any individual samples due to scheduler noise.
115 	 * The CMA filters out the noise so we can make an assertion (on average).
116 	 * https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average
117 	 */
118 	int64_t cma_prev = 0;
119 	int64_t cma;
120 	int64_t x_i;
121 	/* lower and uppoer boundary for assertion */
122 	int64_t lo = CONFIG_TEST_CLOCK_RT_SLEEP_MS;
123 	int64_t hi = CONFIG_TEST_CLOCK_RT_SLEEP_MS + CONFIG_TEST_CLOCK_RT_ERROR_MS;
124 	/* lower and upper watermark */
125 	int64_t lo_wm = INT64_MAX;
126 	int64_t hi_wm = INT64_MIN;
127 
128 	/* Loop n times, sleeping a little bit for each */
129 	(void)clock_gettime(CLOCK_REALTIME, &then);
130 	for (int i = 0; i < CONFIG_TEST_CLOCK_RT_ITERATIONS; ++i) {
131 
132 		zassert_ok(k_usleep(USEC_PER_MSEC * CONFIG_TEST_CLOCK_RT_SLEEP_MS));
133 		(void)clock_gettime(CLOCK_REALTIME, &now);
134 
135 		/* Make the delta milliseconds. */
136 		x_i = tp_diff(&now, &then) / NSEC_PER_MSEC;
137 		then = now;
138 
139 		if (x_i < lo_wm) {
140 			/* update low watermark */
141 			lo_wm = x_i;
142 		}
143 
144 		if (x_i > hi_wm) {
145 			/* update high watermark */
146 			hi_wm = x_i;
147 		}
148 
149 		/* compute cumulative running average */
150 		cma = (x_i + i * cma_prev) / (i + 1);
151 		cma_prev = cma;
152 	}
153 
154 	LOG_INF("n: %d, sleep: %d, margin: %d, lo: %lld, avg: %lld, hi: %lld",
155 		CONFIG_TEST_CLOCK_RT_ITERATIONS, CONFIG_TEST_CLOCK_RT_SLEEP_MS,
156 		CONFIG_TEST_CLOCK_RT_ERROR_MS, lo_wm, cma, hi_wm);
157 	zassert_between_inclusive(cma, lo, hi);
158 }
159 
ZTEST(posix_timers,test_clock_getcpuclockid)160 ZTEST(posix_timers, test_clock_getcpuclockid)
161 {
162 	int ret = 0;
163 	clockid_t clock_id = CLOCK_INVALID;
164 
165 	ret = clock_getcpuclockid((pid_t)0, &clock_id);
166 	zassert_equal(ret, 0, "POSIX clock_getcpuclock id failed");
167 	zassert_equal(clock_id, CLOCK_PROCESS_CPUTIME_ID, "POSIX clock_getcpuclock id failed");
168 
169 	ret = clock_getcpuclockid((pid_t)2482, &clock_id);
170 	zassert_equal(ret, EPERM, "POSIX clock_getcpuclock id failed");
171 }
172 
ZTEST(posix_timers,test_clock_getres)173 ZTEST(posix_timers, test_clock_getres)
174 {
175 	int ret;
176 	struct timespec res;
177 	const struct timespec one_ns = {
178 		.tv_sec = 0,
179 		.tv_nsec = 1,
180 	};
181 
182 	struct arg {
183 		clockid_t clock_id;
184 		struct timespec *res;
185 		int expect;
186 	};
187 
188 	const struct arg args[] = {
189 		/* permuting over "invalid" inputs */
190 		{CLOCK_INVALID, NULL, -1},
191 		{CLOCK_INVALID, &res, -1},
192 		{CLOCK_REALTIME, NULL, 0},
193 		{CLOCK_MONOTONIC, NULL, 0},
194 		{CLOCK_PROCESS_CPUTIME_ID, NULL, 0},
195 
196 		/* all valid inputs */
197 		{CLOCK_REALTIME, &res, 0},
198 		{CLOCK_MONOTONIC, &res, 0},
199 		{CLOCK_PROCESS_CPUTIME_ID, &res, 0},
200 	};
201 
202 	ARRAY_FOR_EACH_PTR(args, arg) {
203 		errno = 0;
204 		res = (struct timespec){0};
205 		ret = clock_getres(arg->clock_id, arg->res);
206 		zassert_equal(ret, arg->expect);
207 		if (ret != 0) {
208 			zassert_equal(errno, EINVAL);
209 			continue;
210 		}
211 		if (arg->res != NULL) {
212 			zassert_true(tp_ge(arg->res, &one_ns));
213 		}
214 	}
215 }
216