helper_thread: Increase timer accuracy
authorBart Van Assche <bvanassche@acm.org>
Sat, 12 Sep 2020 14:58:00 +0000 (07:58 -0700)
committerBart Van Assche <bvanassche@acm.org>
Sat, 26 Sep 2020 02:43:43 +0000 (19:43 -0700)
The helper_thread uses select() to wait for the next event. select() rounds
up its timeout argument to 1 / HZ. Increase timer accuracy by using a
high-res timer if possible. This patch has been tested by running the
following fio command and by verifying that the next_log and
msec_to_next_event were always above 100 ms:

./fio --debug=helperthread --bandwidth-log --write_bw_log=log --log_avg_msec=1000 --ioengine=null --name=null --size=1G --rw=randwrite --loops=999999 --runtime=60

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
configure
helper_thread.c

index 3846881c54e54f468d2aeec9f9ec2ffebcb6f27b..39a9248db119aed131ada9e6a22600e0fb364a01 100755 (executable)
--- a/configure
+++ b/configure
@@ -2682,6 +2682,24 @@ else
   pdb=no
 fi
 print_config "Windows PDB generation" "$pdb"
+
+##########################################
+# check for timerfd support
+timerfd_create="no"
+cat > $TMPC << EOF
+#include <sys/time.h>
+#include <sys/timerfd.h>
+
+int main(int argc, char **argv)
+{
+       return timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+}
+EOF
+if compile_prog "" "" "timerfd_create"; then
+  timerfd_create="yes"
+fi
+print_config "timerfd_create" "$timerfd_create"
+
 #############################################################################
 
 if test "$wordsize" = "64" ; then
@@ -2983,6 +3001,9 @@ fi
 if test "$statx_syscall" = "yes"; then
   output_sym "CONFIG_HAVE_STATX_SYSCALL"
 fi
+if test "$timerfd_create" = "yes"; then
+  output_sym "CONFIG_HAVE_TIMERFD_CREATE"
+fi
 if test "$fallthrough" = "yes"; then
   CFLAGS="$CFLAGS -Wimplicit-fallthrough"
 fi
index f6cdddb8f0532af5be1a0463bb7d9fadf694500f..2d553654c0814d29bc76a4038d97d547e5558adc 100644 (file)
@@ -1,5 +1,8 @@
 #include <signal.h>
 #include <unistd.h>
+#ifdef CONFIG_HAVE_TIMERFD_CREATE
+#include <sys/timerfd.h>
+#endif
 #ifdef CONFIG_VALGRIND_DEV
 #include <valgrind/drd.h>
 #else
@@ -13,6 +16,7 @@
 #include "pshared.h"
 
 static int sleep_accuracy_ms;
+static int timerfd = -1;
 
 enum action {
        A_EXIT          = 1,
@@ -179,6 +183,7 @@ static uint8_t wait_for_action(int fd, unsigned int timeout_ms)
        };
        fd_set rfds, efds;
        uint8_t action = 0;
+       uint64_t exp;
        int res;
 
        res = read_from_pipe(fd, &action, sizeof(action));
@@ -188,7 +193,25 @@ static uint8_t wait_for_action(int fd, unsigned int timeout_ms)
        FD_SET(fd, &rfds);
        FD_ZERO(&efds);
        FD_SET(fd, &efds);
-       res = select(fd + 1, &rfds, NULL, &efds, &timeout);
+#ifdef CONFIG_HAVE_TIMERFD_CREATE
+       {
+               /*
+                * If the timer frequency is 100 Hz, select() will round up
+                * `timeout` to the next multiple of 1 / 100 Hz = 10 ms. Hence
+                * use a high-resolution timer if possible to increase
+                * select() timeout accuracy.
+                */
+               struct itimerspec delta = {};
+
+               delta.it_value.tv_sec = timeout.tv_sec;
+               delta.it_value.tv_nsec = timeout.tv_usec * 1000;
+               res = timerfd_settime(timerfd, 0, &delta, NULL);
+               assert(res == 0);
+               FD_SET(timerfd, &rfds);
+       }
+#endif
+       res = select(max(fd, timerfd) + 1, &rfds, NULL, &efds,
+                    timerfd >= 0 ? NULL : &timeout);
        if (res < 0) {
                log_err("fio: select() call in helper thread failed: %s",
                        strerror(errno));
@@ -196,6 +219,10 @@ static uint8_t wait_for_action(int fd, unsigned int timeout_ms)
        }
        if (FD_ISSET(fd, &rfds))
                read_from_pipe(fd, &action, sizeof(action));
+       if (timerfd >= 0 && FD_ISSET(timerfd, &rfds)) {
+               res = read(timerfd, &exp, sizeof(exp));
+               assert(res == sizeof(exp));
+       }
        return action;
 }
 
@@ -272,6 +299,12 @@ static void *helper_thread_main(void *data)
        assert(clk_tck > 0);
        sleep_accuracy_ms = (1000 + clk_tck - 1) / clk_tck;
 
+#ifdef CONFIG_HAVE_TIMERFD_CREATE
+       timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+       assert(timerfd >= 0);
+       sleep_accuracy_ms = 1;
+#endif
+
        sk_out_assign(hd->sk_out);
 
        /* Let another thread handle signals. */
@@ -317,6 +350,11 @@ static void *helper_thread_main(void *data)
                        print_thread_status();
        }
 
+       if (timerfd >= 0) {
+               close(timerfd);
+               timerfd = -1;
+       }
+
        fio_writeout_logs(false);
 
        sk_out_drop();