1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2021 Oxide Comptuer Company
14 */
15
16 /*
17 * Test a bunch of basics around clocks.
18 */
19
20 #include <time.h>
21 #include <err.h>
22 #include <stdlib.h>
23 #include <libproc.h>
24 #include <thread.h>
25 #include <sys/sysmacros.h>
26
27 typedef hrtime_t (*clock_alttime_f)(void);
28
29 typedef struct clock_gettime_test {
30 clockid_t cgt_clock;
31 clock_alttime_f cgt_alt;
32 const char *cgt_name;
33 } clock_gettime_test_t;
34
35 typedef struct clock_gettime_thr_arg {
36 hrtime_t cgta_usr;
37 hrtime_t cgta_usrsys;
38 } clock_gettime_thr_arg_t;
39
40 static hrtime_t
clock_ts2hrt(const timespec_t * tsp)41 clock_ts2hrt(const timespec_t *tsp)
42 {
43 return ((tsp->tv_sec * NANOSEC) + tsp->tv_nsec);
44 }
45
46 static hrtime_t
clock_gettime_proc(void)47 clock_gettime_proc(void)
48 {
49 psinfo_t ps;
50
51 if (proc_get_psinfo(getpid(), &ps) != 0) {
52 warn("failed to get psinfo for process");
53 return (0);
54 }
55
56 return (clock_ts2hrt(&ps.pr_time));
57 }
58
59 static hrtime_t
clock_gettime_thread(void)60 clock_gettime_thread(void)
61 {
62 lwpsinfo_t lwpsinfo;
63
64 if (proc_get_lwpsinfo(getpid(), thr_self(), &lwpsinfo) != 0) {
65 warn("failed to get lwpsinfo for thread %u", thr_self());
66 return (0);
67 }
68
69 return (clock_ts2hrt(&lwpsinfo.pr_time));
70 }
71
72 clock_gettime_test_t clock_tests[] = {
73 { CLOCK_HIGHRES, gethrtime, "highres" },
74 { CLOCK_VIRTUAL, gethrvtime, "virtual" },
75 { CLOCK_THREAD_CPUTIME_ID, clock_gettime_thread, "thread_cputime" },
76 { CLOCK_PROCESS_CPUTIME_ID, clock_gettime_proc, "proc_cputime" }
77 };
78
79 /*
80 * Do a series of reads of the clock from clock_gettime and its secondary
81 * source. Make sure that we always see increasing values.
82 */
83 static boolean_t
clock_test(clock_gettime_test_t * test)84 clock_test(clock_gettime_test_t *test)
85 {
86 hrtime_t hrt0, hrt1, hrt2, convts0, convts1;
87 struct timespec ts0, ts1;
88 boolean_t ret = B_TRUE;
89
90 if (clock_gettime(test->cgt_clock, &ts0) != 0) {
91 warn("failed to get clock %u", test->cgt_clock);
92 return (B_FALSE);
93 }
94
95 hrt0 = test->cgt_alt();
96 hrt1 = test->cgt_alt();
97
98 if (clock_gettime(test->cgt_clock, &ts1) != 0) {
99 warn("failed to get clock %u", test->cgt_clock);
100 return (B_FALSE);
101 }
102
103 hrt2 = test->cgt_alt();
104
105 convts0 = clock_ts2hrt(&ts0);
106 convts1 = clock_ts2hrt(&ts1);
107
108 if (convts0 > hrt0) {
109 warnx("clock %s traveled backwards, clock_gettime ahead of "
110 "later alternate: clock_gettime %lld, alternate: %lld",
111 test->cgt_name, convts0, hrt0);
112 ret = B_FALSE;
113 }
114
115 if (hrt0 > hrt1) {
116 warnx("clock %s traveled backwards, alternate ahead of "
117 "later alternate: first alternate %lld, later "
118 "alternate: %lld", test->cgt_name, hrt0, hrt1);
119 ret = B_FALSE;
120 }
121
122 if (convts1 > hrt2) {
123 warnx("clock %s traveled backwards, clock_gettime ahead of "
124 "later alternate: clock_gettime %lld, alternate: %lld",
125 test->cgt_name, convts1, hrt2);
126 ret = B_FALSE;
127 }
128
129 if (hrt1 > hrt2) {
130 warnx("clock %s traveled backwards, alternate ahead of "
131 "later alternate: first alternate %lld, later "
132 "alternate: %lld", test->cgt_name, hrt1, hrt2);
133 ret = B_FALSE;
134 }
135
136 if (convts0 > convts1) {
137 warnx("clock %s traveled backwards, clock_gettime ahead of "
138 "later clock_gettime: first clock_gettime %lld, later "
139 "clock_gettime: %lld", test->cgt_name, convts0, convts1);
140 ret = B_FALSE;
141 }
142
143 return (ret);
144 }
145
146 static void *
clock_test_thr(void * arg)147 clock_test_thr(void *arg)
148 {
149 boolean_t ret = B_TRUE;
150
151 for (uint_t i = 0; i < ARRAY_SIZE(clock_tests); i++) {
152 boolean_t rval = clock_test(&clock_tests[i]);
153 if (!rval) {
154 ret = B_FALSE;
155 }
156
157 (void) printf("TEST %s: basic %s usage and interleaving%s\n",
158 rval ? "PASSED" : "FAILED", clock_tests[i].cgt_name,
159 thr_self() == 1 ? "" : " (in thread)");
160 }
161
162 return ((void *)(uintptr_t)ret);
163 }
164
165 static void *
clock_test_cputime_thr(void * arg)166 clock_test_cputime_thr(void *arg)
167 {
168 struct timespec ts;
169 clock_gettime_thr_arg_t *cp = arg;
170
171 if (clock_gettime(CLOCK_VIRTUAL, &ts) != 0) {
172 warn("failed to get clock CLOCK_VIRTUAL");
173 cp->cgta_usr = 0;
174 } else {
175 cp->cgta_usr = clock_ts2hrt(&ts);
176 }
177
178 if (clock_gettime(CLOCK_VIRTUAL, &ts) != 0) {
179 warn("failed to get clock CLOCK_VIRTUAL");
180 cp->cgta_usrsys = 0;
181 } else {
182 cp->cgta_usrsys = clock_ts2hrt(&ts);
183 }
184
185 return (NULL);
186 }
187
188 /*
189 * Compare the value of CLOCK_THREAD_CPUTIME_ID between a new thread and the
190 * main thread.
191 */
192 static boolean_t
clock_test_thread_clock(void)193 clock_test_thread_clock(void)
194 {
195 thread_t thr;
196 clock_gettime_thr_arg_t arg;
197 hrtime_t hrt;
198 struct timespec ts;
199 boolean_t ret = B_TRUE;
200
201 if (thr_create(NULL, 0, clock_test_cputime_thr, &arg, 0, &thr) != 0) {
202 errx(EXIT_FAILURE, "failed to create thread to run basic "
203 "tests!");
204 }
205
206 if (thr_join(thr, NULL, NULL) != 0) {
207 errx(EXIT_FAILURE, "failed to join to thread that ran basic "
208 "tests");
209 }
210
211 if (clock_gettime(CLOCK_VIRTUAL, &ts) != 0) {
212 warn("failed to get clock CLOCK_VIRTUAL");
213 return (B_FALSE);
214 }
215
216 hrt = clock_ts2hrt(&ts);
217 if (arg.cgta_usr > hrt) {
218 warnx("new thread %u somehow had higher CLOCK_VIRTUAL time "
219 "than main thread: new thread: %lld, main thread: %lld",
220 thr, hrt, arg.cgta_usr);
221 ret = B_FALSE;
222 }
223
224 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) {
225 warn("failed to get clock CLOCK_THREAD_CPUTIME_ID");
226 return (B_FALSE);
227 }
228
229 hrt = clock_ts2hrt(&ts);
230 if (arg.cgta_usr > hrt) {
231 warnx("new thread %u somehow had higher "
232 "CLOCK_THREAD_CPUTIME_ID time than main thread: new "
233 "thread: %lld, main thread: %lld", thr, hrt, arg.cgta_usr);
234 ret = B_FALSE;
235 }
236
237 return (ret);
238 }
239
240 /*
241 * This test is a little circumspect. It's basically going to argue that all the
242 * time we spent doing kernel actions should be larger than the additional bit
243 * of user time to make a subsequent system call. That seems probably
244 * reasonable given everything we've done; however, there's no way to feel like
245 * it's not possibly going to lead to false positives. If so, then just delete
246 * this.
247 */
248 static boolean_t
clock_test_thread_sys(void)249 clock_test_thread_sys(void)
250 {
251 struct timespec usr, sys;
252 hrtime_t hrtusr, hrtsys;
253
254 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &sys) != 0) {
255 warn("failed to get clock CLOCK_THREAD_CPUTIME_ID");
256 return (B_FALSE);
257 }
258
259 if (clock_gettime(CLOCK_VIRTUAL, &usr) != 0) {
260 warn("failed to get clock CLOCK_VIRTUAL");
261 return (B_FALSE);
262 }
263
264 hrtusr = clock_ts2hrt(&usr);
265 hrtsys = clock_ts2hrt(&sys);
266
267 if (hrtusr > hrtsys) {
268 warnx("CLOCK_VIRTUAL was greater than CLOCK_THREAD_CPUTIME_ID: "
269 "usr time: %lld, usr/sys time: %lld (this may be a race)",
270 hrtusr, hrtsys);
271 return (B_FALSE);
272 }
273
274 return (B_TRUE);
275 }
276
277 /*
278 * This is similar to clock_test_thread_sys(), but using the process clock and
279 * the thread clock. This is circumspect for similar reasons.
280 */
281 static boolean_t
clock_test_thread_proc(void)282 clock_test_thread_proc(void)
283 {
284 struct timespec thr, proc;
285 hrtime_t hrtthr, hrtproc;
286
287 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &proc) != 0) {
288 warn("failed to get clock CLOCK_VIRTUAL");
289 return (B_FALSE);
290 }
291
292 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thr) != 0) {
293 warn("failed to get clock CLOCK_THREAD_CPUTIME_ID");
294 return (B_FALSE);
295 }
296
297 hrtthr = clock_ts2hrt(&thr);
298 hrtproc = clock_ts2hrt(&proc);
299
300 if (hrtthr > hrtproc) {
301 warnx("CLOCK_THRAD_CPUTIME_ID was greater than "
302 "CLOCK_PROCESS_CPUTIME_ID: thr time: %lld, proc time: %lld "
303 "(this may be a race)", hrtthr, hrtproc);
304 return (B_FALSE);
305 }
306
307 return (B_TRUE);
308 }
309
310 int
main(void)311 main(void)
312 {
313 int ret = EXIT_SUCCESS;
314 void *thr_ret;
315 thread_t thr;
316 boolean_t bval;
317
318 thr_ret = clock_test_thr(NULL);
319 if (!(boolean_t)(uintptr_t)thr_ret) {
320 ret = EXIT_FAILURE;
321 }
322
323 if (thr_create(NULL, 0, clock_test_thr, NULL, 0, &thr) != 0) {
324 errx(EXIT_FAILURE, "failed to create thread to run basic "
325 "tests!");
326 }
327
328 if (thr_join(thr, NULL, &thr_ret) != 0) {
329 errx(EXIT_FAILURE, "failed to join to thread that ran basic "
330 "tests");
331 }
332
333 if (!(boolean_t)(uintptr_t)thr_ret) {
334 ret = EXIT_FAILURE;
335 }
336
337 bval = clock_test_thread_clock();
338 (void) printf("TEST %s: comparing CLOCK_THREAD_CPUTIME_ID and "
339 "CLOCK_VIRTUAL between threads\n", bval ? "PASSED" : "FAILED");
340
341 bval = clock_test_thread_sys();
342 (void) printf("TEST %s: comparing CLOCK_THREAD_CPUTIME_ID and "
343 "CLOCK_VIRTUAL\n", bval ? "PASSED" : "FAILED");
344
345
346 bval = clock_test_thread_proc();
347 (void) printf("TEST %s: comparing CLOCK_THREAD_CPUTIME_ID and "
348 "CLOCK_PROCESS_CPUTIME_ID\n", bval ? "PASSED" : "FAILED");
349 /*
350 * XXX CLOCK_THREAD_CPUTIME_ID > CLOCK_VIRTUAL for same thread?
351 * XXX CLOCK_PROCESS_CPUTIME_ID > CLOCK_THREAD_CPUTIME_ID
352 */
353
354 return (ret);
355 }
356