Fixed inaccurate ETAs: use current average rate over last 30 seconds instead of global rate

using global rate gives wildly inacurrate ETAs if rate changes over time.

this makes pv keep a history of progress over the last 30s interval and compute
current average rate over that, which fixes the issue.

fixed -a, --average-rate: show current average rate instead of global rate

[ quick test ]

- in one shell:
    $ dd if=/dev/zero of=/tmp/foo bs=1M count=5                 # simulate fast initial rate,
    $ (while true; do ls /*; sleep 1; done) >> /tmp/foo         # then slow rate. leave running ...

- in another shell:
    $ tail -f -n +1 /tmp/foo | pv -s 800M > /dev/null

    5.62MiB 0:00:17 [33.1KiB/s] [>                              ]  0% ETA 0:40:02
        bad:  (pv 1.6.6 - 1.6.20)
        ETA of a few minutes that keeps increasing ever and ever ...

    5.62MiB 0:00:16 [33.1KiB/s] [>                              ]  0% ETA 6:45:53
        good:
        ETA of 6+ hours, which is how long it'll take to reach 800Mb at 33k/s
This commit is contained in:
lemonsqueeze
2023-05-29 15:09:36 +02:00
parent a9cef81657
commit 06824aee2d
4 changed files with 81 additions and 28 deletions
+3 -3
View File
@@ -127,7 +127,7 @@ Turn the timer on. This will display the total elapsed time that
has been running for.
.TP
.B \-e, \-\-eta
Turn the ETA timer on. This will attempt to guess, based on previous
Turn the ETA timer on. This will attempt to guess, based on current
transfer rates and the total data size, how long it will be before
completion. This option will have no effect if the total data size cannot
be determined.
@@ -142,8 +142,8 @@ Turn the rate counter on. This will display the current rate of data
transfer.
.TP
.B \-a, \-\-average\-rate
Turn the average rate counter on. This will display the average rate of
data transfer so far.
Turn the average rate counter on. This will display the current average
rate of data transfer (default: last 30s).
.TP
.B \-b, \-\-bytes
Turn the total byte counter on. This will display the total amount of
+15
View File
@@ -45,6 +45,12 @@ extern "C" {
#define MAXIMISE_BUFFER_FILL 1
typedef struct pvhistory {
long long total_bytes;
long double elapsed_sec;
} pvhistory_t;
/*
* Structure for holding PV internal state. Opaque outside the PV library.
*/
@@ -113,6 +119,15 @@ struct pvstate_s {
long double prev_elapsed_sec;
long double prev_rate;
long double prev_trans;
/* Keep track of progress over last intervals to compute current average rate. */
pvhistory_t *history; /* state at previous intervals (circular buffer) */
int history_len; /* total size */
int history_interval; /* seconds between each history entry */
int history_first;
int history_last;
long double current_avg_rate; /* current average rate over last history intervals */
unsigned long long initial_offset;
char *display_buffer;
long display_buffer_size;
+40 -25
View File
@@ -95,16 +95,14 @@ static long pv__calc_percentage(long long so_far, const long long total)
* number of seconds until completion.
*/
static long pv__calc_eta(const long long so_far, const long long total,
const long elapsed)
const long rate)
{
long long amount_left;
if (so_far < 1)
if (so_far < 1 || !rate)
return 0;
amount_left = total - so_far;
amount_left *= (long long) elapsed;
amount_left /= so_far;
amount_left = (total - so_far) / rate;
return (long) amount_left;
}
@@ -430,6 +428,38 @@ static long bound_long(long x, long min, long max)
return x < min ? min : x > max ? max : x;
}
/* Update history and current average rate */
static void update_history_avg_rate(pvstate_t state, long long total_bytes,
long double elapsed_sec, long double rate)
{
int first = state->history_first;
int last = state->history_last;
long double last_elapsed = state->history[last].elapsed_sec;
if (!(last_elapsed == 0.0 || /* Empty */
elapsed_sec > last_elapsed + state->history_interval))
return;
if (last_elapsed) { /* Not empty, add new entry in circular buffer */
int len = state->history_len;
state->history_last = last = (last + 1) % len;
if (last == first)
state->history_first = first = (first + 1) % len;
}
state->history[last].elapsed_sec = elapsed_sec;
state->history[last].total_bytes = total_bytes;
if (first == last)
state->current_avg_rate = rate;
else {
long long bytes = (state->history[last].total_bytes -
state->history[first].total_bytes);
long double sec = (state->history[last].elapsed_sec -
state->history[first].elapsed_sec);
state->current_avg_rate = bytes / sec;
}
}
/*
* Return a pointer to a string (which must not be freed), containing status
@@ -490,24 +520,9 @@ static char *pv__format(pvstate_t state,
}
state->prev_rate = rate;
/*
* We only calculate the overall average rate if this is the last
* update or if the average rate display is enabled. Otherwise it's
* not worth the extra CPU cycles.
*/
average_rate = 0;
if ((bytes_since_last < 0)
|| ((state->components_used & PV_DISPLAY_AVERAGERATE) != 0)) {
/* Sanity check to avoid division by zero */
if (elapsed_sec < 0.000001)
elapsed_sec = 0.000001;
average_rate =
(((long double) total_bytes) -
((long double) state->initial_offset)) /
(long double) elapsed_sec;
if (bytes_since_last < 0)
rate = average_rate;
}
/* Update history and current average rate for ETA. */
update_history_avg_rate(state, total_bytes, elapsed_sec, rate);
average_rate = state->current_avg_rate;
if (state->size <= 0) {
/*
@@ -680,7 +695,7 @@ static char *pv__format(pvstate_t state,
eta =
pv__calc_eta(total_bytes - state->initial_offset,
state->size - state->initial_offset,
elapsed_sec);
state->current_avg_rate);
/*
* Bounds check, so we don't overrun the suffix buffer. This
@@ -732,7 +747,7 @@ static char *pv__format(pvstate_t state,
eta =
pv__calc_eta(total_bytes - state->initial_offset,
state->size - state->initial_offset,
elapsed_sec);
state->current_avg_rate);
/*
* Bounds check, so we don't overrun the suffix buffer. This
+23
View File
@@ -7,8 +7,23 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* alloc / realloc history buffer */
static void pv_alloc_history(pvstate_t state)
{
if (state->history)
free(state->history);
assert(state->history_len);
assert(state->history_interval);
state->history = calloc(state->history_len, sizeof(state->history[0]));
state->history_first = state->history_last = 0;
state->history[0].elapsed_sec = 0.0; /* to be safe, memset() not recommended for doubles */
}
/*
* Create a new state structure, and return it, or 0 (NULL) on error.
*/
@@ -36,6 +51,10 @@ pvstate_t pv_state_alloc(const char *program_name)
#endif /* HAVE_SPLICE */
state->display_visible = false;
state->history_len = 30+1; /* default history for current avg rate */
state->history_interval = 1;
pv_alloc_history(state);
return state;
}
@@ -56,6 +75,10 @@ void pv_state_free(pvstate_t state)
free(state->transfer_buffer);
state->transfer_buffer = NULL;
if (state->history)
free(state->history);
state->history = NULL;
free(state);
return;