progressmeter.c revision 157016
1/*
2 * Copyright (c) 2003 Nils Nordman.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "includes.h"
26RCSID("$OpenBSD: progressmeter.c,v 1.24 2005/06/07 13:25:23 jaredy Exp $");
27
28#include "progressmeter.h"
29#include "atomicio.h"
30#include "misc.h"
31
32#define DEFAULT_WINSIZE 80
33#define MAX_WINSIZE 512
34#define PADDING 1		/* padding between the progress indicators */
35#define UPDATE_INTERVAL 1	/* update the progress meter every second */
36#define STALL_TIME 5		/* we're stalled after this many seconds */
37
38/* determines whether we can output to the terminal */
39static int can_output(void);
40
41/* formats and inserts the specified size into the given buffer */
42static void format_size(char *, int, off_t);
43static void format_rate(char *, int, off_t);
44
45/* window resizing */
46static void sig_winch(int);
47static void setscreensize(void);
48
49/* updates the progressmeter to reflect the current state of the transfer */
50void refresh_progress_meter(void);
51
52/* signal handler for updating the progress meter */
53static void update_progress_meter(int);
54
55static time_t start;		/* start progress */
56static time_t last_update;	/* last progress update */
57static char *file;		/* name of the file being transferred */
58static off_t end_pos;		/* ending position of transfer */
59static off_t cur_pos;		/* transfer position as of last refresh */
60static volatile off_t *counter;	/* progress counter */
61static long stalled;		/* how long we have been stalled */
62static int bytes_per_second;	/* current speed in bytes per second */
63static int win_size;		/* terminal window size */
64static volatile sig_atomic_t win_resized; /* for window resizing */
65
66/* units for format_size */
67static const char unit[] = " KMGT";
68
69static int
70can_output(void)
71{
72	return (getpgrp() == tcgetpgrp(STDOUT_FILENO));
73}
74
75static void
76format_rate(char *buf, int size, off_t bytes)
77{
78	int i;
79
80	bytes *= 100;
81	for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
82		bytes = (bytes + 512) / 1024;
83	if (i == 0) {
84		i++;
85		bytes = (bytes + 512) / 1024;
86	}
87	snprintf(buf, size, "%3lld.%1lld%c%s",
88	    (long long) (bytes + 5) / 100,
89	    (long long) (bytes + 5) / 10 % 10,
90	    unit[i],
91	    i ? "B" : " ");
92}
93
94static void
95format_size(char *buf, int size, off_t bytes)
96{
97	int i;
98
99	for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
100		bytes = (bytes + 512) / 1024;
101	snprintf(buf, size, "%4lld%c%s",
102	    (long long) bytes,
103	    unit[i],
104	    i ? "B" : " ");
105}
106
107void
108refresh_progress_meter(void)
109{
110	char buf[MAX_WINSIZE + 1];
111	time_t now;
112	off_t transferred;
113	double elapsed;
114	int percent;
115	off_t bytes_left;
116	int cur_speed;
117	int hours, minutes, seconds;
118	int i, len;
119	int file_len;
120
121	transferred = *counter - cur_pos;
122	cur_pos = *counter;
123	now = time(NULL);
124	bytes_left = end_pos - cur_pos;
125
126	if (bytes_left > 0)
127		elapsed = now - last_update;
128	else {
129		elapsed = now - start;
130		/* Calculate true total speed when done */
131		transferred = end_pos;
132		bytes_per_second = 0;
133	}
134
135	/* calculate speed */
136	if (elapsed != 0)
137		cur_speed = (transferred / elapsed);
138	else
139		cur_speed = transferred;
140
141#define AGE_FACTOR 0.9
142	if (bytes_per_second != 0) {
143		bytes_per_second = (bytes_per_second * AGE_FACTOR) +
144		    (cur_speed * (1.0 - AGE_FACTOR));
145	} else
146		bytes_per_second = cur_speed;
147
148	/* filename */
149	buf[0] = '\0';
150	file_len = win_size - 35;
151	if (file_len > 0) {
152		len = snprintf(buf, file_len + 1, "\r%s", file);
153		if (len < 0)
154			len = 0;
155		if (len >= file_len + 1)
156			len = file_len;
157		for (i = len;  i < file_len; i++ )
158			buf[i] = ' ';
159		buf[file_len] = '\0';
160	}
161
162	/* percent of transfer done */
163	if (end_pos != 0)
164		percent = ((float)cur_pos / end_pos) * 100;
165	else
166		percent = 100;
167	snprintf(buf + strlen(buf), win_size - strlen(buf),
168	    " %3d%% ", percent);
169
170	/* amount transferred */
171	format_size(buf + strlen(buf), win_size - strlen(buf),
172	    cur_pos);
173	strlcat(buf, " ", win_size);
174
175	/* bandwidth usage */
176	format_rate(buf + strlen(buf), win_size - strlen(buf),
177	    (off_t)bytes_per_second);
178	strlcat(buf, "/s ", win_size);
179
180	/* ETA */
181	if (!transferred)
182		stalled += elapsed;
183	else
184		stalled = 0;
185
186	if (stalled >= STALL_TIME)
187		strlcat(buf, "- stalled -", win_size);
188	else if (bytes_per_second == 0 && bytes_left)
189		strlcat(buf, "  --:-- ETA", win_size);
190	else {
191		if (bytes_left > 0)
192			seconds = bytes_left / bytes_per_second;
193		else
194			seconds = elapsed;
195
196		hours = seconds / 3600;
197		seconds -= hours * 3600;
198		minutes = seconds / 60;
199		seconds -= minutes * 60;
200
201		if (hours != 0)
202			snprintf(buf + strlen(buf), win_size - strlen(buf),
203			    "%d:%02d:%02d", hours, minutes, seconds);
204		else
205			snprintf(buf + strlen(buf), win_size - strlen(buf),
206			    "  %02d:%02d", minutes, seconds);
207
208		if (bytes_left > 0)
209			strlcat(buf, " ETA", win_size);
210		else
211			strlcat(buf, "    ", win_size);
212	}
213
214	atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1);
215	last_update = now;
216}
217
218static void
219update_progress_meter(int ignore)
220{
221	int save_errno;
222
223	save_errno = errno;
224
225	if (win_resized) {
226		setscreensize();
227		win_resized = 0;
228	}
229	if (can_output())
230		refresh_progress_meter();
231
232	signal(SIGALRM, update_progress_meter);
233	alarm(UPDATE_INTERVAL);
234	errno = save_errno;
235}
236
237void
238start_progress_meter(char *f, off_t filesize, off_t *ctr)
239{
240	start = last_update = time(NULL);
241	file = f;
242	end_pos = filesize;
243	cur_pos = 0;
244	counter = ctr;
245	stalled = 0;
246	bytes_per_second = 0;
247
248	setscreensize();
249	if (can_output())
250		refresh_progress_meter();
251
252	signal(SIGALRM, update_progress_meter);
253	signal(SIGWINCH, sig_winch);
254	alarm(UPDATE_INTERVAL);
255}
256
257void
258stop_progress_meter(void)
259{
260	alarm(0);
261
262	if (!can_output())
263		return;
264
265	/* Ensure we complete the progress */
266	if (cur_pos != end_pos)
267		refresh_progress_meter();
268
269	atomicio(vwrite, STDOUT_FILENO, "\n", 1);
270}
271
272static void
273sig_winch(int sig)
274{
275	win_resized = 1;
276}
277
278static void
279setscreensize(void)
280{
281	struct winsize winsize;
282
283	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
284	    winsize.ws_col != 0) {
285		if (winsize.ws_col > MAX_WINSIZE)
286			win_size = MAX_WINSIZE;
287		else
288			win_size = winsize.ws_col;
289	} else
290		win_size = DEFAULT_WINSIZE;
291	win_size += 1;					/* trailing \0 */
292}
293