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