181 lines
4.8 KiB
Python
Executable File
181 lines
4.8 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
#
|
|
# Imports
|
|
#
|
|
import sys
|
|
import time
|
|
import commands
|
|
import argparse
|
|
|
|
|
|
#
|
|
# Misc
|
|
#
|
|
#sys.tracebacklimit = 0
|
|
|
|
|
|
#
|
|
# Global variables
|
|
#
|
|
size_data_total = 0
|
|
size_data_exclusive = 0
|
|
size_snapshot_total = 0
|
|
size_snapshot_exclusive = 0
|
|
|
|
|
|
#
|
|
# Methods
|
|
#
|
|
def get_subvol_list(path):
|
|
command = "btrfs subvolume list -t %s" % (path)
|
|
status, output = commands.getstatusoutput(command)
|
|
|
|
if status is not 0:
|
|
raise Exception(command)
|
|
|
|
# Every line contains the following values: subvol_id, gen, toplevel, path
|
|
return output.splitlines()[2:]
|
|
|
|
|
|
def get_id_root(name, path):
|
|
lines = get_subvol_list(path)
|
|
|
|
# Filter lines where toplevel == 5
|
|
subvol_ids = filter(lambda x: int(x.split()[2]) == 5, lines)
|
|
|
|
# Try to retrieve the subvol_id for the root subvolume (if any)
|
|
if len(subvol_ids) == 1:
|
|
# The path contains a btrfs filesystem without subvolume for data
|
|
return int(subvol_ids[0].split()[0])
|
|
else:
|
|
# The path contains a btrfs filesystem with multiple subvolumes for data
|
|
try:
|
|
return int(filter(lambda x: x.split()[3] == name, subvol_ids)[0].split()[0])
|
|
except IndexError:
|
|
pass
|
|
|
|
|
|
# Volume not found, root is probably the btrfs default (5)
|
|
return 5
|
|
|
|
def get_id_subvolumes(path, subvol_id):
|
|
lines = get_subvol_list(path)
|
|
lines = filter(lambda x: int(x.split()[2]) == subvol_id, lines)
|
|
return list(map(lambda x: int(x.split()[0]), lines))
|
|
|
|
|
|
def get_disk_usage(name, path):
|
|
id_root = get_id_root(name, path)
|
|
id_subvolumes = get_id_subvolumes(path, id_root)
|
|
|
|
command = "btrfs qgroup show --raw %s" % (path)
|
|
status, output = commands.getstatusoutput(command)
|
|
|
|
if status is not 0:
|
|
raise Exception(command)
|
|
|
|
lines = output.splitlines()[2:]
|
|
|
|
# Global variables
|
|
global size_data_total
|
|
global size_data_exclusive
|
|
global size_snapshot_total
|
|
global size_snapshot_exclusive
|
|
|
|
# Total data volume in subvolume
|
|
size_data_total = 0
|
|
|
|
# Total data volume in snapshots
|
|
# -> this variable is useless
|
|
size_snapshot_total = 0
|
|
|
|
# Data exclusively in subvolume
|
|
# -> data that is not (yet) incorporated in a snapshot
|
|
size_data_exclusive = 0
|
|
|
|
|
|
# Data exclusively available in snapshots
|
|
# -> data that was removed from volume
|
|
size_snapshot_exclusive = 0
|
|
|
|
for line in lines:
|
|
split = line.split()
|
|
subvol_id = int(split[0].split("/")[1])
|
|
size_total = float(split[1])
|
|
size_exclusive = float(split[2])
|
|
|
|
if subvol_id == id_root:
|
|
size_data_total = size_total
|
|
size_data_exclusive = size_exclusive
|
|
elif subvol_id in id_subvolumes:
|
|
size_snapshot_total += size_total
|
|
size_snapshot_exclusive += size_exclusive
|
|
|
|
def rescan_quota(path):
|
|
command = "btrfs quota rescan %s" % (path)
|
|
status, output = commands.getstatusoutput(command)
|
|
if status is not 0:
|
|
Exception(command)
|
|
|
|
def print_human_readable(name):
|
|
global size_data_total
|
|
global size_data_exclusive
|
|
global size_snapshot_exclusive
|
|
size_data_total = size_data_total / (1024*1e6)
|
|
size_data_exclusive = size_data_exclusive / (1024*1e6)
|
|
size_snapshot_exclusive = size_snapshot_exclusive / (1024*1e6)
|
|
print "%10s: %6.1f Gb, %6.1f Gb, %6.1f Gb" % (name, size_data_total, size_data_exclusive, size_snapshot_exclusive)
|
|
|
|
def print_rrd(name):
|
|
timestamp = int(time.time())
|
|
print("PUTVAL {}/exec-btrfs_{}/gauge-data_total {}:{:.1f}".format(hostname, name, timestamp, size_data_total))
|
|
print("PUTVAL {}/exec-btrfs_{}/gauge-data_exclusive {}:{:.1f}".format(hostname, name, timestamp, size_data_exclusive))
|
|
print("PUTVAL {}/exec-btrfs_{}/gauge-snapshot_total {}:{:.1f}".format(hostname, name, timestamp, size_snapshot_total))
|
|
print("PUTVAL {}/exec-btrfs_{}/gauge-snapshot_exclusive {}:{:.1f}".format(hostname, name, timestamp, size_snapshot_exclusive))
|
|
|
|
|
|
#
|
|
# Volumes to scan
|
|
#
|
|
hostname = "sepia"
|
|
interval = 10
|
|
volumes = list()
|
|
|
|
# 275 GB SSD
|
|
volumes.append(["@", "/host/root/"])
|
|
volumes.append(["@home", "/host/root/home"])
|
|
volumes.append(["opt", "/host/root/opt"])
|
|
|
|
# 2x 4TB HDD
|
|
volumes.append(["data", "/host/root/media/data"])
|
|
|
|
|
|
#
|
|
# Command line arguments
|
|
#
|
|
parser = argparse.ArgumentParser(description='Get BTRFS disk usage')
|
|
parser.add_argument('-s', action='store_true', help='print in human readable format')
|
|
args = parser.parse_args()
|
|
human_readable = args.s
|
|
|
|
|
|
#
|
|
# Main
|
|
#
|
|
if (human_readable):
|
|
for (name, path) in volumes:
|
|
get_disk_usage(name, path)
|
|
print_human_readable(name)
|
|
else:
|
|
# RRD mode
|
|
while True:
|
|
for (name, path) in volumes:
|
|
get_disk_usage(name, path)
|
|
|
|
print_rrd(name)
|
|
|
|
sys.stdout.flush()
|
|
time.sleep(interval)
|
|
#rescan_quota(path)
|