progress.c 4.55 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
 * Copyright (C) 2015  Bernhard Nortmann <bernhard.nortmann@web.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "progress.h"

#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

/* Less reliable than clock_gettime, but does not require linking with -lrt */
inline double gettime(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec + (double)tv.tv_usec / 1000000.;
}

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/* Calculate transfer rate (in bytes per second) */
inline double rate(size_t transferred, double elapsed)
{
	if (elapsed > 0)
		return (double)transferred / elapsed;
	return 0.;
}

/* Estimate remaining time ("ETA") for given transfer rate */
inline double estimate(size_t remaining, double rate)
{
	if (rate > 0)
		return (double)remaining / rate;
	return 0.;
}

/* Return ETA (in seconds) as string, formatted to minutes and seconds */
const char *format_ETA(double remaining)
{
	static char result[6] = "";

	int seconds = remaining + 0.5; /* simplistic round() */
	if (seconds >= 0 && seconds < 6000) {
		snprintf(result, sizeof(result),
			 "%02d:%02d", seconds / 60, seconds % 60);
		return result;
	}
	return "--:--";
}

61
62
63
64
65
66
/* Private progress state variable */

typedef struct {
	progress_cb_t callback;
	size_t total;
	size_t done;
67
	double start; /* start point (timestamp) for rate and ETA calculation */
68
69
70
71
} progress_private_t;

static progress_private_t progress = {
	.callback = NULL,
72
	.start = 0.
73
74
75
76
77
78
79
80
81
};

/* 'External' API */

void progress_start(progress_cb_t callback, size_t expected_total)
{
	progress.callback = callback;
	progress.total = expected_total;
	progress.done = 0;
82
	progress.start = gettime(); /* reset start time */
83
84
}

85
/* Update progress status, passing information to the callback function. */
86
87
88
89
90
91
92
void progress_update(size_t bytes_done)
{
	progress.done += bytes_done;
	if (progress.callback)
		progress.callback(progress.total, progress.done);
}

93
94
95
96
97
98
99
100
/* Return relative / "elapsed" time, since progress_start() */
static inline double progress_elapsed(void)
{
	if (progress.start != 0.)
		return gettime() - progress.start;
	return 0.;
}

101
102
/* Callback function implementing a simple progress bar written to stdout */
void progress_bar(size_t total, size_t done)
103
{
104
	static const int WIDTH = 48; /* # of characters to use for progress bar */
105
106
107

	float ratio = total > 0 ? (float)done / total : 0;
	int i, pos = WIDTH * ratio;
108
109
	double speed = rate(done, progress_elapsed());
	double eta = estimate(total - done, speed);
110
111
112
113

	printf("\r%3.0f%% [", ratio * 100); /* current percentage */
	for (i = 0; i < pos; i++) putchar('=');
	for (i = pos; i < WIDTH; i++) putchar(' ');
114
115
116
117
118
	if (done < total)
		printf("]%6.1f kB/s, ETA %s ", kilo(speed), format_ETA(eta));
	else
		/* transfer complete, output totals plus a newline */
		printf("] %5.0f kB, %6.1f kB/s\n", kilo(done), kilo(speed));
119
120

	fflush(stdout);
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

/*
 * Progress callback that emits percentage numbers, each on a separate line.
 * The output is suitable for piping it into "dialog --gauge".
 *
 * sunxi-fel multiwrite-with-gauge <...> \
 *	| dialog --title "FEL upload progress" \
 *		 --gauge "" 5 70
 */
void progress_gauge(size_t total, size_t done)
{
	if (total > 0) {
		printf("%.0f\n", (float)done / total * 100);
		fflush(stdout);
	}
}

/*
 * A more sophisticated version of progress_gauge() that also updates the
 * prompt (caption) with additional information. This uses a feature of
 * the dialog utility that parses "XXX" delimiters - see 'man dialog'.
 *
 * sunxi-fel multiwrite-with-xgauge <...> \
 *	| dialog --title "FEL upload progress" \
 *		 --backtitle "Please wait..." \
 *		 --gauge "" 6 70
 */
void progress_gauge_xxx(size_t total, size_t done)
{
	if (total > 0) {
		double speed = rate(done, progress_elapsed());
		double eta = estimate(total - done, speed);
		printf("XXX\n");
		printf("%.0f\n", (float)done / total * 100);
		if (done < total)
			printf("%zu of %zu, %.1f kB/s, ETA %s\n",
				done, total, kilo(speed), format_ETA(eta));
		else
			printf("Done: %.1f kB, at %.1f kB/s\n",
				kilo(done), kilo(speed));
		printf("XXX\n");
		fflush(stdout);
	}
}