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