btrfs, linux, OYB software, shell

Check disk space of your BTRFS snapshots with btrfs-du

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


# 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!!


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 -O /usr/local/sbin/btrfs-du
sudo chmod +x /usr/local/sbin/btrfs-du


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.



# 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


# checks
[[ ${EUID} -ne 0 ]] && {
  printf "Must be run as root. Try 'sudo $( basename "$0" )'\n"
  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
btrfs qgroup show "$LOCATION" 2>&1 | grep -q "quotas not enabled" && {
  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

# 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

## 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

EXCL_RAW=( $( btrfs qgroup show --raw "$LOCATION" | sed '1,2d' | awk '{  print $3 }' ) )

[[ "$QFLAG" == "1" ]] && btrfs quota disable "$LOCATION"

# output
printf "%-60s %-10s %-10s %-10s\n" "Subvolume" "Total" "Exclusive" "ID"
printf "─────────────────────────────────────────────────────────────────────────────────────────\n"

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

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\n",sum/x,hum[x];break } }}' \
              <<< "$EXCL_TOTAL" )

printf "─────────────────────────────────────────────────────────────────────────────────────────\n"
printf "%86s\n" "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
# 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

Author: nachoparker

Humbly sharing things that I find useful [ github dockerhub ]


  1. I get an error /home/miguel/bin/btrfs-du: line 88: EXCL_TOTAL + : syntax error: operand expected (error token is “+ “)

  2. # btrfs-du /home
    INFO: Quota is disabled. Waiting for rescan to finish …

    is that normal?

    1. yes. Quotas need to be enabled for the calculation. If they were enabled, then it will be fast but if not, they will be enabled and then btrfs-du will wait until the scan is finished, report disk usage and finally disable them again. This can take a couple minutes.

      you can make this faster by leaving quota enabled, as the article explains

  3. I was misguided by reading the article, the line :
    “Also, quotas don’t need to be enabled for btrfs-du to work.”
    Somehow implies that btrfs-du works without quota but on my debian install it would not start if quota are not enabled and just say “old btrfs-tools detected, need to enable quota and wait for rescan to display accurate results”

    With quota enabled it does work but then how is it better than using the classic method, as you say that a caveat is that it is using quota…

    Or maybe I got things wrong, let me know 😉

Leave a Reply

Your email address will not be published. Required fields are marked *