| Index: tests/clock/clock_test.c
|
| diff --git a/tests/clock/clock_test.c b/tests/clock/clock_test.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..63010d6b0e111b3e85edb7f9688d23b8dc413609
|
| --- /dev/null
|
| +++ b/tests/clock/clock_test.c
|
| @@ -0,0 +1,364 @@
|
| +/*
|
| + * 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.
|
| + */
|
| +
|
| +
|
| +#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)
|
| +#define NANOS_PER_UNIT (NANOS_PER_MICRO * MICROS_PER_UNIT)
|
| +
|
| +/*
|
| + * 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 * NANOS_PER_MILLI)
|
| +#define DEFAULT_NANOSLEEP_EXTRA_FACTOR (100.0)
|
| +#define DEFAULT_NANOSLEEP_TIME (10 * NANOS_PER_MILLI)
|
| +
|
| +/*
|
| + * Global testing parameters -- fuzziness coefficients in determining
|
| + * what is considered accurate.
|
| + */
|
| +static int g_cputime = 1;
|
| +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 / NANOS_PER_UNIT;
|
| + t_sleep.tv_nsec = sleep_nanos % 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) * NANOS_PER_UNIT +
|
| + (t_end.tv_nsec - t_start.tv_nsec) + g_slop_ms * 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 * NANOS_PER_UNIT + t_now_ts.tv_nsec;
|
| + t_now_tv_nanos = t_now_tv.tv_sec * NANOS_PER_UNIT +
|
| + t_now_tv.tv_usec * 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 * 10000000;
|
| + 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) * 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) * 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 * NANOS_PER_UNIT +
|
| + info[i].thread_time.tv_nsec;
|
| + process_elapsed_nanos = info[i].process_time.tv_sec * 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 = 0;
|
| + 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: clock_test [-f fuzz_factor] [-s sleep_nanos]\n"
|
| + " [-o syscall_overhead_nanos]\n");
|
| + return -1;
|
| + }
|
| + }
|
| +
|
| + num_failures += ClockMonotonicAccuracyTest(sleep_nanos);
|
| + num_failures += ClockRealtimeAccuracyTest();
|
| + if (g_cputime) {
|
| + num_failures += ClockCpuTimeAccuracyTest();
|
| + }
|
| +
|
| + return num_failures;
|
| +}
|
|
|