1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * $Id: progress.c,v 1.2 2007/03/15 19:22:13 andy Exp $
22 ***************************************************************************/
33 #include <curl/curl.h>
38 #define _MPRINTF_REPLACE /* use our functions only */
39 #include <curl/mprintf.h>
41 /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
43 static void time2str(char *r
, long t
)
47 strcpy(r
, "--:--:--");
52 long m
= (t
-(h
*3600))/60;
53 long s
= (t
-(h
*3600)-(m
*60));
54 snprintf(r
, 9, "%2ld:%02ld:%02ld",h
,m
,s
);
57 /* this equals to more than 99 hours, switch to a more suitable output
58 format to fit within the limits. */
60 snprintf(r
, 9, "%3ldd %02ldh", h
/24, h
-(h
/24)*24);
62 snprintf(r
, 9, "%7ldd", h
/24);
66 /* The point of this function would be to return a string of the input data,
67 but never longer than 5 columns (+ one zero byte).
68 Add suffix k, M, G when suitable... */
69 static char *max5data(curl_off_t bytes
, char *max5
)
71 #define ONE_KILOBYTE 1024
72 #define ONE_MEGABYTE (1024* ONE_KILOBYTE)
73 #define ONE_GIGABYTE (1024* ONE_MEGABYTE)
74 #define ONE_TERRABYTE ((curl_off_t)1024* ONE_GIGABYTE)
75 #define ONE_PETABYTE ((curl_off_t)1024* ONE_TERRABYTE)
78 snprintf(max5
, 6, "%5" FORMAT_OFF_T
, bytes
);
80 else if(bytes
< (10000*ONE_KILOBYTE
)) {
81 snprintf(max5
, 6, "%4" FORMAT_OFF_T
"k", (curl_off_t
)(bytes
/ONE_KILOBYTE
));
83 else if(bytes
< (100*ONE_MEGABYTE
)) {
84 /* 'XX.XM' is good as long as we're less than 100 megs */
85 snprintf(max5
, 6, "%2d.%0dM",
86 (int)(bytes
/ONE_MEGABYTE
),
87 (int)(bytes
%ONE_MEGABYTE
)/(ONE_MEGABYTE
/10) );
89 #if SIZEOF_CURL_OFF_T > 4
90 else if(bytes
< ( (curl_off_t
)10000*ONE_MEGABYTE
))
91 /* 'XXXXM' is good until we're at 10000MB or above */
92 snprintf(max5
, 6, "%4" FORMAT_OFF_T
"M", (curl_off_t
)(bytes
/ONE_MEGABYTE
));
94 else if(bytes
< (curl_off_t
)100*ONE_GIGABYTE
)
95 /* 10000 MB - 100 GB, we show it as XX.XG */
96 snprintf(max5
, 6, "%2d.%0dG",
97 (int)(bytes
/ONE_GIGABYTE
),
98 (int)(bytes
%ONE_GIGABYTE
)/(ONE_GIGABYTE
/10) );
100 else if(bytes
< (curl_off_t
)10000 * ONE_GIGABYTE
)
101 /* up to 10000GB, display without decimal: XXXXG */
102 snprintf(max5
, 6, "%4dG", (int)(bytes
/ONE_GIGABYTE
));
104 else if(bytes
< (curl_off_t
)10000 * ONE_TERRABYTE
)
105 /* up to 10000TB, display without decimal: XXXXT */
106 snprintf(max5
, 6, "%4dT", (int)(bytes
/ONE_TERRABYTE
));
108 /* up to 10000PB, display without decimal: XXXXP */
109 snprintf(max5
, 6, "%4dP", (int)(bytes
/ONE_PETABYTE
));
111 /* 16384 petabytes (16 exabytes) is maximum a 64 bit number can hold,
112 but this type is signed so 8192PB will be max.*/
117 snprintf(max5
, 6, "%4" FORMAT_OFF_T
"M", (curl_off_t
)(bytes
/ONE_MEGABYTE
));
125 New proposed interface, 9th of February 2000:
127 pgrsStartNow() - sets start time
128 pgrsSetDownloadSize(x) - known expected download size
129 pgrsSetUploadSize(x) - known expected upload size
130 pgrsSetDownloadCounter() - amount of data currently downloaded
131 pgrsSetUploadCounter() - amount of data currently uploaded
132 pgrsUpdate() - show progress
133 pgrsDone() - transfer complete
137 void Curl_pgrsDone(struct connectdata
*conn
)
139 struct SessionHandle
*data
= conn
->data
;
140 data
->progress
.lastshow
=0;
141 Curl_pgrsUpdate(conn
); /* the final (forced) update */
143 data
->progress
.speeder_c
= 0; /* reset the progress meter display */
146 /* reset all times except redirect */
147 void Curl_pgrsResetTimes(struct SessionHandle
*data
)
149 data
->progress
.t_nslookup
= 0.0;
150 data
->progress
.t_connect
= 0.0;
151 data
->progress
.t_pretransfer
= 0.0;
152 data
->progress
.t_starttransfer
= 0.0;
155 void Curl_pgrsTime(struct SessionHandle
*data
, timerid timer
)
162 case TIMER_STARTSINGLE
:
163 /* This is set at the start of a single fetch */
164 data
->progress
.t_startsingle
= Curl_tvnow();
167 case TIMER_NAMELOOKUP
:
168 data
->progress
.t_nslookup
=
169 Curl_tvdiff_secs(Curl_tvnow(), data
->progress
.t_startsingle
);
172 data
->progress
.t_connect
=
173 Curl_tvdiff_secs(Curl_tvnow(), data
->progress
.t_startsingle
);
175 case TIMER_PRETRANSFER
:
176 data
->progress
.t_pretransfer
=
177 Curl_tvdiff_secs(Curl_tvnow(), data
->progress
.t_startsingle
);
179 case TIMER_STARTTRANSFER
:
180 data
->progress
.t_starttransfer
=
181 Curl_tvdiff_secs(Curl_tvnow(), data
->progress
.t_startsingle
);
183 case TIMER_POSTRANSFER
:
184 /* this is the normal end-of-transfer thing */
187 data
->progress
.t_redirect
=
188 Curl_tvdiff_secs(Curl_tvnow(), data
->progress
.start
);
193 void Curl_pgrsStartNow(struct SessionHandle
*data
)
195 data
->progress
.speeder_c
= 0; /* reset the progress meter display */
196 data
->progress
.start
= Curl_tvnow();
199 void Curl_pgrsSetDownloadCounter(struct SessionHandle
*data
, curl_off_t size
)
201 data
->progress
.downloaded
= size
;
204 void Curl_pgrsSetUploadCounter(struct SessionHandle
*data
, curl_off_t size
)
206 data
->progress
.uploaded
= size
;
209 void Curl_pgrsSetDownloadSize(struct SessionHandle
*data
, curl_off_t size
)
211 data
->progress
.size_dl
= size
;
213 data
->progress
.flags
|= PGRS_DL_SIZE_KNOWN
;
215 data
->progress
.flags
&= ~PGRS_DL_SIZE_KNOWN
;
218 void Curl_pgrsSetUploadSize(struct SessionHandle
*data
, curl_off_t size
)
220 data
->progress
.size_ul
= size
;
222 data
->progress
.flags
|= PGRS_UL_SIZE_KNOWN
;
224 data
->progress
.flags
&= ~PGRS_UL_SIZE_KNOWN
;
227 int Curl_pgrsUpdate(struct connectdata
*conn
)
235 curl_off_t total_transfer
;
236 curl_off_t total_expected_transfer
;
238 struct SessionHandle
*data
= conn
->data
;
239 int nowindex
= data
->progress
.speeder_c
% CURR_TIME
;
241 int countindex
; /* amount of seconds stored in the speeder array */
249 if(data
->progress
.flags
& PGRS_HIDE
)
250 ; /* We do enter this function even if we don't wanna see anything, since
251 this is were lots of the calculations are being made that will be used
252 even when not displayed! */
253 else if(!(data
->progress
.flags
& PGRS_HEADERS_OUT
)) {
254 if (!data
->progress
.callback
) {
255 if(data
->reqdata
.resume_from
)
256 fprintf(data
->set
.err
,
257 "** Resuming transfer from byte position %" FORMAT_OFF_T
259 data
->reqdata
.resume_from
);
260 fprintf(data
->set
.err
,
261 " %% Total %% Received %% Xferd Average Speed Time Time Time Current\n"
262 " Dload Upload Total Spent Left Speed\n");
264 data
->progress
.flags
|= PGRS_HEADERS_OUT
; /* headers are shown */
267 now
= Curl_tvnow(); /* what time is it */
269 /* The time spent so far (from the start) */
270 data
->progress
.timespent
= Curl_tvdiff_secs(now
, data
->progress
.start
);
271 timespent
= (long)data
->progress
.timespent
;
273 /* The average download speed this far */
274 data
->progress
.dlspeed
= (curl_off_t
)
275 ((double)data
->progress
.downloaded
/
276 (data
->progress
.timespent
>0?data
->progress
.timespent
:1));
278 /* The average upload speed this far */
279 data
->progress
.ulspeed
= (curl_off_t
)
280 ((double)data
->progress
.uploaded
/
281 (data
->progress
.timespent
>0?data
->progress
.timespent
:1));
283 if(data
->progress
.lastshow
== Curl_tvlong(now
))
284 return 0; /* never update this more than once a second if the end isn't
286 data
->progress
.lastshow
= now
.tv_sec
;
288 /* Let's do the "current speed" thing, which should use the fastest
289 of the dl/ul speeds. Store the fasted speed at entry 'nowindex'. */
290 data
->progress
.speeder
[ nowindex
] =
291 data
->progress
.downloaded
>data
->progress
.uploaded
?
292 data
->progress
.downloaded
:data
->progress
.uploaded
;
294 /* remember the exact time for this moment */
295 data
->progress
.speeder_time
[ nowindex
] = now
;
297 /* advance our speeder_c counter, which is increased every time we get
298 here and we expect it to never wrap as 2^32 is a lot of seconds! */
299 data
->progress
.speeder_c
++;
301 /* figure out how many index entries of data we have stored in our speeder
302 array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of
303 transfer. Imagine, after one second we have filled in two entries,
304 after two seconds we've filled in three entries etc. */
305 countindex
= ((data
->progress
.speeder_c
>=CURR_TIME
)?
306 CURR_TIME
:data
->progress
.speeder_c
) - 1;
308 /* first of all, we don't do this if there's no counted seconds yet */
312 /* Get the index position to compare with the 'nowindex' position.
313 Get the oldest entry possible. While we have less than CURR_TIME
314 entries, the first entry will remain the oldest. */
315 checkindex
= (data
->progress
.speeder_c
>=CURR_TIME
)?
316 data
->progress
.speeder_c
%CURR_TIME
:0;
318 /* Figure out the exact time for the time span */
319 span_ms
= Curl_tvdiff(now
,
320 data
->progress
.speeder_time
[checkindex
]);
322 span_ms
=1; /* at least one millisecond MUST have passed */
324 /* Calculate the average speed the last 'span_ms' milliseconds */
326 curl_off_t amount
= data
->progress
.speeder
[nowindex
]-
327 data
->progress
.speeder
[checkindex
];
329 if(amount
> 4294967 /* 0xffffffff/1000 */)
330 /* the 'amount' value is bigger than would fit in 32 bits if
331 multiplied with 1000, so we use the double math for this */
332 data
->progress
.current_speed
= (curl_off_t
)
333 ((double)amount
/((double)span_ms
/1000.0));
335 /* the 'amount' value is small enough to fit within 32 bits even
336 when multiplied with 1000 */
337 data
->progress
.current_speed
= amount
*1000/span_ms
;
341 /* the first second we use the main average */
342 data
->progress
.current_speed
=
343 (data
->progress
.ulspeed
>data
->progress
.dlspeed
)?
344 data
->progress
.ulspeed
:data
->progress
.dlspeed
;
346 if(data
->progress
.flags
& PGRS_HIDE
)
349 else if(data
->set
.fprogress
) {
350 /* There's a callback set, so we call that instead of writing
351 anything ourselves. This really is the way to go. */
352 result
= data
->set
.fprogress(data
->set
.progress_client
,
353 (double)data
->progress
.size_dl
,
354 (double)data
->progress
.downloaded
,
355 (double)data
->progress
.size_ul
,
356 (double)data
->progress
.uploaded
);
358 failf(data
, "Callback aborted");
362 /* Figure out the estimated time of arrival for the upload */
363 if((data
->progress
.flags
& PGRS_UL_SIZE_KNOWN
) &&
364 (data
->progress
.ulspeed
>0) &&
365 (data
->progress
.size_ul
> 100) ) {
366 ulestimate
= (long)(data
->progress
.size_ul
/ data
->progress
.ulspeed
);
367 ulpercen
= (int)(100*(data
->progress
.uploaded
/100) /
368 (data
->progress
.size_ul
/100) );
371 /* ... and the download */
372 if((data
->progress
.flags
& PGRS_DL_SIZE_KNOWN
) &&
373 (data
->progress
.dlspeed
>0) &&
374 (data
->progress
.size_dl
>100)) {
375 dlestimate
= (long)(data
->progress
.size_dl
/ data
->progress
.dlspeed
);
376 dlpercen
= (int)(100*(data
->progress
.downloaded
/100) /
377 (data
->progress
.size_dl
/100));
380 /* Now figure out which of them that is slower and use for the for
382 total_estimate
= ulestimate
>dlestimate
?ulestimate
:dlestimate
;
384 /* create the three time strings */
385 time2str(time_left
, total_estimate
> 0?(total_estimate
- timespent
):0);
386 time2str(time_total
, total_estimate
);
387 time2str(time_spent
, timespent
);
389 /* Get the total amount of data expected to get transfered */
390 total_expected_transfer
=
391 (data
->progress
.flags
& PGRS_UL_SIZE_KNOWN
?
392 data
->progress
.size_ul
:data
->progress
.uploaded
)+
393 (data
->progress
.flags
& PGRS_DL_SIZE_KNOWN
?
394 data
->progress
.size_dl
:data
->progress
.downloaded
);
396 /* We have transfered this much so far */
397 total_transfer
= data
->progress
.downloaded
+ data
->progress
.uploaded
;
399 /* Get the percentage of data transfered so far */
400 if(total_expected_transfer
> 100)
401 total_percen
=(int)(100*(total_transfer
/100) /
402 (total_expected_transfer
/100) );
404 fprintf(data
->set
.err
,
405 "\r%3d %s %3d %s %3d %s %s %s %s %s %s %s",
406 total_percen
, /* 3 letters */ /* total % */
407 max5data(total_expected_transfer
, max5
[2]), /* total size */
408 dlpercen
, /* 3 letters */ /* rcvd % */
409 max5data(data
->progress
.downloaded
, max5
[0]), /* rcvd size */
410 ulpercen
, /* 3 letters */ /* xfer % */
411 max5data(data
->progress
.uploaded
, max5
[1]), /* xfer size */
412 max5data(data
->progress
.dlspeed
, max5
[3]), /* avrg dl speed */
413 max5data(data
->progress
.ulspeed
, max5
[4]), /* avrg ul speed */
414 time_total
, /* 8 letters */ /* total time */
415 time_spent
, /* 8 letters */ /* time spent */
416 time_left
, /* 8 letters */ /* time left */
417 max5data(data
->progress
.current_speed
, max5
[5]) /* current speed */
420 /* we flush the output stream to make it appear as soon as possible */
421 fflush(data
->set
.err
);