From 1f320dd7a2c38e77745316ac2fdc20699d98db89 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Wed, 30 Aug 2023 22:23:43 +0100 Subject: [PATCH] Replace all gettimeofday() calls, and elapsed time arithmetic, with a new set of elapsed time functions based on clock_gettime(), to avoid faults due to time zone / DST changes and machine suspend/resume. --- Makefile.am | 1 + doc/ACKNOWLEDGEMENTS.md | 3 +- doc/NEWS.md | 1 + doc/TODO.md | 1 - src/include/pv-internal.h | 25 ++-- src/include/pv.h | 38 +++++ src/pv/elapsedtime.c | 198 +++++++++++++++++++++++++ src/pv/loop.c | 303 ++++++++++++++++++-------------------- src/pv/signal.c | 34 ++--- src/pv/transfer.c | 46 +++--- src/pv/watchpid.c | 2 +- 11 files changed, 433 insertions(+), 219 deletions(-) create mode 100644 src/pv/elapsedtime.c diff --git a/Makefile.am b/Makefile.am index 27b82b3..a738085 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,7 @@ src/main/remote.c \ src/main/version.c \ src/pv/cursor.c \ src/pv/display.c \ +src/pv/elapsedtime.c \ src/pv/file.c \ src/pv/loop.c \ src/pv/number.c \ diff --git a/doc/ACKNOWLEDGEMENTS.md b/doc/ACKNOWLEDGEMENTS.md index 029955f..206baea 100644 --- a/doc/ACKNOWLEDGEMENTS.md +++ b/doc/ACKNOWLEDGEMENTS.md @@ -84,6 +84,7 @@ is acknowledged and greatly appreciated: * [Nick Black](https://nick-black.com) - added "`--bits`" option * [Andrew Schulman](https://github.com/andrew-schulman) - provided reproducible example of terminal size detection issue in 1.7.17/1.7.18 * [fuschia74](https://github.com/fuchsia74) - provided "`--enable-static`" patch for "`configure`" - * [Wilhelm von Thiele](https://github.com/TurtleWilly) - assisted with OS X cleanups ([Issue#73](https://codeberg.org/a-j-wood/pv/issues/73), [Issue#74](https://codeberg.org/a-j-wood/pv/issues/74)) + * [Wilhelm von Thiele](https://github.com/TurtleWilly) - assisted with OS X cleanups ([#73](https://codeberg.org/a-j-wood/pv/issues/73), [#74](https://codeberg.org/a-j-wood/pv/issues/74)) + * Matějů Miroslav, Ing. - suggested fix for ETA and elapsed time faults when suspending and resuming a machine ([#13](https://codeberg.org/a-j-wood/pv/issues/13)) --- diff --git a/doc/NEWS.md b/doc/NEWS.md index 248589d..ade5922 100644 --- a/doc/NEWS.md +++ b/doc/NEWS.md @@ -7,6 +7,7 @@ * security: keep self-contained copies of name and format string in PV internal state for memory safety * fix: only report errors about missing files when starting to transfer from them, not while calculating size, and behave more like `cat`(1) by skipping them and moving on * fix: auto-calculate total line count with "`--line-mode`" when all inputs are regular files + * fix: use `clock_gettime()` in ETA calculation to cope with machine suspend/resume ([#13](https://codeberg.org/a-j-wood/pv/issues/13)) * fix: if "`--width`" or "`--height`" were provided, do not change them when the window size changes ([#36](https://codeberg.org/a-j-wood/pv/issues/36)) * cleanup: switched the build system to GNU Automake * cleanup: added a test for terminal width detection to "`make check`" diff --git a/doc/TODO.md b/doc/TODO.md index 61d178f..2067063 100644 --- a/doc/TODO.md +++ b/doc/TODO.md @@ -4,7 +4,6 @@ Bugs ---- * ([#5](https://codeberg.org/a-j-wood/pv/issues/5)) Transfer IPC leadership on exit of leader - * ([#13](https://codeberg.org/a-j-wood/pv/issues/13)) Use `clock_gettime()` in ETA calculation to cope with machine suspend/resume (Mateju Miroslav) * ([#20](https://codeberg.org/a-j-wood/pv/issues/20)) Terminal state is not restored correctly in all cases (VA) * ([#24](https://codeberg.org/a-j-wood/pv/issues/24)) Debian #890901 - Race condition with multiple "`pv -c`" leaves terminal state inconsistent (Lars Ellenberg, Viktor Ashirov) * ([#34](https://codeberg.org/a-j-wood/pv/issues/34)) Continue timer even if input or output is blocking (Martin Probst - Jun 2017) diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 2343017..4292273 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -30,15 +31,15 @@ extern "C" { #define PV_DISPLAY_OUTPUTBUF 256 #define PV_DISPLAY_FINETA 512 -#define RATE_GRANULARITY 100000 /* usec between -L rate chunks */ -#define RATE_BURST_WINDOW 5 /* rate burst window (multiples of rate) */ -#define REMOTE_INTERVAL 100000 /* usec between checks for -R */ -#define BUFFER_SIZE 409600 /* default transfer buffer size */ -#define BUFFER_SIZE_MAX 524288 /* max auto transfer buffer size */ -#define MAX_READ_AT_ONCE 524288 /* max to read() in one go */ -#define MAX_WRITE_AT_ONCE 524288 /* max to write() in one go */ -#define TRANSFER_READ_TIMEOUT 90000 /* usec to time reads out at */ -#define TRANSFER_WRITE_TIMEOUT 900000 /* usec to time writes out at */ +#define RATE_GRANULARITY 100000000.0L /* nsec between -L rate chunks */ +#define RATE_BURST_WINDOW 5 /* rate burst window (multiples of rate) */ +#define REMOTE_INTERVAL 100000000 /* nsec between checks for -R */ +#define BUFFER_SIZE 409600 /* default transfer buffer size */ +#define BUFFER_SIZE_MAX 524288 /* max auto transfer buffer size */ +#define MAX_READ_AT_ONCE 524288 /* max to read() in one go */ +#define MAX_WRITE_AT_ONCE 524288 /* max to write() in one go */ +#define TRANSFER_READ_TIMEOUT 0.09L /* seconds to time reads out at */ +#define TRANSFER_WRITE_TIMEOUT 0.9L /* seconds to time writes out at */ #define MAXIMISE_BUFFER_FILL 1 @@ -125,8 +126,8 @@ struct pvstate_s { * Signal handling * *******************/ int pv_sig_old_stderr; /* see pv_sig_ttou() */ - struct timeval pv_sig_tstp_time; /* see pv_sig_tstp() / __cont() */ - struct timeval pv_sig_toffset; /* total time spent stopped */ + struct timespec pv_sig_tstp_time; /* see pv_sig_tstp() / __cont() */ + struct timespec pv_sig_toffset; /* total time spent stopped */ volatile sig_atomic_t pv_sig_newsize; /* whether we need to get term size again */ volatile sig_atomic_t pv_sig_abort; /* whether we need to abort right now */ volatile sig_atomic_t reparse_display; /* whether to re-check format string */ @@ -265,7 +266,7 @@ struct pvwatchfd_s { struct stat sb_fd_link; /* lstat of fd symlink */ unsigned long long size; /* size of whole file, 0 if unknown */ long long position; /* position last seen at */ - struct timeval start_time; /* time we started watching the fd */ + struct timespec start_time; /* time we started watching the fd */ }; typedef struct pvwatchfd_s *pvwatchfd_t; diff --git a/src/include/pv.h b/src/include/pv.h index ed9cb29..05ccfa6 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -15,6 +15,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -76,6 +77,43 @@ extern int pv_snprintf(char *, size_t, const char *, ...); * termination with '\0'. */ extern size_t pv_strlcat(char *, const char *, size_t); + +/* + * Functions relating to elapsed time. + */ + +/* + * Read the current elapsed time, relative to an unspecified point in the + * past, and store it in the given timespec buffer. The time is guaranteed + * to not go backwards and does not count time when the system was + * suspended. See clock_gettime(2) with CLOCK_MONOTONIC. + */ +void pv_elapsedtime_read(struct timespec *); + +/* Set the time in the given timespec to zero. */ +void pv_elapsedtime_zero(struct timespec *); + +/* Copy the second timespec into the first. Analogous to strcpy(3). */ +void pv_elapsedtime_copy(struct timespec *, const struct timespec *); + +/* + * Return -1, 0, or 1 depending on whether the first time is earlier than, + * equal to, or later than the second time. Analogous to strcmp(3). + */ +int pv_elapsedtime_compare(const struct timespec *, const struct timespec *); + +/* Add the latter two timespecs and store them in the first timespec. */ +void pv_elapsedtime_add(struct timespec *, const struct timespec *, const struct timespec *); + +/* Add a number of nanoseconds to the given timespec. */ +void pv_elapsedtime_add_nsec(struct timespec *, long long); + +/* Set the first timespec to the second minus the third. */ +void pv_elapsedtime_subtract(struct timespec *, const struct timespec *, const struct timespec *); + +/* Convert a timespec to seconds. */ +long double pv_elapsedtime_seconds(const struct timespec *); + /* * Main PV functions. diff --git a/src/pv/elapsedtime.c b/src/pv/elapsedtime.c new file mode 100644 index 0000000..f0a7fc5 --- /dev/null +++ b/src/pv/elapsedtime.c @@ -0,0 +1,198 @@ +/* + * Functions relating to elapsed time. + * + * Copyright 2023 Andrew Wood + * + * Distributed under the Artistic License v2.0; see `doc/COPYING'. + */ + +#include "config.h" +#include "pv.h" +#include "pv-internal.h" +#include +#include +#include + + +/* + * Read the current elapsed time, relative to an unspecified point in the + * past, and store it in the given timespec buffer. The time is guaranteed + * to not go backwards and does not count time when the system was + * suspended. See clock_gettime(2) with CLOCK_MONOTONIC. + * + * The read should not fail; if it does, the program is aborted with exit + * status 16. + */ +void pv_elapsedtime_read(struct timespec *return_time) +{ + if (0 != clock_gettime(CLOCK_MONOTONIC, return_time)) { + fprintf(stderr, "%s: %s: %s\n", PACKAGE_NAME, "clock_gettime", strerror(errno)); + exit(16); + } +} + + +/* + * Set the time in the given timespec to zero. + */ +void pv_elapsedtime_zero(struct timespec *zero_time) +{ + if (NULL == zero_time) + return; + zero_time->tv_sec = 0; + zero_time->tv_nsec = 0; +} + + +/* + * Copy source_time into dest_time. Analogous to strcpy(3). + */ +void pv_elapsedtime_copy(struct timespec *dest_time, const struct timespec *source_time) +{ + if (NULL == dest_time) + return; + if (NULL == source_time) + return; + dest_time->tv_sec = source_time->tv_sec; + dest_time->tv_nsec = source_time->tv_nsec; +} + + +/* + * Return -1, 0, or 1 depending on whether the first time is earlier than, + * equal to, or later than the second time. Analogous to strcmp(3). + */ +int pv_elapsedtime_compare(const struct timespec *first_time, const struct timespec *second_time) +{ + /* Treat NULL as a zero time. */ + if ((NULL == first_time) && (NULL == second_time)) + return 0; + if ((NULL == first_time) && (NULL != second_time)) + return -1; + if ((NULL != first_time) && (NULL == second_time)) + return 1; + + /* Check the seconds part, first */ + if (first_time->tv_sec < second_time->tv_sec) + return -1; + if (first_time->tv_sec > second_time->tv_sec) + return 1; + + /* Seconds are equal - compare nanoseconds. */ + if (first_time->tv_nsec < second_time->tv_nsec) + return -1; + if (first_time->tv_nsec > second_time->tv_nsec) + return 1; + + /* Nanoseconds are also equal - times are equal. */ + return 0; +} + + +/* + * Add first_time and second_time, writing the result to return_time. + */ +void pv_elapsedtime_add(struct timespec *return_time, const struct timespec *first_time, + const struct timespec *second_time) +{ + long long seconds, nanoseconds; + + if (NULL == return_time) + return; + + seconds = 0; + nanoseconds = 0; + + if (NULL != first_time) { + seconds += first_time->tv_sec; + nanoseconds += first_time->tv_nsec; + } + + if (NULL != second_time) { + seconds += second_time->tv_sec; + nanoseconds += second_time->tv_nsec; + } + + seconds += nanoseconds / 1000000000; + nanoseconds = nanoseconds % 1000000000; + + return_time->tv_sec = seconds; + return_time->tv_nsec = nanoseconds; +} + + +/* + * Add a number of nanoseconds to the given timespec. + */ +void pv_elapsedtime_add_nsec(struct timespec *return_time, long long add_nanoseconds) +{ + long long seconds, nanoseconds; + + if (NULL == return_time) + return; + + seconds = return_time->tv_sec; + nanoseconds = return_time->tv_nsec + add_nanoseconds; + + seconds += nanoseconds / 1000000000; + nanoseconds = nanoseconds % 1000000000; + + return_time->tv_sec = seconds; + return_time->tv_nsec = nanoseconds; +} + + +/* + * Set the return timespec to the first time minus the second time. + */ +void pv_elapsedtime_subtract(struct timespec *return_time, const struct timespec *first_time, + const struct timespec *second_time) +{ + long long seconds, nanoseconds; + + if (NULL == return_time) + return; + + seconds = 0; + nanoseconds = 0; + + if (NULL != first_time) { + seconds += first_time->tv_sec; + nanoseconds += first_time->tv_nsec; + } + + if (NULL != second_time) { + seconds -= second_time->tv_sec; + nanoseconds -= second_time->tv_nsec; + } + + seconds += nanoseconds / 1000000000; + nanoseconds = nanoseconds % 1000000000; + + if (nanoseconds < 0) { + seconds--; + nanoseconds = 1000000000 + nanoseconds; + } + + return_time->tv_sec = seconds; + return_time->tv_nsec = nanoseconds; +} + + +/* + * Convert a timespec to seconds. + */ +long double pv_elapsedtime_seconds(const struct timespec *elapsed_time) +{ + long double seconds; + + if (NULL == elapsed_time) + return 0.0; + + seconds = (long double) elapsed_time->tv_sec; + seconds += (long double) (elapsed_time->tv_nsec) / 1000000000.0L; + + return seconds; +} + +/* EOF */ diff --git a/src/pv/loop.c b/src/pv/loop.c index a0a3682..9410700 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -26,24 +26,6 @@ #include -/* - * Add the given number of microseconds (which may be negative) to the given - * timeval. - */ -static void pv_timeval_add_usec(struct timeval *val, long usec) -{ - val->tv_usec += usec; - while (val->tv_usec < 0) { - val->tv_sec--; - val->tv_usec += 1000000; - } - while (val->tv_usec >= 1000000) { - val->tv_sec++; - val->tv_usec -= 1000000; - } -} - - /* * Pipe data from a list of files to standard output, giving information * about the transfer on standard error according to the given options. @@ -53,12 +35,12 @@ static void pv_timeval_add_usec(struct timeval *val, long usec) int pv_main_loop(pvstate_t state) { long written, lineswritten; - long long total_written, since_last, cansend; + long long total_written, transferred_since_last, cansend; long double target; int eof_in, eof_out, final_update; - struct timeval start_time, next_update, next_ratecheck, cur_time; - struct timeval init_time, next_remotecheck; - long double elapsed; + struct timespec start_time, next_update, next_ratecheck, cur_time; + struct timespec init_time, next_remotecheck, transfer_elapsed; + long double elapsed_seconds; struct stat sb; int fd, file_idx; @@ -71,8 +53,9 @@ int pv_main_loop(pvstate_t state) * "total_written" is the total bytes written since the start, * or in line mode, the total lines written since the start. * - * "since_last" is the bytes written since the last display, - * or in line mode, the lines written since the last display. + * "transferred_since_last" is the bytes written since the last + * display, or in line mode, the lines written since the last + * display. * * The remaining variables are all unchanged by linemode. */ @@ -84,26 +67,22 @@ int pv_main_loop(pvstate_t state) eof_in = 0; eof_out = 0; total_written = 0; - since_last = 0; + transferred_since_last = 0; state->initial_offset = 0; - gettimeofday(&start_time, NULL); - gettimeofday(&cur_time, NULL); + pv_elapsedtime_read(&cur_time); + pv_elapsedtime_copy(&start_time, &cur_time); - next_update.tv_sec = start_time.tv_sec; - next_update.tv_usec = start_time.tv_usec; + pv_elapsedtime_copy(&next_ratecheck, &cur_time); + pv_elapsedtime_copy(&next_remotecheck, &cur_time); + pv_elapsedtime_copy(&next_update, &cur_time); if ((state->delay_start > 0) && (state->delay_start > state->interval)) { - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->delay_start)); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->delay_start)); } else { - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); } - next_ratecheck.tv_sec = start_time.tv_sec; - next_ratecheck.tv_usec = start_time.tv_usec; - next_remotecheck.tv_sec = start_time.tv_sec; - next_remotecheck.tv_usec = start_time.tv_usec; - target = 0; final_update = 0; file_idx = 0; @@ -159,29 +138,26 @@ int pv_main_loop(pvstate_t state) cansend = 0; /* - * Check for remote messages from -R every short while + * Check for remote messages from -R every short while. */ - if ((cur_time.tv_sec > next_remotecheck.tv_sec) - || (cur_time.tv_sec == next_remotecheck.tv_sec && cur_time.tv_usec >= next_remotecheck.tv_usec)) { + if (pv_elapsedtime_compare(&cur_time, &next_remotecheck) > 0) { pv_remote_check(state); - pv_timeval_add_usec(&next_remotecheck, REMOTE_INTERVAL); + pv_elapsedtime_add_nsec(&next_remotecheck, REMOTE_INTERVAL); } if (state->pv_sig_abort) break; if (state->rate_limit > 0) { - gettimeofday(&cur_time, NULL); - if ((cur_time.tv_sec > next_ratecheck.tv_sec) - || (cur_time.tv_sec == next_ratecheck.tv_sec && cur_time.tv_usec >= next_ratecheck.tv_usec)) { - + pv_elapsedtime_read(&cur_time); + if (pv_elapsedtime_compare(&cur_time, &next_ratecheck) > 0) { target += - ((long double) (state->rate_limit)) / (long double) (1000000 / RATE_GRANULARITY); - long double burstMax = ((long double) (state->rate_limit * RATE_BURST_WINDOW)); - if (target > burstMax) { - target = burstMax; + ((long double) (state->rate_limit)) / (long double) (1000000000.0 / RATE_GRANULARITY); + long double burst_max = ((long double) (state->rate_limit * RATE_BURST_WINDOW)); + if (target > burst_max) { + target = burst_max; } - pv_timeval_add_usec(&next_ratecheck, RATE_GRANULARITY); + pv_elapsedtime_add_nsec(&next_ratecheck, RATE_GRANULARITY); } cansend = target; } @@ -216,12 +192,12 @@ int pv_main_loop(pvstate_t state) } if (state->linemode) { - since_last += lineswritten; + transferred_since_last += lineswritten; total_written += lineswritten; if (state->rate_limit > 0) target -= lineswritten; } else { - since_last += written; + transferred_since_last += written; total_written += written; if (state->rate_limit > 0) target -= written; @@ -239,15 +215,19 @@ int pv_main_loop(pvstate_t state) } } - gettimeofday(&cur_time, NULL); + /* Now check the current time. */ + pv_elapsedtime_read(&cur_time); + /* If full EOF, final update, and force a display updaate. */ if (eof_in && eof_out) { final_update = 1; if ((state->display_visible) - || (0 == state->delay_start)) - next_update.tv_sec = cur_time.tv_sec - 1; + || (0 == state->delay_start)) { + pv_elapsedtime_copy(&next_update, &cur_time); + } } + /* Just go round the loop again if there's no display. */ if (state->no_display) continue; @@ -258,6 +238,7 @@ int pv_main_loop(pvstate_t state) * was received. */ if (state->wait) { + /* Restart the loop if nothing written yet. */ if (state->linemode) { if (lineswritten < 1) continue; @@ -279,47 +260,47 @@ int pv_main_loop(pvstate_t state) * SIGTSTOP so things don't mess up. */ pv_sig_nopause(); - gettimeofday(&start_time, NULL); - state->pv_sig_toffset.tv_sec = 0; - state->pv_sig_toffset.tv_usec = 0; + pv_elapsedtime_read(&start_time); + pv_elapsedtime_zero(&(state->pv_sig_toffset)); pv_sig_allowpause(); - next_update.tv_sec = start_time.tv_sec; - next_update.tv_usec = start_time.tv_usec; - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + /* + * Start the display, but only at the next interval, + * not immediately. + */ + pv_elapsedtime_copy(&next_update, &start_time); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); } - if ((cur_time.tv_sec < next_update.tv_sec) - || (cur_time.tv_sec == next_update.tv_sec && cur_time.tv_usec < next_update.tv_usec)) { + /* Restart the loop if it's not time to update the display. */ + if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) { continue; } - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); - if (next_update.tv_sec < cur_time.tv_sec) { - next_update.tv_sec = cur_time.tv_sec; - next_update.tv_usec = cur_time.tv_usec; - } else if (next_update.tv_sec == cur_time.tv_sec && next_update.tv_usec < cur_time.tv_usec) { - next_update.tv_usec = cur_time.tv_usec; - } + /* Set the "next update" time to now, if it's in the past. */ + if (pv_elapsedtime_compare(&next_update, &cur_time) < 0) + pv_elapsedtime_copy(&next_update, &cur_time); - init_time.tv_sec = start_time.tv_sec + state->pv_sig_toffset.tv_sec; - init_time.tv_usec = start_time.tv_usec + state->pv_sig_toffset.tv_usec; - if (init_time.tv_usec >= 1000000) { - init_time.tv_sec++; - init_time.tv_usec -= 1000000; - } - if (init_time.tv_usec < 0) { - init_time.tv_sec--; - init_time.tv_usec += 1000000; - } + /* + * Calculate the effective start time: the time we actually + * started, plus the total time we spent stopped. + */ + pv_elapsedtime_add(&init_time, &start_time, &(state->pv_sig_toffset)); - elapsed = cur_time.tv_sec - init_time.tv_sec; - elapsed += (cur_time.tv_usec - init_time.tv_usec) / 1000000.0; + /* + * Now get the effective elapsed transfer time - current + * time minus effective start time. + */ + pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time); + + elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); if (final_update) - since_last = -1; + transferred_since_last = -1; + /* Resize the display, if a resize signal was received. */ if (state->pv_sig_newsize) { unsigned int new_width, new_height; @@ -335,9 +316,9 @@ int pv_main_loop(pvstate_t state) state->height = new_height; } - pv_display(state, elapsed, since_last, total_written); + pv_display(state, elapsed_seconds, transferred_since_last, total_written); - since_last = 0; + transferred_since_last = 0; } if (state->cursor) { @@ -368,10 +349,10 @@ int pv_main_loop(pvstate_t state) int pv_watchfd_loop(pvstate_t state) { struct pvwatchfd_s info; - long long position_now, total_written, since_last; - struct timeval next_update, cur_time; - struct timeval init_time, next_remotecheck; - long double elapsed; + long long position_now, total_written, transferred_since_last; + struct timespec next_update, cur_time; + struct timespec init_time, next_remotecheck, transfer_elapsed; + long double elapsed_seconds; int ended; int first_check; int rc; @@ -401,29 +382,24 @@ int pv_watchfd_loop(pvstate_t state) } } - gettimeofday(&(info.start_time), NULL); - gettimeofday(&cur_time, NULL); - - next_update.tv_sec = info.start_time.tv_sec; - next_update.tv_usec = info.start_time.tv_usec; - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); - - next_remotecheck.tv_sec = info.start_time.tv_sec; - next_remotecheck.tv_usec = info.start_time.tv_usec; + pv_elapsedtime_read(&cur_time); + pv_elapsedtime_copy(&(info.start_time), &cur_time); + pv_elapsedtime_copy(&next_remotecheck, &cur_time); + pv_elapsedtime_copy(&next_update, &cur_time); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); ended = 0; total_written = 0; - since_last = 0; + transferred_since_last = 0; first_check = 1; while (!ended) { /* - * Check for remote messages from -R every short while + * Check for remote messages from -R every short while. */ - if ((cur_time.tv_sec > next_remotecheck.tv_sec) - || (cur_time.tv_sec == next_remotecheck.tv_sec && cur_time.tv_usec >= next_remotecheck.tv_usec)) { + if (pv_elapsedtime_compare(&cur_time, &next_remotecheck) > 0) { pv_remote_check(state); - pv_timeval_add_usec(&next_remotecheck, REMOTE_INTERVAL); + pv_elapsedtime_add_nsec(&next_remotecheck, REMOTE_INTERVAL); } if (state->pv_sig_abort) @@ -434,7 +410,7 @@ int pv_watchfd_loop(pvstate_t state) if (position_now < 0) { ended = 1; } else { - since_last += position_now - total_written; + transferred_since_last += position_now - total_written; total_written = position_now; if (first_check) { state->initial_offset = position_now; @@ -442,15 +418,19 @@ int pv_watchfd_loop(pvstate_t state) } } - gettimeofday(&cur_time, NULL); + pv_elapsedtime_read(&cur_time); + /* Ended - force a display update. */ if (ended) { ended = 1; - next_update.tv_sec = cur_time.tv_sec - 1; + pv_elapsedtime_copy(&next_update, &cur_time); } - if ((cur_time.tv_sec < next_update.tv_sec) - || (cur_time.tv_sec == next_update.tv_sec && cur_time.tv_usec < next_update.tv_usec)) { + /* + * Restart the loop after a brief delay, if it's not time to + * update the display. + */ + if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; @@ -458,32 +438,30 @@ int pv_watchfd_loop(pvstate_t state) continue; } - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); - if (next_update.tv_sec < cur_time.tv_sec) { - next_update.tv_sec = cur_time.tv_sec; - next_update.tv_usec = cur_time.tv_usec; - } else if (next_update.tv_sec == cur_time.tv_sec && next_update.tv_usec < cur_time.tv_usec) { - next_update.tv_usec = cur_time.tv_usec; - } + /* Set the "next update" time to now, if it's in the past. */ + if (pv_elapsedtime_compare(&next_update, &cur_time) < 0) + pv_elapsedtime_copy(&next_update, &cur_time); - init_time.tv_sec = info.start_time.tv_sec + state->pv_sig_toffset.tv_sec; - init_time.tv_usec = info.start_time.tv_usec + state->pv_sig_toffset.tv_usec; - if (init_time.tv_usec >= 1000000) { - init_time.tv_sec++; - init_time.tv_usec -= 1000000; - } - if (init_time.tv_usec < 0) { - init_time.tv_sec--; - init_time.tv_usec += 1000000; - } + /* + * Calculate the effective start time: the time we actually + * started, plus the total time we spent stopped. + */ + pv_elapsedtime_add(&init_time, &(info.start_time), &(state->pv_sig_toffset)); - elapsed = cur_time.tv_sec - init_time.tv_sec; - elapsed += (cur_time.tv_usec - init_time.tv_usec) / 1000000.0; + /* + * Now get the effective elapsed transfer time - current + * time minus effective start time. + */ + pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time); + + elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); if (ended) - since_last = -1; + transferred_since_last = -1; + /* Resize the display, if a resize signal was received. */ if (state->pv_sig_newsize) { unsigned int new_width, new_height; @@ -499,9 +477,9 @@ int pv_watchfd_loop(pvstate_t state) state->height = new_height; } - pv_display(state, elapsed, since_last, total_written); + pv_display(state, elapsed_seconds, transferred_since_last, total_written); - since_last = 0; + transferred_since_last = 0; } if (!state->numeric) @@ -530,7 +508,7 @@ int pv_watchpid_loop(pvstate_t state) struct pvstate_s *state_array = NULL; int array_length = 0; int fd_to_idx[FD_SETSIZE] = { 0, }; - struct timeval next_update, cur_time; + struct timespec next_update, cur_time; int idx; int prev_displayed_lines, blank_lines; int first_pass = 1; @@ -570,11 +548,9 @@ int pv_watchpid_loop(pvstate_t state) * Get things ready for the main loop. */ - gettimeofday(&cur_time, NULL); - - next_update.tv_sec = cur_time.tv_sec; - next_update.tv_usec = cur_time.tv_usec; - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + pv_elapsedtime_read(&cur_time); + pv_elapsedtime_copy(&next_update, &cur_time); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); for (idx = 0; idx < FD_SETSIZE; idx++) { fd_to_idx[idx] = -1; @@ -588,7 +564,7 @@ int pv_watchpid_loop(pvstate_t state) if (state->pv_sig_abort) break; - gettimeofday(&cur_time, NULL); + pv_elapsedtime_read(&cur_time); if (kill(state->watch_pid, 0) != 0) { if (first_pass) { @@ -603,8 +579,11 @@ int pv_watchpid_loop(pvstate_t state) break; } - if ((cur_time.tv_sec < next_update.tv_sec) - || (cur_time.tv_sec == next_update.tv_sec && cur_time.tv_usec < next_update.tv_usec)) { + /* + * Restart the loop after a brief delay, if it's not time to + * update the display. + */ + if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; @@ -612,15 +591,13 @@ int pv_watchpid_loop(pvstate_t state) continue; } - pv_timeval_add_usec(&next_update, (long) (1000000.0 * state->interval)); + pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval)); - if (next_update.tv_sec < cur_time.tv_sec) { - next_update.tv_sec = cur_time.tv_sec; - next_update.tv_usec = cur_time.tv_usec; - } else if (next_update.tv_sec == cur_time.tv_sec && next_update.tv_usec < cur_time.tv_usec) { - next_update.tv_usec = cur_time.tv_usec; - } + /* Set the "next update" time to now, if it's in the past. */ + if (pv_elapsedtime_compare(&next_update, &cur_time) < 0) + pv_elapsedtime_copy(&next_update, &cur_time); + /* Resize the display, if a resize signal was received. */ if (state->pv_sig_newsize) { state->pv_sig_newsize = 0; pv_screensize(&(state->width), &(state->height)); @@ -651,9 +628,9 @@ int pv_watchpid_loop(pvstate_t state) displayed_lines = 0; for (fd = 0; fd < FD_SETSIZE; fd++) { - long long position_now, since_last; - struct timeval init_time; - long double elapsed; + long long position_now, transferred_since_last; + struct timespec init_time, transfer_elapsed; + long double elapsed_seconds; if (displayed_lines >= (int) (state->height)) break; @@ -689,31 +666,31 @@ int pv_watchpid_loop(pvstate_t state) continue; } - since_last = position_now - info_array[idx].position; + transferred_since_last = position_now - info_array[idx].position; info_array[idx].position = position_now; - init_time.tv_sec = info_array[idx].start_time.tv_sec + state->pv_sig_toffset.tv_sec; - init_time.tv_usec = info_array[idx].start_time.tv_usec + state->pv_sig_toffset.tv_usec; - if (init_time.tv_usec >= 1000000) { - init_time.tv_sec++; - init_time.tv_usec -= 1000000; - } - if (init_time.tv_usec < 0) { - init_time.tv_sec--; - init_time.tv_usec += 1000000; - } + /* + * Calculate the effective start time: the time we actually + * started, plus the total time we spent stopped. + */ + pv_elapsedtime_add(&init_time, &(info_array[idx].start_time), &(state->pv_sig_toffset)); - elapsed = cur_time.tv_sec - init_time.tv_sec; - elapsed += (cur_time.tv_usec - init_time.tv_usec) / 1000000.0; + /* + * Now get the effective elapsed transfer time - current + * time minus effective start time. + */ + pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time); + + elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); if (displayed_lines > 0) { debug("%s", "adding newline"); pv_write_retry(STDERR_FILENO, "\n", 1); } - debug("%s %d [%d]: %Lf / %Ld / %Ld", "fd", fd, idx, elapsed, since_last, position_now); + debug("%s %d [%d]: %Lf / %Ld / %Ld", "fd", fd, idx, elapsed_seconds, transferred_since_last, position_now); - pv_display(&(state_array[idx]), elapsed, since_last, position_now); + pv_display(&(state_array[idx]), elapsed_seconds, transferred_since_last, position_now); displayed_lines++; } diff --git a/src/pv/signal.c b/src/pv/signal.c index 0dc2d21..6ae460e 100644 --- a/src/pv/signal.c +++ b/src/pv/signal.c @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -55,7 +54,7 @@ static void pv_sig_ttou( __attribute__((unused)) static void pv_sig_tstp( __attribute__((unused)) int s) { - gettimeofday(&(pv_sig_state->pv_sig_tstp_time), NULL); + pv_elapsedtime_read(&(pv_sig_state->pv_sig_tstp_time)); raise(SIGSTOP); } @@ -68,11 +67,13 @@ static void pv_sig_tstp( __attribute__((unused)) static void pv_sig_cont( __attribute__((unused)) int s) { - struct timeval tv; + struct timespec current_time; + struct timespec time_spent_stopped; struct termios t; pv_sig_state->pv_sig_newsize = 1; + /* if this SIGCONT didn't follow a SIGTSTP so we have no stop time */ if (0 == pv_sig_state->pv_sig_tstp_time.tv_sec) { tcgetattr(STDERR_FILENO, &t); t.c_lflag |= TOSTOP; @@ -83,21 +84,16 @@ static void pv_sig_cont( __attribute__((unused)) return; } - gettimeofday(&tv, NULL); + pv_elapsedtime_read(¤t_time); - pv_sig_state->pv_sig_toffset.tv_sec += (tv.tv_sec - pv_sig_state->pv_sig_tstp_time.tv_sec); - pv_sig_state->pv_sig_toffset.tv_usec += (tv.tv_usec - pv_sig_state->pv_sig_tstp_time.tv_usec); - if (pv_sig_state->pv_sig_toffset.tv_usec >= 1000000) { - pv_sig_state->pv_sig_toffset.tv_sec++; - pv_sig_state->pv_sig_toffset.tv_usec -= 1000000; - } - if (pv_sig_state->pv_sig_toffset.tv_usec < 0) { - pv_sig_state->pv_sig_toffset.tv_sec--; - pv_sig_state->pv_sig_toffset.tv_usec += 1000000; - } + /* time spent stopped = current time - time SIGTSTP received */ + pv_elapsedtime_subtract(&time_spent_stopped, ¤t_time, &(pv_sig_state->pv_sig_tstp_time)); - pv_sig_state->pv_sig_tstp_time.tv_sec = 0; - pv_sig_state->pv_sig_tstp_time.tv_usec = 0; + /* add time spent stopped the total stopped-time count */ + pv_elapsedtime_add(&(pv_sig_state->pv_sig_toffset), &(pv_sig_state->pv_sig_toffset), &time_spent_stopped); + + /* reset the SIGTSTP receipt time */ + pv_elapsedtime_zero(&(pv_sig_state->pv_sig_tstp_time)); if (pv_sig_state->pv_sig_old_stderr != -1) { dup2(pv_sig_state->pv_sig_old_stderr, STDERR_FILENO); @@ -145,10 +141,8 @@ void pv_sig_init(pvstate_t state) pv_sig_state = state; pv_sig_state->pv_sig_old_stderr = -1; - pv_sig_state->pv_sig_tstp_time.tv_sec = 0; - pv_sig_state->pv_sig_tstp_time.tv_usec = 0; - pv_sig_state->pv_sig_toffset.tv_sec = 0; - pv_sig_state->pv_sig_toffset.tv_usec = 0; + pv_elapsedtime_zero(&(pv_sig_state->pv_sig_tstp_time)); + pv_elapsedtime_zero(&(pv_sig_state->pv_sig_toffset)); /* * Ignore SIGPIPE, so we don't die if stdout is a pipe and the other diff --git a/src/pv/transfer.c b/src/pv/transfer.c index 8d086d1..8daf22a 100644 --- a/src/pv/transfer.c +++ b/src/pv/transfer.c @@ -32,21 +32,21 @@ * buffer as full as we can. * * We stop retrying if the time elapsed since this function was entered - * reaches TRANSFER_READ_TIMEOUT microseconds. + * reaches TRANSFER_READ_TIMEOUT seconds. */ static ssize_t pv__transfer_read_repeated(int fd, void *buf, size_t count) { - struct timeval start_time; + struct timespec start_time; ssize_t total_read; - gettimeofday(&start_time, NULL); + pv_elapsedtime_read(&start_time); total_read = 0; while (count > 0) { ssize_t nread; - struct timeval now; - long elapsed_usec; + struct timespec cur_time, transfer_elapsed; + long double elapsed_seconds; nread = read(fd, buf, count > MAX_READ_AT_ONCE ? MAX_READ_AT_ONCE : count); if (nread < 0) @@ -59,11 +59,13 @@ static ssize_t pv__transfer_read_repeated(int fd, void *buf, size_t count) if (0 == nread) return total_read; - gettimeofday(&now, NULL); - elapsed_usec = 1000000 * (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec); - if (elapsed_usec > TRANSFER_READ_TIMEOUT) { - debug("%s %d: %s (%ld %s)", "fd", fd, - "stopping read - timer expired", elapsed_usec, "usec elapsed"); + pv_elapsedtime_read(&cur_time); + pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &start_time); + elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); + + if (elapsed_seconds > TRANSFER_READ_TIMEOUT) { + debug("%s %d: %s (%lf %s)", "fd", fd, + "stopping read - timer expired", elapsed_seconds, "sec elapsed"); return total_read; } @@ -100,21 +102,21 @@ static ssize_t pv__transfer_read_repeated(int fd, void *buf, size_t count) * fsync() if _POSIX_SYNCHRONIZED_IO is not > 0). * * We stop retrying if the time elapsed since this function was entered - * reaches TRANSFER_WRITE_TIMEOUT microseconds. + * reaches TRANSFER_WRITE_TIMEOUT seconds. */ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool sync_after_write) { - struct timeval start_time; + struct timespec start_time; ssize_t total_written; - gettimeofday(&start_time, NULL); + pv_elapsedtime_read(&start_time); total_written = 0; while (count > 0) { ssize_t nwritten; - struct timeval now; - long elapsed_usec; + struct timespec cur_time, transfer_elapsed; + long double elapsed_seconds; size_t asked_to_write; asked_to_write = count > MAX_WRITE_AT_ONCE ? MAX_WRITE_AT_ONCE : count; @@ -163,11 +165,13 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool if (0 == nwritten) return total_written; - gettimeofday(&now, NULL); - elapsed_usec = 1000000 * (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec); - if (elapsed_usec > TRANSFER_WRITE_TIMEOUT) { - debug("%s %d: %s (%ld %s)", "fd", fd, - "stopping write - timer expired", elapsed_usec, "usec elapsed"); + pv_elapsedtime_read(&cur_time); + pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &start_time); + elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); + + if (elapsed_seconds > TRANSFER_WRITE_TIMEOUT) { + debug("%s %d: %s (%lf %s)", "fd", fd, + "stopping write - timer expired", elapsed_seconds, "sec elapsed"); return total_written; } @@ -175,7 +179,7 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool * Running the select() here seems to make PV eat a lot of * CPU in some cases, so instead we just go round the loop * again and rely on our alarm() to interrupt us if we run - * out of time - also on our gettimeofday() check. + * out of time - also on our elapsed time check. */ if (count > 0) { #if 0 /* disabled after 1.6.0 - see comment above */ diff --git a/src/pv/watchpid.c b/src/pv/watchpid.c index 89e2daf..0b38e7a 100644 --- a/src/pv/watchpid.c +++ b/src/pv/watchpid.c @@ -440,7 +440,7 @@ int pv_watchpid_scanfds(pvstate_t state, pvstate_t pristine, state_array[use_idx].reparse_display = 1; - gettimeofday(&(info_array[use_idx].start_time), NULL); + pv_elapsedtime_read(&(info_array[use_idx].start_time)); state_array[use_idx].initial_offset = 0; info_array[use_idx].position = 0;