blob: 0fdb4e26b5efc2f543c03b9a7ed5343dcfb16dd8 [file] [log] [blame]
/*
* Copyright (c) 2013 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 <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "native_client/src/include/nacl_macros.h"
/*
* This test resembles the trusted version which can be found in
* src/shared/platform/nacl_clock_test.c, but uses the stable ABI
* to test the integration correctness. When making changes, please
* keep both tests in sync if possible.
*/
/*
* On an unloaded i7, 1ms with a 1.25 fuzziness factor and a 100,000
* ns constant syscall overhead works fine. On bots, we have to be
* much more generous. (This is especially true for qemu-based
* testing.)
*/
#define DEFAULT_NANOSLEEP_EXTRA_OVERHEAD (10 * NACL_NANOS_PER_MILLI)
#define DEFAULT_NANOSLEEP_EXTRA_FACTOR (100.0)
#define DEFAULT_NANOSLEEP_TIME (10 * NACL_NANOS_PER_MILLI)
#define THREAD_CYCLES 100000000
/*
* Global testing parameters -- fuzziness coefficients in determining
* what is considered accurate.
*/
static int g_cputime = 0;
static double g_fuzzy_factor = DEFAULT_NANOSLEEP_EXTRA_FACTOR;
static uint64_t g_syscall_overhead = DEFAULT_NANOSLEEP_EXTRA_OVERHEAD;
static uint64_t g_slop_ms = 0;
/*
* ClockMonotonicAccuracyTest samples the CLOCK_MONOTONIC
* clock before and after invoking NaClNanosleep and computes the time
* delta. The test is considered to pass if the time delta is close
* to the requested value. "Close" is a per-host-OS attribute, thus
* the above testing parameters.
*/
static int ClockMonotonicAccuracyTest(uint64_t sleep_nanos) {
int num_failures = 0;
struct timespec t_start;
struct timespec t_sleep;
struct timespec t_end;
uint64_t elapsed_nanos;
uint64_t elapsed_lower_bound;
uint64_t elapsed_upper_bound;
t_sleep.tv_sec = sleep_nanos / NACL_NANOS_PER_UNIT;
t_sleep.tv_nsec = sleep_nanos % NACL_NANOS_PER_UNIT;
printf("\nCLOCK_MONOTONIC accuracy test:\n");
if (0 != clock_gettime(CLOCK_MONOTONIC, &t_start)) {
fprintf(stderr, "clock_test: clock_gettime (start) failed, error %d\n",
errno);
++num_failures;
goto done;
}
for (;;) {
if (0 == nanosleep(&t_sleep, &t_sleep)) {
break;
}
if (EINTR == errno) {
/* interrupted syscall: sleep some more */
continue;
}
fprintf(stderr, "clock_test: nanosleep failed, error %d\n",
errno);
num_failures++;
goto done;
}
if (0 != clock_gettime(CLOCK_MONOTONIC, &t_end)) {
fprintf(stderr, "clock_test: clock_gettime (end) failed, error %d\n",
errno);
return 1;
}
elapsed_nanos = (t_end.tv_sec - t_start.tv_sec) * NACL_NANOS_PER_UNIT +
(t_end.tv_nsec - t_start.tv_nsec) + g_slop_ms * NACL_NANOS_PER_MILLI;
elapsed_lower_bound = sleep_nanos;
elapsed_upper_bound = (uint64_t) (sleep_nanos * g_fuzzy_factor +
g_syscall_overhead);
printf("requested sleep: %20"PRIu64" nS\n", sleep_nanos);
printf("elapsed sleep: %20"PRIu64" nS\n", elapsed_nanos);
printf("sleep lower bound: %20"PRIu64" nS\n", elapsed_lower_bound);
printf("sleep upper bound: %20"PRIu64" nS\n", elapsed_upper_bound);
if (elapsed_nanos < elapsed_lower_bound ||
elapsed_upper_bound < elapsed_nanos) {
printf("discrepancy too large\n");
num_failures++;
}
done:
printf((0 == num_failures) ? "PASSED\n" : "FAILED\n");
return num_failures;
}
/*
* ClockRealtimeAccuracyTest compares the time returned by
* CLOCK_REALTIME against that returned by gettimeofday.
*/
static int ClockRealtimeAccuracyTest(void) {
int num_failures = 0;
struct timespec t_now_ts;
struct timeval t_now_tv;
uint64_t t_now_ts_nanos;
uint64_t t_now_tv_nanos;
int64_t t_now_diff_nanos;
printf("\nCLOCK_REALTIME accuracy test:\n");
if (0 != clock_gettime(CLOCK_REALTIME, &t_now_ts)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
if (0 != gettimeofday(&t_now_tv, NULL)) {
fprintf(stderr, "clock_test: gettimeofday (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
t_now_ts_nanos = t_now_ts.tv_sec * NACL_NANOS_PER_UNIT + t_now_ts.tv_nsec;
t_now_tv_nanos = t_now_tv.tv_sec * NACL_NANOS_PER_UNIT +
t_now_tv.tv_usec * NACL_NANOS_PER_MICRO;
printf("clock_gettime: %20"PRIu64" nS\n", t_now_ts_nanos);
printf("gettimeofday: %20"PRIu64" nS\n", t_now_tv_nanos);
t_now_diff_nanos = t_now_ts_nanos - t_now_tv_nanos;
if (t_now_diff_nanos < 0) {
t_now_diff_nanos = -t_now_diff_nanos;
}
printf("time difference: %20"PRId64" nS\n", t_now_diff_nanos);
if (t_now_ts_nanos < g_syscall_overhead) {
printf("discrepancy too large\n");
num_failures++;
}
done:
printf((0 == num_failures) ? "PASSED\n" : "FAILED\n");
return num_failures;
}
struct ThreadInfo {
size_t cycles;
struct timespec thread_time;
struct timespec process_time;
int num_failures;
};
void *ThreadFunction(void *ptr) {
size_t i;
struct ThreadInfo *info = (struct ThreadInfo *) ptr;
for (i = 1; i < info->cycles; i++) {
/*
* Use a barrier to ensure that the compiler does not optimize the
* empty loop out as we really only want to burn the thread time.
*/
__sync_synchronize();
}
if (0 != clock_gettime(CLOCK_THREAD_CPUTIME_ID, &info->thread_time)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
info->num_failures++;
return NULL;
}
if (0 != clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &info->process_time)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
info->num_failures++;
return NULL;
}
return NULL;
}
static int ClockCpuTimeAccuracyTest(void) {
int num_failures = 0;
int err;
struct timespec t_process_start;
struct timespec t_process_end;
struct timespec t_thread_start;
struct timespec t_thread_end;
uint64_t thread_elapsed = 0;
uint64_t process_elapsed = 0;
uint64_t child_thread_elapsed = 0;
uint64_t elapsed_lower_bound;
uint64_t elapsed_upper_bound;
size_t i;
struct ThreadInfo info[10];
pthread_t thread[10];
printf("\nCLOCK_PROCESS/THREAD_CPUTIME_ID accuracy test:\n");
if (0 != clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t_thread_start)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
if (0 != clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t_process_start)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
for (i = 0; i < NACL_ARRAY_SIZE(thread); i++) {
memset(&info[i], 0, sizeof info[i]);
info[i].cycles = i * THREAD_CYCLES;
if (0 != (err = pthread_create(&thread[i], NULL,
ThreadFunction, &info[i]))) {
fprintf(stderr, "clock_test: pthread_create failed, error %d\n",
err);
num_failures++;
goto done;
}
}
for (i = 0; i < NACL_ARRAY_SIZE(thread); i++) {
if (0 != (err = pthread_join(thread[i], NULL))) {
fprintf(stderr, "clock_test: pthread_join failed, error %d\n",
err);
num_failures++;
goto done;
}
}
if (0 != clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t_process_end)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
if (0 != clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t_thread_end)) {
fprintf(stderr, "clock_test: clock_gettime (now) failed, error %d\n",
errno);
num_failures++;
goto done;
}
thread_elapsed =
(t_thread_end.tv_sec - t_thread_start.tv_sec) * NACL_NANOS_PER_UNIT +
(t_thread_end.tv_nsec - t_thread_start.tv_nsec);
process_elapsed =
(t_process_end.tv_sec - t_process_start.tv_sec) * NACL_NANOS_PER_UNIT +
(t_process_end.tv_nsec - t_process_start.tv_nsec);
for (i = 0; i < NACL_ARRAY_SIZE(thread); i++) {
uint64_t thread_elapsed_nanos;
uint64_t process_elapsed_nanos;
if (info[i].num_failures > 0) {
num_failures += info[i].num_failures;
goto done;
}
thread_elapsed_nanos = info[i].thread_time.tv_sec * NACL_NANOS_PER_UNIT +
info[i].thread_time.tv_nsec;
process_elapsed_nanos = info[i].process_time.tv_sec * NACL_NANOS_PER_UNIT +
info[i].process_time.tv_nsec;
printf("%zd: thread=%20"PRIu64" nS, process=%20"PRIu64" nS\n",
i, thread_elapsed_nanos, process_elapsed_nanos);
child_thread_elapsed += thread_elapsed_nanos;
}
elapsed_lower_bound = thread_elapsed + child_thread_elapsed;
elapsed_upper_bound = (uint64_t) (thread_elapsed +
child_thread_elapsed * g_fuzzy_factor + g_syscall_overhead);
printf("thread time: %20"PRIu64" nS\n", thread_elapsed);
printf("process time: %20"PRIu64" nS\n", process_elapsed);
printf("child thread time: %20"PRIu64" nS\n", child_thread_elapsed);
printf("elapsed lower bound: %20"PRIu64" nS\n", elapsed_lower_bound);
printf("elapsed upper bound: %20"PRIu64" nS\n", elapsed_upper_bound);
if (process_elapsed < elapsed_lower_bound ||
elapsed_upper_bound < process_elapsed) {
printf("discrepancy too large\n");
num_failures++;
}
done:
printf((0 == num_failures) ? "PASSED\n" : "FAILED\n");
return num_failures;
}
int main(int ac, char **av) {
uint64_t sleep_nanos = DEFAULT_NANOSLEEP_TIME;
int opt;
uint32_t num_failures = 0;
while (-1 != (opt = getopt(ac, av, "cf:o:s:S:"))) {
switch (opt) {
case 'c':
g_cputime = 1;
break;
case 'f':
g_fuzzy_factor = strtod(optarg, (char **) NULL);
break;
case 'o':
g_syscall_overhead = strtoul(optarg, (char **) NULL, 0);
break;
case 's':
g_slop_ms = strtoul(optarg, (char **) NULL, 0);
break;
case 'S':
sleep_nanos = strtoul(optarg, (char **) NULL, 0);
break;
default:
fprintf(stderr, "clock_test: unrecognized option `%c'.\n",
opt);
fprintf(stderr,
"Usage: nacl_clock_test [-c] [-f fuzz_factor]\n"
" [-s sleep_nanos] [-o syscall_overhead_nanos]\n");
return -1;
}
}
if (g_cputime) {
num_failures += ClockCpuTimeAccuracyTest();
} else {
num_failures += ClockMonotonicAccuracyTest(sleep_nanos);
num_failures += ClockRealtimeAccuracyTest();
}
return num_failures;
}