progress.c 6.09 KB
Newer Older
1 2 3
/*
 * Simple text-based progress display module for GIT
 *
4
 * Copyright (c) 2007 by Nicolas Pitre <nico@fluxnic.net>
5 6 7 8 9 10
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

11
#include "git-compat-util.h"
12
#include "gettext.h"
13
#include "progress.h"
14
#include "strbuf.h"
15

16 17 18
#define TP_IDX_MAX      8

struct throughput {
19
	off_t curr_total;
20
	off_t prev_total;
21
	struct timeval prev_tv;
22
	unsigned int avg_bytes;
23
	unsigned int avg_misecs;
24
	unsigned int last_bytes[TP_IDX_MAX];
25 26
	unsigned int last_misecs[TP_IDX_MAX];
	unsigned int idx;
27
	char display[32];
28 29
};

30 31 32 33 34 35 36
struct progress {
	const char *title;
	int last_value;
	unsigned total;
	unsigned last_percent;
	unsigned delay;
	unsigned delayed_percent_treshold;
37
	struct throughput *throughput;
38 39
};

40 41 42 43 44 45 46 47 48 49 50 51
static volatile sig_atomic_t progress_update;

static void progress_interval(int signum)
{
	progress_update = 1;
}

static void set_progress_signal(void)
{
	struct sigaction sa;
	struct itimerval v;

52 53
	progress_update = 0;

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = progress_interval;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	sigaction(SIGALRM, &sa, NULL);

	v.it_interval.tv_sec = 1;
	v.it_interval.tv_usec = 0;
	v.it_value = v.it_interval;
	setitimer(ITIMER_REAL, &v, NULL);
}

static void clear_progress_signal(void)
{
	struct itimerval v = {{0,},};
	setitimer(ITIMER_REAL, &v, NULL);
	signal(SIGALRM, SIG_IGN);
	progress_update = 0;
}

74
static int display(struct progress *progress, unsigned n, const char *done)
75
{
76
	const char *eol, *tp;
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91
	if (progress->delay) {
		if (!progress_update || --progress->delay)
			return 0;
		if (progress->total) {
			unsigned percent = n * 100 / progress->total;
			if (percent > progress->delayed_percent_treshold) {
				/* inhibit this progress report entirely */
				clear_progress_signal();
				progress->delay = -1;
				progress->total = 0;
				return 0;
			}
		}
	}
92 93

	progress->last_value = n;
94
	tp = (progress->throughput) ? progress->throughput->display : "";
95
	eol = done ? done : "   \r";
96 97 98 99
	if (progress->total) {
		unsigned percent = n * 100 / progress->total;
		if (percent != progress->last_percent || progress_update) {
			progress->last_percent = percent;
100 101 102
			fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
				progress->title, percent, n,
				progress->total, tp, eol);
103
			fflush(stderr);
104 105 106 107
			progress_update = 0;
			return 1;
		}
	} else if (progress_update) {
108
		fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol);
109
		fflush(stderr);
110 111 112
		progress_update = 0;
		return 1;
	}
113

114 115 116
	return 0;
}

117
static void throughput_string(struct strbuf *buf, off_t total,
118 119
			      unsigned int rate)
{
120 121 122 123 124
	strbuf_addstr(buf, ", ");
	strbuf_humanise_bytes(buf, total);
	strbuf_addstr(buf, " | ");
	strbuf_humanise_bytes(buf, rate * 1024);
	strbuf_addstr(buf, "/s");
125 126
}

127
void display_throughput(struct progress *progress, off_t total)
128 129 130 131 132 133 134 135 136 137 138 139 140
{
	struct throughput *tp;
	struct timeval tv;
	unsigned int misecs;

	if (!progress)
		return;
	tp = progress->throughput;

	gettimeofday(&tv, NULL);

	if (!tp) {
		progress->throughput = tp = calloc(1, sizeof(*tp));
141
		if (tp) {
142
			tp->prev_total = tp->curr_total = total;
143
			tp->prev_tv = tv;
144
		}
145 146
		return;
	}
147
	tp->curr_total = total;
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

	/*
	 * We have x = bytes and y = microsecs.  We want z = KiB/s:
	 *
	 *	z = (x / 1024) / (y / 1000000)
	 *	z = x / y * 1000000 / 1024
	 *	z = x / (y * 1024 / 1000000)
	 *	z = x / y'
	 *
	 * To simplify things we'll keep track of misecs, or 1024th of a sec
	 * obtained with:
	 *
	 *	y' = y * 1024 / 1000000
	 *	y' = y / (1000000 / 1024)
	 *	y' = y / 977
	 */
	misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024;
	misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977;

	if (misecs > 512) {
168
		struct strbuf buf = STRBUF_INIT;
169 170 171
		unsigned int count, rate;

		count = total - tp->prev_total;
172
		tp->prev_total = total;
173
		tp->prev_tv = tv;
174
		tp->avg_bytes += count;
175
		tp->avg_misecs += misecs;
176
		rate = tp->avg_bytes / tp->avg_misecs;
177 178
		tp->avg_bytes -= tp->last_bytes[tp->idx];
		tp->avg_misecs -= tp->last_misecs[tp->idx];
179
		tp->last_bytes[tp->idx] = count;
180 181
		tp->last_misecs[tp->idx] = misecs;
		tp->idx = (tp->idx + 1) % TP_IDX_MAX;
182

183 184 185
		throughput_string(&buf, total, rate);
		strncpy(tp->display, buf.buf, sizeof(tp->display));
		strbuf_release(&buf);
186
		if (progress->last_value != -1 && progress_update)
187
			display(progress, progress->last_value, NULL);
188 189 190
	}
}

191
int display_progress(struct progress *progress, unsigned n)
192
{
193
	return progress ? display(progress, n, NULL) : 0;
194 195
}

196 197
struct progress *start_progress_delay(const char *title, unsigned total,
				       unsigned percent_treshold, unsigned delay)
198
{
199 200 201 202
	struct progress *progress = malloc(sizeof(*progress));
	if (!progress) {
		/* unlikely, but here's a good fallback */
		fprintf(stderr, "%s...\n", title);
203
		fflush(stderr);
204 205
		return NULL;
	}
206
	progress->title = title;
207
	progress->total = total;
208
	progress->last_value = -1;
209 210 211
	progress->last_percent = -1;
	progress->delayed_percent_treshold = percent_treshold;
	progress->delay = delay;
212
	progress->throughput = NULL;
213
	set_progress_signal();
214
	return progress;
215 216
}

217
struct progress *start_progress(const char *title, unsigned total)
218
{
219
	return start_progress_delay(title, total, 0, 0);
220 221
}

222
void stop_progress(struct progress **p_progress)
223
{
224
	stop_progress_msg(p_progress, _("done"));
225 226 227
}

void stop_progress_msg(struct progress **p_progress, const char *msg)
228
{
229 230 231 232
	struct progress *progress = *p_progress;
	if (!progress)
		return;
	*p_progress = NULL;
233 234
	if (progress->last_value != -1) {
		/* Force the last update */
235 236
		char buf[128], *bufp;
		size_t len = strlen(msg) + 5;
237
		struct throughput *tp = progress->throughput;
238 239

		bufp = (len < sizeof(buf)) ? buf : xmalloc(len + 1);
240
		if (tp) {
241
			struct strbuf strbuf = STRBUF_INIT;
242 243
			unsigned int rate = !tp->avg_misecs ? 0 :
					tp->avg_bytes / tp->avg_misecs;
244 245 246
			throughput_string(&strbuf, tp->curr_total, rate);
			strncpy(tp->display, strbuf.buf, sizeof(tp->display));
			strbuf_release(&strbuf);
247
		}
248
		progress_update = 1;
249 250 251 252
		sprintf(bufp, ", %s.\n", msg);
		display(progress, progress->last_value, bufp);
		if (buf != bufp)
			free(bufp);
253
	}
254
	clear_progress_signal();
255
	free(progress->throughput);
256
	free(progress);
257
}