btrfsmaintenance/btrfs-defrag-plugin.sh

152 lines
3.7 KiB
Bash
Raw Normal View History

#!/bin/bash
#
# This plugin defragments rpm files after update.
#
# If the filesystem is btrfs, run defrag command in /var/lib/rpm, set the
# desired extent size to 32MiB, but this may change in the result depending
# on the fragmentation of the free space
#
## Why 32MiB:
# - the worst fragmentation has been observed on /var/lib/rpm/Packages
# - this can grow up to several hundred of megabytes
# - the file gets updated at random places
# - although the file will be composed of many extents, it's faster to
# merge only the extents that affect some portions of the file, instead
# of the whole file; the difference is negligible
# - due to the free space fragmentation over time, it's hard to find
# contiguous space, the bigger the extent is, the worse and the extent
# size hint is not reached anyway
DEBUG="false"
EXTENT_SIZE="32M"
RPMDIR=$(rpm --eval "%_dbpath")
SCRIPTNAME="$(basename "$0")"
cleanup() {
test -n "$tmpdir" -a -d "$tmpdir" && execute rm -rf "$tmpdir"
}
trap cleanup EXIT
tmpdir=$(mktemp -d /tmp/btrfs-defrag-plugin.XXXXXX)
log() {
logger -p info -t $SCRIPTNAME --id=$$ "$@"
}
debug() {
$DEBUG && log "$@"
}
respond() {
debug "<< [$1]"
echo -ne "$1\n\n\x00"
}
execute() {
debug -- "Executing: $@"
$@ 2> $tmpdir/cmd-output
ret=$?
if $DEBUG; then
if test $ret -ne 0; then
log -- "Command failed, output follows:"
log -f $tmpdir/cmd-output
log -- "End output"
else
log -- "Command succeeded"
fi
fi
return $ret
}
btrfs_defrag() {
# defrag options:
# - verbose
# - recursive
# - flush each file before going to the next one
# - set the extent target hint
execute btrfs filesystem defragment -v -f -r -t "$EXTENT_SIZE" "$RPMDIR"
}
debug_fragmentation() {
if $DEBUG; then
log -- "Fragmentation $1"
execute filefrag $RPMDIR/* > $tmpdir/filefrag-output
if test $? -eq 0; then
log -f $tmpdir/filefrag-output
log -- "End output"
else
log "Non-fatal error ignored."
fi
fi
}
ret=0
# The frames are terminated with NUL. Use that as the delimeter and get
# the whole frame in one go.
while read -d '' -r FRAME; do
echo ">>" $FRAME | debug
# We only want the command, which is the first word
read COMMAND <<<$FRAME
# libzypp will only close the plugin on errors, which may also be logged.
# It will also log if the plugin exits unexpectedly. We don't want
# to create a noisy log when using another file system, so we just
# wait until COMMITEND to do anything. We also need to ACK _DISCONNECT
# or libzypp will kill the script, which means we can't clean up.
debug "COMMAND=[$COMMAND]"
case "$COMMAND" in
COMMITEND) ;;
_DISCONNECT)
respond "ACK"
break
;;
*)
respond "_ENOMETHOD"
continue
;;
esac
# We don't have anything to do if it's not btrfs.
FSTYPE=$(execute stat -f --format=%T $RPMDIR)
if test $? -ne 0; then
respond "ERROR"
ret=1
break
fi
debug "Output follows:"
debug "$FSTYPE"
debug -- "End output"
if test "$FSTYPE" != "btrfs"; then
debug "Nothing to do: RPM Database is on $FSTYPE file system."
respond "_ENOMETHOD"
continue
fi
debug_fragmentation "before defrag run"
btrfs_defrag > $tmpdir/defrag-output
if test $? -ne 0; then
respond "ERROR"
ret=1
break
fi
# Log the output if we're in debug mode
debug "Output follows:"
debug -f $tmpdir/defrag-output
debug -- "End output"
debug_fragmentation "after defrag run"
respond "ACK"
done
debug "Terminating with exit code $ret"
exit $ret