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