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
+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;