extensively commented source code, parts refreshened, the "current speed" is
[platform/upstream/curl.git] / lib / progress.c
1 /*****************************************************************************
2  *                                  _   _ ____  _     
3  *  Project                     ___| | | |  _ \| |    
4  *                             / __| | | | |_) | |    
5  *                            | (__| |_| |  _ <| |___ 
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2001, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * In order to be useful for every potential user, curl and libcurl are
11  * dual-licensed under the MPL and the MIT/X-derivate licenses.
12  *
13  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
14  * copies of the Software, and permit persons to whom the Software is
15  * furnished to do so, under the terms of the MPL or the MIT/X-derivate
16  * licenses. You may pick one of these licenses.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * $Id$
22  *****************************************************************************/
23
24 #include "setup.h"
25
26 #include <string.h>
27
28 #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
29 #if defined(__MINGW32__)
30 #include <winsock.h>
31 #endif
32 #include <time.h>
33 #endif
34
35 /* 20000318 mgs
36  * later we use _scrsize to determine the screen width, this emx library
37  * function needs stdlib.h to be included */
38 #if defined(__EMX__)
39 #include <stdlib.h>
40 #endif
41
42 #include <curl/curl.h>
43 #include "urldata.h"
44 #include "sendf.h"
45
46 #include "progress.h"
47
48 static void time2str(char *r, int t)
49 {
50   int h = (t/3600);
51   int m = (t-(h*3600))/60;
52   int s = (t-(h*3600)-(m*60));
53   sprintf(r,"%2d:%02d:%02d",h,m,s);
54 }
55
56 /* The point of this function would be to return a string of the input data,
57    but never longer than 5 columns. Add suffix k, M, G when suitable... */
58 static char *max5data(double bytes, char *max5)
59 {
60 #define ONE_KILOBYTE 1024
61 #define ONE_MEGABYTE (1024*1024)
62
63   if(bytes < 100000) {
64     sprintf(max5, "%5d", (int)bytes);
65     return max5;
66   }
67   if(bytes < (9999*ONE_KILOBYTE)) {
68     sprintf(max5, "%4dk", (int)bytes/ONE_KILOBYTE);
69     return max5;
70   }
71   if(bytes < (100*ONE_MEGABYTE)) {
72     /* 'XX.XM' is good as long as we're less than 100 megs */
73     sprintf(max5, "%4.1fM", bytes/ONE_MEGABYTE);
74     return max5;
75   }
76   sprintf(max5, "%4dM", (int)bytes/ONE_MEGABYTE);
77   return max5;
78 }
79
80 /* 
81
82    New proposed interface, 9th of February 2000:
83
84    pgrsStartNow() - sets start time
85    pgrsSetDownloadSize(x) - known expected download size
86    pgrsSetUploadSize(x) - known expected upload size
87    pgrsSetDownloadCounter() - amount of data currently downloaded
88    pgrsSetUploadCounter() - amount of data currently uploaded
89    pgrsUpdate() - show progress
90    pgrsDone() - transfer complete
91
92 */
93
94 void Curl_pgrsDone(struct connectdata *conn)
95 {
96   struct SessionHandle *data = conn->data;
97   if(!(data->progress.flags & PGRS_HIDE)) {
98     data->progress.lastshow=0;
99     Curl_pgrsUpdate(conn); /* the final (forced) update */
100     if(!data->progress.callback)
101       /* only output if we don't use progress callback */
102       fprintf(data->set.err, "\n");
103   }
104 }
105
106 void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
107 {
108   switch(timer) {
109   default:
110   case TIMER_NONE:
111     /* mistake filter */
112     break;
113   case TIMER_STARTSINGLE:
114     /* This is set at the start of a single fetch, there may be several
115        fetches within an operation, why we add all other times relative
116        to this one */
117     data->progress.t_startsingle = Curl_tvnow();
118     break;
119
120   case TIMER_NAMELOOKUP:
121     data->progress.t_nslookup +=
122       Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000;
123     break;
124   case TIMER_CONNECT:
125     data->progress.t_connect +=
126       Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000;
127     break;
128   case TIMER_PRETRANSFER:
129     data->progress.t_pretransfer +=
130       Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000;
131     break;
132   case TIMER_POSTRANSFER:
133     /* this is the normal end-of-transfer thing */
134     break;
135   }
136 }
137
138 void Curl_pgrsStartNow(struct SessionHandle *data)
139 {
140   data->progress.speeder_c = 0; /* reset the progress meter display */
141   data->progress.start = Curl_tvnow();
142 }
143
144 void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, double size)
145 {
146   data->progress.downloaded = size;
147 }
148
149 void Curl_pgrsSetUploadCounter(struct SessionHandle *data, double size)
150 {
151   data->progress.uploaded = size;
152 }
153
154 void Curl_pgrsSetDownloadSize(struct SessionHandle *data, double size)
155 {
156   if(size > 0) {
157     data->progress.size_dl = size;
158     data->progress.flags |= PGRS_DL_SIZE_KNOWN;
159   }
160 }
161
162 void Curl_pgrsSetUploadSize(struct SessionHandle *data, double size)
163 {
164   if(size > 0) {
165     data->progress.size_ul = size;
166     data->progress.flags |= PGRS_UL_SIZE_KNOWN;
167   }
168 }
169
170 /* EXAMPLE OUTPUT to follow:
171
172   % Total    % Received % Xferd  Average Speed          Time             Curr.
173                                  Dload  Upload Total    Current  Left    Speed
174 100 12345  100 12345  100 12345  12345  12345 12:12:12 12:12:12 12:12:12 12345
175
176  */
177
178 int Curl_pgrsUpdate(struct connectdata *conn)
179 {
180   struct timeval now;
181   int result;
182
183   char max5[6][10];
184   double dlpercen=0;
185   double ulpercen=0;
186   double total_percen=0;
187
188   double total_transfer;
189   double total_expected_transfer;
190
191   struct SessionHandle *data = conn->data;
192
193   int nowindex = data->progress.speeder_c% CURR_TIME;
194   int checkindex;
195
196   int countindex; /* amount of seconds stored in the speeder array */
197
198   char time_left[10];
199   char time_total[10];
200   char time_current[10];
201       
202   double ulestimate=0;
203   double dlestimate=0;
204   
205   double total_estimate;
206
207
208   if(data->progress.flags & PGRS_HIDE)
209     ; /* We do enter this function even if we don't wanna see anything, since
210          this is were lots of the calculations are being made that will be used
211          even when not displayed! */
212   else if(!(data->progress.flags & PGRS_HEADERS_OUT)) {
213     if (!data->progress.callback) {
214       if(conn->resume_from)
215         fprintf(data->set.err, "** Resuming transfer from byte position %d\n",
216                 conn->resume_from);
217       fprintf(data->set.err,
218               "  %% Total    %% Received %% Xferd  Average Speed          Time             Curr.\n"
219               "                                 Dload  Upload Total    Current  Left    Speed\n");
220     }
221     data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */
222   }
223
224   now = Curl_tvnow(); /* what time is it */
225
226   /* The exact time spent so far (from the start) */
227   data->progress.timespent = Curl_tvdiff (now, data->progress.start)/1000;
228
229   /* The average download speed this far */
230   data->progress.dlspeed =
231     data->progress.downloaded/(data->progress.timespent?
232                                data->progress.timespent:1);
233
234   /* The average upload speed this far */
235   data->progress.ulspeed =
236     data->progress.uploaded/(data->progress.timespent?
237                              data->progress.timespent:1);
238
239   if(data->progress.lastshow == Curl_tvlong(now))
240     return 0; /* never update this more than once a second if the end isn't 
241                  reached */
242   data->progress.lastshow = now.tv_sec;
243
244   /* Let's do the "current speed" thing, which should use the fastest
245      of the dl/ul speeds. Store the fasted speed at entry 'nowindex'. */
246   data->progress.speeder[ nowindex ] =
247     data->progress.downloaded>data->progress.uploaded?
248     data->progress.downloaded:data->progress.uploaded;
249
250   /* remember the exact time for this moment */
251   data->progress.speeder_time [ nowindex ] = now;
252
253   /* advance our speeder_c counter, which is increased every time we get
254      here and we expect it to never wrap as 2^32 is a lot of seconds! */
255   data->progress.speeder_c++;
256
257   /* figure out how many index entries of data we have stored in our speeder
258      array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of
259      transfer. Imagine, after one second we have filled in two entries,
260      after two seconds we've filled in three entries etc. */
261   countindex = ((data->progress.speeder_c>=CURR_TIME)?
262                 CURR_TIME:data->progress.speeder_c) - 1;
263
264   /* first of all, we don't do this if there's no counted seconds yet */
265   if(countindex) {
266     long span_ms;
267
268     /* Get the index position to compare with the 'nowindex' position.
269        Get the oldest entry possible. While we have less than CURR_TIME
270        entries, the first entry will remain the oldest. */
271     checkindex = (data->progress.speeder_c>=CURR_TIME)?
272       data->progress.speeder_c%CURR_TIME:0;
273
274     /* Figure out the exact time for the time span */
275     span_ms = Curl_tvdiff(now,
276                           data->progress.speeder_time[checkindex]);
277
278     /* Calculate the average speed the last 'countindex' seconds */
279     data->progress.current_speed =
280       (data->progress.speeder[nowindex]-
281        data->progress.speeder[checkindex])/((double)span_ms/1000);
282   }
283   else
284     /* the first second we only have one speed information to use */
285     data->progress.current_speed = data->progress.speeder[nowindex];
286
287   if(data->progress.flags & PGRS_HIDE)
288     return 0;
289
290   else if(data->set.fprogress) {
291     /* There's a callback set, so we call that instead of writing
292        anything ourselves. This really is the way to go. */
293     result= data->set.fprogress(data->set.progress_client,
294                                 data->progress.size_dl,
295                                 data->progress.downloaded,
296                                 data->progress.size_ul,
297                                 data->progress.uploaded);
298     if(result)
299       failf(data, "Callback aborted");
300     return result;
301   }
302
303   /* Figure out the estimated time of arrival for the upload */
304   if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && data->progress.ulspeed){
305     ulestimate = data->progress.size_ul / data->progress.ulspeed;
306     ulpercen = (data->progress.uploaded / data->progress.size_ul)*100;
307   }
308
309   /* ... and the download */
310   if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && data->progress.dlspeed) {
311     dlestimate = data->progress.size_dl / data->progress.dlspeed;
312     dlpercen = (data->progress.downloaded / data->progress.size_dl)*100;
313   }
314     
315   /* Now figure out which of them that is slower and use for the for
316      total estimate! */
317   total_estimate = ulestimate>dlestimate?ulestimate:dlestimate;
318
319
320   /* If we have a total estimate, we can display that and the expected
321      time left */
322   if(total_estimate) {
323     time2str(time_left, total_estimate-(int) data->progress.timespent); 
324     time2str(time_total, total_estimate);
325   }
326   else {
327     /* otherwise we blank those times */
328     strcpy(time_left,  "--:--:--");
329     strcpy(time_total, "--:--:--");
330   }
331   /* The time spent so far is always known */
332   time2str(time_current, data->progress.timespent);
333
334   /* Get the total amount of data expected to get transfered */
335   total_expected_transfer = 
336     (data->progress.flags & PGRS_UL_SIZE_KNOWN?
337      data->progress.size_ul:data->progress.uploaded)+
338     (data->progress.flags & PGRS_DL_SIZE_KNOWN?
339      data->progress.size_dl:data->progress.downloaded);
340       
341   /* We have transfered this much so far */
342   total_transfer = data->progress.downloaded + data->progress.uploaded;
343
344   /* Get the percentage of data transfered so far */
345   if(total_expected_transfer)
346     total_percen=(double)(total_transfer/total_expected_transfer)*100;
347
348   fprintf(data->set.err,
349           "\r%3d %s  %3d %s  %3d %s  %s  %s %s %s %s %s",
350           (int)total_percen,                            /* total % */
351           max5data(total_expected_transfer, max5[2]),   /* total size */
352           (int)dlpercen,                                /* rcvd % */
353           max5data(data->progress.downloaded, max5[0]), /* rcvd size */
354           (int)ulpercen,                                /* xfer % */
355           max5data(data->progress.uploaded, max5[1]),   /* xfer size */
356
357           max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */
358           max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */
359           time_total,                           /* total time */
360           time_current,                         /* current time */
361           time_left,                            /* time left */
362           max5data(data->progress.current_speed, max5[5]) /* current speed */
363           );
364
365   /* we flush the output stream to make it appear as soon as possible */
366   fflush(data->set.err);
367
368   return 0;
369 }
370
371 /*
372  * local variables:
373  * eval: (load-file "../curl-mode.el")
374  * end:
375  * vim600: fdm=marker
376  * vim: et sw=2 ts=2 sts=2 tw=78
377  */