Line data Source code
1 : // SPDX-License-Identifier: GPL-3.0-or-later
2 : // Copyright (C) 2013 Andrea Mazzoleni
3 :
4 : #include "portable.h"
5 :
6 : #include "support.h"
7 : #include "elem.h"
8 : #include "state.h"
9 : #include "parity.h"
10 : #include "handle.h"
11 : #include "raid/raid.h"
12 :
13 : /****************************************************************************/
14 : /* status */
15 :
16 33 : unsigned day_ago(time_t ref, time_t now)
17 : {
18 : /* in case some dates is in the future */
19 33 : if (now < ref)
20 0 : return 0;
21 :
22 33 : return (now - ref) / (24 * 3600);
23 : }
24 :
25 : #define GRAPH_COLUMN 70
26 : #define GRAPH_ROW 15
27 :
28 : /**
29 : * Bit used to mark unscrubbed time info.
30 : */
31 : #define TIME_NEW 1
32 :
33 17 : int state_status(struct snapraid_state* state)
34 : {
35 : char esc_buffer[ESC_MAX];
36 : block_off_t blockmax;
37 : block_off_t i;
38 : time_t now;
39 : block_off_t count;
40 : unsigned l;
41 : unsigned dayoldest, daymedian, daynewest;
42 : unsigned bar_scrubbed[GRAPH_COLUMN];
43 : unsigned bar_new[GRAPH_COLUMN];
44 : unsigned barmax;
45 : time_t oldest, newest, median;
46 : unsigned x, y;
47 : tommy_node* node_disk;
48 : unsigned file_count;
49 : unsigned file_fragmented;
50 : unsigned extra_fragment;
51 : unsigned file_zerosubsecond;
52 : uint64_t file_size;
53 : uint64_t file_block_count;
54 : uint64_t file_block_free;
55 : unsigned dealloc_count;
56 : block_off_t parity_block_free;
57 : uint64_t all_wasted;
58 : int free_not_zero;
59 :
60 : /* get the present time */
61 17 : now = time(0);
62 :
63 : /* keep track if at least a free info is available */
64 17 : free_not_zero = 0;
65 :
66 17 : blockmax = parity_allocated_size(state);
67 :
68 17 : log_tag("summary:block_size:%u\n", state->block_size);
69 17 : log_tag("summary:parity_block_count:%u\n", blockmax);
70 :
71 : /* get the minimum parity free space */
72 17 : parity_block_free = state->parity[0].free_blocks;
73 109 : for (l = 0; l < state->level; ++l) {
74 92 : log_tag("summary:parity_block_total:%s:%u\n", lev_config_name(l), state->parity[l].total_blocks);
75 92 : log_tag("summary:parity_block_free:%s:%u\n", lev_config_name(l), state->parity[l].free_blocks);
76 92 : if (state->parity[l].free_blocks < parity_block_free)
77 1 : parity_block_free = state->parity[l].free_blocks;
78 92 : if (state->parity[l].free_blocks != 0)
79 81 : free_not_zero = 1;
80 : }
81 17 : log_tag("summary:parity_block_free_min:%u\n", parity_block_free);
82 :
83 17 : printf("SnapRAID status report:\n");
84 17 : printf("\n");
85 17 : printf(" Files Fragmented Excess Wasted Used Free Use Name\n");
86 17 : printf(" Files Fragments GB GB GB\n");
87 :
88 : /* count fragments */
89 17 : dealloc_count = 0;
90 17 : file_count = 0;
91 17 : file_size = 0;
92 17 : file_block_count = 0;
93 17 : file_block_free = 0;
94 17 : file_fragmented = 0;
95 17 : extra_fragment = 0;
96 17 : file_zerosubsecond = 0;
97 17 : all_wasted = 0;
98 113 : for (node_disk = state->disklist; node_disk != 0; node_disk = node_disk->next) {
99 96 : struct snapraid_disk* disk = node_disk->data;
100 : tommy_node* node;
101 : block_off_t j;
102 96 : unsigned disk_file_count = 0;
103 96 : unsigned disk_file_fragmented = 0;
104 96 : unsigned disk_extra_fragment = 0;
105 96 : unsigned disk_file_zerosubsecond = 0;
106 96 : block_off_t disk_block_count = 0;
107 96 : uint64_t disk_file_size = 0;
108 96 : block_off_t disk_block_latest_used = 0;
109 : block_off_t disk_block_max_by_space;
110 : block_off_t disk_block_max_by_parity;
111 : block_off_t disk_block_max;
112 : uint64_t disk_used_bytes;
113 : uint64_t disk_free_bytes;
114 : int64_t wasted;
115 :
116 96 : dealloc_count += tommy_list_count(&disk->dealloclist);
117 :
118 : /* for each file in the disk */
119 96 : node = disk->filelist;
120 100637 : while (node) {
121 : struct snapraid_file* file;
122 :
123 100541 : file = node->data;
124 100541 : node = node->next; /* next node */
125 :
126 100541 : if (file->mtime_nsec == STAT_NSEC_INVALID
127 100541 : || file->mtime_nsec == 0
128 : ) {
129 0 : ++file_zerosubsecond;
130 0 : ++disk_file_zerosubsecond;
131 0 : if (disk_file_zerosubsecond < 50)
132 0 : log_tag("zerosubsecond:%s:%s: \n", disk->name, file->sub);
133 0 : if (disk_file_zerosubsecond == 50)
134 0 : log_tag("zerosubsecond:%s:%s: (more follow)\n", disk->name, file->sub);
135 : }
136 :
137 : /* check fragmentation */
138 100541 : if (file->blockmax != 0) {
139 : block_off_t prev_pos;
140 : block_off_t last_pos;
141 : int fragmented;
142 :
143 100398 : fragmented = 0;
144 100398 : prev_pos = fs_file2par_get(disk, file, 0);
145 240698 : for (j = 1; j < file->blockmax; ++j) {
146 140300 : block_off_t parity_pos = fs_file2par_get(disk, file, j);
147 140300 : if (prev_pos + 1 != parity_pos) {
148 713 : fragmented = 1;
149 713 : ++extra_fragment;
150 713 : ++disk_extra_fragment;
151 : }
152 140300 : prev_pos = parity_pos;
153 : }
154 :
155 : /* keep track of latest block used */
156 100398 : last_pos = fs_file2par_get(disk, file, file->blockmax - 1);
157 100398 : if (last_pos > disk_block_latest_used) {
158 96495 : disk_block_latest_used = last_pos;
159 : }
160 :
161 100398 : if (fragmented) {
162 593 : ++file_fragmented;
163 593 : ++disk_file_fragmented;
164 : }
165 :
166 100398 : disk_block_count += file->blockmax;
167 : }
168 :
169 : /* count files */
170 100541 : ++file_count;
171 100541 : ++disk_file_count;
172 100541 : file_size += file->size;
173 100541 : file_block_count += file->blockmax;
174 100541 : disk_file_size += file->size;
175 : }
176 :
177 96 : if (disk->free_blocks != 0)
178 59 : free_not_zero = 1;
179 :
180 : /* get the free block info */
181 96 : disk_block_max_by_space = disk_block_count + disk->free_blocks;
182 96 : disk_block_max_by_parity = blockmax + parity_block_free;
183 :
184 : /*
185 : * The maximum usable space in a disk is limited by the smallest
186 : * of the disk size and the parity size
187 : * the wasted space is the space that we have to leave
188 : * free on the data disk, when the parity is filled up
189 : */
190 96 : if (disk_block_max_by_space < disk_block_max_by_parity) {
191 84 : disk_block_max = disk_block_max_by_space;
192 : } else {
193 12 : disk_block_max = disk_block_max_by_parity;
194 : }
195 :
196 : /*
197 : * Wasted space is the difference of the two maximum size
198 : * if negative, it's extra space available in parity
199 : */
200 96 : wasted = (int64_t)disk_block_max_by_space - (int64_t)disk_block_max_by_parity;
201 96 : wasted *= state->block_size;
202 :
203 96 : if (wasted > 0)
204 6 : all_wasted += wasted;
205 96 : file_block_free += disk_block_max - disk_block_count;
206 :
207 96 : printf("%8u", disk_file_count);
208 96 : printf("%8u", disk_file_fragmented);
209 96 : printf("%8u", disk_extra_fragment);
210 96 : if (wasted < -100LL * GIGA) {
211 84 : printf(" -");
212 : } else {
213 12 : printf("%8.1f", (double)wasted / GIGA);
214 : }
215 96 : printf("%8" PRIu64, disk_file_size / GIGA);
216 :
217 96 : if (disk_block_max == 0 && disk_block_count == 0) {
218 : /* if the disk is empty and we don't have the free space info */
219 37 : printf(" -");
220 37 : printf(" - ");
221 : } else {
222 59 : printf("%8" PRIu64, (disk_block_max - disk_block_count) * (uint64_t)state->block_size / GIGA);
223 59 : printf(" %3u%%", muldiv(disk_block_count, 100, disk_block_max));
224 : }
225 96 : printf(" %s\n", disk->name);
226 :
227 96 : log_tag("summary:disk_file_count:%s:%u\n", disk->name, disk_file_count);
228 96 : log_tag("summary:disk_block_count:%s:%u\n", disk->name, disk_block_count);
229 96 : log_tag("summary:disk_fragmented_file_count:%s:%u\n", disk->name, disk_file_fragmented);
230 96 : log_tag("summary:disk_excess_fragment_count:%s:%u\n", disk->name, disk_extra_fragment);
231 96 : log_tag("summary:disk_zerosubsecond_file_count:%s:%u\n", disk->name, disk_file_zerosubsecond);
232 96 : log_tag("summary:disk_file_size:%s:%" PRIu64 "\n", disk->name, disk_file_size);
233 96 : log_tag("summary:disk_block_allocated:%s:%u\n", disk->name, disk_block_latest_used + 1);
234 96 : log_tag("summary:disk_block_total:%s:%u\n", disk->name, disk->total_blocks);
235 96 : log_tag("summary:disk_block_free:%s:%u\n", disk->name, disk->free_blocks);
236 96 : log_tag("summary:disk_block_max_by_space:%s:%u\n", disk->name, disk_block_max_by_space);
237 96 : log_tag("summary:disk_block_max_by_parity:%s:%u\n", disk->name, disk_block_max_by_parity);
238 96 : log_tag("summary:disk_block_max:%s:%u\n", disk->name, disk_block_max);
239 96 : log_tag("summary:disk_space_wasted:%s:%" PRId64 "\n", disk->name, wasted);
240 :
241 96 : disk_used_bytes = disk_block_count * (uint64_t)state->block_size;
242 96 : disk_free_bytes = (disk_block_max - disk_block_count) * (uint64_t)state->block_size;
243 96 : log_tag("summary:disk_used:%s:%" PRIu64 "\n", disk->name, disk_used_bytes);
244 96 : log_tag("summary:disk_free:%s:%" PRIu64 "\n", disk->name, disk_free_bytes);
245 96 : log_tag("summary:disk_use_percent:%s:%u\n", disk->name, muldiv(disk_block_count, 100, disk_block_max));
246 : }
247 :
248 : /* totals */
249 17 : printf(" --------------------------------------------------------------------------\n");
250 17 : printf("%8u", file_count);
251 17 : printf("%8u", file_fragmented);
252 17 : printf("%8u", extra_fragment);
253 17 : printf("%8.1f", (double)all_wasted / GIGA);
254 17 : printf("%8" PRIu64, file_size / GIGA);
255 17 : printf("%8" PRIu64, file_block_free * state->block_size / GIGA);
256 17 : printf(" %3u%%", muldiv(file_block_count, 100, file_block_count + file_block_free));
257 17 : printf("\n");
258 :
259 : /* warn about invalid data free info */
260 17 : if (!free_not_zero)
261 1 : printf("\nWARNING! Free space info will be valid after the first sync.\n");
262 :
263 17 : log_tag("summary:file_count:%u\n", file_count);
264 17 : log_tag("summary:file_block_count:%" PRIu64 "\n", file_block_count);
265 17 : log_tag("summary:fragmented_file_count:%u\n", file_fragmented);
266 17 : log_tag("summary:excess_fragment_count:%u\n", extra_fragment);
267 17 : log_tag("summary:zerosubsecond_file_count:%u\n", file_zerosubsecond);
268 17 : log_tag("summary:file_size:%" PRIu64 "\n", file_size);
269 17 : log_tag("summary:parity_size:%" PRIu64 "\n", blockmax * (uint64_t)state->block_size);
270 17 : log_tag("summary:parity_size_max:%" PRIu64 "\n", (blockmax + parity_block_free) * (uint64_t)state->block_size);
271 17 : log_tag("summary:hash:%s\n", hash_config_name(state->hash));
272 17 : log_tag("summary:prev_hash:%s\n", hash_config_name(state->prevhash));
273 17 : log_tag("summary:best_hash:%s\n", hash_config_name(state->besthash));
274 17 : log_tag("summary:total_wasted:%" PRIu64 "\n", all_wasted);
275 17 : log_tag("summary:total_used:%" PRIu64 "\n", file_size);
276 17 : log_tag("summary:total_free:%" PRIu64 "\n", file_block_free * state->block_size);
277 17 : log_tag("summary:total_use_percent:%u\n", muldiv(file_block_count, 100, file_block_count + file_block_free));
278 17 : log_flush();
279 :
280 17 : oldest = 0;
281 17 : median = 0;
282 17 : newest = 0;
283 17 : count = 0;
284 38 : for (tommy_node* j = tommy_list_head(&state->bucketlist); j != 0; j = j->next) {
285 21 : struct snapraid_bucket* bucket = j->data;
286 21 : block_off_t bucket_count = bucket->count_scrubbed + bucket->count_justsynced;
287 :
288 21 : if (count == 0)
289 11 : oldest = bucket->time_at;
290 21 : if (count < state->bucketcount / 2)
291 17 : median = bucket->time_at;
292 21 : newest = bucket->time_at;
293 :
294 21 : count += bucket_count;
295 : }
296 :
297 17 : if (!count) {
298 6 : log_fatal(EUSER, "The array is empty.\n");
299 6 : return 0;
300 : }
301 :
302 11 : dayoldest = day_ago(oldest, now);
303 11 : daymedian = day_ago(median, now);
304 11 : daynewest = day_ago(newest, now);
305 :
306 11 : log_tag("summary:scrub_oldest_days:%u\n", dayoldest);
307 11 : log_tag("summary:scrub_median_days:%u\n", daymedian);
308 11 : log_tag("summary:scrub_newest_days:%u\n", daynewest);
309 :
310 : /* compute graph limits */
311 11 : barmax = 0;
312 11 : memset(bar_scrubbed, 0, sizeof(bar_scrubbed));
313 11 : memset(bar_new, 0, sizeof(bar_new));
314 32 : for (tommy_node* j = tommy_list_head(&state->bucketlist); j != 0; j = j->next) {
315 21 : struct snapraid_bucket* bucket = j->data;
316 :
317 21 : unsigned column = muldiv(bucket->time_at - oldest, GRAPH_COLUMN, newest - oldest + 1);
318 :
319 21 : bar_scrubbed[column] += bucket->count_scrubbed;
320 21 : bar_new[column] += bucket->count_justsynced;
321 :
322 21 : if (bar_scrubbed[column] + bar_new[column] > barmax)
323 14 : barmax = bar_scrubbed[column] + bar_new[column];
324 : }
325 :
326 : /* output scrub history as structured data */
327 11 : log_tag("scrub_graph_range:%u:%u\n", GRAPH_COLUMN, barmax);
328 781 : for (i = 0; i < GRAPH_COLUMN; ++i) {
329 770 : unsigned days_ago = dayoldest - (dayoldest - daynewest) * i / (GRAPH_COLUMN - 1);
330 770 : log_tag("scrub_graph_bar:%u:%u:%u:%u\n", i, days_ago, bar_scrubbed[i], bar_new[i]);
331 : }
332 :
333 11 : printf("\n\n");
334 :
335 : /* print the graph */
336 176 : for (y = 0; y < GRAPH_ROW; ++y) {
337 165 : if (y == 0)
338 11 : printf("%3u%%|", barmax * 100 / count);
339 154 : else if (y == GRAPH_ROW - 1)
340 11 : printf(" 0%%|");
341 143 : else if (y == GRAPH_ROW / 2)
342 11 : printf("%3u%%|", barmax * 50 / count);
343 : else
344 132 : printf(" |");
345 11715 : for (x = 0; x < GRAPH_COLUMN; ++x) {
346 11550 : unsigned pivot_upper = barmax * (GRAPH_ROW - y) / GRAPH_ROW;
347 11550 : unsigned pivot_lower = barmax * (GRAPH_ROW - 1 - y) / GRAPH_ROW;
348 11550 : unsigned both = bar_scrubbed[x] + bar_new[x];
349 11550 : unsigned scrubbed = bar_scrubbed[x];
350 :
351 11550 : if (both > pivot_upper) {
352 178 : if (scrubbed > pivot_lower)
353 58 : printf("*");
354 : else
355 120 : printf("o");
356 11372 : } else if (both > pivot_lower) {
357 21 : if (scrubbed == both)
358 5 : printf("*");
359 : else
360 16 : printf("o");
361 : } else {
362 11351 : if (y == GRAPH_ROW - 1)
363 749 : printf("_");
364 : else
365 10602 : printf(" ");
366 : }
367 : }
368 165 : printf("\n");
369 : }
370 11 : printf(" %3u days ago of the last scrub/sync %3u\n", dayoldest, daynewest);
371 :
372 11 : printf("\n");
373 :
374 11 : printf("The oldest block was scrubbed %u days ago, the median %u, the newest %u.\n", dayoldest, daymedian, daynewest);
375 :
376 11 : printf("\n");
377 :
378 11 : if (newest > now) {
379 0 : printf("WARNING! You have scrub dates in the future! The next sync/scrub will truncate them!\n");
380 : }
381 :
382 11 : if (state->unsynced_blocks) {
383 3 : printf("WARNING! The array is NOT fully synced.\n");
384 3 : printf("You have a sync in progress at %u%%.\n", muldiv(blockmax - state->unsynced_blocks, 100, blockmax));
385 3 : if (dealloc_count) {
386 3 : printf("WARNING! There are %u files updated or deleted from the array that may reduce the recovery probability until the next sync.\n", dealloc_count);
387 15 : for (node_disk = state->disklist; node_disk != 0; node_disk = node_disk->next) {
388 12 : struct snapraid_disk* disk = node_disk->data;
389 1645 : for (tommy_node* node = tommy_list_head(&disk->dealloclist); node != 0; node = node->next) {
390 1633 : struct snapraid_dealloc* dealloc = node->data;
391 1633 : msg_verbose("\t%s\n", fmt_term(disk, dealloc->sub, esc_buffer));
392 : }
393 : }
394 : }
395 : } else {
396 8 : printf("No sync is in progress.\n");
397 : }
398 :
399 11 : if (state->unscrubbed_blocks) {
400 9 : printf("%u%% of the array is not scrubbed.\n", muldiv_upper(state->unscrubbed_blocks, 100, blockmax));
401 : } else {
402 2 : printf("The full array was scrubbed at least one time.\n");
403 : }
404 :
405 11 : if (file_zerosubsecond) {
406 0 : printf("You have %u files with a zero sub-second timestamp.\n", file_zerosubsecond);
407 0 : printf("Run 'snapraid touch' to set their sub-second timestamps to a non-zero value.\n");
408 : } else {
409 11 : printf("No file has a zero sub-second timestamp.\n");
410 : }
411 :
412 11 : if (state->rehash_blocks) {
413 1 : printf("You have a rehash in progress at %u%%.\n", muldiv(count - state->rehash_blocks, 100, count));
414 : } else {
415 10 : if (state->besthash != state->hash) {
416 7 : printf("No rehash is in progress, but for optimal performance one is recommended.\n");
417 : } else {
418 3 : printf("No rehash is in progress or needed.\n");
419 : }
420 : }
421 :
422 11 : if (state->bad_blocks) {
423 2 : printf("DANGER! In the array there are %u errors!\n\n", state->bad_blocks);
424 :
425 : block_off_t bad_range;
426 : block_off_t bad_count;
427 : block_off_t range_start;
428 : block_off_t range_count;
429 :
430 2 : printf("They are at blocks:");
431 :
432 : /* print some of the errors */
433 2 : bad_range = 0;
434 2 : bad_count = 0;
435 2 : range_start = 0;
436 2 : range_count = 0;
437 870 : for (i = 0; i <= blockmax; ++i) { /* one extra iteration to print the final range */
438 870 : snapraid_info info = 0;
439 870 : int is_bad = 0;
440 :
441 870 : if (i < blockmax) {
442 870 : info = info_get(&state->infoarr, i);
443 870 : if (info != 0) /* unused blocks are never bad */
444 870 : is_bad = info_get_bad(info);
445 : }
446 870 : if (is_bad) {
447 : /* create or extend the range */
448 252 : if (!range_count)
449 202 : range_start = i;
450 252 : ++range_count;
451 : } else {
452 : /* break the range */
453 618 : if (range_count) {
454 202 : if (range_count == 1) {
455 161 : printf(" %u", range_start);
456 : } else {
457 41 : printf(" %u-%u", range_start, range_start + range_count - 1);
458 : }
459 202 : bad_count += range_count;
460 202 : ++bad_range;
461 202 : range_count = 0;
462 : }
463 : }
464 :
465 870 : if (bad_range > 100) {
466 2 : printf(" and %u more...", state->bad_blocks - bad_count);
467 2 : break;
468 : }
469 : }
470 :
471 2 : printf("\n");
472 :
473 2 : printf("To fix them use the command 'snapraid -e fix'.\n");
474 2 : printf("The errors will disappear from the 'status' at the next 'scrub' command.\n");
475 : } else {
476 9 : printf("No error detected.\n");
477 : }
478 :
479 11 : if (state->bad_blocks)
480 2 : log_tag("summary:exit:bad\n");
481 9 : else if (state->unsynced_blocks != 0)
482 3 : log_tag("summary:exit:unsynced\n");
483 : else
484 6 : log_tag("summary:exit:ok\n");
485 :
486 11 : return 0;
487 : }
488 :
|