blob: 8aa17c895bb7b4acb775b0cbb95e0ce817cd66f9 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "libxfs.h"
#include "command.h"
#include "output.h"
#include "init.h"
#include "io.h"
#include "type.h"
#include "input.h"
#include "libfrog/convert.h"
static int refc_maxrecs(struct xfs_mount *mp, int blocklen, int leaf)
{
return libxfs_refcountbt_maxrecs(blocklen, leaf != 0);
}
static int rmap_maxrecs(struct xfs_mount *mp, int blocklen, int leaf)
{
return libxfs_rmapbt_maxrecs(blocklen, leaf);
}
struct btmap {
const char *tag;
int (*maxrecs)(struct xfs_mount *mp, int blocklen,
int leaf);
} maps[] = {
{"bnobt", libxfs_allocbt_maxrecs},
{"cntbt", libxfs_allocbt_maxrecs},
{"inobt", libxfs_inobt_maxrecs},
{"finobt", libxfs_inobt_maxrecs},
{"bmapbt", libxfs_bmbt_maxrecs},
{"refcountbt", refc_maxrecs},
{"rmapbt", rmap_maxrecs},
};
static void
btheight_help(void)
{
struct btmap *m;
int i;
dbprintf(_(
"\n"
" For a given number of btree records and a btree type, report the number of\n"
" records and blocks for each level of the btree, and the total btree size.\n"
" The btree type must be given after the options. A raw btree geometry can\n"
" be provided in the format \"record_bytes:key_bytes:ptr_bytes:header_type\"\n"
" where header_type is one of \"short\", \"long\", \"shortcrc\", or \"longcrc\".\n"
"\n"
" Options:\n"
" -b -- Override the btree block size.\n"
" -n -- Number of records we want to store.\n"
" -w max -- Show only the best case scenario.\n"
" -w min -- Show only the worst case scenario.\n"
"\n"
" Supported btree types:\n"
" "
));
for (i = 0, m = maps; i < ARRAY_SIZE(maps); i++, m++)
printf("%s ", m->tag);
printf("\n");
}
static void
calc_height(
unsigned long long nr_records,
uint *records_per_block)
{
unsigned int level = 0;
unsigned long long total_blocks = 0;
unsigned long long blocks;
char *levels_suffix = "s";
char *totblocks_suffix = "s";
while (nr_records) {
unsigned int level_rpb = records_per_block[level != 0];
char *recs_suffix = "s";
char *blocks_suffix = "s";
blocks = (nr_records + level_rpb - 1) / level_rpb;
if (nr_records == 1)
recs_suffix = "";
if (blocks == 1)
blocks_suffix = "";
printf(_("level %u: %llu record%s, %llu block%s\n"),
level, nr_records, recs_suffix, blocks,
blocks_suffix);
total_blocks += blocks;
nr_records = blocks == 1 ? 0 : blocks;
level++;
}
if (level == 1)
levels_suffix = "";
if (total_blocks == 1)
totblocks_suffix = "";
printf(_("%u level%s, %llu block%s total\n"), level, levels_suffix,
total_blocks, totblocks_suffix);
}
static int
construct_records_per_block(
char *tag,
int blocksize,
unsigned int *records_per_block)
{
char *toktag;
struct btmap *m;
unsigned int record_size, key_size, ptr_size;
char *p;
int i, ret;
for (i = 0, m = maps; i < ARRAY_SIZE(maps); i++, m++) {
if (!strcmp(m->tag, tag)) {
records_per_block[0] = m->maxrecs(mp, blocksize, 1);
records_per_block[1] = m->maxrecs(mp, blocksize, 0);
return 0;
}
}
toktag = strdup(tag);
ret = -1;
p = strtok(toktag, ":");
if (!p) {
fprintf(stderr, _("%s: record size not found.\n"), tag);
goto out;
}
record_size = cvt_u16(p, 0);
if (errno) {
perror(p);
goto out;
}
if (record_size == 0) {
fprintf(stderr, _("%s: record size cannot be zero.\n"), tag);
goto out;
}
p = strtok(NULL, ":");
if (!p) {
fprintf(stderr, _("%s: key size not found.\n"), tag);
goto out;
}
key_size = cvt_u16(p, 0);
if (errno) {
perror(p);
goto out;
}
if (key_size == 0) {
fprintf(stderr, _("%s: key size cannot be zero.\n"), tag);
goto out;
}
p = strtok(NULL, ":");
if (!p) {
fprintf(stderr, _("%s: pointer size not found.\n"), tag);
goto out;
}
ptr_size = cvt_u16(p, 0);
if (errno) {
perror(p);
goto out;
}
if (ptr_size == 0) {
fprintf(stderr, _("%s: pointer size cannot be zero.\n"), tag);
goto out;
}
p = strtok(NULL, ":");
if (!p) {
fprintf(stderr, _("%s: header type not found.\n"), tag);
goto out;
}
if (!strcmp(p, "short"))
blocksize -= XFS_BTREE_SBLOCK_LEN;
else if (!strcmp(p, "shortcrc"))
blocksize -= XFS_BTREE_SBLOCK_CRC_LEN;
else if (!strcmp(p, "long"))
blocksize -= XFS_BTREE_LBLOCK_LEN;
else if (!strcmp(p, "longcrc"))
blocksize -= XFS_BTREE_LBLOCK_CRC_LEN;
else {
fprintf(stderr, _("%s: unrecognized btree header type."),
p);
goto out;
}
if (record_size > blocksize) {
fprintf(stderr,
_("%s: record size must be less than selected block size (%u bytes).\n"),
tag, blocksize);
goto out;
}
if (key_size > blocksize) {
fprintf(stderr,
_("%s: key size must be less than selected block size (%u bytes).\n"),
tag, blocksize);
goto out;
}
if (ptr_size > blocksize) {
fprintf(stderr,
_("%s: pointer size must be less than selected block size (%u bytes).\n"),
tag, blocksize);
goto out;
}
p = strtok(NULL, ":");
if (p) {
fprintf(stderr,
_("%s: unrecognized raw btree geometry."),
tag);
goto out;
}
records_per_block[0] = blocksize / record_size;
records_per_block[1] = blocksize / (key_size + ptr_size);
ret = 0;
out:
free(toktag);
return ret;
}
#define REPORT_DEFAULT (-1U)
#define REPORT_MAX (1 << 0)
#define REPORT_MIN (1 << 1)
static void
report(
char *tag,
unsigned int report_what,
unsigned long long nr_records,
unsigned int blocksize)
{
unsigned int records_per_block[2];
int ret;
ret = construct_records_per_block(tag, blocksize, records_per_block);
if (ret)
return;
if (report_what & REPORT_MAX) {
if (records_per_block[0] < 2) {
fprintf(stderr,
_("%s: cannot calculate best case scenario due to leaf geometry underflow.\n"),
tag);
return;
}
if (records_per_block[1] < 4) {
fprintf(stderr,
_("%s: cannot calculate best case scenario due to node geometry underflow.\n"),
tag);
return;
}
printf(
_("%s: best case per %u-byte block: %u records (leaf) / %u keyptrs (node)\n"),
tag, blocksize, records_per_block[0],
records_per_block[1]);
calc_height(nr_records, records_per_block);
}
if (report_what & REPORT_MIN) {
records_per_block[0] /= 2;
records_per_block[1] /= 2;
if (records_per_block[0] < 1) {
fprintf(stderr,
_("%s: cannot calculate worst case scenario due to leaf geometry underflow.\n"),
tag);
return;
}
if (records_per_block[1] < 2) {
fprintf(stderr,
_("%s: cannot calculate worst case scenario due to node geometry underflow.\n"),
tag);
return;
}
printf(
_("%s: worst case per %u-byte block: %u records (leaf) / %u keyptrs (node)\n"),
tag, blocksize, records_per_block[0],
records_per_block[1]);
calc_height(nr_records, records_per_block);
}
}
static int
btheight_f(
int argc,
char **argv)
{
long long blocksize = mp->m_sb.sb_blocksize;
uint64_t nr_records = 0;
int report_what = REPORT_DEFAULT;
int i, c;
while ((c = getopt(argc, argv, "b:n:w:")) != -1) {
switch (c) {
case 'b':
errno = 0;
blocksize = cvtnum(mp->m_sb.sb_blocksize,
mp->m_sb.sb_sectsize,
optarg);
if (errno) {
perror(optarg);
return 0;
}
break;
case 'n':
nr_records = cvt_u64(optarg, 0);
if (errno) {
perror(optarg);
return 0;
}
break;
case 'w':
if (!strcmp(optarg, "min"))
report_what = REPORT_MIN;
else if (!strcmp(optarg, "max"))
report_what = REPORT_MAX;
else {
btheight_help();
return 0;
}
break;
default:
btheight_help();
return 0;
}
}
if (nr_records == 0) {
fprintf(stderr,
_("Number of records must be greater than zero.\n"));
return 0;
}
if (blocksize > INT_MAX) {
fprintf(stderr,
_("The largest block size this command will consider is %u bytes.\n"),
INT_MAX);
return 0;
}
if (blocksize < 128) {
fprintf(stderr,
_("The smallest block size this command will consider is 128 bytes.\n"));
return 0;
}
if (argc == optind) {
btheight_help();
return 0;
}
for (i = optind; i < argc; i++)
report(argv[i], report_what, nr_records, blocksize);
return 0;
}
static const cmdinfo_t btheight_cmd =
{ "btheight", "b", btheight_f, 1, -1, 0,
"[-b blksz] [-n recs] [-w max|-w min] btree types...",
N_("compute btree heights"), btheight_help };
void
btheight_init(void)
{
add_command(&btheight_cmd);
}