1 /*-
2  * Copyright (c) 2011-2014 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
4  * Copyright (c) 2011 Will Andrews <will@FreeBSD.org>
5  * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
6  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
7  * Copyright (c) 2015 Matthew Seaman <matthew@FreeBSD.org>
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer
15  *    in this position and unchanged.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 
33 #include <libutil.h>
34 #include <utstring.h>
35 #include <stdlib.h>
36 #include <stdbool.h>
37 #include <time.h>
38 #include <unistd.h>
39 
40 #define STALL_TIME 5
41 
42 static char *progress_message = NULL;
43 static UT_string *msg_buf = NULL;
44 static int last_progress_percent = -1;
45 static bool progress_started = false;
46 static bool progress_interrupted = false;
47 static bool progress_debit = false;
48 static int64_t last_tick = 0;
49 static int64_t stalled;
50 static int64_t bytes_per_second;
51 static time_t last_update;
52 static time_t begin = 0;
53 static int add_deps_depth;
54 static bool signal_handler_installed = false;
55 static bool quiet = false;
56 
57 /* units for format_size */
58 static const char *unit_SI[] = { " ", "k", "M", "G", "T", };
59 
60 static void
format_rate_SI(char * buf,int size,off_t bytes)61 format_rate_SI(char *buf, int size, off_t bytes)
62 {
63     int i;
64 
65     bytes *= 100;
66     for (i = 0; bytes >= 100*1000 && unit_SI[i][0] != 'T'; i++)
67         bytes = (bytes + 500) / 1000;
68     if (i == 0) {
69         i++;
70         bytes = (bytes + 500) / 1000;
71     }
72     snprintf(buf, size, "%3lld.%1lld%s%s",
73         (long long) (bytes + 5) / 100,
74         (long long) (bytes + 5) / 10 % 10,
75         unit_SI[i],
76         i ? "B" : " ");
77 }
78 
79 void
provides_progressbar_stop(void)80 provides_progressbar_stop(void)
81 {
82     if (progress_started) {
83         if (!isatty(STDOUT_FILENO))
84             printf(" done");
85         putchar('\n');
86     }
87     last_progress_percent = -1;
88     progress_started = false;
89     progress_interrupted = false;
90 }
91 
92 void
provides_progressbar_start(const char * pmsg)93 provides_progressbar_start(const char *pmsg) {
94     free(progress_message);
95     progress_message = NULL;
96     progress_debit = true;
97 
98     if (quiet)
99         return;
100     if (pmsg != NULL)
101         progress_message = strdup(pmsg);
102     else {
103         progress_message = strdup(utstring_body(msg_buf));
104     }
105     last_progress_percent = -1;
106     last_tick = 0;
107     begin = last_update = time(NULL);
108     bytes_per_second = 0;
109     stalled = 0;
110 
111     progress_started = true;
112     progress_interrupted = false;
113     if (!isatty(STDOUT_FILENO))
114         printf("%s: ", progress_message);
115     else
116         printf("%s:   0%%", progress_message);
117 }
118 
119 static void
draw_progressbar(int64_t current,int64_t total)120 draw_progressbar(int64_t current, int64_t total)
121 {
122     int percent;
123     int64_t transferred;
124     time_t elapsed = 0, now = 0;
125     char buf[8];
126     int64_t bytes_left;
127     int cur_speed;
128     int hours, minutes, seconds;
129     float age_factor;
130 
131     if (!progress_started) {
132         provides_progressbar_stop();
133         return;
134     }
135 
136     if (progress_debit) {
137         now = time(NULL);
138         elapsed = (now >= last_update) ? now - last_update : 0;
139     }
140 
141     percent = (total != 0) ? (current * 100. / total) : 100;
142 
143     /**
144      * Wait for interval for debit bars to keep calc per second.
145      * If not debit, show on every % change, or if ticking after
146      * an interruption (which removed our progressbar output).
147      */
148     if (current >= total || (progress_debit && elapsed >= 1) ||
149         (!progress_debit &&
150         (percent != last_progress_percent || progress_interrupted))) {
151         last_progress_percent = percent;
152 
153         printf("\r%s: %3d%%", progress_message, percent);
154         if (progress_debit) {
155             transferred = current - last_tick;
156             last_tick = current;
157             bytes_left = total - current;
158             if (bytes_left <= 0) {
159                 elapsed = now - begin;
160                 /* Always show at least 1 second at end. */
161                 if (elapsed == 0)
162                     elapsed = 1;
163                 /* Calculate true total speed when done */
164                 transferred = total;
165                 bytes_per_second = 0;
166             }
167 
168             if (elapsed != 0)
169                 cur_speed = (transferred / elapsed);
170             else
171                 cur_speed = transferred;
172 
173 #define AGE_FACTOR_SLOW_START 3
174             if (now - begin <= AGE_FACTOR_SLOW_START)
175                 age_factor = 0.4;
176             else
177                 age_factor = 0.9;
178 
179             if (bytes_per_second != 0) {
180                 bytes_per_second =
181                     (bytes_per_second * age_factor) +
182                     (cur_speed * (1.0 - age_factor));
183             } else
184                 bytes_per_second = cur_speed;
185 
186             humanize_number(buf, sizeof(buf),
187                 current,"B", HN_AUTOSCALE, HN_IEC_PREFIXES);
188             printf(" %*s", (int)sizeof(buf), buf);
189 
190             if (bytes_left > 0)
191                 format_rate_SI(buf, sizeof(buf), transferred);
192             else /* Show overall speed when done */
193                 format_rate_SI(buf, sizeof(buf),
194                     bytes_per_second);
195             printf(" %s/s ", buf);
196 
197             if (!transferred)
198                 stalled += elapsed;
199             else
200                 stalled = 0;
201 
202             if (stalled >= STALL_TIME)
203                 printf(" - stalled -");
204             else if (bytes_per_second == 0 && bytes_left > 0)
205                 printf("   --:-- ETA");
206             else {
207                 if (bytes_left > 0)
208                     seconds = bytes_left / bytes_per_second;
209                 else
210                     seconds = elapsed;
211 
212                 hours = seconds / 3600;
213                 seconds -= hours * 3600;
214                 minutes = seconds / 60;
215                 seconds -= minutes * 60;
216 
217                 if (hours != 0)
218                     printf("%02d:%02d:%02d", hours,
219                         minutes, seconds);
220                 else
221                     printf("   %02d:%02d", minutes, seconds);
222 
223                 if (bytes_left > 0)
224                     printf(" ETA");
225                 else
226                     printf("    ");
227                 last_update = now;
228             }
229         }
230         fflush(stdout);
231     }
232     if (current >= total)
233         return provides_progressbar_stop();
234 }
235 
236 void
provides_progressbar_tick(int64_t current,int64_t total)237 provides_progressbar_tick(int64_t current, int64_t total)
238 {
239     int percent;
240 
241     if (!quiet && progress_started) {
242         if (isatty(STDOUT_FILENO))
243             draw_progressbar(current, total);
244         else {
245             if (progress_interrupted) {
246                 printf("%s...", progress_message);
247             } else if (!getenv("NO_TICK")){
248                 percent = (total != 0) ? (current * 100. / total) : 100;
249                 if (last_progress_percent / 10 < percent / 10) {
250                     last_progress_percent = percent;
251                     printf(".");
252                     fflush(stdout);
253                 }
254             }
255             if (current >= total)
256                 provides_progressbar_stop();
257         }
258     }
259     progress_interrupted = false;
260 }
261