Created
February 12, 2018 23:10
-
-
Save robbat2/bf2b0515b544d12188d365c8dbf44edc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # XFS defrag helper script | |
| # Copyright 2010-2018 Robin H. Johnson <robbat2@gentoo.org> | |
| # Originally written for IsoHunt, later improved for BC Libraries Cooperative & Dreamhost | |
| # | |
| # For each filesystem | |
| # 1. get complete output of inode/fragments | |
| # 2. select all inodes w/ more than 10 excess fragments (actual-ideal) | |
| # 3. just the inode lines, not directories or other stats | |
| # 4. sort inodes by number of fragments, descending | |
| # 5. extract the inode number | |
| # 6. convert inode number to file name | |
| # 7. dump file names to temp file | |
| # 8. defrag files in order from tempfile | |
| # | |
| # | |
| # You must NOT pipe xfs_db & xfs_fsr together | |
| # That WILL cause a deadlock in XFS! | |
| XFS_VOL="$1" | |
| # set the number of fragments past which we fire | |
| : ${FRAGLIMIT:=10} | |
| export TMPDIR=$(mktemp -d /tmp/xfs-defrag.XXXXXX) | |
| export TMPFILE=$(mktemp -p $TMPDIR files-to-defrag.XXXXXX) | |
| trap "rm -rf $TMPDIR" EXIT | |
| echo $TMPDIR | |
| cd $TMPDIR | |
| AWKSCRIPT="${TMPDIR}/xfs_db_parse.awk" | |
| cat >"${AWKSCRIPT}" <<'EOF' | |
| BEGIN { | |
| # Truncate file | |
| printf "" >"inode.frags"; | |
| # set limit over excess fragments | |
| if (limit < 1) { | |
| limit = 1; | |
| } | |
| } | |
| # find lines | |
| /^inode [0-9]+ actual [0-9]+ ideal [0-9]+/ { | |
| inode=$2; | |
| actual=$4; | |
| ideal=$6; | |
| if(actual - ideal > limit) { | |
| print inode, actual-ideal >>"inode.frags"; | |
| } | |
| next; | |
| } | |
| # stats | |
| /actual.*ideal.*fragmentation.factor/ { print > "stats"; next } | |
| # useless output | |
| /Note, this number is largely meaningless./ { next } | |
| /Files on this filesystem average/ { next } | |
| # other lines | |
| // { | |
| printf "xfs-defrag: unknown xfs_db output: '%s'\n", $0 > "/dev/stderr" | |
| } | |
| EOF | |
| found=0 | |
| grep -w xfs /proc/mounts | while read dev mnt fstype rest ; do | |
| if [ -n "$XFS_VOL" ]; then | |
| [ "$XFS_VOL" != "$dev" ] && [ "$XFS_VOL" != "$mnt" ] && continue | |
| found=1 | |
| fi | |
| echo Scanning $mnt | |
| # This command will generate a LOT of output, we must pipe | |
| # this will write (inode,fragments) to the file 'inode.frags'. | |
| xfs_db -r -c 'frag -v -f' $dev | | |
| awk -v limit=$FRAGLIMIT -f "${AWKSCRIPT}" | |
| # sort by inode number | |
| sort -k 1b,1 <inode.frags >inode.frags.tmp && mv -f inode.frags.tmp inode.frags | |
| # If there are no fragmented files, skip to next FS | |
| [ -s inode.frags ] || continue | |
| # find the filenames for each inode, print 'inode filename' | |
| # WARNING: filename may contain spaces! | |
| find $mnt \ | |
| \( \ | |
| -false \ | |
| $(awk '{printf "-or -inum %d ",$1}' inode.frags) \ | |
| \) \ | |
| -printf "%i %h/%f\n" \ | |
| >inode.filename | |
| # sort by inode number | |
| # WARNING: filename may contain spaces! | |
| sort -k 1b,1 <inode.filename >inode.filename.tmp && mv inode.filename.tmp -f inode.filename | |
| # Now we need to join the stats, so we can visit files by name, in order of | |
| # how many fragments they have. | |
| join -j 1 inode.frags inode.filename >joined | |
| # 'joined' now contains | |
| # inode numfrag filename | |
| # WARNING: filename may contain spaces! | |
| sort -k +2nr joined |cut -d' ' -f3- >>$TMPFILE | |
| # TMPFILE now contains | |
| # filename | |
| # Do not scan more | |
| [ -n "$XFS_VOL" -a "$found" = "1" ] && break | |
| done | |
| # You must NOT pipe xfs_db & xfs_fsr together | |
| # That WILL cause a deadlock in XFS! | |
| # Run FSR on files listed | |
| if [ -z "$DRYRUN" ] && [ -s "${TMPFILE}" ]; then | |
| # xfs_fsr does not run properly under xargs normally, because it wants an | |
| # interactive stdin. | |
| # if stdin is not interactive [tested with isatty(3)], then xfs_fsr output | |
| # goes to syslog! | |
| # to work around this, we use 'script -c "$CMD" /dev/null' to force the | |
| # command to be in a new interactive shell. | |
| cat $TMPFILE \ | |
| | tr '\n' '\0' \ | |
| | ionice -c 3 \ | |
| xargs -0 --replace \ | |
| /usr/bin/script -c 'xfs_fsr -v {}' /dev/null \ | |
| | grep --line-buffered -e '^extents ' -e '(skipping)' | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment