xref: /curl/src/tool_progress.c (revision c0450488)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
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 https://curl.se/docs/copyright.html.
13  *
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.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "tool_setup.h"
25 #include "tool_operate.h"
26 #include "tool_progress.h"
27 #include "tool_util.h"
28 
29 #include "curlx.h"
30 
31 /* The point of this function would be to return a string of the input data,
32    but never longer than 5 columns (+ one zero byte).
33    Add suffix k, M, G when suitable... */
max5data(curl_off_t bytes,char * max5)34 static char *max5data(curl_off_t bytes, char *max5)
35 {
36 #define ONE_KILOBYTE  CURL_OFF_T_C(1024)
37 #define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE)
38 #define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE)
39 #define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE)
40 #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE)
41 
42   if(bytes < CURL_OFF_T_C(100000))
43     msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes);
44 
45   else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE)
46     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE);
47 
48   else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE)
49     /* 'XX.XM' is good as long as we are less than 100 megs */
50     msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0"
51               CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE,
52               (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) );
53 
54   else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE)
55     /* 'XXXXM' is good until we are at 10000MB or above */
56     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE);
57 
58   else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE)
59     /* 10000 MB - 100 GB, we show it as XX.XG */
60     msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0"
61               CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE,
62               (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) );
63 
64   else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE)
65     /* up to 10000GB, display without decimal: XXXXG */
66     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE);
67 
68   else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE)
69     /* up to 10000TB, display without decimal: XXXXT */
70     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE);
71 
72   else
73     /* up to 10000PB, display without decimal: XXXXP */
74     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE);
75 
76   /* 16384 petabytes (16 exabytes) is the maximum a 64-bit unsigned number can
77      hold, but our data type is signed so 8192PB will be the maximum. */
78   return max5;
79 }
80 
xferinfo_cb(void * clientp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)81 int xferinfo_cb(void *clientp,
82                 curl_off_t dltotal,
83                 curl_off_t dlnow,
84                 curl_off_t ultotal,
85                 curl_off_t ulnow)
86 {
87   struct per_transfer *per = clientp;
88   struct OperationConfig *config = per->config;
89   per->dltotal = dltotal;
90   per->dlnow = dlnow;
91   per->ultotal = ultotal;
92   per->ulnow = ulnow;
93 
94   if(per->abort)
95     return 1;
96 
97   if(config->readbusy) {
98     config->readbusy = FALSE;
99     curl_easy_pause(per->curl, CURLPAUSE_CONT);
100   }
101 
102   return 0;
103 }
104 
105 /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
106    byte) */
time2str(char * r,curl_off_t seconds)107 static void time2str(char *r, curl_off_t seconds)
108 {
109   curl_off_t h;
110   if(seconds <= 0) {
111     strcpy(r, "--:--:--");
112     return;
113   }
114   h = seconds / CURL_OFF_T_C(3600);
115   if(h <= CURL_OFF_T_C(99)) {
116     curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60);
117     curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60));
118     msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T
119               ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s);
120   }
121   else {
122     /* this equals to more than 99 hours, switch to a more suitable output
123        format to fit within the limits. */
124     curl_off_t d = seconds / CURL_OFF_T_C(86400);
125     h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600);
126     if(d <= CURL_OFF_T_C(999))
127       msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T
128                 "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h);
129     else
130       msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d);
131   }
132 }
133 
134 static curl_off_t all_dltotal = 0;
135 static curl_off_t all_ultotal = 0;
136 static curl_off_t all_dlalready = 0;
137 static curl_off_t all_ulalready = 0;
138 
139 curl_off_t all_xfers = 0;   /* current total */
140 
141 struct speedcount {
142   curl_off_t dl;
143   curl_off_t ul;
144   struct timeval stamp;
145 };
146 #define SPEEDCNT 10
147 static unsigned int speedindex;
148 static bool indexwrapped;
149 static struct speedcount speedstore[SPEEDCNT];
150 
151 /*
152   |DL% UL%  Dled  Uled  Xfers  Live Total     Current  Left    Speed
153   |  6 --   9.9G     0     2     2   0:00:40  0:00:02  0:00:37 4087M
154 */
progress_meter(struct GlobalConfig * global,struct timeval * start,bool final)155 bool progress_meter(struct GlobalConfig *global,
156                     struct timeval *start,
157                     bool final)
158 {
159   static struct timeval stamp;
160   static bool header = FALSE;
161   struct timeval now;
162   long diff;
163 
164   if(global->noprogress || global->silent)
165     return FALSE;
166 
167   now = tvnow();
168   diff = tvdiff(now, stamp);
169 
170   if(!header) {
171     header = TRUE;
172     fputs("DL% UL%  Dled  Uled  Xfers  Live "
173           "Total     Current  Left    Speed\n",
174           tool_stderr);
175   }
176   if(final || (diff > 500)) {
177     char time_left[10];
178     char time_total[10];
179     char time_spent[10];
180     char buffer[3][6];
181     curl_off_t spent = tvdiff(now, *start)/1000;
182     char dlpercen[4]="--";
183     char ulpercen[4]="--";
184     struct per_transfer *per;
185     curl_off_t all_dlnow = 0;
186     curl_off_t all_ulnow = 0;
187     bool dlknown = TRUE;
188     bool ulknown = TRUE;
189     curl_off_t all_running = 0; /* in progress */
190     curl_off_t speed = 0;
191     unsigned int i;
192     stamp = now;
193 
194     /* first add the amounts of the already completed transfers */
195     all_dlnow += all_dlalready;
196     all_ulnow += all_ulalready;
197 
198     for(per = transfers; per; per = per->next) {
199       all_dlnow += per->dlnow;
200       all_ulnow += per->ulnow;
201       if(!per->dltotal)
202         dlknown = FALSE;
203       else if(!per->dltotal_added) {
204         /* only add this amount once */
205         all_dltotal += per->dltotal;
206         per->dltotal_added = TRUE;
207       }
208       if(!per->ultotal)
209         ulknown = FALSE;
210       else if(!per->ultotal_added) {
211         /* only add this amount once */
212         all_ultotal += per->ultotal;
213         per->ultotal_added = TRUE;
214       }
215       if(per->added)
216         all_running++;
217     }
218     if(dlknown && all_dltotal)
219       /* TODO: handle integer overflow */
220       msnprintf(dlpercen, sizeof(dlpercen), "%3" CURL_FORMAT_CURL_OFF_T,
221                 all_dlnow * 100 / all_dltotal);
222     if(ulknown && all_ultotal)
223       /* TODO: handle integer overflow */
224       msnprintf(ulpercen, sizeof(ulpercen), "%3" CURL_FORMAT_CURL_OFF_T,
225                 all_ulnow * 100 / all_ultotal);
226 
227     /* get the transfer speed, the higher of the two */
228 
229     i = speedindex;
230     speedstore[i].dl = all_dlnow;
231     speedstore[i].ul = all_ulnow;
232     speedstore[i].stamp = now;
233     if(++speedindex >= SPEEDCNT) {
234       indexwrapped = TRUE;
235       speedindex = 0;
236     }
237 
238     {
239       long deltams;
240       curl_off_t dl;
241       curl_off_t ul;
242       curl_off_t dls;
243       curl_off_t uls;
244       if(indexwrapped) {
245         /* 'speedindex' is the oldest stored data */
246         deltams = tvdiff(now, speedstore[speedindex].stamp);
247         dl = all_dlnow - speedstore[speedindex].dl;
248         ul = all_ulnow - speedstore[speedindex].ul;
249       }
250       else {
251         /* since the beginning */
252         deltams = tvdiff(now, *start);
253         dl = all_dlnow;
254         ul = all_ulnow;
255       }
256       if(!deltams) /* no division by zero please */
257         deltams++;
258       dls = (curl_off_t)((double)dl / ((double)deltams/1000.0));
259       uls = (curl_off_t)((double)ul / ((double)deltams/1000.0));
260       speed = dls > uls ? dls : uls;
261     }
262 
263 
264     if(dlknown && speed) {
265       curl_off_t est = all_dltotal / speed;
266       curl_off_t left = (all_dltotal - all_dlnow) / speed;
267       time2str(time_left, left);
268       time2str(time_total, est);
269     }
270     else {
271       time2str(time_left, 0);
272       time2str(time_total, 0);
273     }
274     time2str(time_spent, spent);
275 
276     fprintf(tool_stderr,
277             "\r"
278             "%-3s " /* percent downloaded */
279             "%-3s " /* percent uploaded */
280             "%s " /* Dled */
281             "%s " /* Uled */
282             "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */
283             "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */
284             " %s "  /* Total time */
285             "%s "  /* Current time */
286             "%s "  /* Time left */
287             "%s "  /* Speed */
288             "%5s" /* final newline */,
289 
290             dlpercen,  /* 3 letters */
291             ulpercen,  /* 3 letters */
292             max5data(all_dlnow, buffer[0]),
293             max5data(all_ulnow, buffer[1]),
294             all_xfers,
295             all_running,
296             time_total,
297             time_spent,
298             time_left,
299             max5data(speed, buffer[2]), /* speed */
300             final ? "\n" :"");
301     return TRUE;
302   }
303   return FALSE;
304 }
305 
progress_finalize(struct per_transfer * per)306 void progress_finalize(struct per_transfer *per)
307 {
308   /* get the numbers before this transfer goes away */
309   all_dlalready += per->dlnow;
310   all_ulalready += per->ulnow;
311   if(!per->dltotal_added) {
312     all_dltotal += per->dltotal;
313     per->dltotal_added = TRUE;
314   }
315   if(!per->ultotal_added) {
316     all_ultotal += per->ultotal;
317     per->ultotal_added = TRUE;
318   }
319 }
320