-
Notifications
You must be signed in to change notification settings - Fork 82
/
progress.c
364 lines (308 loc) · 8.54 KB
/
progress.c
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
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*
* progress.c
*
* This program 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.
*
* 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.
*
*/
#include <sys/ioctl.h>
#include <stdatomic.h>
#include "debug.h"
#include "opt.h"
#include "util.h"
#include "progress.h"
/*
* This implements a multi progressbar
* To do this, it reserves n + 3 lines from the bottom
* of the screen and save the position
* Those lines will be used in the following way:
* one line per thread (up to n lines), and then 3 lines for the
* totals:
* ### thread 1 progress
* ### thread 2 progress
* ###
* ### thread n progress
* ### Total bytes
* ### Total files
* ### Listing status
*
* n is derived from the maximum number of threads, not from the actual
* number of thread. In such cases, empty lines will follow the "totals"
* block.
*
* Every time the progress thread tries to print the progress, it:
* - jump back to the saved position
* - print one line for each running thread, cleaning the existing line
* - print the totals (again, cleaning the existing line)
*
* A function pscan_printf() is provided to print data while the progress
* thread is running
* It will grab the lock and print the data before the "progress" area:
* - jump back to the saved position
* - print the data
* - reserves n + 3 lines
* - save the new position so that the data is not overwritten
*
* If stdout is not a tty, no asci code are printed, so this acts as
* an append-only progressbar.
*/
struct pscan_global pscan = {};
static GThread *printer = NULL;
bool tty;
unsigned int w_col;
/* Sums of the per-thread stats */
static uint64_t files_scanned, bytes_scanned;
/*
* Used to track the status of our search extents from blocks
*/
static uint64_t search_total, search_processed;
#define s_save_pos() if (tty) printf("\33[s");
#define s_restore_pos() if (tty) printf("\33[u");
#define s_clear() if (tty) printf("\33[J");
#define s_printf(args...) do { if (tty) printf("\33[K"); printf(args); } while (0)
#define percent(val1, val2) ((double) val1 / (double) val2 * 100)
void pscan_finish_listing(void)
{
pscan.listing_completed = true;
}
void pscan_set_progress(uint64_t added_files, uint64_t added_bytes)
{
pscan.total_files_count += added_files;
pscan.total_bytes_count += added_bytes;
}
#define BUF_LEN 10*1024
static void print_thread_progress(struct pscan_thread *tprogress)
{
char buf[BUF_LEN];
switch (tprogress->status) {
case thread_idle:
snprintf(buf, BUF_LEN, "[%u] idle", tprogress->tid);
break;
case thread_scanning:
snprintf(buf, BUF_LEN, "[%u] %-20s%s: %s/%s (%05.2f%%)",
tprogress->tid,
"checksumming:",
tprogress->file_path,
pretty_size(tprogress->file_scanned_bytes),
pretty_size(tprogress->file_total_bytes),
percent(tprogress->file_scanned_bytes, tprogress->file_total_bytes));
break;
case thread_waiting_lock:
snprintf(buf, BUF_LEN, "[%u] %-20s%s (size: %s)",
tprogress->tid,
"waiting for lock:",
tprogress->file_path,
pretty_size(tprogress->file_total_bytes));
break;
case thread_committing:
snprintf(buf, BUF_LEN, "[%u] %-20s%s (size: %s)",
tprogress->tid,
"committing:",
tprogress->file_path,
pretty_size(tprogress->file_total_bytes));
break;
}
/* Truncate the output to keep at most one line per thread */
s_printf("%.*s\n", w_col, buf);
}
static void print_total_progress(void)
{
s_printf("\tFiles scanned: %lu/%lu (%05.2f%%)\n",
files_scanned, pscan.total_files_count,
(double)files_scanned / (double)pscan.total_files_count * 100);
s_printf("\tBytes scanned: %s/%s (%05.2f%%)\n",
pretty_size(bytes_scanned),
pretty_size(pscan.total_bytes_count),
(double)bytes_scanned / (double)pscan.total_bytes_count * 100);
s_printf("\tFile listing: %s\n",
pscan.listing_completed ? "completed" : "in progress");
}
static void prepare_screen_area(void)
{
/*
* Prepare one empty line for each scan threads
* plus one line for the total.
* This is required to bypass the scrolling and let us
* save/restore the cursor position.
*/
for (unsigned int i = 0; i < options.io_threads + 3; i++)
s_printf("\n");
/* Go back to the first line */
printf("\33[%iA", options.io_threads + 3);
/*
* Save the cursor position.
* We will restore it every time we print progress.
*/
s_save_pos()
}
static void *print_progress(void)
{
files_scanned = 0;
bytes_scanned = 0;
s_restore_pos();
for (unsigned int i = 0; i < pscan.thread_count; i++) {
print_thread_progress(pscan.threads[i]);
files_scanned += pscan.threads[i]->total_scanned_files;
bytes_scanned += pscan.threads[i]->total_scanned_bytes;
}
print_total_progress();
return NULL;
}
static void *pscan_progress_thread(void *)
{
struct winsize w;
do {
/* Refresh the tty properties */
if (tty) {
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
w_col = w.ws_col;
} else {
w_col = UINT_MAX;
}
g_mutex_lock(&pscan.mutex);
print_progress();
g_mutex_unlock(&pscan.mutex);
/* Do not waste too much cpu */
usleep(1000 * (tty ? 100 : 1000));
} while (!pscan.listing_completed
|| files_scanned != pscan.total_files_count
|| bytes_scanned != pscan.total_bytes_count);
return NULL;
}
struct pscan_thread *pscan_register_thread(pid_t tid)
{
struct pscan_thread *tprogress = calloc(1, sizeof(struct pscan_thread));
tprogress->tid = tid;
g_mutex_lock(&pscan.mutex);
pscan.threads = realloc(pscan.threads, (pscan.thread_count + 1) *
sizeof(struct pscan_thread *));
pscan.threads[pscan.thread_count] = tprogress;
pscan.thread_count++;
g_mutex_unlock(&pscan.mutex);
return tprogress;
}
void pscan_run()
{
tty = isatty(STDOUT_FILENO);
if (tty) {
/* hide the cursor */
printf("\33[?25l");
prepare_screen_area();
}
/* Will abort on failure */
printer = g_thread_new("progress_printer", pscan_progress_thread, NULL);
}
void pscan_join()
{
g_thread_join(printer);
/* Show the cursor again */
printf("\33[?25h");
/* Clear the screen from all thread-progress */
s_restore_pos();
s_clear();
s_restore_pos();
print_total_progress();
for (unsigned int i = 0; i < pscan.thread_count; i++)
free(pscan.threads[i]);
printer = NULL;
}
void pscan_reset_thread(struct pscan_thread **progress)
{
if (!progress || !*progress)
return;
uint64_t scanned = (*progress)->file_scanned_bytes;
uint64_t total = (*progress)->file_total_bytes;
(*progress)->status = thread_idle;
/*
* The file may have shrinked between the statx and
* the end of the scan.
* Does not matter much, we fake-fill the missing bytes
* so the global progress don't diverge much
*/
if (scanned < total)
(*progress)->total_scanned_bytes += total - scanned;
/*
* Also, the file may have grow.
*/
if (scanned > total)
(*progress)->total_scanned_bytes -= scanned - total;
(*progress)->total_scanned_files++;
(*progress)->file_path[0] = '\0';
}
bool is_progress_printer_running()
{
return printer ? true : false;
}
void pscan_printf(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
g_mutex_lock(&pscan.mutex);
if (tty) {
s_restore_pos();
s_clear();
s_restore_pos();
}
vfprintf(stdout, fmt, args);
if (tty) {
s_save_pos();
prepare_screen_area();
}
va_end(args);
/*
* We reprint the progress immediately to reduce
* the time during which the screen is left empty:
* between the prepare_screen_area() and the progress_thread()'s
* next iteration.
*/
print_progress();
g_mutex_unlock(&pscan.mutex);
}
static void *psearch_progress_thread(void *)
{
static int last_pos = -1;
do {
int pos;
int width = 40;
pos = (float) search_processed / search_total * width;
/* Only update our status every width% */
if (pos > last_pos) {
last_pos = pos;
printf("\r[");
for(int i = 0; i < width; i++) {
if (i < pos)
printf("#");
else if (i == pos)
printf("%%");
else
printf(" ");
}
printf("]");
}
/* Do not waste too much cpu */
usleep(100);
} while (search_total != search_processed);
printf("\n");
return NULL;
}
void psearch_run(uint64_t num_filerecs)
{
search_processed = 0;
search_total = num_filerecs;
printer = g_thread_new("progress_printer", psearch_progress_thread, NULL);
}
void psearch_join(void)
{
g_thread_join(printer);
printer = NULL;
}
void psearch_update_processed_count(unsigned int processed)
{
atomic_fetch_add(&search_processed, processed);
}