Git fork
at reftables-rust 385 lines 9.6 kB view raw
1/* 2 * Simple text-based progress display module for GIT 3 * 4 * Copyright (c) 2007 by Nicolas Pitre <nico@fluxnic.net> 5 * 6 * This code is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 11#define GIT_TEST_PROGRESS_ONLY 12#define DISABLE_SIGN_COMPARE_WARNINGS 13 14#include "git-compat-util.h" 15#include "pager.h" 16#include "progress.h" 17#include "repository.h" 18#include "strbuf.h" 19#include "trace.h" 20#include "trace2.h" 21#include "utf8.h" 22#include "parse.h" 23 24#define TP_IDX_MAX 8 25 26struct throughput { 27 off_t curr_total; 28 off_t prev_total; 29 uint64_t prev_ns; 30 unsigned int avg_bytes; 31 unsigned int avg_misecs; 32 unsigned int last_bytes[TP_IDX_MAX]; 33 unsigned int last_misecs[TP_IDX_MAX]; 34 unsigned int idx; 35 struct strbuf display; 36}; 37 38struct progress { 39 struct repository *repo; 40 const char *title; 41 uint64_t last_value; 42 uint64_t total; 43 unsigned last_percent; 44 unsigned delay; 45 unsigned sparse; 46 struct throughput *throughput; 47 uint64_t start_ns; 48 struct strbuf counters_sb; 49 int title_len; 50 int split; 51}; 52 53static volatile sig_atomic_t progress_update; 54 55/* 56 * These are only intended for testing the progress output, i.e. exclusively 57 * for 'test-tool progress'. 58 */ 59int progress_testing; 60uint64_t progress_test_ns = 0; 61void progress_test_force_update(void) 62{ 63 progress_update = 1; 64} 65 66 67static void progress_interval(int signum UNUSED) 68{ 69 progress_update = 1; 70} 71 72static void set_progress_signal(void) 73{ 74 struct sigaction sa; 75 struct itimerval v; 76 77 if (progress_testing) 78 return; 79 80 progress_update = 0; 81 82 memset(&sa, 0, sizeof(sa)); 83 sa.sa_handler = progress_interval; 84 sigemptyset(&sa.sa_mask); 85 sa.sa_flags = SA_RESTART; 86 sigaction(SIGALRM, &sa, NULL); 87 88 v.it_interval.tv_sec = 1; 89 v.it_interval.tv_usec = 0; 90 v.it_value = v.it_interval; 91 setitimer(ITIMER_REAL, &v, NULL); 92} 93 94static void clear_progress_signal(void) 95{ 96 struct itimerval v = {{0,},}; 97 98 if (progress_testing) 99 return; 100 101 setitimer(ITIMER_REAL, &v, NULL); 102 signal(SIGALRM, SIG_IGN); 103 progress_update = 0; 104} 105 106static int is_foreground_fd(int fd) 107{ 108 int tpgrp = tcgetpgrp(fd); 109 return tpgrp < 0 || tpgrp == getpgid(0); 110} 111 112static void display(struct progress *progress, uint64_t n, const char *done) 113{ 114 const char *tp; 115 struct strbuf *counters_sb = &progress->counters_sb; 116 int show_update = 0; 117 int update = !!progress_update; 118 int last_count_len = counters_sb->len; 119 120 progress_update = 0; 121 122 if (progress->delay && (!update || --progress->delay)) 123 return; 124 125 progress->last_value = n; 126 tp = (progress->throughput) ? progress->throughput->display.buf : ""; 127 if (progress->total) { 128 unsigned percent = n * 100 / progress->total; 129 if (percent != progress->last_percent || update) { 130 progress->last_percent = percent; 131 132 strbuf_reset(counters_sb); 133 strbuf_addf(counters_sb, 134 "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent, 135 (uintmax_t)n, (uintmax_t)progress->total, 136 tp); 137 show_update = 1; 138 } 139 } else if (update) { 140 strbuf_reset(counters_sb); 141 strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp); 142 show_update = 1; 143 } 144 145 if (show_update) { 146 if (is_foreground_fd(fileno(stderr)) || done) { 147 const char *eol = done ? done : "\r"; 148 size_t clear_len = counters_sb->len < last_count_len ? 149 last_count_len - counters_sb->len + 1 : 150 0; 151 /* The "+ 2" accounts for the ": ". */ 152 size_t progress_line_len = progress->title_len + 153 counters_sb->len + 2; 154 int cols = term_columns(); 155 156 if (progress->split) { 157 fprintf(stderr, " %s%*s", counters_sb->buf, 158 (int) clear_len, eol); 159 } else if (!done && cols < progress_line_len) { 160 clear_len = progress->title_len + 1 < cols ? 161 cols - progress->title_len - 1 : 0; 162 fprintf(stderr, "%s:%*s\n %s%s", 163 progress->title, (int) clear_len, "", 164 counters_sb->buf, eol); 165 progress->split = 1; 166 } else { 167 fprintf(stderr, "%s: %s%*s", progress->title, 168 counters_sb->buf, (int) clear_len, eol); 169 } 170 fflush(stderr); 171 } 172 } 173} 174 175static void throughput_string(struct strbuf *buf, uint64_t total, 176 unsigned int rate) 177{ 178 strbuf_reset(buf); 179 strbuf_addstr(buf, ", "); 180 strbuf_humanise_bytes(buf, total); 181 strbuf_addstr(buf, " | "); 182 strbuf_humanise_rate(buf, rate * 1024); 183} 184 185static uint64_t progress_getnanotime(struct progress *progress) 186{ 187 if (progress_testing) 188 return progress->start_ns + progress_test_ns; 189 else 190 return getnanotime(); 191} 192 193void display_throughput(struct progress *progress, uint64_t total) 194{ 195 struct throughput *tp; 196 uint64_t now_ns; 197 unsigned int misecs, count, rate; 198 199 if (!progress) 200 return; 201 tp = progress->throughput; 202 203 now_ns = progress_getnanotime(progress); 204 205 if (!tp) { 206 progress->throughput = CALLOC_ARRAY(tp, 1); 207 tp->prev_total = tp->curr_total = total; 208 tp->prev_ns = now_ns; 209 strbuf_init(&tp->display, 0); 210 return; 211 } 212 tp->curr_total = total; 213 214 /* only update throughput every 0.5 s */ 215 if (now_ns - tp->prev_ns <= 500000000) 216 return; 217 218 /* 219 * We have x = bytes and y = nanosecs. We want z = KiB/s: 220 * 221 * z = (x / 1024) / (y / 1000000000) 222 * z = x / y * 1000000000 / 1024 223 * z = x / (y * 1024 / 1000000000) 224 * z = x / y' 225 * 226 * To simplify things we'll keep track of misecs, or 1024th of a sec 227 * obtained with: 228 * 229 * y' = y * 1024 / 1000000000 230 * y' = y * (2^10 / 2^42) * (2^42 / 1000000000) 231 * y' = y / 2^32 * 4398 232 * y' = (y * 4398) >> 32 233 */ 234 misecs = ((now_ns - tp->prev_ns) * 4398) >> 32; 235 236 count = total - tp->prev_total; 237 tp->prev_total = total; 238 tp->prev_ns = now_ns; 239 tp->avg_bytes += count; 240 tp->avg_misecs += misecs; 241 rate = tp->avg_bytes / tp->avg_misecs; 242 tp->avg_bytes -= tp->last_bytes[tp->idx]; 243 tp->avg_misecs -= tp->last_misecs[tp->idx]; 244 tp->last_bytes[tp->idx] = count; 245 tp->last_misecs[tp->idx] = misecs; 246 tp->idx = (tp->idx + 1) % TP_IDX_MAX; 247 248 throughput_string(&tp->display, total, rate); 249 if (progress->last_value != -1 && progress_update) 250 display(progress, progress->last_value, NULL); 251} 252 253void display_progress(struct progress *progress, uint64_t n) 254{ 255 if (progress) 256 display(progress, n, NULL); 257} 258 259static struct progress *start_progress_delay(struct repository *r, 260 const char *title, uint64_t total, 261 unsigned delay, unsigned sparse) 262{ 263 struct progress *progress = xmalloc(sizeof(*progress)); 264 progress->repo = r; 265 progress->title = title; 266 progress->total = total; 267 progress->last_value = -1; 268 progress->last_percent = -1; 269 progress->delay = delay; 270 progress->sparse = sparse; 271 progress->throughput = NULL; 272 progress->start_ns = getnanotime(); 273 strbuf_init(&progress->counters_sb, 0); 274 progress->title_len = utf8_strwidth(title); 275 progress->split = 0; 276 set_progress_signal(); 277 trace2_region_enter("progress", title, r); 278 return progress; 279} 280 281static int get_default_delay(void) 282{ 283 static int delay_in_secs = -1; 284 285 if (delay_in_secs < 0) 286 delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1); 287 288 return delay_in_secs; 289} 290 291struct progress *start_delayed_progress(struct repository *r, 292 const char *title, uint64_t total) 293{ 294 return start_progress_delay(r, title, total, get_default_delay(), 0); 295} 296 297struct progress *start_progress(struct repository *r, 298 const char *title, uint64_t total) 299{ 300 return start_progress_delay(r, title, total, 0, 0); 301} 302 303/* 304 * Here "sparse" means that the caller might use some sampling criteria to 305 * decide when to call display_progress() rather than calling it for every 306 * integer value in[0 .. total). In particular, the caller might not call 307 * display_progress() for the last value in the range. 308 * 309 * When "sparse" is set, stop_progress() will automatically force the done 310 * message to show 100%. 311 */ 312struct progress *start_sparse_progress(struct repository *r, 313 const char *title, uint64_t total) 314{ 315 return start_progress_delay(r, title, total, 0, 1); 316} 317 318struct progress *start_delayed_sparse_progress(struct repository *r, 319 const char *title, 320 uint64_t total) 321{ 322 return start_progress_delay(r, title, total, get_default_delay(), 1); 323} 324 325static void finish_if_sparse(struct progress *progress) 326{ 327 if (progress->sparse && 328 progress->last_value != progress->total) 329 display_progress(progress, progress->total); 330} 331 332static void force_last_update(struct progress *progress, const char *msg) 333{ 334 char *buf; 335 struct throughput *tp = progress->throughput; 336 337 if (tp) { 338 uint64_t now_ns = progress_getnanotime(progress); 339 unsigned int misecs, rate; 340 misecs = ((now_ns - progress->start_ns) * 4398) >> 32; 341 rate = tp->curr_total / (misecs ? misecs : 1); 342 throughput_string(&tp->display, tp->curr_total, rate); 343 } 344 progress_update = 1; 345 buf = xstrfmt(", %s.\n", msg); 346 display(progress, progress->last_value, buf); 347 free(buf); 348} 349 350static void log_trace2(struct progress *progress) 351{ 352 trace2_data_intmax("progress", progress->repo, "total_objects", 353 progress->total); 354 355 if (progress->throughput) 356 trace2_data_intmax("progress", progress->repo, "total_bytes", 357 progress->throughput->curr_total); 358 359 trace2_region_leave("progress", progress->title, progress->repo); 360} 361 362void stop_progress_msg(struct progress **p_progress, const char *msg) 363{ 364 struct progress *progress; 365 366 if (!p_progress) 367 BUG("don't provide NULL to stop_progress_msg"); 368 369 progress = *p_progress; 370 if (!progress) 371 return; 372 *p_progress = NULL; 373 374 finish_if_sparse(progress); 375 if (progress->last_value != -1) 376 force_last_update(progress, msg); 377 log_trace2(progress); 378 379 clear_progress_signal(); 380 strbuf_release(&progress->counters_sb); 381 if (progress->throughput) 382 strbuf_release(&progress->throughput->display); 383 free(progress->throughput); 384 free(progress); 385}