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