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:
+3
-3
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user