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 41 clock_ts2hrt(const timespec_t *tsp) 42 { 43 return ((tsp->tv_sec * NANOSEC) + tsp->tv_nsec); 44 } 45 46 static hrtime_t 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 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 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 * 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 * 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 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 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 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 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