X-Git-Url: https://iankelling.org/git/?a=blobdiff_plain;f=btrfsmaint;h=85f2f349711dd92584430c059acffd4071d2aa46;hb=refs%2Fheads%2Fmaster;hp=4de4b765ea951022864c989f4b60f9885ce981a4;hpb=f46ee5570766081a5a73ce0d2132c8a06ee966fb;p=distro-setup diff --git a/btrfsmaint b/btrfsmaint deleted file mode 120000 index 4de4b76..0000000 --- a/btrfsmaint +++ /dev/null @@ -1 +0,0 @@ -/a/f/ans/roles/btrfs/files/btrfsmaint \ No newline at end of file diff --git a/btrfsmaint b/btrfsmaint new file mode 100755 index 0000000..f50f549 --- /dev/null +++ b/btrfsmaint @@ -0,0 +1,255 @@ +#!/bin/bash +# I, Ian Kelling, follow the GNU license recommendations at +# https://www.gnu.org/licenses/license-recommendations.en.html. They +# recommend that small programs, < 300 lines, be licensed under the +# Apache License 2.0. This file contains or is part of one or more small +# programs. If a small program grows beyond 300 lines, I plan to switch +# its license to GPL. + +# Copyright 2024 Ian Kelling + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@" + +set -e; . /usr/local/lib/bash-bear; set +e + +# inspired from +# https://github.com/kdave/btrfsmaintenance + +if [[ $INVOCATION_ID ]]; then + err-cleanup() { + exim -odf -i root </dev/null || return 0 + export XAUTHORITY=/home/iank/.Xauthority + export DISPLAY=:0 + locked=false + if lock_info=$(xscreensaver-command -time 2>/dev/null); then + if [[ $lock_info != *non-blanked* ]]; then + locked=true + fi + fi +} + +usage() { + cat <&2; exit 1; } + +check=false +dryrun=false +force=false +stats=true + +temp=$(getopt -l help,check,dryrun,force,no-stats h "$@") || usage 1 +eval set -- "$temp" +while true; do + case $1 in + --check) check=true ;; + --dryrun) dryrun=true ;; + --force) force=true ;; + --no-stats) stats=false ;; + -h|--help) usage ;; + --) shift; break ;; + *) echo "$0: unexpected args: $*" >&2 ; usage 1 ;; + esac + shift +done +readonly check dryrun force stats +##### end command line parsing ######## + + +main() { + if ! $force; then + check-idle + if ! $check; then + min=0 + max_min=300 + # When the cron kicks in, we may not be idle (physically sleeping) yet, so + # wait. + while ! $locked && (( min < max_min )); do + min=$(( min + 1 )) + sleep 60 + check-idle + done + # If we've waited a really long time for idle, just give up. + if (( min == max_min )); then + return + fi + fi + fi + + + fnd="findmnt --types btrfs --noheading" + for x in $($fnd --output "SOURCE" --nofsroot | sort -u); do + mnt=$($fnd --output "TARGET" --first-only --source $x) + [[ $mnt ]] || continue + + #### begin look for diff in stats, eg: increasing error count #### + if $stats; then + tmp=$(mktemp) + # ${mnt%/} so that if mnt is / we avoid making a buggy looking path + stats_path=${mnt%/}/btrfs-dev-stats + if [[ ! -e $stats_path ]]; then + btrfs dev stats -c $mnt >$stats_path ||: # populate initial reading + elif ! btrfs dev stats -c $mnt >$tmp; then + if ! diff -q $stats_path $tmp; then + mv $stats_path $stats_path.1 + cat $tmp >$stats_path + diff=$(diff -u $stats_path $tmp 2>&1 ||:) + printf "diff of: btrfs dev stats -c %s\n%s\n" "$mnt" "$diff" + exim -odf -i root </dev/null ||: + fi + fi + continue + fi + + # for comparing before and after balance. + # the log is already fairly verbose, so commented. + # e btrfs filesystem df $mnt + # e df -H $mnt + if btrfs filesystem df $mnt | grep -q "Data+Metadata"; then + for usage in $dusage; do + e ionice -c 3 btrfs balance start -dusage=$usage -musage=$usage $mnt + done + else + e ionice -c 3 btrfs balance start -dusage=0 $mnt + for usage in $dusage; do + e ionice -c 3 btrfs balance start -dusage=$usage $mnt + done + e ionice -c 3 btrfs balance start -musage=0 $mnt + for usage in $musage; do + e ionice -c 3 btrfs balance start -musage=$usage $mnt + done + fi + date= + scrub_status=$(btrfs scrub status $mnt) + if printf "%s\n" "$scrub_status" | grep -i '^status:[[:space:]]*finished$' &>/dev/null; then + date=$(printf "%s\n" "$scrub_status" | sed -rn 's/^Scrub started:[[:space:]]*(.*)/\1/p') + fi + if [[ ! $date ]]; then + # output from older versions, at least btrfs v4.15.1 + date=$( + printf "%s\n" "$scrub_status" | \ + sed -rn 's/^\s*scrub started at (.*) and finished.*/\1/p' + ) + fi + if ! $force && [[ $date ]]; then + if $dryrun; then + echo "$0: last scrub finish for $mnt: $date" + fi + date=$(date --date="$date" +%s) + # if date is sooner than 60 days ago + # the wiki recommends 30 days or so, but + # I'm going with 60 days. + if (( date > EPOCHSECONDS - 60*60*24*60 )); then + if $dryrun; then + echo "$0: skiping scrub of $mnt, last was $(( (EPOCHSECONDS - date) / 60/60/24 )) days ago, < 30 days" + fi + continue + fi + fi + # btrfsmaintenance does -c 2 -n 4, but I want lowest pri. + e btrfs scrub start -Bd -c 3 $mnt + + # We normally only do one disk since this is meant to be run in + # downtime and if we try to do all disks, we invariably end up doing + # a scrub after downtime. So, just do one disk per day. + if ! $force; then + return 0 + fi + done +} + +loop-main() { + while true; do + main + sleep 60 + done +} + +if $check; then + loop-main +else + main +fi