From b5346e0b1a44da9b45d5f01159d69fe8998a2b12 Mon Sep 17 00:00:00 2001 From: Dave Chinner Date: Wed, 11 Feb 2015 11:03:25 +1100 Subject: [PATCH] spaceman: Free space mapping command Add freespace mapping tool modelled on the xfs_db freesp command. The advantage of this command over xfs_db is that it can be done online and is coherent with concurrent modifications to the filesystem. This requires kernel extensions to the fiemap command to map free space indexes. The XFS_IOC_FIEMAPFS ioctl API definitions in this change are derived from a patch supplied by Dhruvesh Rathore, Amey Ruikar and Somdeep Dey. Signed-off-by: Dave Chinner --- include/xfs_fs.h | 13 ++ spaceman/Makefile | 15 +- spaceman/freesp.c | 385 ++++++++++++++++++++++++++++++++++++++++++++++ spaceman/init.c | 1 + spaceman/space.h | 6 + 5 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 spaceman/freesp.c diff --git a/include/xfs_fs.h b/include/xfs_fs.h index 59c40fc45..72300dba0 100644 --- a/include/xfs_fs.h +++ b/include/xfs_fs.h @@ -493,6 +493,13 @@ typedef struct xfs_swapext #define XFS_IOC_SETXFLAGS FS_IOC_SETFLAGS #define XFS_IOC_GETVERSION FS_IOC_GETVERSION +/* + * XFS_IOC_FIEMAPFS flags + */ +#define XFS_FIEMAPFS_FLAG_FREESP 0x1 /* map fs freespace tree */ +#define XFS_FIEMAPFS_FLAG_FREESP_SIZE 0x2 /* ordered by size */ +#define XFS_FIEMAPFS_FLAG_FREESP_SIZE_HINT 0x4 /* search continue request */ + /* * ioctl commands that replace IRIX fcntl()'s * For 'documentation' purposed more than anything else, @@ -503,6 +510,9 @@ typedef struct xfs_swapext #define XFS_IOC_DIOINFO _IOR ('X', 30, struct dioattr) #define XFS_IOC_FSGETXATTR _IOR ('X', 31, struct fsxattr) #define XFS_IOC_FSSETXATTR _IOW ('X', 32, struct fsxattr) +#ifdef HAVE_FIEMAP +#define XFS_IOC_FIEMAPFS _IOWR('X', 33, struct fiemap) +#endif #define XFS_IOC_ALLOCSP64 _IOW ('X', 36, struct xfs_flock64) #define XFS_IOC_FREESP64 _IOW ('X', 37, struct xfs_flock64) #define XFS_IOC_GETBMAP _IOWR('X', 38, struct getbmap) @@ -547,6 +557,9 @@ typedef struct xfs_swapext #ifndef FIFREEZE #define XFS_IOC_FREEZE _IOWR('X', 119, int) #define XFS_IOC_THAW _IOWR('X', 120, int) +#else +#define XFS_IOC_FREEZE FIFREEZE +#define XFS_IOC_THAW FITHAW #endif #define XFS_IOC_FSSETDM_BY_HANDLE _IOW ('X', 121, struct xfs_fsop_setdm_handlereq) diff --git a/spaceman/Makefile b/spaceman/Makefile index 08709b339..1370601ab 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -7,8 +7,12 @@ include $(TOPDIR)/include/builddefs LTCOMMAND = xfs_spaceman HFILES = init.h space.h -CFILES = init.c \ - ag.c file.c prealloc.c trim.c +CFILES = ag.c \ + file.c \ + init.c \ + prealloc.c \ + trim.c + LLDLIBS = $(LIBXCMD) LTDEPENDENCIES = $(LIBXCMD) @@ -22,6 +26,13 @@ ifeq ($(ENABLE_EDITLINE),yes) LLDLIBS += $(LIBEDITLINE) $(LIBTERMCAP) endif +ifeq ($(HAVE_FIEMAP),yes) +CFILES += freesp.c +LCFLAGS += -DHAVE_FIEMAP +else +LSRCFILES += freesp.c +endif + default: depend $(LTCOMMAND) include $(BUILDRULES) diff --git a/spaceman/freesp.c b/spaceman/freesp.c new file mode 100644 index 000000000..b5b353da2 --- /dev/null +++ b/spaceman/freesp.c @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. + * Copyright (c) 2012 Red Hat, Inc. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include "init.h" +#include "space.h" + +typedef struct histent +{ + int low; + int high; + long long count; + long long blocks; +} histent_t; + +static int agcount; +static xfs_agnumber_t *aglist; +static int countflag; +static int dumpflag; +static int equalsize; +static histent_t *hist; +static int histcount; +static int multsize; +static int seen1; +static int summaryflag; +static long long totblocks; +static long long totexts; + +static cmdinfo_t freesp_cmd; + +static void +addhistent( + int h) +{ + 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) +{ + int i; + + if (dumpflag) + printf("%8d %8d %8Zu\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 ((histent_t *)a)->low - ((histent_t *)b)->low; +} + +static void +histinit( + int maxlen) +{ + int 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("%7d %7d %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 fiemap *fiemap; + off64_t blocksize = file->geom.blocksize; + uint64_t last_logical = agno * file->geom.agblocks * blocksize; + uint64_t length = file->geom.agblocks * blocksize; + off64_t fsbperag; + int fiemap_flags; + int last = 0; + int map_size; + + + last_logical = (off64_t)file->geom.agblocks * blocksize * agno; + length = (off64_t)file->geom.agblocks * blocksize; + fsbperag = (off64_t)file->geom.agblocks * blocksize; + + map_size = sizeof(struct fiemap) + + sizeof(struct fiemap_extent) * NR_EXTENTS; + fiemap = malloc(map_size); + if (!fiemap) { + fprintf(stderr, _("%s: fiemap malloc failed.\n"), progname); + exitcode = 1; + return; + } + if (countflag) + fiemap_flags = XFS_FIEMAPFS_FLAG_FREESP_SIZE; + else + fiemap_flags = XFS_FIEMAPFS_FLAG_FREESP; + + while (!last) { + struct fiemap_extent *extent; + xfs_agblock_t agbno; + int ret; + int i; + + memset(fiemap, 0, map_size); + fiemap->fm_flags = fiemap_flags; + + fiemap->fm_start = last_logical; + fiemap->fm_length = length; + fiemap->fm_extent_count = NR_EXTENTS; + + ret = ioctl(file->fd, XFS_IOC_FIEMAPFS, (unsigned long)fiemap); + if (ret < 0) { + fprintf(stderr, "%s: ioctl(XFS_IOC_FIEMAPFS) [\"%s\"]: " + "%s\n", progname, file->name, strerror(errno)); + free(fiemap); + exitcode = 1; + return; + } + + /* No more extents to map, exit */ + if (!fiemap->fm_mapped_extents) + break; + + for (i = 0; i < fiemap->fm_mapped_extents; i++) { + off64_t aglen; + + extent = &fiemap->fm_extents[i]; + + + agbno = (extent->fe_physical - (fsbperag * agno)) / + blocksize; + aglen = extent->fe_length / blocksize; + + addtohist(agno, agbno, aglen); + + + if (extent->fe_flags & FIEMAP_EXTENT_LAST) { + last = 1; + break; + } + } + + if (fiemap_flags == XFS_FIEMAPFS_FLAG_FREESP) { + /* move our range past over what we just searched */ + last_logical = max(last_logical, + extent->fe_logical + extent->fe_length); + } else { + /* + * we want to start the search from the current + * extent, but size ordered free space can be found + * anywhere in the range we asked for so we cannot move + * last_logical around. This means we need to give the + * search the last extent we've found back to the kernel + * for it to start it's search again. Move + * it to extent zero, and flag it as a continued call. + */ + memcpy(&fiemap->fm_extents[0], extent, + sizeof(fiemap->fm_extents[0])); + fiemap_flags |= XFS_FIEMAPFS_FLAG_FREESP_SIZE_HINT; + } + + } +} +static void +aglistadd( + char *a) +{ + aglist = realloc(aglist, (agcount + 1) * sizeof(*aglist)); + aglist[agcount] = (xfs_agnumber_t)atoi(a); + agcount++; +} + +static int +init( + int argc, + char **argv) +{ + int c; + int speced = 0; + + agcount = countflag = dumpflag = equalsize = multsize = optind = 0; + histcount = seen1 = summaryflag = 0; + totblocks = totexts = 0; + aglist = NULL; + hist = NULL; + while ((c = getopt(argc, argv, "a:bcde:h:m:s")) != EOF) { + switch (c) { + case 'a': + aglistadd(optarg); + break; + case 'b': + if (speced) + return 0; + multsize = 2; + speced = 1; + break; + case 'c': + countflag = 1; + break; + case 'd': + dumpflag = 1; + break; + case 'e': + if (speced) + return 0; + equalsize = atoi(optarg); + speced = 1; + break; + case 'h': + if (speced && !histcount) + return 0; + addhistent(atoi(optarg)); + speced = 1; + break; + case 'm': + if (speced) + return 0; + multsize = atoi(optarg); + speced = 1; + break; + case 's': + summaryflag = 1; + break; + case '?': + return 0; + } + } + if (optind != argc) + return 0; + if (!speced) + multsize = 2; + histinit(file->geom.agblocks); + return 1; +} + +/* + * Report on freespace usage in xfs filesystem. + */ +static int +freesp_f( + int argc, + char **argv) +{ + xfs_agnumber_t agno; + + if (!init(argc, argv)) + return 0; + for (agno = 0; agno < file->geom.agcount; agno++) { + if (inaglist(agno)) + scan_ag(agno); + } + if (histcount) + 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" +"Options: [-bcds] [-a agno] [-e bsize] [-h h1]... [-m bmult]\n" +"\n" +" -b -- binary histogram bin size\n" +" -c -- scan the by-count (size) ordered freespace tree\n" +" -d -- debug output\n" +" -s -- emit freespace summary information\n" +" -a agno -- scan only the given AG agno\n" +" -e bsize -- use fixed histogram bin size of bsize\n" +" -h h1 -- use custom histogram bin size of h1. Multiple specifications allowed.\n" +" -m bmult -- use histogram bin size multiplier of bmult\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 = "[-bcds] [-a agno] [-e bsize] [-h h1]... [-m bmult]\n"; + freesp_cmd.flags = CMD_FLAG_GLOBAL; + freesp_cmd.oneline = _("Examine filesystem free space"); + freesp_cmd.help = freesp_help; + + add_command(&freesp_cmd); +} + diff --git a/spaceman/init.c b/spaceman/init.c index e16ad83a2..f8a3cb3e7 100644 --- a/spaceman/init.c +++ b/spaceman/init.c @@ -38,6 +38,7 @@ static void init_commands(void) { file_init(); + freesp_init(); help_init(); prealloc_init(); quit_init(); diff --git a/spaceman/space.h b/spaceman/space.h index 0ae31163f..455bb063c 100644 --- a/spaceman/space.h +++ b/spaceman/space.h @@ -36,3 +36,9 @@ extern void help_init(void); extern void prealloc_init(void); extern void quit_init(void); extern void trim_init(void); + +#ifdef HAVE_FIEMAP +extern void freesp_init(void); +#else +static inline void freesp_init(void) {}; +#endif -- 2.47.2