Coverage Report

Created: 2025-12-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/progress.c
Line
Count
Source
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
0
#define TP_IDX_MAX      8
25
26
struct 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
38
struct 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
53
static 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
 */
59
int progress_testing;
60
uint64_t progress_test_ns = 0;
61
void progress_test_force_update(void)
62
0
{
63
0
  progress_update = 1;
64
0
}
65
66
67
static void progress_interval(int signum UNUSED)
68
0
{
69
0
  progress_update = 1;
70
0
}
71
72
static void set_progress_signal(void)
73
0
{
74
0
  struct sigaction sa;
75
0
  struct itimerval v;
76
77
0
  if (progress_testing)
78
0
    return;
79
80
0
  progress_update = 0;
81
82
0
  memset(&sa, 0, sizeof(sa));
83
0
  sa.sa_handler = progress_interval;
84
0
  sigemptyset(&sa.sa_mask);
85
0
  sa.sa_flags = SA_RESTART;
86
0
  sigaction(SIGALRM, &sa, NULL);
87
88
0
  v.it_interval.tv_sec = 1;
89
0
  v.it_interval.tv_usec = 0;
90
0
  v.it_value = v.it_interval;
91
0
  setitimer(ITIMER_REAL, &v, NULL);
92
0
}
93
94
static void clear_progress_signal(void)
95
0
{
96
0
  struct itimerval v = {{0,},};
97
98
0
  if (progress_testing)
99
0
    return;
100
101
0
  setitimer(ITIMER_REAL, &v, NULL);
102
0
  signal(SIGALRM, SIG_IGN);
103
0
  progress_update = 0;
104
0
}
105
106
static int is_foreground_fd(int fd)
107
0
{
108
0
  int tpgrp = tcgetpgrp(fd);
109
0
  return tpgrp < 0 || tpgrp == getpgid(0);
110
0
}
111
112
static void display(struct progress *progress, uint64_t n, const char *done)
113
0
{
114
0
  const char *tp;
115
0
  struct strbuf *counters_sb = &progress->counters_sb;
116
0
  int show_update = 0;
117
0
  int update = !!progress_update;
118
0
  int last_count_len = counters_sb->len;
119
120
0
  progress_update = 0;
121
122
0
  if (progress->delay && (!update || --progress->delay))
123
0
    return;
124
125
0
  progress->last_value = n;
126
0
  tp = (progress->throughput) ? progress->throughput->display.buf : "";
127
0
  if (progress->total) {
128
0
    unsigned percent = n * 100 / progress->total;
129
0
    if (percent != progress->last_percent || update) {
130
0
      progress->last_percent = percent;
131
132
0
      strbuf_reset(counters_sb);
133
0
      strbuf_addf(counters_sb,
134
0
            "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent,
135
0
            (uintmax_t)n, (uintmax_t)progress->total,
136
0
            tp);
137
0
      show_update = 1;
138
0
    }
139
0
  } else if (update) {
140
0
    strbuf_reset(counters_sb);
141
0
    strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp);
142
0
    show_update = 1;
143
0
  }
144
145
0
  if (show_update) {
146
0
    if (is_foreground_fd(fileno(stderr)) || done) {
147
0
      const char *eol = done ? done : "\r";
148
0
      size_t clear_len = counters_sb->len < last_count_len ?
149
0
          last_count_len - counters_sb->len + 1 :
150
0
          0;
151
      /* The "+ 2" accounts for the ": ". */
152
0
      size_t progress_line_len = progress->title_len +
153
0
            counters_sb->len + 2;
154
0
      int cols = term_columns();
155
156
0
      if (progress->split) {
157
0
        fprintf(stderr, "  %s%*s", counters_sb->buf,
158
0
          (int) clear_len, eol);
159
0
      } else if (!done && cols < progress_line_len) {
160
0
        clear_len = progress->title_len + 1 < cols ?
161
0
              cols - progress->title_len - 1 : 0;
162
0
        fprintf(stderr, "%s:%*s\n  %s%s",
163
0
          progress->title, (int) clear_len, "",
164
0
          counters_sb->buf, eol);
165
0
        progress->split = 1;
166
0
      } else {
167
0
        fprintf(stderr, "%s: %s%*s", progress->title,
168
0
          counters_sb->buf, (int) clear_len, eol);
169
0
      }
170
0
      fflush(stderr);
171
0
    }
172
0
  }
173
0
}
174
175
static void throughput_string(struct strbuf *buf, uint64_t total,
176
            unsigned int rate)
177
0
{
178
0
  strbuf_reset(buf);
179
0
  strbuf_addstr(buf, ", ");
180
0
  strbuf_humanise_bytes(buf, total);
181
0
  strbuf_addstr(buf, " | ");
182
0
  strbuf_humanise_rate(buf, rate * 1024);
183
0
}
184
185
static uint64_t progress_getnanotime(struct progress *progress)
186
0
{
187
0
  if (progress_testing)
188
0
    return progress->start_ns + progress_test_ns;
189
0
  else
190
0
    return getnanotime();
191
0
}
192
193
void display_throughput(struct progress *progress, uint64_t total)
194
0
{
195
0
  struct throughput *tp;
196
0
  uint64_t now_ns;
197
0
  unsigned int misecs, count, rate;
198
199
0
  if (!progress)
200
0
    return;
201
0
  tp = progress->throughput;
202
203
0
  now_ns = progress_getnanotime(progress);
204
205
0
  if (!tp) {
206
0
    progress->throughput = CALLOC_ARRAY(tp, 1);
207
0
    tp->prev_total = tp->curr_total = total;
208
0
    tp->prev_ns = now_ns;
209
0
    strbuf_init(&tp->display, 0);
210
0
    return;
211
0
  }
212
0
  tp->curr_total = total;
213
214
  /* only update throughput every 0.5 s */
215
0
  if (now_ns - tp->prev_ns <= 500000000)
216
0
    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
0
  misecs = ((now_ns - tp->prev_ns) * 4398) >> 32;
235
236
0
  count = total - tp->prev_total;
237
0
  tp->prev_total = total;
238
0
  tp->prev_ns = now_ns;
239
0
  tp->avg_bytes += count;
240
0
  tp->avg_misecs += misecs;
241
0
  rate = tp->avg_bytes / tp->avg_misecs;
242
0
  tp->avg_bytes -= tp->last_bytes[tp->idx];
243
0
  tp->avg_misecs -= tp->last_misecs[tp->idx];
244
0
  tp->last_bytes[tp->idx] = count;
245
0
  tp->last_misecs[tp->idx] = misecs;
246
0
  tp->idx = (tp->idx + 1) % TP_IDX_MAX;
247
248
0
  throughput_string(&tp->display, total, rate);
249
0
  if (progress->last_value != -1 && progress_update)
250
0
    display(progress, progress->last_value, NULL);
251
0
}
252
253
void display_progress(struct progress *progress, uint64_t n)
254
0
{
255
0
  if (progress)
256
0
    display(progress, n, NULL);
257
0
}
258
259
static struct progress *start_progress_delay(struct repository *r,
260
               const char *title, uint64_t total,
261
               unsigned delay, unsigned sparse)
262
0
{
263
0
  struct progress *progress = xmalloc(sizeof(*progress));
264
0
  progress->repo = r;
265
0
  progress->title = title;
266
0
  progress->total = total;
267
0
  progress->last_value = -1;
268
0
  progress->last_percent = -1;
269
0
  progress->delay = delay;
270
0
  progress->sparse = sparse;
271
0
  progress->throughput = NULL;
272
0
  progress->start_ns = getnanotime();
273
0
  strbuf_init(&progress->counters_sb, 0);
274
0
  progress->title_len = utf8_strwidth(title);
275
0
  progress->split = 0;
276
0
  set_progress_signal();
277
0
  trace2_region_enter("progress", title, r);
278
0
  return progress;
279
0
}
280
281
static int get_default_delay(void)
282
0
{
283
0
  static int delay_in_secs = -1;
284
285
0
  if (delay_in_secs < 0)
286
0
    delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1);
287
288
0
  return delay_in_secs;
289
0
}
290
291
struct progress *start_delayed_progress(struct repository *r,
292
          const char *title, uint64_t total)
293
0
{
294
0
  return start_progress_delay(r, title, total, get_default_delay(), 0);
295
0
}
296
297
struct progress *start_progress(struct repository *r,
298
        const char *title, uint64_t total)
299
0
{
300
0
  return start_progress_delay(r, title, total, 0, 0);
301
0
}
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
 */
312
struct progress *start_sparse_progress(struct repository *r,
313
               const char *title, uint64_t total)
314
0
{
315
0
  return start_progress_delay(r, title, total, 0, 1);
316
0
}
317
318
struct progress *start_delayed_sparse_progress(struct repository *r,
319
                 const char *title,
320
                 uint64_t total)
321
0
{
322
0
  return start_progress_delay(r, title, total, get_default_delay(), 1);
323
0
}
324
325
static void finish_if_sparse(struct progress *progress)
326
0
{
327
0
  if (progress->sparse &&
328
0
      progress->last_value != progress->total)
329
0
    display_progress(progress, progress->total);
330
0
}
331
332
static void force_last_update(struct progress *progress, const char *msg)
333
0
{
334
0
  char *buf;
335
0
  struct throughput *tp = progress->throughput;
336
337
0
  if (tp) {
338
0
    uint64_t now_ns = progress_getnanotime(progress);
339
0
    unsigned int misecs, rate;
340
0
    misecs = ((now_ns - progress->start_ns) * 4398) >> 32;
341
0
    rate = tp->curr_total / (misecs ? misecs : 1);
342
0
    throughput_string(&tp->display, tp->curr_total, rate);
343
0
  }
344
0
  progress_update = 1;
345
0
  buf = xstrfmt(", %s.\n", msg);
346
0
  display(progress, progress->last_value, buf);
347
0
  free(buf);
348
0
}
349
350
static void log_trace2(struct progress *progress)
351
0
{
352
0
  trace2_data_intmax("progress", progress->repo, "total_objects",
353
0
         progress->total);
354
355
0
  if (progress->throughput)
356
0
    trace2_data_intmax("progress", progress->repo, "total_bytes",
357
0
           progress->throughput->curr_total);
358
359
0
  trace2_region_leave("progress", progress->title, progress->repo);
360
0
}
361
362
void stop_progress_msg(struct progress **p_progress, const char *msg)
363
0
{
364
0
  struct progress *progress;
365
366
0
  if (!p_progress)
367
0
    BUG("don't provide NULL to stop_progress_msg");
368
369
0
  progress = *p_progress;
370
0
  if (!progress)
371
0
    return;
372
0
  *p_progress = NULL;
373
374
0
  finish_if_sparse(progress);
375
0
  if (progress->last_value != -1)
376
0
    force_last_update(progress, msg);
377
0
  log_trace2(progress);
378
379
0
  clear_progress_signal();
380
0
  strbuf_release(&progress->counters_sb);
381
0
  if (progress->throughput)
382
0
    strbuf_release(&progress->throughput->display);
383
0
  free(progress->throughput);
384
0
  free(progress);
385
0
}