misc minor fixes
[distro-setup] / btrfsmaint
1 #!/bin/bash
2
3
4 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
5
6 source /a/bin/errhandle/err
7
8 # inspired from
9 # https://github.com/kdave/btrfsmaintenance
10
11
12 # Man page says we could also use a range, i suppose it would be
13 # logical to use a pattern like 5..10 10..20,
14 # but I don't know if this would help us at all.
15 dusage="5 10"
16 musage="5"
17
18 e() {
19 echo "cron: $*"
20 if ! $dryrun; then
21 "$@"
22 fi
23 }
24
25 check-idle() {
26 type -p xprintidle &>/dev/null || return 0
27 export DISPLAY=:0
28 # a hours, a movie could run that long.
29 idle_limit=$((1000 * 60 * 60 * 2))
30 idle_time=$idle_limit
31 while read -r user; do
32 new_idle_time=$(sudo -u $user xprintidle 2>/dev/null) ||:
33 if [[ $new_idle_time && $new_idle_time -lt $idle_time ]]; then
34 idle_time=$new_idle_time
35 fi
36 done < <(users | tr " " "\n" | sort -u)
37 if (( idle_time < idle_limit )); then
38 idle=false
39 else
40 idle=true
41 fi
42 }
43
44 usage() {
45 cat <<EOF
46 Usage: ${0##*/} [ARGS]
47 Do btrfs maintence or stop if xprintidle shows a user
48
49 force Run regardless of user idle status on all disks.
50 check Only check if an existing maintence should be cancelled due to
51 nonidle user. Also, runs in a loop every 20 seconds for 10
52 minutes.
53
54 Note: Uses util-linux getopt option parsing: spaces between args and
55 options, short options can be combined, options before args.
56 EOF
57 exit $1
58 }
59
60
61 force=false
62 check=false
63 dryrun=false
64 if [[ $1 ]]; then
65 case $1 in
66 check)
67 check=true
68 ;;
69 force)
70 force=true
71 ;;
72 dryrun)
73 dryrun=true
74 ;;
75 *)
76 echo "$0: error: unexpected arg" >&2
77 usage 1
78 ;;
79 esac
80 fi
81
82
83 main() {
84 idle=true
85 if ! $force; then
86 check-idle
87 if ! $check; then
88 min=0
89 max_min=300
90 # When the cron kicks in, we may not be idle (physically sleeping) yet, so
91 # wait.
92 while ! $idle && (( min < max_min )); do
93 min=$(( min + 1 ))
94 sleep 60
95 check-idle
96 done
97 # If we've waited a really long time for idle, just give up.
98 if (( min == max_min )); then
99 return
100 fi
101 fi
102 fi
103
104
105 tmp=$(mktemp)
106 fnd="findmnt --types btrfs --noheading"
107 for x in $($fnd --output "SOURCE" --nofsroot | sort -u); do
108 mnt=$($fnd --output "TARGET" --first-only --source $x)
109 [[ $mnt ]] || continue
110
111 #### begin look for diff in stats, eg: increasing error count ####
112
113 # Only run for $check, since it runs in parallel to non-check, avoid
114 # race condition.
115 if $check; then
116 if ! btrfs dev stats -c $mnt >$tmp; then
117 if diff -q $mnt/btrfs-dev-stats $tmp; then
118 diff -u $mnt/btrfs-dev-stats $tmp | mail -s "$HOSTNAME: error: btrfs dev stats -c $mnt" root@localhost
119 cat $tmp >$mnt/btrfs-dev-stats
120 fi
121 fi
122 rm -f $tmp
123 fi
124 #### end look for diff in stats, eg: increasing error count ####
125
126 if $check; then
127 if ! $idle; then
128 if $dryrun; then
129 echo "$0: not idle. if this wasnt a dry run, btrfs scrub cancel $mnt"
130 else
131 btrfs scrub cancel $mnt &>/dev/null ||:
132 fi
133 fi
134 continue
135 fi
136
137 # for comparing before and after balance.
138 # the log is already fairly verbose, so commented.
139 # e btrfs filesystem df $mnt
140 # e df -H $mnt
141 if btrfs filesystem df $mnt | grep -q "Data+Metadata"; then
142 for usage in $dusage; do
143 e ionice -c 3 btrfs balance start -dusage=$usage -musage=$usage $mnt
144 done
145 else
146 e ionice -c 3 btrfs balance start -dusage=0 $mnt
147 for usage in $dusage; do
148 e ionice -c 3 btrfs balance start -dusage=$usage $mnt
149 done
150 e ionice -c 3 btrfs balance start -musage=0 $mnt
151 for usage in $musage; do
152 e ionice -c 3 btrfs balance start -musage=$usage $mnt
153 done
154 fi
155 date=
156 scrub_status=$(btrfs scrub status $mnt)
157 if printf "%s\n" "$scrub_status" | grep -i '^status:[[:space:]]*finished$' &>/dev/null; then
158 date=$(printf "%s\n" "$scrub_status" | sed -rn 's/^Scrub started:[[:space:]]*(.*)/\1/p')
159 fi
160 if [[ ! $date ]]; then
161 # output from older versions, at least btrfs v4.15.1
162 date=$(
163 printf "%s\n" "$scrub_status" | \
164 sed -rn 's/^\s*scrub started at (.*) and finished.*/\1/p'
165 )
166 fi
167 if [[ $date ]]; then
168 if $dryrun; then
169 echo "$0: last scrub finish for $mnt: $date"
170 fi
171 date=$(date --date="$date" +%s)
172 # if date is sooner than 60 days ago
173 # the wiki recommends 30 days or so, but
174 # I'm going with 60 days.
175 if (( date > EPOCHSECONDS - 60*60*24*60 )); then
176 if $dryrun; then
177 echo "$0: skiping scrub of $mnt, last was $(( (EPOCHSECONDS - date) / 60/60/24 )) days ago, < 30 days"
178 fi
179 continue
180 fi
181 fi
182 # -c 2 -n 4 is from btrfsmaintenance, does ionice
183 e btrfs scrub start -Bd -c 2 -n 4 $mnt
184
185 # We normally only do one disk since this is meant to be run while I sleep
186 # and if we try to do all disks, we invariably end up doing a scrub still
187 # after I've woken up. So, just do one per day.
188 if ! $force; then
189 return 0
190 fi
191 done
192 }
193
194 loop-main() {
195 while true; do
196 main
197 sleep 60
198 done
199 }
200
201 if $check; then
202 loop-main
203 else
204 main
205 fi