btrfs-progs: move help defines to own header
[platform/upstream/btrfs-progs.git] / cmds-inspect-tree-stats.c
1 /*
2  * Copyright (C) 2011 Red Hat.  All rights reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License v2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 021110-1307, USA.
17  */
18
19 #include <ctype.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <zlib.h>
28
29 #include "kerncompat.h"
30 #include "ctree.h"
31 #include "disk-io.h"
32 #include "print-tree.h"
33 #include "transaction.h"
34 #include "list.h"
35 #include "volumes.h"
36 #include "utils.h"
37 #include "commands.h"
38 #include "cmds-inspect-tree-stats.h"
39 #include "help.h"
40
41 static int verbose = 0;
42 static int no_pretty = 0;
43
44 struct seek {
45         u64 distance;
46         u64 count;
47         struct rb_node n;
48 };
49
50 struct root_stats {
51         u64 total_nodes;
52         u64 total_leaves;
53         u64 total_bytes;
54         u64 total_inline;
55         u64 total_seeks;
56         u64 forward_seeks;
57         u64 backward_seeks;
58         u64 total_seek_len;
59         u64 max_seek_len;
60         u64 total_clusters;
61         u64 total_cluster_size;
62         u64 min_cluster_size;
63         u64 max_cluster_size;
64         u64 lowest_bytenr;
65         u64 highest_bytenr;
66         struct rb_root seek_root;
67         int total_levels;
68 };
69
70 static int add_seek(struct rb_root *root, u64 dist)
71 {
72         struct rb_node **p = &root->rb_node;
73         struct rb_node *parent = NULL;
74         struct seek *seek = NULL;
75
76         while (*p) {
77                 parent = *p;
78                 seek = rb_entry(parent, struct seek, n);
79
80                 if (dist < seek->distance) {
81                         p = &(*p)->rb_left;
82                 } else if (dist > seek->distance) {
83                         p = &(*p)->rb_right;
84                 } else {
85                         seek->count++;
86                         return 0;
87                 }
88         }
89
90         seek = malloc(sizeof(struct seek));
91         if (!seek)
92                 return -ENOMEM;
93         seek->distance = dist;
94         seek->count = 1;
95         rb_link_node(&seek->n, parent, p);
96         rb_insert_color(&seek->n, root);
97         return 0;
98 }
99
100 static int walk_leaf(struct btrfs_root *root, struct btrfs_path *path,
101                      struct root_stats *stat, int find_inline)
102 {
103         struct extent_buffer *b = path->nodes[0];
104         struct btrfs_file_extent_item *fi;
105         struct btrfs_key found_key;
106         int i;
107
108         stat->total_bytes += root->nodesize;
109         stat->total_leaves++;
110
111         if (!find_inline)
112                 return 0;
113
114         for (i = 0; i < btrfs_header_nritems(b); i++) {
115                 btrfs_item_key_to_cpu(b, &found_key, i);
116                 if (found_key.type != BTRFS_EXTENT_DATA_KEY)
117                         continue;
118
119                 fi = btrfs_item_ptr(b, i, struct btrfs_file_extent_item);
120                 if (btrfs_file_extent_type(b, fi) == BTRFS_FILE_EXTENT_INLINE)
121                         stat->total_inline +=
122                                 btrfs_file_extent_inline_item_len(b,
123                                                         btrfs_item_nr(i));
124         }
125
126         return 0;
127 }
128
129 static u64 calc_distance(u64 block1, u64 block2)
130 {
131         if (block1 < block2)
132                 return block2 - block1;
133         return block1 - block2;
134 }
135
136 static int walk_nodes(struct btrfs_root *root, struct btrfs_path *path,
137                       struct root_stats *stat, int level, int find_inline)
138 {
139         struct extent_buffer *b = path->nodes[level];
140         u64 last_block;
141         u64 cluster_size = root->nodesize;
142         int i;
143         int ret = 0;
144
145         stat->total_bytes += root->nodesize;
146         stat->total_nodes++;
147
148         last_block = btrfs_header_bytenr(b);
149         for (i = 0; i < btrfs_header_nritems(b); i++) {
150                 struct extent_buffer *tmp = NULL;
151                 u64 cur_blocknr = btrfs_node_blockptr(b, i);
152
153                 path->slots[level] = i;
154                 if ((level - 1) > 0 || find_inline) {
155                         tmp = read_tree_block(root, cur_blocknr,
156                                               root->nodesize,
157                                               btrfs_node_ptr_generation(b, i));
158                         if (!extent_buffer_uptodate(tmp)) {
159                                 error("failed to read blocknr %llu",
160                                         btrfs_node_blockptr(b, i));
161                                 continue;
162                         }
163                         path->nodes[level - 1] = tmp;
164                 }
165                 if (level - 1)
166                         ret = walk_nodes(root, path, stat, level - 1,
167                                          find_inline);
168                 else
169                         ret = walk_leaf(root, path, stat, find_inline);
170                 if (last_block + root->nodesize != cur_blocknr) {
171                         u64 distance = calc_distance(last_block +
172                                                      root->nodesize,
173                                                      cur_blocknr);
174                         stat->total_seeks++;
175                         stat->total_seek_len += distance;
176                         if (stat->max_seek_len < distance)
177                                 stat->max_seek_len = distance;
178                         if (add_seek(&stat->seek_root, distance)) {
179                                 error("cannot add new seek at distance %llu",
180                                                 (unsigned long long)distance);
181                                 ret = -ENOMEM;
182                                 break;
183                         }
184
185                         if (last_block < cur_blocknr)
186                                 stat->forward_seeks++;
187                         else
188                                 stat->backward_seeks++;
189                         if (cluster_size != root->nodesize) {
190                                 stat->total_cluster_size += cluster_size;
191                                 stat->total_clusters++;
192                                 if (cluster_size < stat->min_cluster_size)
193                                         stat->min_cluster_size = cluster_size;
194                                 if (cluster_size > stat->max_cluster_size)
195                                         stat->max_cluster_size = cluster_size;
196                         }
197                         cluster_size = root->nodesize;
198                 } else {
199                         cluster_size += root->nodesize;
200                 }
201                 last_block = cur_blocknr;
202                 if (cur_blocknr < stat->lowest_bytenr)
203                         stat->lowest_bytenr = cur_blocknr;
204                 if (cur_blocknr > stat->highest_bytenr)
205                         stat->highest_bytenr = cur_blocknr;
206                 free_extent_buffer(tmp);
207                 if (ret) {
208                         error("walking down path failed: %d",  ret);
209                         break;
210                 }
211         }
212
213         return ret;
214 }
215
216 static void print_seek_histogram(struct root_stats *stat)
217 {
218         struct rb_node *n = rb_first(&stat->seek_root);
219         struct seek *seek;
220         u64 tick_interval;
221         u64 group_start = 0;
222         u64 group_count = 0;
223         u64 group_end = 0;
224         u64 i;
225         u64 max_seek = stat->max_seek_len;
226         int digits = 1;
227
228         if (stat->total_seeks < 20)
229                 return;
230
231         while ((max_seek /= 10))
232                 digits++;
233
234         /* Make a tick count as 5% of the total seeks */
235         tick_interval = stat->total_seeks / 20;
236         printf("\tSeek histogram\n");
237         for (; n; n = rb_next(n)) {
238                 u64 ticks, gticks = 0;
239
240                 seek = rb_entry(n, struct seek, n);
241                 ticks = seek->count / tick_interval;
242                 if (group_count)
243                         gticks = group_count / tick_interval;
244
245                 if (ticks <= 2 && gticks <= 2) {
246                         if (group_count == 0)
247                                 group_start = seek->distance;
248                         group_end = seek->distance;
249                         group_count += seek->count;
250                         continue;
251                 }
252
253                 if (group_count) {
254
255                         gticks = group_count / tick_interval;
256                         printf("\t\t%*Lu - %*Lu: %*Lu ", digits, group_start,
257                                digits, group_end, digits, group_count);
258                         if (gticks) {
259                                 for (i = 0; i < gticks; i++)
260                                         printf("#");
261                                 printf("\n");
262                         } else {
263                                 printf("|\n");
264                         }
265                         group_count = 0;
266                 }
267
268                 if (ticks <= 2)
269                         continue;
270
271                 printf("\t\t%*Lu - %*Lu: %*Lu ", digits, seek->distance,
272                        digits, seek->distance, digits, seek->count);
273                 for (i = 0; i < ticks; i++)
274                         printf("#");
275                 printf("\n");
276         }
277         if (group_count) {
278                 u64 gticks;
279
280                 gticks = group_count / tick_interval;
281                 printf("\t\t%*Lu - %*Lu: %*Lu ", digits, group_start,
282                        digits, group_end, digits, group_count);
283                 if (gticks) {
284                         for (i = 0; i < gticks; i++)
285                                 printf("#");
286                         printf("\n");
287                 } else {
288                         printf("|\n");
289                 }
290                 group_count = 0;
291         }
292 }
293
294 static void timeval_subtract(struct timeval *result, struct timeval *x,
295                              struct timeval *y)
296 {
297         if (x->tv_usec < y->tv_usec) {
298                 int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
299                 y->tv_usec -= 1000000 * nsec;
300                 y->tv_sec += nsec;
301         }
302
303         if (x->tv_usec - y->tv_usec > 1000000) {
304                 int nsec = (x->tv_usec - y->tv_usec) / 1000000;
305                 y->tv_usec += 1000000 * nsec;
306                 y->tv_sec -= nsec;
307         }
308
309         result->tv_sec = x->tv_sec - y->tv_sec;
310         result->tv_usec = x->tv_usec - y->tv_usec;
311 }
312
313 static int calc_root_size(struct btrfs_root *tree_root, struct btrfs_key *key,
314                           int find_inline)
315 {
316         struct btrfs_root *root;
317         struct btrfs_path path;
318         struct rb_node *n;
319         struct timeval start, end, diff = {0};
320         struct root_stats stat;
321         int level;
322         int ret = 0;
323         int size_fail = 0;
324
325         root = btrfs_read_fs_root(tree_root->fs_info, key);
326         if (IS_ERR(root)) {
327                 error("failed to read root %llu", key->objectid);
328                 return 1;
329         }
330
331         btrfs_init_path(&path);
332         memset(&stat, 0, sizeof(stat));
333         level = btrfs_header_level(root->node);
334         stat.lowest_bytenr = btrfs_header_bytenr(root->node);
335         stat.highest_bytenr = stat.lowest_bytenr;
336         stat.min_cluster_size = (u64)-1;
337         stat.max_cluster_size = root->nodesize;
338         path.nodes[level] = root->node;
339         if (gettimeofday(&start, NULL)) {
340                 error("cannot get time: %s", strerror(errno));
341                 goto out;
342         }
343         if (!level) {
344                 ret = walk_leaf(root, &path, &stat, find_inline);
345                 if (ret)
346                         goto out;
347                 goto out_print;
348         }
349
350         ret = walk_nodes(root, &path, &stat, level, find_inline);
351         if (ret)
352                 goto out;
353         if (gettimeofday(&end, NULL)) {
354                 error("cannot get time: %s", strerror(errno));
355                 goto out;
356         }
357         timeval_subtract(&diff, &end, &start);
358 out_print:
359         if (stat.min_cluster_size == (u64)-1) {
360                 stat.min_cluster_size = 0;
361                 stat.total_clusters = 1;
362         }
363
364         if (no_pretty || size_fail) {
365                 printf("\tTotal size: %llu\n", stat.total_bytes);
366                 printf("\t\tInline data: %llu\n", stat.total_inline);
367                 printf("\tTotal seeks: %llu\n", stat.total_seeks);
368                 printf("\t\tForward seeks: %llu\n", stat.forward_seeks);
369                 printf("\t\tBackward seeks: %llu\n", stat.backward_seeks);
370                 printf("\t\tAvg seek len: %llu\n", stat.total_seeks ?
371                         stat.total_seek_len / stat.total_seeks : 0);
372                 print_seek_histogram(&stat);
373                 printf("\tTotal clusters: %llu\n", stat.total_clusters);
374                 printf("\t\tAvg cluster size: %llu\n", stat.total_cluster_size /
375                        stat.total_clusters);
376                 printf("\t\tMin cluster size: %llu\n", stat.min_cluster_size);
377                 printf("\t\tMax cluster size: %llu\n", stat.max_cluster_size);
378                 printf("\tTotal disk spread: %llu\n", stat.highest_bytenr -
379                        stat.lowest_bytenr);
380                 printf("\tTotal read time: %d s %d us\n", (int)diff.tv_sec,
381                        (int)diff.tv_usec);
382                 printf("\tLevels: %d\n", level + 1);
383         } else {
384                 printf("\tTotal size: %s\n", pretty_size(stat.total_bytes));
385                 printf("\t\tInline data: %s\n", pretty_size(stat.total_inline));
386                 printf("\tTotal seeks: %llu\n", stat.total_seeks);
387                 printf("\t\tForward seeks: %llu\n", stat.forward_seeks);
388                 printf("\t\tBackward seeks: %llu\n", stat.backward_seeks);
389                 printf("\t\tAvg seek len: %s\n", stat.total_seeks ?
390                         pretty_size(stat.total_seek_len / stat.total_seeks) :
391                         pretty_size(0));
392                 print_seek_histogram(&stat);
393                 printf("\tTotal clusters: %llu\n", stat.total_clusters);
394                 printf("\t\tAvg cluster size: %s\n",
395                                 pretty_size((stat.total_cluster_size /
396                                                 stat.total_clusters)));
397                 printf("\t\tMin cluster size: %s\n",
398                                 pretty_size(stat.min_cluster_size));
399                 printf("\t\tMax cluster size: %s\n",
400                                 pretty_size(stat.max_cluster_size));
401                 printf("\tTotal disk spread: %s\n",
402                                 pretty_size(stat.highest_bytenr -
403                                         stat.lowest_bytenr));
404                 printf("\tTotal read time: %d s %d us\n", (int)diff.tv_sec,
405                        (int)diff.tv_usec);
406                 printf("\tLevels: %d\n", level + 1);
407         }
408 out:
409         while ((n = rb_first(&stat.seek_root)) != NULL) {
410                 struct seek *seek = rb_entry(n, struct seek, n);
411                 rb_erase(n, &stat.seek_root);
412                 free(seek);
413         }
414
415         /*
416          * We only use path to save node data in iterating, without holding
417          * eb's ref_cnt in path.  Don't use btrfs_release_path() here, it will
418          * free these eb again, and cause many problems, as negative ref_cnt or
419          * invalid memory access.
420          */
421         return ret;
422 }
423
424 const char * const cmd_inspect_tree_stats_usage[] = {
425         "btrfs inspect-internal tree-stats [options] <device>",
426         "Print various stats for trees",
427         "-b             raw numbers in bytes",
428         NULL
429 };
430
431 int cmd_inspect_tree_stats(int argc, char **argv)
432 {
433         struct btrfs_key key;
434         struct btrfs_root *root;
435         int opt;
436         int ret = 0;
437
438         while ((opt = getopt(argc, argv, "vb")) != -1) {
439                 switch (opt) {
440                 case 'v':
441                         verbose++;
442                         break;
443                 case 'b':
444                         no_pretty = 1;
445                         break;
446                 default:
447                         usage(cmd_inspect_tree_stats_usage);
448                 }
449         }
450
451         if (check_argc_exact(argc - optind, 1)) {
452                 usage(cmd_inspect_tree_stats_usage);
453         }
454
455         ret = check_mounted(argv[optind]);
456         if (ret < 0) {
457                 warning("unable to check mount status of: %s",
458                                 strerror(-ret));
459         } else if (ret) {
460                 warning("%s already mounted, results may be inaccurate",
461                                 argv[optind]);
462         }
463
464         root = open_ctree(argv[optind], 0, 0);
465         if (!root) {
466                 error("cannot open ctree");
467                 exit(1);
468         }
469
470         printf("Calculating size of root tree\n");
471         key.objectid = BTRFS_ROOT_TREE_OBJECTID;
472         ret = calc_root_size(root, &key, 0);
473         if (ret)
474                 goto out;
475
476         printf("Calculating size of extent tree\n");
477         key.objectid = BTRFS_EXTENT_TREE_OBJECTID;
478         ret = calc_root_size(root, &key, 0);
479         if (ret)
480                 goto out;
481
482         printf("Calculating size of csum tree\n");
483         key.objectid = BTRFS_CSUM_TREE_OBJECTID;
484         ret = calc_root_size(root, &key, 0);
485         if (ret)
486                 goto out;
487
488         key.objectid = BTRFS_FS_TREE_OBJECTID;
489         key.offset = (u64)-1;
490         printf("Calculating size of fs tree\n");
491         ret = calc_root_size(root, &key, 1);
492         if (ret)
493                 goto out;
494 out:
495         close_ctree(root);
496         return ret;
497 }