Git fork
at 3da4413dbcdb8df021739ca6f20fe4f0bcd1fd3c 383 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 last_count_len = counters_sb->len; 118 119 if (progress->delay && (!progress_update || --progress->delay)) 120 return; 121 122 progress->last_value = n; 123 tp = (progress->throughput) ? progress->throughput->display.buf : ""; 124 if (progress->total) { 125 unsigned percent = n * 100 / progress->total; 126 if (percent != progress->last_percent || progress_update) { 127 progress->last_percent = percent; 128 129 strbuf_reset(counters_sb); 130 strbuf_addf(counters_sb, 131 "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent, 132 (uintmax_t)n, (uintmax_t)progress->total, 133 tp); 134 show_update = 1; 135 } 136 } else if (progress_update) { 137 strbuf_reset(counters_sb); 138 strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp); 139 show_update = 1; 140 } 141 142 if (show_update) { 143 if (is_foreground_fd(fileno(stderr)) || done) { 144 const char *eol = done ? done : "\r"; 145 size_t clear_len = counters_sb->len < last_count_len ? 146 last_count_len - counters_sb->len + 1 : 147 0; 148 /* The "+ 2" accounts for the ": ". */ 149 size_t progress_line_len = progress->title_len + 150 counters_sb->len + 2; 151 int cols = term_columns(); 152 153 if (progress->split) { 154 fprintf(stderr, " %s%*s", counters_sb->buf, 155 (int) clear_len, eol); 156 } else if (!done && cols < progress_line_len) { 157 clear_len = progress->title_len + 1 < cols ? 158 cols - progress->title_len - 1 : 0; 159 fprintf(stderr, "%s:%*s\n %s%s", 160 progress->title, (int) clear_len, "", 161 counters_sb->buf, eol); 162 progress->split = 1; 163 } else { 164 fprintf(stderr, "%s: %s%*s", progress->title, 165 counters_sb->buf, (int) clear_len, eol); 166 } 167 fflush(stderr); 168 } 169 progress_update = 0; 170 } 171} 172 173static void throughput_string(struct strbuf *buf, uint64_t total, 174 unsigned int rate) 175{ 176 strbuf_reset(buf); 177 strbuf_addstr(buf, ", "); 178 strbuf_humanise_bytes(buf, total); 179 strbuf_addstr(buf, " | "); 180 strbuf_humanise_rate(buf, rate * 1024); 181} 182 183static uint64_t progress_getnanotime(struct progress *progress) 184{ 185 if (progress_testing) 186 return progress->start_ns + progress_test_ns; 187 else 188 return getnanotime(); 189} 190 191void display_throughput(struct progress *progress, uint64_t total) 192{ 193 struct throughput *tp; 194 uint64_t now_ns; 195 unsigned int misecs, count, rate; 196 197 if (!progress) 198 return; 199 tp = progress->throughput; 200 201 now_ns = progress_getnanotime(progress); 202 203 if (!tp) { 204 progress->throughput = CALLOC_ARRAY(tp, 1); 205 tp->prev_total = tp->curr_total = total; 206 tp->prev_ns = now_ns; 207 strbuf_init(&tp->display, 0); 208 return; 209 } 210 tp->curr_total = total; 211 212 /* only update throughput every 0.5 s */ 213 if (now_ns - tp->prev_ns <= 500000000) 214 return; 215 216 /* 217 * We have x = bytes and y = nanosecs. We want z = KiB/s: 218 * 219 * z = (x / 1024) / (y / 1000000000) 220 * z = x / y * 1000000000 / 1024 221 * z = x / (y * 1024 / 1000000000) 222 * z = x / y' 223 * 224 * To simplify things we'll keep track of misecs, or 1024th of a sec 225 * obtained with: 226 * 227 * y' = y * 1024 / 1000000000 228 * y' = y * (2^10 / 2^42) * (2^42 / 1000000000) 229 * y' = y / 2^32 * 4398 230 * y' = (y * 4398) >> 32 231 */ 232 misecs = ((now_ns - tp->prev_ns) * 4398) >> 32; 233 234 count = total - tp->prev_total; 235 tp->prev_total = total; 236 tp->prev_ns = now_ns; 237 tp->avg_bytes += count; 238 tp->avg_misecs += misecs; 239 rate = tp->avg_bytes / tp->avg_misecs; 240 tp->avg_bytes -= tp->last_bytes[tp->idx]; 241 tp->avg_misecs -= tp->last_misecs[tp->idx]; 242 tp->last_bytes[tp->idx] = count; 243 tp->last_misecs[tp->idx] = misecs; 244 tp->idx = (tp->idx + 1) % TP_IDX_MAX; 245 246 throughput_string(&tp->display, total, rate); 247 if (progress->last_value != -1 && progress_update) 248 display(progress, progress->last_value, NULL); 249} 250 251void display_progress(struct progress *progress, uint64_t n) 252{ 253 if (progress) 254 display(progress, n, NULL); 255} 256 257static struct progress *start_progress_delay(struct repository *r, 258 const char *title, uint64_t total, 259 unsigned delay, unsigned sparse) 260{ 261 struct progress *progress = xmalloc(sizeof(*progress)); 262 progress->repo = r; 263 progress->title = title; 264 progress->total = total; 265 progress->last_value = -1; 266 progress->last_percent = -1; 267 progress->delay = delay; 268 progress->sparse = sparse; 269 progress->throughput = NULL; 270 progress->start_ns = getnanotime(); 271 strbuf_init(&progress->counters_sb, 0); 272 progress->title_len = utf8_strwidth(title); 273 progress->split = 0; 274 set_progress_signal(); 275 trace2_region_enter("progress", title, r); 276 return progress; 277} 278 279static int get_default_delay(void) 280{ 281 static int delay_in_secs = -1; 282 283 if (delay_in_secs < 0) 284 delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2); 285 286 return delay_in_secs; 287} 288 289struct progress *start_delayed_progress(struct repository *r, 290 const char *title, uint64_t total) 291{ 292 return start_progress_delay(r, title, total, get_default_delay(), 0); 293} 294 295struct progress *start_progress(struct repository *r, 296 const char *title, uint64_t total) 297{ 298 return start_progress_delay(r, title, total, 0, 0); 299} 300 301/* 302 * Here "sparse" means that the caller might use some sampling criteria to 303 * decide when to call display_progress() rather than calling it for every 304 * integer value in[0 .. total). In particular, the caller might not call 305 * display_progress() for the last value in the range. 306 * 307 * When "sparse" is set, stop_progress() will automatically force the done 308 * message to show 100%. 309 */ 310struct progress *start_sparse_progress(struct repository *r, 311 const char *title, uint64_t total) 312{ 313 return start_progress_delay(r, title, total, 0, 1); 314} 315 316struct progress *start_delayed_sparse_progress(struct repository *r, 317 const char *title, 318 uint64_t total) 319{ 320 return start_progress_delay(r, title, total, get_default_delay(), 1); 321} 322 323static void finish_if_sparse(struct progress *progress) 324{ 325 if (progress->sparse && 326 progress->last_value != progress->total) 327 display_progress(progress, progress->total); 328} 329 330static void force_last_update(struct progress *progress, const char *msg) 331{ 332 char *buf; 333 struct throughput *tp = progress->throughput; 334 335 if (tp) { 336 uint64_t now_ns = progress_getnanotime(progress); 337 unsigned int misecs, rate; 338 misecs = ((now_ns - progress->start_ns) * 4398) >> 32; 339 rate = tp->curr_total / (misecs ? misecs : 1); 340 throughput_string(&tp->display, tp->curr_total, rate); 341 } 342 progress_update = 1; 343 buf = xstrfmt(", %s.\n", msg); 344 display(progress, progress->last_value, buf); 345 free(buf); 346} 347 348static void log_trace2(struct progress *progress) 349{ 350 trace2_data_intmax("progress", progress->repo, "total_objects", 351 progress->total); 352 353 if (progress->throughput) 354 trace2_data_intmax("progress", progress->repo, "total_bytes", 355 progress->throughput->curr_total); 356 357 trace2_region_leave("progress", progress->title, progress->repo); 358} 359 360void stop_progress_msg(struct progress **p_progress, const char *msg) 361{ 362 struct progress *progress; 363 364 if (!p_progress) 365 BUG("don't provide NULL to stop_progress_msg"); 366 367 progress = *p_progress; 368 if (!progress) 369 return; 370 *p_progress = NULL; 371 372 finish_if_sparse(progress); 373 if (progress->last_value != -1) 374 force_last_update(progress, msg); 375 log_trace2(progress); 376 377 clear_progress_signal(); 378 strbuf_release(&progress->counters_sb); 379 if (progress->throughput) 380 strbuf_release(&progress->throughput->display); 381 free(progress->throughput); 382 free(progress); 383}