progress.c 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
 * Simple text-based progress display module for GIT
 *
 * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org>
 *
 * 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 12 13
#include "git-compat-util.h"
#include "progress.h"

14 15 16 17 18 19 20 21 22 23 24 25 26
#define TP_IDX_MAX      8

struct throughput {
	struct timeval prev_tv;
	unsigned long count;
	unsigned long avg_bytes;
	unsigned long last_bytes[TP_IDX_MAX];
	unsigned int avg_misecs;
	unsigned int last_misecs[TP_IDX_MAX];
	unsigned int idx;
	char display[20];
};

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

37 38 39 40 41 42 43 44 45 46 47 48
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;

49 50
	progress_update = 0;

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
	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;
}

71
static int display(struct progress *progress, unsigned n, int done)
72
{
73
	char *eol, *tp;
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88
	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;
			}
		}
	}
89 90

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

109 110 111
	return 0;
}

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
void display_throughput(struct progress *progress, unsigned long n)
{
	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));
		if (tp)
			tp->prev_tv = tv;
		return;
	}

	tp->count += n;

	/*
	 * 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) {
		tp->prev_tv = tv;
		tp->avg_bytes += tp->count;
		tp->avg_misecs += misecs;
		snprintf(tp->display, sizeof(tp->display),
			 ", %lu KiB/s", tp->avg_bytes / tp->avg_misecs);
		tp->avg_bytes -= tp->last_bytes[tp->idx];
		tp->avg_misecs -= tp->last_misecs[tp->idx];
		tp->last_bytes[tp->idx] = tp->count;
		tp->last_misecs[tp->idx] = misecs;
		tp->idx = (tp->idx + 1) % TP_IDX_MAX;
		tp->count = 0;
	}
}

166
int display_progress(struct progress *progress, unsigned n)
167
{
168
	return progress ? display(progress, n, 0) : 0;
169 170
}

171 172
struct progress *start_progress_delay(const char *title, unsigned total,
				       unsigned percent_treshold, unsigned delay)
173
{
174 175 176 177 178 179
	struct progress *progress = malloc(sizeof(*progress));
	if (!progress) {
		/* unlikely, but here's a good fallback */
		fprintf(stderr, "%s...\n", title);
		return NULL;
	}
180
	progress->title = title;
181
	progress->total = total;
182
	progress->last_value = -1;
183 184 185
	progress->last_percent = -1;
	progress->delayed_percent_treshold = percent_treshold;
	progress->delay = delay;
186
	progress->throughput = NULL;
187
	set_progress_signal();
188
	return progress;
189 190
}

191
struct progress *start_progress(const char *title, unsigned total)
192
{
193
	return start_progress_delay(title, total, 0, 0);
194 195
}

196
void stop_progress(struct progress **p_progress)
197
{
198 199 200 201
	struct progress *progress = *p_progress;
	if (!progress)
		return;
	*p_progress = NULL;
202 203 204 205 206
	if (progress->last_value != -1) {
		/* Force the last update */
		progress_update = 1;
		display(progress, progress->last_value, 1);
	}
207
	clear_progress_signal();
208
	free(progress->throughput);
209
	free(progress);
210
}