mirror of
https://github.com/curl/curl.git
synced 2026-01-26 15:03:21 +00:00
When a download size is known and rate limiting is in effect, adjust the duration of each measurement step and its rate for maximum precision. Since it is unpredictable how long the last bytes of a download will take, download speed can be thrown off if the "last bytes" are a significant amount of the total download. Make the "last bytes" small in comparision to the rest and "stretch" the rate limit intervals to accommodate the difference. Fix ngtcp2 receive data acknowldgements to be based on a local window size tracking. This allows window updates controlled by rate limits. Fix ratelimit wait time calculation to accomodate negative tokens. h3 rate limit, update timeers Make download rate limits work correctly in ngtcp2. Fix multi handling of rate limits to set a timer for when limits will update again. Without running the transfer on limit updates, protocols like h2/h3 may stall if the server does not send due to stream windows being too small. scorecard: measure download speedlimits When running scorecard with --limit-rate=n, show the reported download speed averages plus percentage deviation from the limit. Closes #20228
688 lines
21 KiB
C
688 lines
21 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
#include "curl_setup.h"
|
|
|
|
#include "urldata.h"
|
|
#include "curl_trc.h"
|
|
#include "multiif.h"
|
|
#include "progress.h"
|
|
#include "transfer.h"
|
|
#include "curlx/strcopy.h"
|
|
|
|
/* check rate limits within this many recent milliseconds, at minimum. */
|
|
#define MIN_RATE_LIMIT_PERIOD 3000
|
|
|
|
#ifndef CURL_DISABLE_PROGRESS_METER
|
|
/* Provide a string that is 7 letters long (plus the zero byte).
|
|
|
|
Unit test 1636.
|
|
*/
|
|
UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds);
|
|
UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds)
|
|
{
|
|
curl_off_t h;
|
|
if(seconds <= 0) {
|
|
curlx_strcopy(r, rsize, STRCONST(" "));
|
|
return;
|
|
}
|
|
h = seconds / 3600;
|
|
if(h <= 99) {
|
|
curl_off_t m = (seconds - (h * 3600)) / 60;
|
|
if(h <= 9) {
|
|
curl_off_t s = (seconds - (h * 3600)) - (m * 60);
|
|
if(h)
|
|
curl_msnprintf(r, rsize, "%" FMT_OFF_T ":%02" FMT_OFF_T ":"
|
|
"%02" FMT_OFF_T, h, m, s);
|
|
else
|
|
curl_msnprintf(r, rsize, " %02" FMT_OFF_T ":%02" FMT_OFF_T, m, s);
|
|
}
|
|
else
|
|
curl_msnprintf(r, rsize, "%" FMT_OFF_T "h %02" FMT_OFF_T "m", h, m);
|
|
}
|
|
else {
|
|
curl_off_t d = seconds / 86400;
|
|
h = (seconds - (d * 86400)) / 3600;
|
|
if(d <= 99)
|
|
curl_msnprintf(r, rsize, "%2" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h);
|
|
else if(d <= 999)
|
|
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "d", d);
|
|
else { /* more than 999 days */
|
|
curl_off_t m = d / 30;
|
|
if(m <= 999)
|
|
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "m", m);
|
|
else { /* more than 999 months */
|
|
curl_off_t y = d / 365;
|
|
if(y <= 99999)
|
|
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "y", y);
|
|
else
|
|
curlx_strcopy(r, rsize, STRCONST(">99999y"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The point of this function would be to return a string of the input data,
|
|
but never longer than 6 columns (+ one zero byte).
|
|
Add suffix k, M, G when suitable...
|
|
|
|
Unit test 1636
|
|
*/
|
|
UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen);
|
|
UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen)
|
|
{
|
|
/* a signed 64-bit value is 8192 petabytes maximum, shown as
|
|
8.0E (exabytes)*/
|
|
if(bytes < 100000)
|
|
curl_msnprintf(max6, mlen, "%6" CURL_FORMAT_CURL_OFF_T, bytes);
|
|
else {
|
|
const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 };
|
|
int k = 0;
|
|
curl_off_t nbytes;
|
|
curl_off_t rest;
|
|
do {
|
|
nbytes = bytes / 1024;
|
|
if(nbytes < 1000)
|
|
break;
|
|
bytes = nbytes;
|
|
k++;
|
|
DEBUGASSERT(unit[k]);
|
|
} while(unit[k]);
|
|
rest = bytes % 1024;
|
|
if(nbytes <= 99)
|
|
/* xx.yyU */
|
|
curl_msnprintf(max6, mlen, "%2" CURL_FORMAT_CURL_OFF_T
|
|
".%02" CURL_FORMAT_CURL_OFF_T "%c", nbytes,
|
|
rest * 100 / 1024, unit[k]);
|
|
else
|
|
/* xxx.yU */
|
|
curl_msnprintf(max6, mlen, "%3" CURL_FORMAT_CURL_OFF_T
|
|
".%" CURL_FORMAT_CURL_OFF_T "%c", nbytes,
|
|
rest * 10 / 1024, unit[k]);
|
|
}
|
|
return max6;
|
|
}
|
|
#endif
|
|
|
|
static void pgrs_speedinit(struct Curl_easy *data)
|
|
{
|
|
memset(&data->state.keeps_speed, 0, sizeof(struct curltime));
|
|
}
|
|
|
|
/*
|
|
* @unittest: 1606
|
|
*/
|
|
UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data,
|
|
const struct curltime *pnow)
|
|
{
|
|
if(!data->set.low_speed_time || !data->set.low_speed_limit ||
|
|
Curl_xfer_recv_is_paused(data) || Curl_xfer_send_is_paused(data))
|
|
/* A paused transfer is not qualified for speed checks */
|
|
return CURLE_OK;
|
|
|
|
if(data->progress.current_speed >= 0) {
|
|
if(data->progress.current_speed < data->set.low_speed_limit) {
|
|
if(!data->state.keeps_speed.tv_sec)
|
|
/* under the limit at this moment */
|
|
data->state.keeps_speed = *pnow;
|
|
else {
|
|
/* how long has it been under the limit */
|
|
timediff_t howlong =
|
|
curlx_ptimediff_ms(pnow, &data->state.keeps_speed);
|
|
|
|
if(howlong >= data->set.low_speed_time * 1000) {
|
|
/* too long */
|
|
failf(data, "Operation too slow. Less than %" FMT_OFF_T
|
|
" bytes/sec transferred the last %u seconds",
|
|
data->set.low_speed_limit, data->set.low_speed_time);
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
/* faster right now */
|
|
data->state.keeps_speed.tv_sec = 0;
|
|
}
|
|
|
|
/* since low speed limit is enabled, set the expire timer to make this
|
|
connection's speed get checked again in a second */
|
|
Curl_expire(data, 1000, EXPIRE_SPEEDCHECK);
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
const struct curltime *Curl_pgrs_now(struct Curl_easy *data)
|
|
{
|
|
struct curltime *pnow = data->multi ?
|
|
&data->multi->now : &data->progress.now;
|
|
curlx_pnow(pnow);
|
|
return pnow;
|
|
}
|
|
|
|
/*
|
|
New proposed interface, 9th of February 2000:
|
|
|
|
pgrsStartNow() - sets start time
|
|
pgrsSetDownloadSize(x) - known expected download size
|
|
pgrsSetUploadSize(x) - known expected upload size
|
|
pgrsSetDownloadCounter() - amount of data currently downloaded
|
|
pgrsSetUploadCounter() - amount of data currently uploaded
|
|
pgrsUpdate() - show progress
|
|
pgrsDone() - transfer complete
|
|
*/
|
|
|
|
int Curl_pgrsDone(struct Curl_easy *data)
|
|
{
|
|
int rc;
|
|
data->progress.lastshow = 0;
|
|
rc = Curl_pgrsUpdate(data); /* the final (forced) update */
|
|
if(rc)
|
|
return rc;
|
|
|
|
if(!data->progress.hide && !data->progress.callback)
|
|
/* only output if we do not use a progress callback and we are not
|
|
* hidden */
|
|
curl_mfprintf(data->set.err, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Curl_pgrsReset(struct Curl_easy *data)
|
|
{
|
|
Curl_pgrsSetUploadCounter(data, 0);
|
|
data->progress.dl.cur_size = 0;
|
|
Curl_pgrsSetUploadSize(data, -1);
|
|
Curl_pgrsSetDownloadSize(data, -1);
|
|
data->progress.speeder_c = 0; /* reset speed records */
|
|
pgrs_speedinit(data);
|
|
}
|
|
|
|
/* reset the known transfer sizes */
|
|
void Curl_pgrsResetTransferSizes(struct Curl_easy *data)
|
|
{
|
|
Curl_pgrsSetDownloadSize(data, -1);
|
|
Curl_pgrsSetUploadSize(data, -1);
|
|
}
|
|
|
|
void Curl_pgrsRecvPause(struct Curl_easy *data, bool enable)
|
|
{
|
|
if(!enable) {
|
|
data->progress.speeder_c = 0; /* reset speed records */
|
|
pgrs_speedinit(data); /* reset low speed measurements */
|
|
}
|
|
}
|
|
|
|
void Curl_pgrsSendPause(struct Curl_easy *data, bool enable)
|
|
{
|
|
if(!enable) {
|
|
data->progress.speeder_c = 0; /* reset speed records */
|
|
pgrs_speedinit(data); /* reset low speed measurements */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Curl_pgrsTimeWas(). Store the timestamp time at the given label.
|
|
*/
|
|
void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer,
|
|
struct curltime timestamp)
|
|
{
|
|
timediff_t *delta = NULL;
|
|
|
|
switch(timer) {
|
|
default:
|
|
case TIMER_NONE:
|
|
/* mistake filter */
|
|
break;
|
|
case TIMER_STARTOP:
|
|
/* This is set at the start of a transfer */
|
|
data->progress.t_startop = timestamp;
|
|
data->progress.t_startqueue = timestamp;
|
|
data->progress.t_postqueue = 0;
|
|
break;
|
|
case TIMER_STARTSINGLE:
|
|
/* This is set at the start of each single transfer */
|
|
data->progress.t_startsingle = timestamp;
|
|
data->progress.is_t_startransfer_set = FALSE;
|
|
break;
|
|
case TIMER_POSTQUEUE:
|
|
/* Queue time is accumulative from all involved redirects */
|
|
data->progress.t_postqueue +=
|
|
curlx_ptimediff_us(×tamp, &data->progress.t_startqueue);
|
|
break;
|
|
case TIMER_STARTACCEPT:
|
|
data->progress.t_acceptdata = timestamp;
|
|
break;
|
|
case TIMER_NAMELOOKUP:
|
|
delta = &data->progress.t_nslookup;
|
|
break;
|
|
case TIMER_CONNECT:
|
|
delta = &data->progress.t_connect;
|
|
break;
|
|
case TIMER_APPCONNECT:
|
|
delta = &data->progress.t_appconnect;
|
|
break;
|
|
case TIMER_PRETRANSFER:
|
|
delta = &data->progress.t_pretransfer;
|
|
break;
|
|
case TIMER_STARTTRANSFER:
|
|
delta = &data->progress.t_starttransfer;
|
|
/* prevent updating t_starttransfer unless:
|
|
* 1. this is the first time we are setting t_starttransfer
|
|
* 2. a redirect has occurred since the last time t_starttransfer was set
|
|
* This prevents repeated invocations of the function from incorrectly
|
|
* changing the t_starttransfer time.
|
|
*/
|
|
if(data->progress.is_t_startransfer_set) {
|
|
return;
|
|
}
|
|
else {
|
|
data->progress.is_t_startransfer_set = TRUE;
|
|
break;
|
|
}
|
|
case TIMER_POSTRANSFER:
|
|
delta = &data->progress.t_posttransfer;
|
|
break;
|
|
case TIMER_REDIRECT:
|
|
data->progress.t_redirect = curlx_ptimediff_us(×tamp,
|
|
&data->progress.start);
|
|
data->progress.t_startqueue = timestamp;
|
|
break;
|
|
}
|
|
if(delta) {
|
|
timediff_t us = curlx_ptimediff_us(×tamp,
|
|
&data->progress.t_startsingle);
|
|
if(us < 1)
|
|
us = 1; /* make sure at least one microsecond passed */
|
|
*delta += us;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Curl_pgrsTime(). Store the current time at the given label. This fetches a
|
|
* fresh "now" and returns it.
|
|
*
|
|
* @unittest: 1399
|
|
*/
|
|
void Curl_pgrsTime(struct Curl_easy *data, timerid timer)
|
|
{
|
|
Curl_pgrsTimeWas(data, timer, *Curl_pgrs_now(data));
|
|
}
|
|
|
|
void Curl_pgrsStartNow(struct Curl_easy *data)
|
|
{
|
|
struct Progress *p = &data->progress;
|
|
|
|
p->speeder_c = 0; /* reset the progress meter display */
|
|
p->start = *Curl_pgrs_now(data);
|
|
p->is_t_startransfer_set = FALSE;
|
|
p->dl.cur_size = 0;
|
|
p->ul.cur_size = 0;
|
|
/* the sizes are unknown at start */
|
|
p->dl_size_known = FALSE;
|
|
p->ul_size_known = FALSE;
|
|
}
|
|
|
|
void Curl_pgrs_download_inc(struct Curl_easy *data, size_t delta)
|
|
{
|
|
if(delta) {
|
|
data->progress.dl.cur_size += delta;
|
|
Curl_rlimit_drain(&data->progress.dl.rlimit, delta, Curl_pgrs_now(data));
|
|
}
|
|
}
|
|
|
|
void Curl_pgrs_upload_inc(struct Curl_easy *data, size_t delta)
|
|
{
|
|
if(delta) {
|
|
data->progress.ul.cur_size += delta;
|
|
Curl_rlimit_drain(&data->progress.ul.rlimit, delta, Curl_pgrs_now(data));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the number of uploaded bytes so far.
|
|
*/
|
|
void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size)
|
|
{
|
|
data->progress.ul.cur_size = size;
|
|
}
|
|
|
|
void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size)
|
|
{
|
|
if(size >= 0) {
|
|
data->progress.dl.total_size = size;
|
|
data->progress.dl_size_known = TRUE;
|
|
}
|
|
else {
|
|
data->progress.dl.total_size = 0;
|
|
data->progress.dl_size_known = FALSE;
|
|
}
|
|
}
|
|
|
|
void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size)
|
|
{
|
|
if(size >= 0) {
|
|
data->progress.ul.total_size = size;
|
|
data->progress.ul_size_known = TRUE;
|
|
}
|
|
else {
|
|
data->progress.ul.total_size = 0;
|
|
data->progress.ul_size_known = FALSE;
|
|
}
|
|
}
|
|
|
|
void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent)
|
|
{
|
|
data->progress.earlydata_sent = sent;
|
|
}
|
|
|
|
/* returns the average speed in bytes / second */
|
|
static curl_off_t trspeed(curl_off_t size, /* number of bytes */
|
|
curl_off_t us) /* microseconds */
|
|
{
|
|
if(us < 1)
|
|
return size * 1000000;
|
|
else if(size < CURL_OFF_T_MAX / 1000000)
|
|
return (size * 1000000) / us;
|
|
else if(us >= 1000000)
|
|
return size / (us / 1000000);
|
|
else
|
|
return CURL_OFF_T_MAX;
|
|
}
|
|
|
|
/* returns TRUE if it is time to show the progress meter */
|
|
static bool progress_calc(struct Curl_easy *data,
|
|
const struct curltime *pnow)
|
|
{
|
|
struct Progress * const p = &data->progress;
|
|
int i_next, i_oldest, i_latest;
|
|
timediff_t duration_us;
|
|
curl_off_t amount;
|
|
|
|
/* The time spent so far (from the start) in microseconds */
|
|
p->timespent = curlx_ptimediff_us(pnow, &p->start);
|
|
p->dl.speed = trspeed(p->dl.cur_size, p->timespent);
|
|
p->ul.speed = trspeed(p->ul.cur_size, p->timespent);
|
|
|
|
if(!p->speeder_c) { /* no previous record exists */
|
|
p->speed_amount[0] = p->dl.cur_size + p->ul.cur_size;
|
|
p->speed_time[0] = *pnow;
|
|
p->speeder_c++;
|
|
/* use the overall average at the start */
|
|
p->current_speed = p->ul.speed + p->dl.speed;
|
|
p->lastshow = pnow->tv_sec;
|
|
return TRUE;
|
|
}
|
|
/* We have at least one record now. Where to put the next and
|
|
* where is the latest one? */
|
|
i_next = p->speeder_c % CURL_SPEED_RECORDS;
|
|
i_latest = (i_next > 0) ? (i_next - 1) : (CURL_SPEED_RECORDS - 1);
|
|
|
|
/* Make a new record only when some time has passed.
|
|
* Too frequent calls otherwise ruin the history. */
|
|
if(curlx_ptimediff_ms(pnow, &p->speed_time[i_latest]) >= 1000) {
|
|
p->speeder_c++;
|
|
i_latest = i_next;
|
|
p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size;
|
|
p->speed_time[i_latest] = *pnow;
|
|
}
|
|
else if(data->req.done) {
|
|
/* When a transfer is done, and we did not have a current speed
|
|
* already, update the last record. Otherwise, stay at the speed
|
|
* we have. The last chunk of data, when rate limiting, would increase
|
|
* reported speed since it no longer measures a full second. */
|
|
if(!p->current_speed) {
|
|
p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size;
|
|
p->speed_time[i_latest] = *pnow;
|
|
}
|
|
}
|
|
else {
|
|
/* transfer ongoing, wait for more time to pass. */
|
|
return FALSE;
|
|
}
|
|
|
|
i_oldest = (p->speeder_c < CURL_SPEED_RECORDS) ? 0 :
|
|
((i_latest + 1) % CURL_SPEED_RECORDS);
|
|
|
|
/* How much we transferred between oldest and current records */
|
|
amount = p->speed_amount[i_latest] - p->speed_amount[i_oldest];
|
|
/* How long this took */
|
|
duration_us = curlx_ptimediff_us(&p->speed_time[i_latest],
|
|
&p->speed_time[i_oldest]);
|
|
if(duration_us <= 0)
|
|
duration_us = 1;
|
|
|
|
if(amount > (CURL_OFF_T_MAX / 1000000)) {
|
|
/* the 'amount' value is bigger than would fit in 64 bits if
|
|
multiplied with 1000000, so we use the double math for this */
|
|
p->current_speed =
|
|
(curl_off_t)(((double)amount * 1000000.0) / (double)duration_us);
|
|
}
|
|
else {
|
|
p->current_speed = amount * 1000000 / duration_us;
|
|
}
|
|
|
|
if((p->lastshow == pnow->tv_sec) && !data->req.done)
|
|
return FALSE;
|
|
p->lastshow = pnow->tv_sec;
|
|
return TRUE;
|
|
}
|
|
|
|
#ifndef CURL_DISABLE_PROGRESS_METER
|
|
|
|
struct pgrs_estimate {
|
|
curl_off_t secs;
|
|
curl_off_t percent;
|
|
};
|
|
|
|
static curl_off_t pgrs_est_percent(curl_off_t total, curl_off_t cur)
|
|
{
|
|
if(total > 10000)
|
|
return cur / (total / 100);
|
|
else if(total > 0)
|
|
return (cur * 100) / total;
|
|
return 0;
|
|
}
|
|
|
|
static void pgrs_estimates(struct pgrs_dir *d,
|
|
bool total_known,
|
|
struct pgrs_estimate *est)
|
|
{
|
|
est->secs = 0;
|
|
est->percent = 0;
|
|
if(total_known && (d->speed > 0)) {
|
|
est->secs = d->total_size / d->speed;
|
|
est->percent = pgrs_est_percent(d->total_size, d->cur_size);
|
|
}
|
|
}
|
|
|
|
static void progress_meter(struct Curl_easy *data)
|
|
{
|
|
struct Progress *p = &data->progress;
|
|
char max6[6][7];
|
|
struct pgrs_estimate dl_estm;
|
|
struct pgrs_estimate ul_estm;
|
|
struct pgrs_estimate total_estm;
|
|
curl_off_t total_cur_size;
|
|
curl_off_t total_expected_size;
|
|
curl_off_t dl_size;
|
|
char time_left[8];
|
|
char time_total[8];
|
|
char time_spent[8];
|
|
curl_off_t cur_secs = (curl_off_t)p->timespent / 1000000; /* seconds */
|
|
|
|
if(!p->headers_out) {
|
|
if(data->state.resume_from) {
|
|
curl_mfprintf(data->set.err,
|
|
"** Resuming transfer from byte position %" FMT_OFF_T "\n",
|
|
data->state.resume_from);
|
|
}
|
|
curl_mfprintf(data->set.err,
|
|
" %% Total %% Received %% Xferd Average Speed "
|
|
"Time Time Time Current\n"
|
|
" Dload Upload "
|
|
"Total Spent Left Speed\n");
|
|
p->headers_out = TRUE; /* headers are shown */
|
|
}
|
|
|
|
/* Figure out the estimated time of arrival for upload and download */
|
|
pgrs_estimates(&p->ul, (bool)p->ul_size_known, &ul_estm);
|
|
pgrs_estimates(&p->dl, (bool)p->dl_size_known, &dl_estm);
|
|
|
|
/* Since both happen at the same time, total expected duration is max. */
|
|
total_estm.secs = CURLMAX(ul_estm.secs, dl_estm.secs);
|
|
/* create the three time strings */
|
|
time2str(time_left, sizeof(time_left),
|
|
total_estm.secs > 0 ? (total_estm.secs - cur_secs) : 0);
|
|
time2str(time_total, sizeof(time_total), total_estm.secs);
|
|
time2str(time_spent, sizeof(time_spent), cur_secs);
|
|
|
|
/* Get the total amount of data expected to get transferred */
|
|
total_expected_size = p->ul_size_known ? p->ul.total_size : p->ul.cur_size;
|
|
|
|
dl_size = p->dl_size_known ? p->dl.total_size : p->dl.cur_size;
|
|
|
|
/* integer overflow check */
|
|
if((CURL_OFF_T_MAX - total_expected_size) < dl_size)
|
|
total_expected_size = CURL_OFF_T_MAX; /* capped */
|
|
else
|
|
total_expected_size += dl_size;
|
|
|
|
/* We have transferred this much so far */
|
|
total_cur_size = p->dl.cur_size + p->ul.cur_size;
|
|
|
|
/* Get the percentage of data transferred so far */
|
|
total_estm.percent = pgrs_est_percent(total_expected_size, total_cur_size);
|
|
|
|
curl_mfprintf(data->set.err,
|
|
"\r"
|
|
"%3" FMT_OFF_T " %s "
|
|
"%3" FMT_OFF_T " %s "
|
|
"%3" FMT_OFF_T " %s %s %s %s %s %s %s",
|
|
total_estm.percent, /* 3 letters */ /* total % */
|
|
max6out(total_expected_size, max6[2],
|
|
sizeof(max6[2])), /* total size */
|
|
dl_estm.percent, /* 3 letters */ /* rcvd % */
|
|
max6out(p->dl.cur_size, max6[0],
|
|
sizeof(max6[0])), /* rcvd size */
|
|
ul_estm.percent, /* 3 letters */ /* xfer % */
|
|
max6out(p->ul.cur_size, max6[1],
|
|
sizeof(max6[1])), /* xfer size */
|
|
max6out(p->dl.speed, max6[3],
|
|
sizeof(max6[3])), /* avrg dl speed */
|
|
max6out(p->ul.speed, max6[4],
|
|
sizeof(max6[4])), /* avrg ul speed */
|
|
time_total, /* 7 letters */ /* total time */
|
|
time_spent, /* 7 letters */ /* time spent */
|
|
time_left, /* 7 letters */ /* time left */
|
|
max6out(p->current_speed, max6[5],
|
|
sizeof(max6[5])) /* current speed */
|
|
);
|
|
|
|
/* we flush the output stream to make it appear as soon as possible */
|
|
fflush(data->set.err);
|
|
}
|
|
#else /* CURL_DISABLE_PROGRESS_METER */
|
|
#define progress_meter(x) Curl_nop_stmt
|
|
#endif
|
|
|
|
/*
|
|
* Curl_pgrsUpdate() returns 0 for success or the value returned by the
|
|
* progress callback!
|
|
*/
|
|
static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress)
|
|
{
|
|
if(!data->progress.hide) {
|
|
if(data->set.fxferinfo) {
|
|
int result;
|
|
/* There is a callback set, call that */
|
|
Curl_set_in_callback(data, TRUE);
|
|
result = data->set.fxferinfo(data->set.progress_client,
|
|
data->progress.dl.total_size,
|
|
data->progress.dl.cur_size,
|
|
data->progress.ul.total_size,
|
|
data->progress.ul.cur_size);
|
|
Curl_set_in_callback(data, FALSE);
|
|
if(result != CURL_PROGRESSFUNC_CONTINUE) {
|
|
if(result) {
|
|
failf(data, "Callback aborted");
|
|
return CURLE_ABORTED_BY_CALLBACK;
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
}
|
|
else if(data->set.fprogress) {
|
|
int result;
|
|
/* The older deprecated callback is set, call that */
|
|
Curl_set_in_callback(data, TRUE);
|
|
result = data->set.fprogress(data->set.progress_client,
|
|
(double)data->progress.dl.total_size,
|
|
(double)data->progress.dl.cur_size,
|
|
(double)data->progress.ul.total_size,
|
|
(double)data->progress.ul.cur_size);
|
|
Curl_set_in_callback(data, FALSE);
|
|
if(result != CURL_PROGRESSFUNC_CONTINUE) {
|
|
if(result) {
|
|
failf(data, "Callback aborted");
|
|
return CURLE_ABORTED_BY_CALLBACK;
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
}
|
|
|
|
if(showprogress)
|
|
progress_meter(data);
|
|
}
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode pgrs_update(struct Curl_easy *data,
|
|
const struct curltime *pnow)
|
|
{
|
|
bool showprogress = progress_calc(data, pnow);
|
|
return pgrsupdate(data, showprogress);
|
|
}
|
|
|
|
CURLcode Curl_pgrsUpdate(struct Curl_easy *data)
|
|
{
|
|
return pgrs_update(data, Curl_pgrs_now(data));
|
|
}
|
|
|
|
CURLcode Curl_pgrsCheck(struct Curl_easy *data)
|
|
{
|
|
CURLcode result;
|
|
|
|
result = pgrs_update(data, Curl_pgrs_now(data));
|
|
if(!result && !data->req.done)
|
|
result = pgrs_speedcheck(data, Curl_pgrs_now(data));
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Update all progress, do not do progress meter/callbacks.
|
|
*/
|
|
void Curl_pgrsUpdate_nometer(struct Curl_easy *data)
|
|
{
|
|
(void)progress_calc(data, Curl_pgrs_now(data));
|
|
}
|