]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
ed60d210 DW |
2 | /* |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
ed60d210 | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
ed60d210 | 5 | */ |
a440f877 | 6 | #include "xfs.h" |
ed60d210 DW |
7 | #include <dirent.h> |
8 | #include <pthread.h> | |
9 | #include <sys/statvfs.h> | |
10 | #include <time.h> | |
42b4c8e8 | 11 | #include "libfrog/paths.h" |
ed60d210 DW |
12 | #include "disk.h" |
13 | #include "read_verify.h" | |
14 | #include "xfs_scrub.h" | |
15 | #include "common.h" | |
16 | #include "counter.h" | |
17 | #include "progress.h" | |
18 | ||
19 | /* | |
20 | * Progress Tracking | |
21 | * | |
22 | * For scrub phases that expect to take a long time, this facility uses | |
23 | * the threaded counter and some phase/state information to report the | |
24 | * progress of a particular phase to stdout. Each phase that wants | |
25 | * progress information needs to set up the tracker with an estimate of | |
26 | * the work to be done and periodic updates when work items finish. In | |
27 | * return, the progress tracker will print a pretty progress bar and | |
28 | * twiddle to a tty, or a raw numeric output compatible with fsck -C. | |
29 | */ | |
30 | struct progress_tracker { | |
31 | FILE *fp; | |
32 | const char *tag; | |
33 | struct ptcounter *ptc; | |
34 | uint64_t max; | |
35 | unsigned int phase; | |
36 | int rshift; | |
37 | int twiddle; | |
38 | bool isatty; | |
39 | bool terminate; | |
40 | pthread_t thread; | |
41 | ||
42 | /* static state */ | |
43 | pthread_mutex_t lock; | |
44 | pthread_cond_t wakeup; | |
45 | }; | |
46 | ||
47 | static struct progress_tracker pt = { | |
48 | .lock = PTHREAD_MUTEX_INITIALIZER, | |
49 | .wakeup = PTHREAD_COND_INITIALIZER, | |
50 | }; | |
51 | ||
52 | /* Add some progress. */ | |
53 | void | |
54 | progress_add( | |
55 | uint64_t x) | |
56 | { | |
57 | if (pt.fp) | |
58 | ptcounter_add(pt.ptc, x); | |
59 | } | |
60 | ||
61 | static const char twiddles[] = "|/-\\"; | |
62 | ||
63 | static void | |
64 | progress_report( | |
65 | uint64_t sum) | |
66 | { | |
67 | char buf[81]; | |
68 | int tag_len; | |
69 | int num_len; | |
70 | int pbar_len; | |
71 | int plen; | |
72 | ||
73 | if (!pt.fp) | |
74 | return; | |
75 | ||
76 | if (sum > pt.max) | |
77 | sum = pt.max; | |
78 | ||
79 | /* Emulate fsck machine-readable output (phase, cur, max, label) */ | |
80 | if (!pt.isatty) { | |
81 | snprintf(buf, sizeof(buf), _("%u %"PRIu64" %"PRIu64" %s"), | |
82 | pt.phase, sum, pt.max, pt.tag); | |
83 | fprintf(pt.fp, "%s\n", buf); | |
84 | fflush(pt.fp); | |
85 | return; | |
86 | } | |
87 | ||
88 | /* Interactive twiddle progress bar. */ | |
89 | if (debug) { | |
90 | num_len = snprintf(buf, sizeof(buf), | |
91 | "%c %"PRIu64"/%"PRIu64" (%.1f%%)", | |
92 | twiddles[pt.twiddle], | |
93 | sum >> pt.rshift, | |
94 | pt.max >> pt.rshift, | |
95 | 100.0 * sum / pt.max); | |
96 | } else { | |
97 | num_len = snprintf(buf, sizeof(buf), | |
98 | "%c (%.1f%%)", | |
99 | twiddles[pt.twiddle], | |
100 | 100.0 * sum / pt.max); | |
101 | } | |
102 | memmove(buf + sizeof(buf) - (num_len + 1), buf, num_len + 1); | |
103 | tag_len = snprintf(buf, sizeof(buf), _("Phase %u: |"), pt.phase); | |
104 | pbar_len = sizeof(buf) - (num_len + 1 + tag_len); | |
105 | plen = (int)((double)pbar_len * sum / pt.max); | |
106 | memset(buf + tag_len, '=', plen); | |
107 | memset(buf + tag_len + plen, ' ', pbar_len - plen); | |
108 | pt.twiddle = (pt.twiddle + 1) % 4; | |
109 | fprintf(pt.fp, "%c%s\r%c", START_IGNORE, buf, END_IGNORE); | |
110 | fflush(pt.fp); | |
111 | } | |
112 | ||
113 | #define NSEC_PER_SEC (1000000000) | |
114 | static void * | |
115 | progress_report_thread(void *arg) | |
116 | { | |
117 | struct timespec abstime; | |
118 | int ret; | |
119 | ||
120 | pthread_mutex_lock(&pt.lock); | |
121 | while (1) { | |
da3dd6c0 DW |
122 | uint64_t progress_val; |
123 | ||
ed60d210 DW |
124 | /* Every half second. */ |
125 | ret = clock_gettime(CLOCK_REALTIME, &abstime); | |
126 | if (ret) | |
127 | break; | |
128 | abstime.tv_nsec += NSEC_PER_SEC / 2; | |
129 | if (abstime.tv_nsec > NSEC_PER_SEC) { | |
130 | abstime.tv_sec++; | |
131 | abstime.tv_nsec -= NSEC_PER_SEC; | |
132 | } | |
8808a003 DW |
133 | ret = pthread_cond_timedwait(&pt.wakeup, &pt.lock, &abstime); |
134 | if (ret && ret != ETIMEDOUT) | |
135 | break; | |
ed60d210 DW |
136 | if (pt.terminate) |
137 | break; | |
da3dd6c0 DW |
138 | ret = ptcounter_value(pt.ptc, &progress_val); |
139 | if (!ret) | |
140 | progress_report(progress_val); | |
ed60d210 DW |
141 | } |
142 | pthread_mutex_unlock(&pt.lock); | |
143 | return NULL; | |
144 | } | |
145 | ||
146 | /* End a phase of progress reporting. */ | |
147 | void | |
148 | progress_end_phase(void) | |
149 | { | |
150 | if (!pt.fp) | |
151 | return; | |
152 | ||
153 | pthread_mutex_lock(&pt.lock); | |
154 | pt.terminate = true; | |
155 | pthread_mutex_unlock(&pt.lock); | |
156 | pthread_cond_broadcast(&pt.wakeup); | |
157 | pthread_join(pt.thread, NULL); | |
158 | ||
159 | progress_report(pt.max); | |
160 | ptcounter_free(pt.ptc); | |
161 | pt.max = 0; | |
162 | pt.ptc = NULL; | |
163 | if (pt.fp) { | |
164 | fprintf(pt.fp, CLEAR_EOL); | |
165 | fflush(pt.fp); | |
166 | } | |
167 | pt.fp = NULL; | |
168 | } | |
169 | ||
d86e83b8 DW |
170 | /* |
171 | * Set ourselves up to report progress. If errors are encountered, this | |
172 | * function will log them and return nonzero. | |
173 | */ | |
174 | int | |
ed60d210 DW |
175 | progress_init_phase( |
176 | struct scrub_ctx *ctx, | |
177 | FILE *fp, | |
178 | unsigned int phase, | |
179 | uint64_t max, | |
180 | int rshift, | |
181 | unsigned int nr_threads) | |
182 | { | |
183 | int ret; | |
184 | ||
185 | assert(pt.fp == NULL); | |
186 | if (fp == NULL || max == 0) { | |
187 | pt.fp = NULL; | |
d86e83b8 | 188 | return 0; |
ed60d210 DW |
189 | } |
190 | pt.fp = fp; | |
191 | pt.isatty = isatty(fileno(fp)); | |
192 | pt.tag = ctx->mntpoint; | |
193 | pt.max = max; | |
194 | pt.phase = phase; | |
195 | pt.rshift = rshift; | |
196 | pt.twiddle = 0; | |
197 | pt.terminate = false; | |
198 | ||
da3dd6c0 DW |
199 | ret = ptcounter_alloc(nr_threads, &pt.ptc); |
200 | if (ret) { | |
201 | str_liberror(ctx, ret, _("allocating progress counter")); | |
ed60d210 | 202 | goto out_max; |
da3dd6c0 | 203 | } |
ed60d210 DW |
204 | |
205 | ret = pthread_create(&pt.thread, NULL, progress_report_thread, NULL); | |
0a9ac205 DW |
206 | if (ret) { |
207 | str_liberror(ctx, ret, _("creating progress reporting thread")); | |
ed60d210 | 208 | goto out_ptcounter; |
0a9ac205 | 209 | } |
ed60d210 | 210 | |
d86e83b8 | 211 | return 0; |
ed60d210 DW |
212 | |
213 | out_ptcounter: | |
214 | ptcounter_free(pt.ptc); | |
215 | pt.ptc = NULL; | |
216 | out_max: | |
217 | pt.max = 0; | |
218 | pt.fp = NULL; | |
d86e83b8 | 219 | return ret; |
ed60d210 | 220 | } |