btrfs-progs: inspect list-chunks: better sorting, updated output
Enhance the sorting capabilities of 'inspect list-chunks' to allow multiple keys. Drop the gaps, this works only for pstart and it's hard to make it work with arbitrary sort keys. Usage is printed by default, assuming this is an interesting info and even if it slows down the output (due to extra lookups) it's more convenient to print it rather than not. The options related to usage and empty were removed. Output changes: - rename Number to PNumber, meaning physical number on the device - print Devid, device number, can be also sort key Examples: btrfs inspect list-chunks /mnt btrfs inspect list-chunks --sort length,usage btrfs inspect list-chunks --sort lstart Depending on the sort key order, the output can be wild, for that the PNumber and LNumber give some hint where the chunks lie in their space. Example output: $ sudo ./btrfs inspect list-chunks --sort length,usage / Devid PNumber Type/profile PStart Length PEnd LNumber LStart Usage% ----- ------- ----------------- --------- --------- --------- ------- --------- ------ 1 7 Data/single 1.52GiB 16.00MiB 1.54GiB 69 191.68GiB 86.04 1 3 System/DUP 117.00MiB 32.00MiB 149.00MiB 40 140.17GiB 0.05 1 2 System/DUP 85.00MiB 32.00MiB 117.00MiB 39 140.17GiB 0.05 1 15 Data/single 8.04GiB 64.00MiB 8.10GiB 61 188.60GiB 94.46 1 1 Data/single 1.00MiB 84.00MiB 85.00MiB 68 191.60GiB 74.24 1 5 Metadata/DUP 341.00MiB 192.00MiB 533.00MiB 60 188.41GiB 82.58 1 4 Metadata/DUP 149.00MiB 192.00MiB 341.00MiB 59 188.41GiB 82.58 1 20 Metadata/DUP 9.29GiB 256.00MiB 9.54GiB 38 139.92GiB 57.76 1 19 Metadata/DUP 9.04GiB 256.00MiB 9.29GiB 37 139.92GiB 57.76 1 22 Metadata/DUP 9.79GiB 256.00MiB 10.04GiB 25 113.15GiB 57.93 1 21 Metadata/DUP 9.54GiB 256.00MiB 9.79GiB 24 113.15GiB 57.93 1 46 Metadata/DUP 29.29GiB 256.00MiB 29.54GiB 43 142.71GiB 62.38 Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
ba24cf8498
commit
db2cd62f2c
275
cmds/inspect.c
275
cmds/inspect.c
|
@ -718,7 +718,7 @@ struct list_chunks_entry {
|
|||
u64 flags;
|
||||
u64 lnumber;
|
||||
u64 used;
|
||||
u32 number;
|
||||
u64 pnumber;
|
||||
};
|
||||
|
||||
struct list_chunks_ctx {
|
||||
|
@ -727,7 +727,19 @@ struct list_chunks_ctx {
|
|||
struct list_chunks_entry *stats;
|
||||
};
|
||||
|
||||
static int cmp_cse_devid_start(const void *va, const void *vb)
|
||||
static int cmp_cse_devid(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
||||
if (a->devid < b->devid)
|
||||
return -1;
|
||||
if (a->devid > b->devid)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmp_cse_devid_pstart(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
@ -739,38 +751,37 @@ static int cmp_cse_devid_start(const void *va, const void *vb)
|
|||
|
||||
if (a->start < b->start)
|
||||
return -1;
|
||||
if (a->start == b->start) {
|
||||
error(
|
||||
"chunks start on same offset in the same device: devid %llu start %llu",
|
||||
a->devid, a->start);
|
||||
if (a->start == b->start)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cmp_cse_devid_lstart(const void *va, const void *vb)
|
||||
static int cmp_cse_pstart(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
||||
if (a->devid < b->devid)
|
||||
if (a->start < b->start)
|
||||
return -1;
|
||||
if (a->devid > b->devid)
|
||||
return 1;
|
||||
if (a->start == b->start)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cmp_cse_lstart(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
||||
if (a->lstart < b->lstart)
|
||||
return -1;
|
||||
if (a->lstart == b->lstart) {
|
||||
error(
|
||||
"chunks logically start on same offset in the same device: devid %llu start %llu",
|
||||
a->devid, a->lstart);
|
||||
if (a->lstart == b->lstart)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Compare entries by usage percent, descending. */
|
||||
static int cmp_cse_devid_usage(const void *va, const void *vb)
|
||||
static int cmp_cse_usage(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
@ -785,7 +796,7 @@ static int cmp_cse_devid_usage(const void *va, const void *vb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cmp_cse_length_physical(const void *va, const void *vb)
|
||||
static int cmp_cse_length(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
@ -794,173 +805,126 @@ static int cmp_cse_length_physical(const void *va, const void *vb)
|
|||
return -1;
|
||||
if (a->length > b->length)
|
||||
return 1;
|
||||
|
||||
if (a->start < b->start)
|
||||
return -1;
|
||||
if (a->start > b->start)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmp_cse_length_logical(const void *va, const void *vb)
|
||||
{
|
||||
const struct list_chunks_entry *a = va;
|
||||
const struct list_chunks_entry *b = vb;
|
||||
|
||||
if (a->length < b->length)
|
||||
return -1;
|
||||
if (a->length > b->length)
|
||||
return 1;
|
||||
|
||||
if (a->lstart < b->lstart)
|
||||
return -1;
|
||||
if (a->lstart > b->lstart)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_list_chunks(struct list_chunks_ctx *ctx, const char* sortmode,
|
||||
unsigned unit_mode, bool with_usage, bool with_empty)
|
||||
static int print_list_chunks(struct list_chunks_ctx *ctx, const char *sortmode,
|
||||
unsigned unit_mode)
|
||||
{
|
||||
u64 devid;
|
||||
struct list_chunks_entry e;
|
||||
struct string_table *table;
|
||||
int col_count, col;
|
||||
int i;
|
||||
int chidx;
|
||||
u64 lastend;
|
||||
u64 number;
|
||||
u32 gaps;
|
||||
u32 tabidx;
|
||||
static const struct sortdef sortit[] = {
|
||||
{ .name = "pstart", .comp = (sort_cmp_t)cmp_cse_devid_start,
|
||||
.desc = "sort by physical srart offset, group by device" },
|
||||
{ .name = "lstart", .comp = (sort_cmp_t)cmp_cse_devid_lstart,
|
||||
.desc = "sort by logical offset" },
|
||||
{ .name = "usage", .comp = (sort_cmp_t)cmp_cse_devid_usage,
|
||||
.desc = "sort by chunk usage" },
|
||||
{ .name = "length_p", .comp = (sort_cmp_t)cmp_cse_length_physical,
|
||||
.desc = "sort by lentgh, secondary by physical offset" },
|
||||
{ .name = "length_l", .comp = (sort_cmp_t)cmp_cse_length_logical,
|
||||
.desc = "sort by lentgh, secondary by logical offset" },
|
||||
};
|
||||
enum {
|
||||
CHUNK_SORT_PSTART,
|
||||
CHUNK_SORT_LSTART,
|
||||
CHUNK_SORT_USAGE,
|
||||
CHUNK_SORT_LENGTH_P,
|
||||
CHUNK_SORT_LENGTH_L,
|
||||
CHUNK_SORT_LENGTH,
|
||||
CHUNK_SORT_DEFAULT = CHUNK_SORT_PSTART
|
||||
};
|
||||
unsigned int sort_mode;
|
||||
static const struct sortdef sortit[] = {
|
||||
{ .name = "devid", .comp = (sort_cmp_t)cmp_cse_devid,
|
||||
.desc = "sort by device id (default, with pstart)",
|
||||
.id = CHUNK_SORT_PSTART
|
||||
},
|
||||
{ .name = "pstart", .comp = (sort_cmp_t)cmp_cse_pstart,
|
||||
.desc = "sort by physical start offset",
|
||||
.id = CHUNK_SORT_PSTART
|
||||
},
|
||||
{ .name = "lstart", .comp = (sort_cmp_t)cmp_cse_lstart,
|
||||
.desc = "sort by logical offset",
|
||||
.id = CHUNK_SORT_LSTART
|
||||
},
|
||||
{ .name = "usage", .comp = (sort_cmp_t)cmp_cse_usage,
|
||||
.desc = "sort by chunk usage",
|
||||
.id = CHUNK_SORT_USAGE
|
||||
},
|
||||
{ .name = "length", .comp = (sort_cmp_t)cmp_cse_length,
|
||||
.desc = "sort by lentgh",
|
||||
.id = CHUNK_SORT_LENGTH
|
||||
},
|
||||
SORTDEF_END
|
||||
};
|
||||
const char *tmp;
|
||||
struct compare comp;
|
||||
int id;
|
||||
|
||||
if (!sortmode) {
|
||||
sort_mode = CHUNK_SORT_DEFAULT;
|
||||
sortmode = "pstart";
|
||||
} else if (strcmp(sortmode, "pstart") == 0) {
|
||||
sort_mode = CHUNK_SORT_PSTART;
|
||||
} else if (strcmp(sortmode, "lstart") == 0) {
|
||||
sort_mode = CHUNK_SORT_LSTART;
|
||||
} else if (strcmp(sortmode, "usage") == 0) {
|
||||
sort_mode = CHUNK_SORT_USAGE;
|
||||
with_usage = true;
|
||||
} else if (strcmp(sortmode, "length_p") == 0) {
|
||||
sort_mode = CHUNK_SORT_LENGTH_P;
|
||||
} else if (strcmp(sortmode, "length_l") == 0) {
|
||||
sort_mode = CHUNK_SORT_LENGTH_L;
|
||||
} else {
|
||||
error("unknown sort mode: %s", sortmode);
|
||||
exit(1);
|
||||
}
|
||||
compare_init(&comp, sortit);
|
||||
|
||||
tmp = sortmode;
|
||||
do {
|
||||
id = compare_parse_key_to_id(&comp, &tmp);
|
||||
if (id == -1) {
|
||||
error("unknown sort key: %s", tmp);
|
||||
return 1;
|
||||
}
|
||||
compare_add_sort_id(&comp, id);
|
||||
} while (id >= 0);
|
||||
|
||||
/*
|
||||
* Chunks are sorted logically as found by the ioctl, we need to sort
|
||||
* them once to find the physical ordering. This is the default mode.
|
||||
*/
|
||||
qsort(ctx->stats, ctx->length, sizeof(ctx->stats[0]), cmp_cse_devid_start);
|
||||
qsort(ctx->stats, ctx->length, sizeof(ctx->stats[0]), cmp_cse_devid_pstart);
|
||||
devid = 0;
|
||||
number = 0;
|
||||
gaps = 0;
|
||||
lastend = 0;
|
||||
for (i = 0; i < ctx->length; i++) {
|
||||
e = ctx->stats[i];
|
||||
if (e.devid != devid) {
|
||||
devid = e.devid;
|
||||
number = 0;
|
||||
}
|
||||
ctx->stats[i].number = number;
|
||||
number++;
|
||||
if (with_empty && sort_mode == CHUNK_SORT_PSTART && e.start != lastend)
|
||||
gaps++;
|
||||
lastend = e.start + e.length;
|
||||
ctx->stats[i].pnumber = number++;
|
||||
}
|
||||
|
||||
compare_init(&comp, sortit);
|
||||
compare_add_sort_key(&comp, sortmode);
|
||||
qsort_r(ctx->stats, ctx->length, sizeof(ctx->stats[0]), (sort_r_cmp_t)compare_cmp_multi,
|
||||
&comp);
|
||||
/* Skip additonal sort if nothing defined by user. */
|
||||
if (comp.count > 0)
|
||||
qsort_r(ctx->stats, ctx->length, sizeof(ctx->stats[0]),
|
||||
(sort_r_cmp_t)compare_cmp_multi, &comp);
|
||||
|
||||
/* Optional usage, two rows for header and separator, gaps */
|
||||
table = table_create(7 + (int)with_usage, 2 + ctx->length + gaps);
|
||||
col_count = 9;
|
||||
/* Two rows for header and separator. */
|
||||
table = table_create(col_count, 2 + ctx->length);
|
||||
if (!table) {
|
||||
error_msg(ERROR_MSG_MEMORY, NULL);
|
||||
return 1;
|
||||
}
|
||||
devid = 0;
|
||||
/* Print header */
|
||||
col = 0;
|
||||
tabidx = 0;
|
||||
chidx = 1;
|
||||
table_printf(table, col++, tabidx, ">Devid");
|
||||
table_printf(table, col++, tabidx, ">PNumber");
|
||||
table_printf(table, col++, tabidx, ">Type/profile");
|
||||
table_printf(table, col++, tabidx, ">PStart");
|
||||
table_printf(table, col++, tabidx, ">Length");
|
||||
table_printf(table, col++, tabidx, ">PEnd");
|
||||
table_printf(table, col++, tabidx, ">LNumber");
|
||||
table_printf(table, col++, tabidx, ">LStart");
|
||||
table_printf(table, col++, tabidx, ">Usage%%");
|
||||
for (int j = 0; j < col_count; j++)
|
||||
table_printf(table, j, tabidx + 1, "*-");
|
||||
|
||||
tabidx = 2;
|
||||
devid = 0;
|
||||
for (i = 0; i < ctx->length; i++) {
|
||||
e = ctx->stats[i];
|
||||
/* TODO: print header and devid */
|
||||
if (e.devid != devid) {
|
||||
int j;
|
||||
|
||||
if (e.devid != devid)
|
||||
devid = e.devid;
|
||||
table_printf(table, 0, tabidx, ">Number");
|
||||
table_printf(table, 1, tabidx, ">Type/profile");
|
||||
table_printf(table, 2, tabidx, ">PStart");
|
||||
table_printf(table, 3, tabidx, ">Length");
|
||||
table_printf(table, 4, tabidx, ">PEnd");
|
||||
table_printf(table, 5, tabidx, ">LNumber");
|
||||
table_printf(table, 6, tabidx, ">LStart");
|
||||
if (with_usage) {
|
||||
table_printf(table, 7, tabidx, ">Usage%%");
|
||||
table_printf(table, 7, tabidx + 1, "*-");
|
||||
}
|
||||
for (j = 0; j < 7; j++)
|
||||
table_printf(table, j, tabidx + 1, "*-");
|
||||
|
||||
chidx = 1;
|
||||
lastend = 0;
|
||||
tabidx += 2;
|
||||
}
|
||||
if (with_empty && sort_mode == CHUNK_SORT_PSTART && e.start != lastend) {
|
||||
table_printf(table, 0, tabidx, ">-");
|
||||
table_printf(table, 1, tabidx, ">%s", "empty");
|
||||
table_printf(table, 2, tabidx, ">-");
|
||||
table_printf(table, 3, tabidx, ">%s",
|
||||
pretty_size_mode(e.start - lastend, unit_mode));
|
||||
table_printf(table, 4, tabidx, ">-");
|
||||
table_printf(table, 5, tabidx, ">-");
|
||||
table_printf(table, 6, tabidx, ">-");
|
||||
if (with_usage)
|
||||
table_printf(table, 7, tabidx, ">-");
|
||||
tabidx++;
|
||||
}
|
||||
|
||||
table_printf(table, 0, tabidx, ">%llu", chidx++);
|
||||
table_printf(table, 1, tabidx, ">%10s/%-6s",
|
||||
col = 0;
|
||||
table_printf(table, col++, tabidx, ">%llu", e.devid);
|
||||
table_printf(table, col++, tabidx, ">%llu", e.pnumber + 1);
|
||||
table_printf(table, col++, tabidx, ">%10s/%-6s",
|
||||
btrfs_group_type_str(e.flags),
|
||||
btrfs_group_profile_str(e.flags));
|
||||
table_printf(table, 2, tabidx, ">%s", pretty_size_mode(e.start, unit_mode));
|
||||
table_printf(table, 3, tabidx, ">%s", pretty_size_mode(e.length, unit_mode));
|
||||
table_printf(table, 4, tabidx, ">%s", pretty_size_mode(e.start + e.length, unit_mode));
|
||||
table_printf(table, 5, tabidx, ">%llu", e.lnumber + 1);
|
||||
table_printf(table, 6, tabidx, ">%s", pretty_size_mode(e.lstart, unit_mode));
|
||||
if (with_usage)
|
||||
table_printf(table, 7, tabidx, ">%6.2f",
|
||||
(float)e.used / e.length * 100);
|
||||
lastend = e.start + e.length;
|
||||
table_printf(table, col++, tabidx, ">%s", pretty_size_mode(e.start, unit_mode));
|
||||
table_printf(table, col++, tabidx, ">%s", pretty_size_mode(e.length, unit_mode));
|
||||
table_printf(table, col++, tabidx, ">%s", pretty_size_mode(e.start + e.length, unit_mode));
|
||||
table_printf(table, col++, tabidx, ">%llu", e.lnumber + 1);
|
||||
table_printf(table, col++, tabidx, ">%s", pretty_size_mode(e.lstart, unit_mode));
|
||||
table_printf(table, col++, tabidx, ">%6.2f", (float)e.used / e.length * 100);
|
||||
tabidx++;
|
||||
}
|
||||
table_dump(table);
|
||||
|
@ -1019,8 +983,6 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
int i;
|
||||
unsigned unit_mode;
|
||||
char *sortmode = NULL;
|
||||
bool with_usage = true;
|
||||
bool with_empty = true;
|
||||
const char *path;
|
||||
struct list_chunks_ctx ctx = {
|
||||
.length = 0,
|
||||
|
@ -1033,15 +995,10 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
while (1) {
|
||||
int c;
|
||||
enum { GETOPT_VAL_SORT = GETOPT_VAL_FIRST,
|
||||
GETOPT_VAL_USAGE, GETOPT_VAL_NO_USAGE,
|
||||
GETOPT_VAL_EMPTY, GETOPT_VAL_NO_EMPTY
|
||||
};
|
||||
static const struct option long_options[] = {
|
||||
{"sort", required_argument, NULL, GETOPT_VAL_SORT },
|
||||
{"usage", no_argument, NULL, GETOPT_VAL_USAGE },
|
||||
{"no-usage", no_argument, NULL, GETOPT_VAL_NO_USAGE },
|
||||
{"empty", no_argument, NULL, GETOPT_VAL_EMPTY },
|
||||
{"no-empty", no_argument, NULL, GETOPT_VAL_NO_EMPTY },
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
|
@ -1054,14 +1011,6 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
free(sortmode);
|
||||
sortmode = strdup(optarg);
|
||||
break;
|
||||
case GETOPT_VAL_USAGE:
|
||||
case GETOPT_VAL_NO_USAGE:
|
||||
with_usage = (c == GETOPT_VAL_USAGE);
|
||||
break;
|
||||
case GETOPT_VAL_EMPTY:
|
||||
case GETOPT_VAL_NO_EMPTY:
|
||||
with_empty = (c == GETOPT_VAL_EMPTY);
|
||||
break;
|
||||
default:
|
||||
usage_unknown_option(cmd, argv);
|
||||
}
|
||||
|
@ -1136,7 +1085,7 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
e->lstart = sh.offset;
|
||||
e->length = item->length;
|
||||
e->flags = item->type;
|
||||
e->number = -1;
|
||||
e->pnumber = -1;
|
||||
while (devid > lnumber_size) {
|
||||
u64 *tmp;
|
||||
unsigned old_size = lnumber_size;
|
||||
|
@ -1152,13 +1101,9 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
lnumber = tmp;
|
||||
}
|
||||
e->lnumber = lnumber[devid]++;
|
||||
if (with_usage) {
|
||||
if (used == (u64)-1)
|
||||
used = fill_usage(fd, sh.offset);
|
||||
e->used = used;
|
||||
} else {
|
||||
e->used = 0;
|
||||
}
|
||||
if (used == (u64)-1)
|
||||
used = fill_usage(fd, sh.offset);
|
||||
e->used = used;
|
||||
|
||||
ctx.length++;
|
||||
|
||||
|
@ -1184,7 +1129,7 @@ static int cmd_inspect_list_chunks(const struct cmd_struct *cmd,
|
|||
break;
|
||||
}
|
||||
|
||||
ret = print_list_chunks(&ctx, sortmode, unit_mode, with_usage, with_empty);
|
||||
ret = print_list_chunks(&ctx, sortmode, unit_mode);
|
||||
close(fd);
|
||||
|
||||
out:
|
||||
|
|
|
@ -74,6 +74,8 @@ void test() {
|
|||
typedef int (*sort_cmp_t)(const void *a, const void *b);
|
||||
typedef int (*sort_r_cmp_t)(const void *a, const void *b, void *data);
|
||||
|
||||
#define SORTDEF_END { .name = NULL, .comp = NULL }
|
||||
|
||||
struct sortdef {
|
||||
const char *name;
|
||||
const char *desc;
|
||||
|
|
Loading…
Reference in New Issue