diff options
-rw-r--r-- | support/Makefile | 1 | ||||
-rw-r--r-- | support/timespec.c | 64 | ||||
-rw-r--r-- | support/timespec.h | 8 | ||||
-rw-r--r-- | support/tst-timespec.c | 320 | ||||
-rw-r--r-- | time/tst-cpuclock1.c | 52 |
5 files changed, 415 insertions, 30 deletions
diff --git a/support/Makefile b/support/Makefile index 764b471..e74e0dd 100644 --- a/support/Makefile +++ b/support/Makefile @@ -238,6 +238,7 @@ tests = \ tst-test_compare \ tst-test_compare_blob \ tst-test_compare_string \ + tst-timespec \ tst-xreadlink \ tst-xsigstack \ diff --git a/support/timespec.c b/support/timespec.c index ea6b947..9f5449e 100644 --- a/support/timespec.c +++ b/support/timespec.c @@ -19,6 +19,8 @@ #include <support/timespec.h> #include <stdio.h> #include <stdint.h> +#include <assert.h> +#include <intprops.h> void test_timespec_before_impl (const char *file, int line, @@ -57,3 +59,65 @@ test_timespec_equal_or_after_impl (const char *file, int line, (intmax_t) diff.tv_sec, (intmax_t) diff.tv_nsec); } } + +/* Convert TIME to nanoseconds stored in a long. + Returns long maximum or minimum if the conversion overflows + or underflows, respectively. */ +long +support_timespec_ns (struct timespec time) +{ + long time_ns; + if (INT_MULTIPLY_WRAPV(time.tv_sec, TIMESPEC_HZ, &time_ns)) + { + return (time.tv_sec < 0) ? TYPE_MINIMUM(long): TYPE_MAXIMUM(long); + } + if (INT_ADD_WRAPV(time_ns, time.tv_nsec, &time_ns)) + { + return (time.tv_nsec < 0) ? TYPE_MINIMUM(long): TYPE_MAXIMUM(long); + } + return time_ns; +} + +/* Returns time normalized timespec with .tv_nsec < TIMESPEC_HZ + and the whole seconds added to .tv_sec. If an overflow or + underflow occurs the values are clamped to its maximum or + minimum respectively. */ +struct timespec +support_timespec_normalize (struct timespec time) +{ + struct timespec norm; + if (INT_ADD_WRAPV (time.tv_sec, (time.tv_nsec / TIMESPEC_HZ), &norm.tv_sec)) + { + norm.tv_sec = (time.tv_nsec < 0) ? TYPE_MINIMUM (time_t): TYPE_MAXIMUM (time_t); + norm.tv_nsec = (time.tv_nsec < 0) ? -1 * (TIMESPEC_HZ - 1) : TIMESPEC_HZ - 1; + return norm; + } + norm.tv_nsec = time.tv_nsec % TIMESPEC_HZ; + return norm; +} + +/* Returns TRUE if the observed time is within the given percentage + bounds of the expected time, and FALSE otherwise. + For example the call + + support_timespec_check_in_range(expected, observed, 0.5, 1.2); + + will check if + + 0.5 of expected <= observed <= 1.2 of expected + + In other words it will check if observed time is within 50% to + 120% of the expected time. */ +int +support_timespec_check_in_range (struct timespec expected, struct timespec observed, + double lower_bound, double upper_bound) +{ + assert (upper_bound >= lower_bound); + long expected_norm, observed_norm; + expected_norm = support_timespec_ns (expected); + /* Don't divide by zero */ + assert(expected_norm != 0); + observed_norm = support_timespec_ns (observed); + double ratio = (double)observed_norm / expected_norm; + return (lower_bound <= ratio && ratio <= upper_bound); +} diff --git a/support/timespec.h b/support/timespec.h index c5852df..fd54667 100644 --- a/support/timespec.h +++ b/support/timespec.h @@ -48,6 +48,14 @@ void test_timespec_equal_or_after_impl (const char *file, int line, const struct timespec left, const struct timespec right); +long support_timespec_ns (struct timespec time); + +struct timespec support_timespec_normalize (struct timespec time); + +int support_timespec_check_in_range (struct timespec expected, struct timespec observed, + double lower_bound, double upper_bound); + + /* Check that the timespec on the left represents a time before the time on the right. */ #define TEST_TIMESPEC_BEFORE(left, right) \ diff --git a/support/tst-timespec.c b/support/tst-timespec.c new file mode 100644 index 0000000..7142355 --- /dev/null +++ b/support/tst-timespec.c @@ -0,0 +1,320 @@ +/* Test for support_timespec_check_in_range function. + Copyright (C) 2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <support/timespec.h> +#include <support/check.h> +#include <limits.h> + +#define TIMESPEC_HZ 1000000000 + +struct timespec_ns_test_case +{ + struct timespec time; + long time_ns; +}; + +struct timespec_norm_test_case +{ + struct timespec time; + struct timespec norm; +}; + +struct timespec_test_case +{ + struct timespec expected; + struct timespec observed; + double upper_bound; + double lower_bound; + int result; +}; + +/* Test cases for timespec_ns */ +struct timespec_ns_test_case ns_cases[] = { + {.time = {.tv_sec = 0, .tv_nsec = 0}, + .time_ns = 0, + }, + {.time = {.tv_sec = 0, .tv_nsec = 1}, + .time_ns = 1, + }, + {.time = {.tv_sec = 1, .tv_nsec = 0}, + .time_ns = TIMESPEC_HZ, + }, + {.time = {.tv_sec = 1, .tv_nsec = 1}, + .time_ns = TIMESPEC_HZ + 1, + }, + {.time = {.tv_sec = 0, .tv_nsec = -1}, + .time_ns = -1, + }, + {.time = {.tv_sec = -1, .tv_nsec = 0}, + .time_ns = -TIMESPEC_HZ, + }, + {.time = {.tv_sec = -1, .tv_nsec = -1}, + .time_ns = -TIMESPEC_HZ - 1, + }, + {.time = {.tv_sec = 1, .tv_nsec = -1}, + .time_ns = TIMESPEC_HZ - 1, + }, + {.time = {.tv_sec = -1, .tv_nsec = 1}, + .time_ns = -TIMESPEC_HZ + 1, + }, + /* Overflow bondary by 2 */ + {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ - 1}, + .time_ns = LONG_MAX - 1, + }, + /* Overflow bondary */ + {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ}, + .time_ns = LONG_MAX, + }, + /* Underflow bondary by 1 */ + {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ + 1}, + .time_ns = LONG_MIN + 1, + }, + /* Underflow bondary */ + {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ}, + .time_ns = LONG_MIN, + }, + /* Multiplication overflow */ + {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ + 1, .tv_nsec = 1}, + .time_ns = LONG_MAX, + }, + /* Multiplication underflow */ + {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ - 1, .tv_nsec = -1}, + .time_ns = LONG_MIN, + }, + /* Sum overflows */ + {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ + 1}, + .time_ns = LONG_MAX, + }, + /* Sum underflow */ + {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ - 1}, + .time_ns = LONG_MIN, + } +}; + +/* Test cases for timespec_norm */ +struct timespec_norm_test_case norm_cases[] = { + /* Positive cases */ + {.time = {.tv_sec = 0, .tv_nsec = 0}, + .norm = {.tv_sec = 0, .tv_nsec = 0} + }, + {.time = {.tv_sec = 1, .tv_nsec = 0}, + .norm = {.tv_sec = 1, .tv_nsec = 0} + }, + {.time = {.tv_sec = 0, .tv_nsec = 1}, + .norm = {.tv_sec = 0, .tv_nsec = 1} + }, + {.time = {.tv_sec = 0, .tv_nsec = TIMESPEC_HZ}, + .norm = {.tv_sec = 1, .tv_nsec = 0} + }, + {.time = {.tv_sec = 0, .tv_nsec = TIMESPEC_HZ + 1}, + .norm = {.tv_sec = 1, .tv_nsec = 1} + }, + {.time = {.tv_sec = 1, .tv_nsec = TIMESPEC_HZ}, + .norm = {.tv_sec = 2, .tv_nsec = 0} + }, + {.time = {.tv_sec = 1, .tv_nsec = TIMESPEC_HZ + 1}, + .norm = {.tv_sec = 2, .tv_nsec = 1} + }, + /* Negative cases */ + {.time = {.tv_sec = 0, .tv_nsec = -TIMESPEC_HZ}, + .norm = {.tv_sec = -1, .tv_nsec = 0} + }, + {.time = {.tv_sec = 0, .tv_nsec = -TIMESPEC_HZ - 1}, + .norm = {.tv_sec = -1, .tv_nsec = -1} + }, + {.time = {.tv_sec = -1, .tv_nsec = -TIMESPEC_HZ}, + .norm = {.tv_sec = -2, .tv_nsec = 0} + }, + {.time = {.tv_sec = -1, .tv_nsec = -TIMESPEC_HZ - 1}, + .norm = {.tv_sec = -2, .tv_nsec = -1} + }, + /* Overflow bondary by 2 */ + {.time = {.tv_sec = LONG_MAX - 2, .tv_nsec = TIMESPEC_HZ + 1}, + .norm = {.tv_sec = LONG_MAX - 1, 1}, + }, + /* Overflow bondary by 1 */ + {.time = {.tv_sec = LONG_MAX - 1, .tv_nsec = TIMESPEC_HZ + 1}, + .norm = {.tv_sec = LONG_MAX, .tv_nsec = 1}, + }, + /* Underflow bondary by 2 */ + {.time = {.tv_sec = LONG_MIN + 2, .tv_nsec = -TIMESPEC_HZ - 1}, + .norm = {.tv_sec = LONG_MIN + 1, -1}, + }, + /* Underflow bondary by 1 */ + {.time = {.tv_sec = LONG_MIN + 1, .tv_nsec = -TIMESPEC_HZ - 1}, + .norm = {.tv_sec = LONG_MIN, .tv_nsec = -1}, + }, + /* SUM overflow */ + {.time = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ}, + .norm = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 1}, + }, + /* SUM underflow */ + {.time = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ}, + .norm = {.tv_sec = LONG_MIN, .tv_nsec = -1 * (TIMESPEC_HZ - 1)}, + } +}; + +/* Test cases for timespec_check_in_range */ +struct timespec_test_case check_cases[] = { + /* 0 - In range */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 1, .tv_nsec = 0}, + .upper_bound = 1, .lower_bound = 1, .result = 1, + }, + /* 1 - Out of range */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 2, .tv_nsec = 0}, + .upper_bound = 1, .lower_bound = 1, .result = 0, + }, + /* 2 - Upper Bound */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 2, .tv_nsec = 0}, + .upper_bound = 2, .lower_bound = 1, .result = 1, + }, + /* 3 - Lower Bound */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 0, .tv_nsec = 0}, + .upper_bound = 1, .lower_bound = 0, .result = 1, + }, + /* 4 - Out of range by nanosecs */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 1, .tv_nsec = 500}, + .upper_bound = 1, .lower_bound = 1, .result = 0, + }, + /* 5 - In range by nanosecs */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 1, .tv_nsec = 50000}, + .upper_bound = 1.3, .lower_bound = 1, .result = 1, + }, + /* 6 - Big nanosecs */ + {.expected = {.tv_sec = 1, .tv_nsec = 0}, + .observed = {.tv_sec = 0, .tv_nsec = 4000000}, + .upper_bound = 1, .lower_bound = .001, .result = 1, + }, + /* 7 - In range Negative values */ + {.expected = {.tv_sec = -1, .tv_nsec = 0}, + .observed = {.tv_sec = -1, .tv_nsec = 0}, + .upper_bound = 1, .lower_bound = 1, .result = 1, + }, + /* 8 - Out of range Negative values */ + {.expected = {.tv_sec = -1, .tv_nsec = 0}, + .observed = {.tv_sec = -1, .tv_nsec = 0}, + .upper_bound = -1, .lower_bound = -1, .result = 0, + }, + /* 9 - Negative values with negative nanosecs */ + {.expected = {.tv_sec = -1, .tv_nsec = 0}, + .observed = {.tv_sec = -1, .tv_nsec = -2000}, + .upper_bound = 1, .lower_bound = 1, .result = 0, + }, + /* 10 - Strict bounds */ + {.expected = {.tv_sec = -1, .tv_nsec = 0}, + .observed = {.tv_sec = -1, .tv_nsec = -20000}, + .upper_bound = 1.00002, .lower_bound = 1.0000191, .result = 1, + }, + /* 11 - Strict bounds with loose upper bound */ + {.expected = {.tv_sec = 1, .tv_nsec = 20000}, + .observed = {.tv_sec = 1, .tv_nsec = 30000}, + .upper_bound = 1.0000100000, .lower_bound = 1.0000099998, .result = 1, + }, + /* 12 - Strict bounds with loose lower bound */ + {.expected = {.tv_sec = 1, .tv_nsec = 20000}, + .observed = {.tv_sec = 1, .tv_nsec = 30000}, + .upper_bound = 1.0000099999, .lower_bound = 1.00000999979, .result = 1, + }, + /* 13 - Strict bounds highest precision */ + {.expected = {.tv_sec = 1, .tv_nsec = 20000}, + .observed = {.tv_sec = 1, .tv_nsec = 30000}, + .upper_bound = 1.00000999980001, .lower_bound = 1.00000999979999, .result = 1, + }, + /* Maximum/Minimum long values */ + /* 14 */ + {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 1}, + .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 2}, + .upper_bound = 1, .lower_bound = .9, .result = 1, + }, + /* 15 - support_timespec_ns overflow */ + {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ}, + .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ}, + .upper_bound = 1, .lower_bound = 1, .result = 1, + }, + /* 16 - support_timespec_ns overflow + underflow */ + {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ}, + .observed = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ}, + .upper_bound = 1, .lower_bound = 1, .result = 0, + }, + /* 17 - support_timespec_ns underflow */ + {.expected = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ}, + .observed = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ}, + .upper_bound = 1, .lower_bound = 1, .result = 1, + }, + /* 18 - support_timespec_ns underflow + overflow */ + {.expected = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ}, + .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ}, + .upper_bound = 1, .lower_bound = 1, .result = 0, + }, + /* 19 - Biggest division */ + {.expected = {.tv_sec = LONG_MAX / TIMESPEC_HZ , .tv_nsec = TIMESPEC_HZ - 1}, + .observed = {.tv_sec = 0, .tv_nsec = 1}, + .upper_bound = 1, .lower_bound = 1.0842021724855044e-19, .result = 1, + }, + /* 20 - Lowest division */ + {.expected = {.tv_sec = 0, .tv_nsec = 1}, + .observed = {.tv_sec = LONG_MAX / TIMESPEC_HZ , .tv_nsec = TIMESPEC_HZ - 1}, + .upper_bound = LONG_MAX, .lower_bound = 1, .result = 1, + }, +}; + +static int +do_test (void) +{ + int i = 0; + int ntests = sizeof (ns_cases) / sizeof (ns_cases[0]); + + printf("Testing support_timespec_ns\n"); + for (i = 0; i < ntests; i++) + { + TEST_COMPARE (support_timespec_ns (ns_cases[i].time), + ns_cases[i].time_ns); + } + + ntests = sizeof (norm_cases) / sizeof (norm_cases[0]); + struct timespec result; + printf("Testing support_timespec_normalize\n"); + for (i = 0; i < ntests; i++) + { + result = support_timespec_normalize (norm_cases[i].time); + TEST_COMPARE (norm_cases[i].norm.tv_sec, result.tv_sec); + TEST_COMPARE (norm_cases[i].norm.tv_nsec, result.tv_nsec); + } + + ntests = sizeof (check_cases) / sizeof (check_cases[0]); + printf("Testing support_timespec_check_in_range\n"); + for (i = 0; i < ntests; i++) + { + /* Its hard to find which test failed with just the TEST_COMPARE report. + So here we print every running testcase as well. */ + printf("Test case %d\n", i); + TEST_COMPARE (support_timespec_check_in_range + (check_cases[i].expected, check_cases[i].observed, + check_cases[i].lower_bound, + check_cases[i].upper_bound), check_cases[i].result); + } + return 0; +} + +#include <support/test-driver.c> diff --git a/time/tst-cpuclock1.c b/time/tst-cpuclock1.c index 0120906..1ac611a 100644 --- a/time/tst-cpuclock1.c +++ b/time/tst-cpuclock1.c @@ -26,6 +26,7 @@ #include <signal.h> #include <stdint.h> #include <sys/wait.h> +#include <support/timespec.h> /* This function is intended to rack up both user and system time. */ static void @@ -155,16 +156,12 @@ do_test (void) printf ("live PID %d after sleep => %ju.%.9ju\n", child, (uintmax_t) after.tv_sec, (uintmax_t) after.tv_nsec); - struct timespec diff = { .tv_sec = after.tv_sec - before.tv_sec, - .tv_nsec = after.tv_nsec - before.tv_nsec }; - if (diff.tv_nsec < 0) - { - --diff.tv_sec; - diff.tv_nsec += 1000000000; - } - if (diff.tv_sec != 0 - || diff.tv_nsec > 600000000 - || diff.tv_nsec < 100000000) + /* The bound values are empirically defined by testing this code over high cpu + usage and different nice values. Of all the values we keep the 90th + percentile of values and use those values for our testing allowed range. */ + struct timespec diff = timespec_sub (support_timespec_normalize (after), + support_timespec_normalize (before)); + if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.1)) { printf ("before - after %ju.%.9ju outside reasonable range\n", (uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec); @@ -194,19 +191,16 @@ do_test (void) } else { - struct timespec d = { .tv_sec = afterns.tv_sec - after.tv_sec, - .tv_nsec = afterns.tv_nsec - after.tv_nsec }; - if (d.tv_nsec < 0) - { - --d.tv_sec; - d.tv_nsec += 1000000000; - } - if (d.tv_sec > 0 - || d.tv_nsec < sleeptime.tv_nsec - || d.tv_nsec > sleeptime.tv_nsec * 2) + /* The bound values are empirically defined by testing this code over + high cpu usage and different nice values. Of all the values we keep + the 90th percentile of values and use those values for our testing + allowed range. */ + diff = timespec_sub (support_timespec_normalize (afterns), + support_timespec_normalize (after)); + if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.2)) { printf ("nanosleep time %ju.%.9ju outside reasonable range\n", - (uintmax_t) d.tv_sec, (uintmax_t) d.tv_nsec); + (uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec); result = 1; } } @@ -240,15 +234,13 @@ do_test (void) /* Should be close to 0.6. */ printf ("dead PID %d => %ju.%.9ju\n", child, (uintmax_t) dead.tv_sec, (uintmax_t) dead.tv_nsec); - - diff.tv_sec = dead.tv_sec - after.tv_sec; - diff.tv_nsec = dead.tv_nsec - after.tv_nsec; - if (diff.tv_nsec < 0) - { - --diff.tv_sec; - diff.tv_nsec += 1000000000; - } - if (diff.tv_sec != 0 || diff.tv_nsec > 200000000) + /* The bound values are empirically defined by testing this code over high cpu + usage and different nice values. Of all the values we keep the 90th + percentile of values and use those values for our testing allowed range. */ + diff = timespec_sub (support_timespec_normalize (dead), + support_timespec_normalize (after)); + sleeptime.tv_nsec = 100000000; + if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.2)) { printf ("dead - after %ju.%.9ju outside reasonable range\n", (uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec); |