Today I want to share a simple way to check the state of our BTRFS snapshots.

Usage

# btrfs-du /path/

If path is omitted, it will default to / .

We can list easily our snapshots, plus have a clear view of how much data they have in common and how much total overhead are we holding.

Four months worth of snapshots for only 7.48GB!!

Installation

Get the script and make it executable. You can do this in two lines, but better inspect it first. Don’t trust anyone blindly.

sudo wget https://raw.githubusercontent.com/nachoparker/btrfs-du/master/btrfs-du -O /usr/local/sbin/btrfs-du sudo chmod +x /usr/local/sbin/btrfs-du

Details

Normally you can only get disk usage information through the quota info in qgroups

This is not ideal for a couple reasons:

First, we need to have quota enabled, which can have a performance impact with many subvolumes.

Second, we still have to list our subvolumes to correlate subvolume IDs to subvolume names. Two steps for a simple operation.

I was inspired by btrfs-size by Kyle Agronick, but not only the output wasn’t very clean to my taste, but also it was terribly slow on my root subvolume where docker uses BTRFS as storage driver and I have very many subvolumes. I am talking close to a minute.

Looking into the code to speed it up I decided that it would be cleaner to rewrite it as it was more complex that it needed to be. Also, quotas don’t need to be enabled for btrfs-du to work.

Keep in mind that if quotas are not enabled for the subvolume, the command can take several seconds to calculate the sizes. We can speed the execution by having quotas pre-enabled, but as mentioned before note that this can have an impact in a big subvolume with many snapshots.

Code

!/bin/bash # # Script that outputs the filesystem usage of snapshots in a location ( root if omited ) # # Usage: # sudo btrfs-du ( path ) # # Copyleft 2017 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com> # GPL licensed (see end of file) * Use at your own risk! # # Based on btrfs-size by Kyle Agronick # # More at https://ownyourbits.com/2017/12/06/check-disk-space-of-your-btrfs-snapshots-with-btrfs-du/ # LOCATION=${1:-/} # checks [[ ${EUID} -ne 0 ]] && { printf "Must be run as root. Try 'sudo $( basename "$0" )'

" exit 1 } [[ -d "$LOCATION" ]] || { echo "$LOCATION not found" exit 1 } [[ "$( stat -fc%T "$LOCATION" )" != "btrfs" ]] && { echo "$LOCATION is not in a BTRFS system" exit 1 } # quota management sync btrfs qgroup show "$LOCATION" 2>&1 | grep -q "quotas not enabled" && { QFLAG=1 btrfs quota enable "$LOCATION" } # if we just enabled quota, might have to wait for rescan OUT=$( btrfs qgroup show --si --sort=qgroupid "$LOCATION" 2>&1 ) grep -q -e "rescan is running" -e "data inconsistent" <<< "$OUT" && { echo "INFO: Quota is disabled. Waiting for rescan to finish ..." while true; do sleep 2 OUT=$( btrfs qgroup show --si --sort=qgroupid "$LOCATION" 2>&1 ) grep -q -e "rescan is running" -e "data inconsistent" <<< "$OUT" || break done } # data declare -A TOT EXCL NAME ## qgroup data OUT=$( sed '1,3d;s|^.*/||' <<< "$OUT" ) ID__=( $( awk '{ print $1 }' <<< "$OUT" ) ) TOT_=( $( awk '{ print $2 }' <<< "$OUT" ) ) EXC_=( $( awk '{ print $3 }' <<< "$OUT" ) ) for (( i = 0 ; i < ${#ID__[@]} ; i++ )); do TOT[${ID__[$i]}]=${TOT_[$i]} EXC[${ID__[$i]}]=${EXC_[$i]} done ## naming data OUT=$( btrfs subvolume list --sort=rootid "$LOCATION" | cut -d ' ' -f 2,9 ) ID__=( $( awk '{ print $1 }' <<< "$OUT" ) ) NAM_=( $( awk '{ print $2 }' <<< "$OUT" ) ) for (( i = 0 ; i < ${#ID__[@]} ; i++ )); do NAME[${ID__[$i]}]=${NAM_[$i]} done EXCL_RAW=( $( btrfs qgroup show --raw "$LOCATION" | sed '1,2d' | awk '{ print $3 }' ) ) EXCL_TOTAL=0 [[ "$QFLAG" == "1" ]] && btrfs quota disable "$LOCATION" # output printf "%-60s %-10s %-10s %-10s

" "Subvolume" "Total" "Exclusive" "ID" printf "─────────────────────────────────────────────────────────────────────────────────────────

" ## matching by IDs in btrfs subvolume list for (( i = 0 ; i < ${#ID__[@]} ; i++ )); do printf "%-60s %-10s %-10s %-10s

" ${NAME[${ID__[$i]}]} ${TOT[${ID__[$i]}]} ${EXC[${ID__[$i]}]} ${ID__[$i]} EXCL_TOTAL=$(( EXCL_TOTAL + ${EXCL_RAW[$i]} )) done EXCL_TOTAL=$( awk '{ sum=$1 ; hum[1024^4]="TB";hum[1024^3]="GB";hum[1024^2]="MB";hum[1024]="KB"; for (x=1024^4; x>=1024; x/=1024){ if (sum>=x) { printf "%.2f%s

",sum/x,hum[x];break } }}' \ <<< "$EXCL_TOTAL" ) printf "─────────────────────────────────────────────────────────────────────────────────────────

" printf "%86s

" "Total exclusive data: $EXCL_TOTAL" # License # # This script 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; either version 2 of the License, or # (at your option) any later version. # # This script is distributed in the hope that it will 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 script; if not, write to the # Free Software Foundation, Inc., 59 Temple Place, Suite 330, # Boston, MA 02111-1307 USA