blob: a1d2e7563212fd27ffda63dafb088142278b0cda [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "native_client/src/trusted/service_runtime/include/machine/_types.h"
/*
* Newlib's time.h not working right: getting the nanosleep
* declaration from the newlib time.h file requires defining
* _POSIX_TIMERS, but we don't implement any of the clock_settime,
* clock_gettime, clock_getres, timer_create, timer_delete,
* timer_settime, timer_gettime, and timer_getoverrun functions. In
* glibc's time.h, these function declarations are obtained by
* ensuring _POSIX_C_SOURCE >= 199309L.
*
* Sigh.
*/
#define NANOS_PER_MICRO (1000)
#define MICROS_PER_MILLI (1000)
#define NANOS_PER_MILLI (NANOS_PER_MICRO * MICROS_PER_MILLI)
#define MICROS_PER_UNIT (1000 * 1000)
/*
* We don't convert to floating point, so a precondition is that
* tv_usec is within range, i.e., the timeval has been normalized.
*/
void PrintTimeval(FILE *iob, struct timeval const *tv) {
fprintf(iob, "%"PRId64".%06ld", (int64_t) tv->tv_sec, tv->tv_usec);
}
/* timespec fields should have been ts_nsec etc */
void PrintTimespec(FILE *iob, struct timespec const *ts) {
fprintf(iob, "%"PRId64".%09ld", (uint64_t) ts->tv_sec, ts->tv_nsec);
}
void NormalizeTimeval(struct timeval *tv) {
int first = 1;
while (tv->tv_usec < 0) {
tv->tv_usec += MICROS_PER_UNIT;
tv->tv_sec -= 1;
if (!first) {
fprintf(stderr, "NormalizedTimeval: usec too small, 2x normalize!\n");
PrintTimeval(stderr, tv); putc('\n', stderr);
}
first = 0;
}
first = 1;
while (tv->tv_usec >= MICROS_PER_UNIT) {
tv->tv_usec -= MICROS_PER_UNIT;
tv->tv_sec += 1;
if (!first) {
fprintf(stderr, "NormalizedTimeval: usec too large, 2x normalize!\n");
PrintTimeval(stderr, tv); putc('\n', stderr);
}
first = 0;
}
}
/*
* Returns failure count. t_suspend should not be shorter than 1us,
* since elapsed time measurement cannot possibly be any finer in
* granularity. In practice, 1ms is probably the best we can hope for
* in timer resolution, so even if nanosleep suspends for 1us, the
* gettimeofday resolution may cause a false failure report.
*/
int TestNanoSleep(struct timespec *t_suspend,
uint64_t slop_ms) {
struct timespec t_remain;
struct timeval t_start;
int rv;
struct timeval t_end;
struct timeval t_elapsed;
printf("%40s: ", "Requesting nanosleep duration");
PrintTimespec(stdout, t_suspend);
printf(" seconds\n");
t_remain = *t_suspend;
/*
* BUG: ntp or other time adjustments can mess up timing.
* BUG: time-of-day clock resolution may be not be fine enough to
* measure nanosleep duration.
*/
if (-1 == gettimeofday(&t_start, NULL)) {
printf("gettimeofday for start time failed\n");
return 1;
}
while (-1 == (rv = nanosleep(&t_remain, &t_remain)) &&
EINTR == errno) {
}
if (-1 == rv) {
printf("nanosleep failed, errno = %d\n", errno);
return 1;
}
if (-1 == gettimeofday(&t_end, NULL)) {
printf("gettimeofday for end time failed\n");
return 1;
}
/*
* We add a microsecond in case of rounding/synchronization issues
* between the nanosleep/scheduler clock and the time-of-day clock,
* where the time-of-day clock doesn't _quite_ get incremented in
* time even though the entire nanosleep duration had passed.
* (We've seen this occur on the mac.)
*/
t_elapsed.tv_sec = t_end.tv_sec - t_start.tv_sec;
t_elapsed.tv_usec = t_end.tv_usec - t_start.tv_usec + 1;
NormalizeTimeval(&t_elapsed);
printf("%40s: ", "Actual nanosleep duration");
PrintTimeval(stdout, &t_elapsed);
printf(" seconds\n");
/*
* On WinXP, Sleep(num_ms) sometimes -- though rarely -- return
* earlier than it is supposed to. This may be due to gettimeofday
* issues when running on VMs, rather than actual insomnia. In any
* case, We permit adding in some slop here to the elapsed time so
* that we can ignore the sporadic random test failures that would
* occur.
*/
t_elapsed.tv_usec += slop_ms * MICROS_PER_MILLI;
NormalizeTimeval(&t_elapsed);
printf("%40s: ", "Slop adjusted duration");
PrintTimeval(stdout, &t_elapsed);
printf(" seconds\n");
if (t_elapsed.tv_sec < t_suspend->tv_sec ||
(t_elapsed.tv_sec == t_suspend->tv_sec &&
(NANOS_PER_MICRO * t_elapsed.tv_usec < t_suspend->tv_nsec))) {
printf("Elapsed time too short!\n");
printf("Error\n");
return 1;
} else {
printf("OK\n");
return 0;
}
}
int main(int argc, char **argv) {
int num_errors = 0;
int ix;
int opt;
uint64_t slop_ms = 0;
static struct timespec t_suspend[] = {
{ 0, 1 * NANOS_PER_MILLI, },
{ 0, 2 * NANOS_PER_MILLI, },
{ 0, 5 * NANOS_PER_MILLI, },
{ 0, 10 * NANOS_PER_MILLI, },
{ 0, 25 * NANOS_PER_MILLI, },
{ 0, 50 * NANOS_PER_MILLI, },
{ 0, 100 * NANOS_PER_MILLI, },
{ 0, 250 * NANOS_PER_MILLI, },
{ 0, 500 * NANOS_PER_MILLI, },
{ 1, 0 * NANOS_PER_MILLI, },
{ 1, 500 * NANOS_PER_MILLI, },
};
while (EOF != (opt = getopt(argc, argv, "s:"))) {
switch (opt) {
case 's':
slop_ms = strtoull(optarg, (char **) NULL, 0);
break;
default:
fprintf(stderr,
"Usage: nanosleep_test [-s slop_for_time_compare_in_ms]\n");
return 1;
}
}
for (ix = 0; ix < sizeof t_suspend/sizeof t_suspend[0]; ++ix) {
num_errors += TestNanoSleep(&t_suspend[ix], slop_ms);
}
if (0 != num_errors) {
printf("FAILED\n");
return 1;
} else {
printf("PASSED\n");
return 0;
}
}