blob: de301c195fb3b476073e65663e3d574f464e9a33 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
* Copyright (c) 2012 Red Hat, Inc.
* Copyright (c) 2017 Oracle.
* All Rights Reserved.
*/
#include "libxfs.h"
#include <linux/fiemap.h>
#include "libfrog/fsgeom.h"
#include "command.h"
#include "init.h"
#include "libfrog/paths.h"
#include "space.h"
#include "input.h"
struct histent
{
long long low;
long long high;
long long count;
long long blocks;
};
static int agcount;
static xfs_agnumber_t *aglist;
static struct histent *hist;
static int dumpflag;
static long long equalsize;
static long long multsize;
static int histcount;
static int seen1;
static int summaryflag;
static int gflag;
static bool rtflag;
static long long totblocks;
static long long totexts;
static cmdinfo_t freesp_cmd;
static void
addhistent(
long long h)
{
if (histcount == INT_MAX) {
printf(_("Too many histogram buckets.\n"));
return;
}
hist = realloc(hist, (histcount + 1) * sizeof(*hist));
if (h == 0)
h = 1;
hist[histcount].low = h;
hist[histcount].count = hist[histcount].blocks = 0;
histcount++;
if (h == 1)
seen1 = 1;
}
static void
addtohist(
xfs_agnumber_t agno,
xfs_agblock_t agbno,
off64_t len)
{
long i;
if (dumpflag)
printf("%8d %8d %8"PRId64"\n", agno, agbno, len);
totexts++;
totblocks += len;
for (i = 0; i < histcount; i++) {
if (hist[i].high >= len) {
hist[i].count++;
hist[i].blocks += len;
break;
}
}
}
static int
hcmp(
const void *a,
const void *b)
{
return ((struct histent *)a)->low - ((struct histent *)b)->low;
}
static void
histinit(
long long maxlen)
{
long long i;
if (equalsize) {
for (i = 1; i < maxlen; i += equalsize)
addhistent(i);
} else if (multsize) {
for (i = 1; i < maxlen; i *= multsize)
addhistent(i);
} else {
if (!seen1)
addhistent(1);
qsort(hist, histcount, sizeof(*hist), hcmp);
}
for (i = 0; i < histcount; i++) {
if (i < histcount - 1)
hist[i].high = hist[i + 1].low - 1;
else
hist[i].high = maxlen;
}
}
static void
printhist(void)
{
int i;
printf("%7s %7s %7s %7s %6s\n",
_("from"), _("to"), _("extents"), _("blocks"), _("pct"));
for (i = 0; i < histcount; i++) {
if (hist[i].count)
printf("%7lld %7lld %7lld %7lld %6.2f\n", hist[i].low,
hist[i].high, hist[i].count, hist[i].blocks,
hist[i].blocks * 100.0 / totblocks);
}
}
static int
inaglist(
xfs_agnumber_t agno)
{
int i;
if (agcount == 0)
return 1;
for (i = 0; i < agcount; i++)
if (aglist[i] == agno)
return 1;
return 0;
}
#define NR_EXTENTS 128
static void
scan_ag(
xfs_agnumber_t agno)
{
struct fsmap_head *fsmap;
struct fsmap *extent;
struct fsmap *l, *h;
struct fsmap *p;
struct xfs_fd *xfd = &file->xfd;
off64_t aglen;
xfs_agblock_t agbno;
unsigned long long freeblks = 0;
unsigned long long freeexts = 0;
int ret;
int i;
fsmap = malloc(fsmap_sizeof(NR_EXTENTS));
if (!fsmap) {
fprintf(stderr, _("%s: fsmap malloc failed.\n"), progname);
exitcode = 1;
return;
}
memset(fsmap, 0, sizeof(*fsmap));
fsmap->fmh_count = NR_EXTENTS;
l = fsmap->fmh_keys;
h = fsmap->fmh_keys + 1;
if (agno != NULLAGNUMBER) {
l->fmr_physical = cvt_agbno_to_b(xfd, agno, 0);
h->fmr_physical = cvt_agbno_to_b(xfd, agno + 1, 0);
l->fmr_device = h->fmr_device = file->fs_path.fs_datadev;
} else {
l->fmr_physical = 0;
h->fmr_physical = ULLONG_MAX;
l->fmr_device = h->fmr_device = file->fs_path.fs_rtdev;
}
h->fmr_owner = ULLONG_MAX;
h->fmr_flags = UINT_MAX;
h->fmr_offset = ULLONG_MAX;
while (true) {
ret = ioctl(file->xfd.fd, FS_IOC_GETFSMAP, fsmap);
if (ret < 0) {
fprintf(stderr, _("%s: FS_IOC_GETFSMAP [\"%s\"]: %s\n"),
progname, file->name, strerror(errno));
free(fsmap);
exitcode = 1;
return;
}
/* No more extents to map, exit */
if (!fsmap->fmh_entries)
break;
for (i = 0, extent = fsmap->fmh_recs;
i < fsmap->fmh_entries;
i++, extent++) {
if (!(extent->fmr_flags & FMR_OF_SPECIAL_OWNER) ||
extent->fmr_owner != XFS_FMR_OWN_FREE)
continue;
agbno = cvt_b_to_agbno(xfd, extent->fmr_physical);
aglen = cvt_b_to_off_fsbt(xfd, extent->fmr_length);
freeblks += aglen;
freeexts++;
addtohist(agno, agbno, aglen);
}
p = &fsmap->fmh_recs[fsmap->fmh_entries - 1];
if (p->fmr_flags & FMR_OF_LAST)
break;
fsmap_advance(fsmap);
}
if (gflag) {
if (agno == NULLAGNUMBER)
printf(_(" rtdev %10llu %10llu\n"), freeexts,
freeblks);
else
printf(_("%10u %10llu %10llu\n"), agno, freeexts,
freeblks);
}
free(fsmap);
}
static void
aglistadd(
char *a)
{
xfs_agnumber_t x;
aglist = realloc(aglist, (agcount + 1) * sizeof(*aglist));
x = cvt_u32(a, 0);
if (errno) {
printf(_("Unrecognized AG number: %s\n"), a);
return;
}
aglist[agcount] = x;
agcount++;
}
static int
init(
int argc,
char **argv)
{
struct xfs_fsop_geom *fsgeom = &file->xfd.fsgeom;
long long x;
int c;
int speced = 0; /* only one of -b -e -h or -m */
agcount = dumpflag = equalsize = multsize = optind = gflag = 0;
histcount = seen1 = summaryflag = 0;
totblocks = totexts = 0;
aglist = NULL;
hist = NULL;
rtflag = false;
while ((c = getopt(argc, argv, "a:bde:gh:m:rs")) != EOF) {
switch (c) {
case 'a':
aglistadd(optarg);
break;
case 'b':
if (speced)
goto many_spec;
multsize = 2;
speced = 1;
break;
case 'd':
dumpflag = 1;
break;
case 'e':
if (speced)
goto many_spec;
equalsize = cvt_s64(optarg, 0);
if (errno)
return command_usage(&freesp_cmd);
speced = 1;
break;
case 'g':
histcount = 0;
gflag++;
break;
case 'h':
if (speced && !histcount)
goto many_spec;
/* addhistent increments histcount */
x = cvt_s64(optarg, 0);
if (errno)
return command_usage(&freesp_cmd);
addhistent(x);
speced = 1;
break;
case 'm':
if (speced)
goto many_spec;
multsize = cvt_s64(optarg, 0);
if (errno)
return command_usage(&freesp_cmd);
speced = 1;
break;
case 'r':
rtflag = true;
break;
case 's':
summaryflag = 1;
break;
default:
return command_usage(&freesp_cmd);
}
}
if (optind != argc)
return 0;
if (!speced)
multsize = 2;
histinit(fsgeom->agblocks);
return 1;
many_spec:
return command_usage(&freesp_cmd);
}
/*
* Report on freespace usage in xfs filesystem.
*/
static int
freesp_f(
int argc,
char **argv)
{
struct xfs_fsop_geom *fsgeom = &file->xfd.fsgeom;
xfs_agnumber_t agno;
if (!init(argc, argv))
return 0;
if (gflag)
printf(_(" AG extents blocks\n"));
if (rtflag)
scan_ag(NULLAGNUMBER);
for (agno = 0; !rtflag && agno < fsgeom->agcount; agno++) {
if (inaglist(agno))
scan_ag(agno);
}
if (histcount && !gflag)
printhist();
if (summaryflag) {
printf(_("total free extents %lld\n"), totexts);
printf(_("total free blocks %lld\n"), totblocks);
printf(_("average free extent size %g\n"),
(double)totblocks / (double)totexts);
}
if (aglist)
free(aglist);
if (hist)
free(hist);
return 0;
}
static void
freesp_help(void)
{
printf(_(
"\n"
"Examine filesystem free space\n"
"\n"
" -a agno -- Scan only the given AG agno.\n"
" -b -- binary histogram bin size\n"
" -d -- debug output\n"
" -e bsize -- Use fixed histogram bin size of bsize\n"
" -g -- Print only a per-AG summary.\n"
" -h hbsz -- Use custom histogram bin size of h1.\n"
" Multiple specifications are allowed.\n"
" -m bmult -- Use histogram bin size multiplier of bmult.\n"
" -r -- Display realtime device free space information.\n"
" -s -- Emit freespace summary information.\n"
"\n"
"Only one of -b, -e, -h, or -m may be specified.\n"
"\n"));
}
void
freesp_init(void)
{
freesp_cmd.name = "freesp";
freesp_cmd.altname = "fsp";
freesp_cmd.cfunc = freesp_f;
freesp_cmd.argmin = 0;
freesp_cmd.argmax = -1;
freesp_cmd.args = "[-dgrs] [-a agno]... [ -b | -e bsize | -h h1... | -m bmult ]";
freesp_cmd.flags = CMD_FLAG_ONESHOT;
freesp_cmd.oneline = _("Examine filesystem free space");
freesp_cmd.help = freesp_help;
add_command(&freesp_cmd);
}