Skip to content

Instantly share code, notes, and snippets.

@Feuermurmel
Created June 25, 2014 19:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Feuermurmel/a2a86fccbdadca9e45e5 to your computer and use it in GitHub Desktop.
Save Feuermurmel/a2a86fccbdadca9e45e5 to your computer and use it in GitHub Desktop.
A script for LVM2 to display a quick overview over which LVs use space on which PVs.
#! /usr/bin/env python3.2
import sys, subprocess, argparse, itertools
class UserError(Exception):
def __init__(self, msg, *args):
super().__init__(msg.format(*args))
class CommandError(Exception):
def __init__(self, msg, *args):
super().__init__(msg.format(*args))
def log(msg, *args):
print(msg.format(*args), file = sys.stderr)
def command(*args, output = False):
if output:
stdout = subprocess.PIPE
else:
stdout = None
process = subprocess.Popen(args, stdout = stdout)
output_data, _ = process.communicate()
if process.returncode:
raise CommandError('Command failed with status {}: {}', process.returncode, ' '.join(args))
if output:
return output_data.decode()
else:
return None
def si_size(bytes):
units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']
order = 0
while bytes >= 10 ** (order + 1):
order += 1
unit_index = order // 3
if unit_index > 0:
granularity = 10 ** (order - 2)
value = bytes // granularity * granularity / 1000 ** unit_index
number = '{:f}'.format(value).rstrip('0').rstrip('.')
else:
number = str(bytes)
return '{} {}'.format(number, units[unit_index])
class Segment:
def __init__(self, vg_name, pv_name, lv_name, size, extent_size):
self.vg_name = vg_name
self.pv_name = pv_name
self.lv_name = lv_name
self.size = size
self.extent_size = extent_size
@classmethod
def get_segments(cls):
def iter_segments():
for i in command('pvs', '--noheadings', '--nosuffix', '--unit=b', '--separator=\t', '--options=vg_name,pv_name,lv_name,seg_size,vg_extent_size', output = True).splitlines():
vg_name, pv_name, lv_name, size, extent_size = i.lstrip().split('\t')
if not lv_name:
lv_name = None
yield cls(vg_name, pv_name, lv_name, int(size), int(extent_size))
return list(iter_segments())
def group(seq, key_fn):
def sort_key_fn(x):
key = key_fn(x)
if key is None:
return 2,
else:
return 1, key
def iter_groups():
for key, values in itertools.groupby(sorted(seq, key = sort_key_fn), key_fn):
yield key, list(values)
return iter_groups()
def sum_segments(segments):
return sum(i.size for i in segments)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-e', '--extents', action = 'store_true', help = 'Display sizes in number of physical extents.')
return parser.parse_args()
def main():
args = parse_args()
segments = Segment.get_segments()
first = True
def format_lv_name(lv_name):
if lv_name is None:
return '*free*'
else:
return lv_name
for vg_name, vg_segments in group(segments, lambda x: x.vg_name):
if first:
first = False
else:
print()
if args.extents:
extent_size = vg_segments[0].extent_size
def format_size(bytes):
return '{} PE'.format(bytes // extent_size)
else:
format_size = si_size
vg_size = sum_segments(vg_segments)
print('{} ({}):'.format(vg_name, format_size(vg_size)))
for lv_name, lv_segments in group(vg_segments, lambda x: x.lv_name):
lv_size = sum_segments(lv_segments)
def iter_pvs():
for pv_name, pv_segments in group(lv_segments, lambda x: x.pv_name):
pv_usage_size = sum_segments(pv_segments)
yield '{} ({})'.format(pv_name, format_size(pv_usage_size))
print(' {} ({}): {}'.format(format_lv_name(lv_name), format_size(lv_size), ', '.join(iter_pvs())))
first_pv = True
for pv_name, pv_segments in group(vg_segments, lambda x: x.pv_name):
if first_pv:
first_pv = False
print()
pv_size = sum_segments(pv_segments)
def iter_lvs():
for lv_name, lv_segments in group(pv_segments, lambda x: x.lv_name):
lv_usage_size = sum_segments(lv_segments)
yield '{} ({})'.format(format_lv_name(lv_name), format_size(lv_usage_size))
print(' {} ({}): {}'.format(pv_name, format_size(pv_size), ', '.join(iter_lvs())))
try:
main()
except UserError as e:
log('Error: {}', e)
except KeyboardInterrupt:
log('Operation interrupted.')
@Feuermurmel
Copy link
Author

Sample output:

apu-vg1 (4.5 TB):
     backup (1.82 TB): /dev/sde (825 GB), /dev/sdg (1 TB)
     data (1.93 TB): /dev/sdc (1 TB), /dev/sdf (932 GB)
     *free* (742 GB): /dev/sdd (500 GB), /dev/sde (175 GB), /dev/sdf (67.6 GB)

     /dev/sdc (1 TB): data (1 TB)
     /dev/sdd (500 GB): *free* (500 GB)
     /dev/sde (1 TB): backup (825 GB), *free* (175 GB)
     /dev/sdf (1 TB): data (932 GB), *free* (67.6 GB)
     /dev/sdg (1 TB): backup (1 TB)

test (1 TB):
     lv-test (1 TB): /dev/sda (1 TB)

     /dev/sda (1 TB): lv-test (1 TB)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment