Commit | Line | Data |
---|---|---|
5bccfe41 JS |
1 | /* Leap second stress test |
2 | * by: John Stultz (john.stultz@linaro.org) | |
3 | * (C) Copyright IBM 2012 | |
4 | * (C) Copyright 2013, 2015 Linaro Limited | |
5 | * Licensed under the GPLv2 | |
6 | * | |
7 | * This test signals the kernel to insert a leap second | |
8 | * every day at midnight GMT. This allows for stessing the | |
9 | * kernel's leap-second behavior, as well as how well applications | |
10 | * handle the leap-second discontinuity. | |
11 | * | |
12 | * Usage: leap-a-day [-s] [-i <num>] | |
13 | * | |
14 | * Options: | |
15 | * -s: Each iteration, set the date to 10 seconds before midnight GMT. | |
16 | * This speeds up the number of leapsecond transitions tested, | |
17 | * but because it calls settimeofday frequently, advancing the | |
18 | * time by 24 hours every ~16 seconds, it may cause application | |
19 | * disruption. | |
20 | * | |
21 | * -i: Number of iterations to run (default: infinite) | |
22 | * | |
23 | * Other notes: Disabling NTP prior to running this is advised, as the two | |
24 | * may conflict in their commands to the kernel. | |
25 | * | |
26 | * To build: | |
27 | * $ gcc leap-a-day.c -o leap-a-day -lrt | |
28 | * | |
29 | * This program is free software: you can redistribute it and/or modify | |
30 | * it under the terms of the GNU General Public License as published by | |
31 | * the Free Software Foundation, either version 2 of the License, or | |
32 | * (at your option) any later version. | |
33 | * | |
34 | * This program is distributed in the hope that it will be useful, | |
35 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
36 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
37 | * GNU General Public License for more details. | |
38 | */ | |
39 | ||
40 | ||
41 | ||
42 | #include <stdio.h> | |
43 | #include <stdlib.h> | |
44 | #include <time.h> | |
45 | #include <sys/time.h> | |
46 | #include <sys/timex.h> | |
0c4a5fc9 | 47 | #include <sys/errno.h> |
5bccfe41 JS |
48 | #include <string.h> |
49 | #include <signal.h> | |
50 | #include <unistd.h> | |
51 | #ifdef KTEST | |
52 | #include "../kselftest.h" | |
53 | #else | |
54 | static inline int ksft_exit_pass(void) | |
55 | { | |
56 | exit(0); | |
57 | } | |
58 | static inline int ksft_exit_fail(void) | |
59 | { | |
60 | exit(1); | |
61 | } | |
62 | #endif | |
63 | ||
64 | #define NSEC_PER_SEC 1000000000ULL | |
65 | #define CLOCK_TAI 11 | |
66 | ||
0c4a5fc9 JS |
67 | time_t next_leap; |
68 | int error_found; | |
69 | ||
5bccfe41 JS |
70 | /* returns 1 if a <= b, 0 otherwise */ |
71 | static inline int in_order(struct timespec a, struct timespec b) | |
72 | { | |
73 | if (a.tv_sec < b.tv_sec) | |
74 | return 1; | |
75 | if (a.tv_sec > b.tv_sec) | |
76 | return 0; | |
77 | if (a.tv_nsec > b.tv_nsec) | |
78 | return 0; | |
79 | return 1; | |
80 | } | |
81 | ||
82 | struct timespec timespec_add(struct timespec ts, unsigned long long ns) | |
83 | { | |
84 | ts.tv_nsec += ns; | |
85 | while (ts.tv_nsec >= NSEC_PER_SEC) { | |
86 | ts.tv_nsec -= NSEC_PER_SEC; | |
87 | ts.tv_sec++; | |
88 | } | |
89 | return ts; | |
90 | } | |
91 | ||
92 | char *time_state_str(int state) | |
93 | { | |
94 | switch (state) { | |
95 | case TIME_OK: return "TIME_OK"; | |
96 | case TIME_INS: return "TIME_INS"; | |
97 | case TIME_DEL: return "TIME_DEL"; | |
98 | case TIME_OOP: return "TIME_OOP"; | |
99 | case TIME_WAIT: return "TIME_WAIT"; | |
100 | case TIME_BAD: return "TIME_BAD"; | |
101 | } | |
102 | return "ERROR"; | |
103 | } | |
104 | ||
105 | /* clear NTP time_status & time_state */ | |
106 | int clear_time_state(void) | |
107 | { | |
108 | struct timex tx; | |
109 | int ret; | |
110 | ||
111 | /* | |
112 | * We have to call adjtime twice here, as kernels | |
113 | * prior to 6b1859dba01c7 (included in 3.5 and | |
114 | * -stable), had an issue with the state machine | |
115 | * and wouldn't clear the STA_INS/DEL flag directly. | |
116 | */ | |
117 | tx.modes = ADJ_STATUS; | |
118 | tx.status = STA_PLL; | |
119 | ret = adjtimex(&tx); | |
120 | ||
121 | /* Clear maxerror, as it can cause UNSYNC to be set */ | |
122 | tx.modes = ADJ_MAXERROR; | |
123 | tx.maxerror = 0; | |
124 | ret = adjtimex(&tx); | |
125 | ||
126 | /* Clear the status */ | |
127 | tx.modes = ADJ_STATUS; | |
128 | tx.status = 0; | |
129 | ret = adjtimex(&tx); | |
130 | ||
131 | return ret; | |
132 | } | |
133 | ||
134 | /* Make sure we cleanup on ctrl-c */ | |
135 | void handler(int unused) | |
136 | { | |
137 | clear_time_state(); | |
138 | exit(0); | |
139 | } | |
140 | ||
0c4a5fc9 JS |
141 | void sigalarm(int signo) |
142 | { | |
143 | struct timex tx; | |
0c4a5fc9 JS |
144 | int ret; |
145 | ||
146 | tx.modes = 0; | |
147 | ret = adjtimex(&tx); | |
148 | ||
51a16c1e JS |
149 | if (tx.time.tv_sec < next_leap) { |
150 | printf("Error: Early timer expiration! (Should be %ld)\n", next_leap); | |
151 | error_found = 1; | |
152 | printf("adjtimex: %10ld sec + %6ld us (%i)\t%s\n", | |
153 | tx.time.tv_sec, | |
0c4a5fc9 JS |
154 | tx.time.tv_usec, |
155 | tx.tai, | |
156 | time_state_str(ret)); | |
0c4a5fc9 JS |
157 | } |
158 | if (ret != TIME_WAIT) { | |
51a16c1e | 159 | printf("Error: Timer seeing incorrect NTP state? (Should be TIME_WAIT)\n"); |
0c4a5fc9 | 160 | error_found = 1; |
51a16c1e JS |
161 | printf("adjtimex: %10ld sec + %6ld us (%i)\t%s\n", |
162 | tx.time.tv_sec, | |
163 | tx.time.tv_usec, | |
164 | tx.tai, | |
165 | time_state_str(ret)); | |
0c4a5fc9 JS |
166 | } |
167 | } | |
168 | ||
169 | ||
5bccfe41 JS |
170 | /* Test for known hrtimer failure */ |
171 | void test_hrtimer_failure(void) | |
172 | { | |
173 | struct timespec now, target; | |
174 | ||
175 | clock_gettime(CLOCK_REALTIME, &now); | |
176 | target = timespec_add(now, NSEC_PER_SEC/2); | |
177 | clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &target, NULL); | |
178 | clock_gettime(CLOCK_REALTIME, &now); | |
179 | ||
0c4a5fc9 | 180 | if (!in_order(target, now)) { |
5bccfe41 | 181 | printf("ERROR: hrtimer early expiration failure observed.\n"); |
0c4a5fc9 JS |
182 | error_found = 1; |
183 | } | |
5bccfe41 JS |
184 | } |
185 | ||
186 | int main(int argc, char **argv) | |
187 | { | |
0c4a5fc9 JS |
188 | timer_t tm1; |
189 | struct itimerspec its1; | |
190 | struct sigevent se; | |
191 | struct sigaction act; | |
192 | int signum = SIGRTMAX; | |
5bccfe41 JS |
193 | int settime = 0; |
194 | int tai_time = 0; | |
195 | int insert = 1; | |
196 | int iterations = -1; | |
197 | int opt; | |
198 | ||
199 | /* Process arguments */ | |
200 | while ((opt = getopt(argc, argv, "sti:")) != -1) { | |
201 | switch (opt) { | |
202 | case 's': | |
203 | printf("Setting time to speed up testing\n"); | |
204 | settime = 1; | |
205 | break; | |
206 | case 'i': | |
207 | iterations = atoi(optarg); | |
208 | break; | |
209 | case 't': | |
210 | tai_time = 1; | |
211 | break; | |
212 | default: | |
213 | printf("Usage: %s [-s] [-i <iterations>]\n", argv[0]); | |
214 | printf(" -s: Set time to right before leap second each iteration\n"); | |
215 | printf(" -i: Number of iterations\n"); | |
216 | printf(" -t: Print TAI time\n"); | |
217 | exit(-1); | |
218 | } | |
219 | } | |
220 | ||
221 | /* Make sure TAI support is present if -t was used */ | |
222 | if (tai_time) { | |
223 | struct timespec ts; | |
224 | ||
225 | if (clock_gettime(CLOCK_TAI, &ts)) { | |
226 | printf("System doesn't support CLOCK_TAI\n"); | |
227 | ksft_exit_fail(); | |
228 | } | |
229 | } | |
230 | ||
231 | signal(SIGINT, handler); | |
232 | signal(SIGKILL, handler); | |
233 | ||
0c4a5fc9 JS |
234 | /* Set up timer signal handler: */ |
235 | sigfillset(&act.sa_mask); | |
236 | act.sa_flags = 0; | |
237 | act.sa_handler = sigalarm; | |
238 | sigaction(signum, &act, NULL); | |
239 | ||
5bccfe41 JS |
240 | if (iterations < 0) |
241 | printf("This runs continuously. Press ctrl-c to stop\n"); | |
242 | else | |
243 | printf("Running for %i iterations. Press ctrl-c to stop\n", iterations); | |
244 | ||
245 | printf("\n"); | |
246 | while (1) { | |
247 | int ret; | |
248 | struct timespec ts; | |
249 | struct timex tx; | |
0c4a5fc9 | 250 | time_t now; |
5bccfe41 JS |
251 | |
252 | /* Get the current time */ | |
253 | clock_gettime(CLOCK_REALTIME, &ts); | |
254 | ||
255 | /* Calculate the next possible leap second 23:59:60 GMT */ | |
256 | next_leap = ts.tv_sec; | |
257 | next_leap += 86400 - (next_leap % 86400); | |
258 | ||
259 | if (settime) { | |
260 | struct timeval tv; | |
261 | ||
262 | tv.tv_sec = next_leap - 10; | |
263 | tv.tv_usec = 0; | |
264 | settimeofday(&tv, NULL); | |
265 | printf("Setting time to %s", ctime(&tv.tv_sec)); | |
266 | } | |
267 | ||
268 | /* Reset NTP time state */ | |
269 | clear_time_state(); | |
270 | ||
271 | /* Set the leap second insert flag */ | |
272 | tx.modes = ADJ_STATUS; | |
273 | if (insert) | |
274 | tx.status = STA_INS; | |
275 | else | |
276 | tx.status = STA_DEL; | |
277 | ret = adjtimex(&tx); | |
278 | if (ret < 0) { | |
279 | printf("Error: Problem setting STA_INS/STA_DEL!: %s\n", | |
280 | time_state_str(ret)); | |
281 | return ksft_exit_fail(); | |
282 | } | |
283 | ||
284 | /* Validate STA_INS was set */ | |
285 | tx.modes = 0; | |
286 | ret = adjtimex(&tx); | |
287 | if (tx.status != STA_INS && tx.status != STA_DEL) { | |
288 | printf("Error: STA_INS/STA_DEL not set!: %s\n", | |
289 | time_state_str(ret)); | |
290 | return ksft_exit_fail(); | |
291 | } | |
292 | ||
293 | if (tai_time) { | |
294 | printf("Using TAI time," | |
295 | " no inconsistencies should be seen!\n"); | |
296 | } | |
297 | ||
298 | printf("Scheduling leap second for %s", ctime(&next_leap)); | |
299 | ||
0c4a5fc9 | 300 | /* Set up timer */ |
51a16c1e | 301 | printf("Setting timer for %ld - %s", next_leap, ctime(&next_leap)); |
0c4a5fc9 JS |
302 | memset(&se, 0, sizeof(se)); |
303 | se.sigev_notify = SIGEV_SIGNAL; | |
304 | se.sigev_signo = signum; | |
305 | se.sigev_value.sival_int = 0; | |
306 | if (timer_create(CLOCK_REALTIME, &se, &tm1) == -1) { | |
307 | printf("Error: timer_create failed\n"); | |
308 | return ksft_exit_fail(); | |
309 | } | |
310 | its1.it_value.tv_sec = next_leap; | |
311 | its1.it_value.tv_nsec = 0; | |
312 | its1.it_interval.tv_sec = 0; | |
313 | its1.it_interval.tv_nsec = 0; | |
314 | timer_settime(tm1, TIMER_ABSTIME, &its1, NULL); | |
315 | ||
5bccfe41 JS |
316 | /* Wake up 3 seconds before leap */ |
317 | ts.tv_sec = next_leap - 3; | |
318 | ts.tv_nsec = 0; | |
319 | ||
0c4a5fc9 | 320 | |
5bccfe41 JS |
321 | while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL)) |
322 | printf("Something woke us up, returning to sleep\n"); | |
323 | ||
324 | /* Validate STA_INS is still set */ | |
325 | tx.modes = 0; | |
326 | ret = adjtimex(&tx); | |
327 | if (tx.status != STA_INS && tx.status != STA_DEL) { | |
328 | printf("Something cleared STA_INS/STA_DEL, setting it again.\n"); | |
329 | tx.modes = ADJ_STATUS; | |
330 | if (insert) | |
331 | tx.status = STA_INS; | |
332 | else | |
333 | tx.status = STA_DEL; | |
334 | ret = adjtimex(&tx); | |
335 | } | |
336 | ||
337 | /* Check adjtimex output every half second */ | |
338 | now = tx.time.tv_sec; | |
339 | while (now < next_leap + 2) { | |
340 | char buf[26]; | |
341 | struct timespec tai; | |
0c4a5fc9 | 342 | int ret; |
5bccfe41 JS |
343 | |
344 | tx.modes = 0; | |
345 | ret = adjtimex(&tx); | |
346 | ||
347 | if (tai_time) { | |
348 | clock_gettime(CLOCK_TAI, &tai); | |
349 | printf("%ld sec, %9ld ns\t%s\n", | |
350 | tai.tv_sec, | |
351 | tai.tv_nsec, | |
352 | time_state_str(ret)); | |
353 | } else { | |
354 | ctime_r(&tx.time.tv_sec, buf); | |
355 | buf[strlen(buf)-1] = 0; /*remove trailing\n */ | |
356 | ||
357 | printf("%s + %6ld us (%i)\t%s\n", | |
358 | buf, | |
359 | tx.time.tv_usec, | |
360 | tx.tai, | |
361 | time_state_str(ret)); | |
362 | } | |
363 | now = tx.time.tv_sec; | |
364 | /* Sleep for another half second */ | |
365 | ts.tv_sec = 0; | |
366 | ts.tv_nsec = NSEC_PER_SEC / 2; | |
367 | clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); | |
368 | } | |
369 | /* Switch to using other mode */ | |
370 | insert = !insert; | |
371 | ||
372 | /* Note if kernel has known hrtimer failure */ | |
373 | test_hrtimer_failure(); | |
374 | ||
0c4a5fc9 JS |
375 | printf("Leap complete\n"); |
376 | if (error_found) { | |
377 | printf("Errors observed\n"); | |
378 | clear_time_state(); | |
379 | return ksft_exit_fail(); | |
380 | } | |
381 | printf("\n"); | |
5bccfe41 JS |
382 | if ((iterations != -1) && !(--iterations)) |
383 | break; | |
384 | } | |
385 | ||
386 | clear_time_state(); | |
387 | return ksft_exit_pass(); | |
388 | } |