* the cd context manager pwd(); with cd(other_dir): pwd(); pwd();
will print current working dir, then other_dir, then first dir again
+* a wrapper around "df" to determine size and usage of file systems
.. todo:: create unittest
import contextlib
import os
+from call_helpers import call_and_capture
+from warnings import warn
+from math import floor, ceil
+
@contextlib.contextmanager
yield
finally:
os.chdir(prev_cwd)
+
+
+DF_CMD = ['/usr/bin/df', '--no-sync', '--portability']
+
+class FilesystemStats:
+ """ representation of 1 line of the 'df' command
+
+ has fields filesystem, size, used, available, capacity, mount_point
+
+ Note that only apprixomately capacity == used/size
+ and that only approximately used + available == size
+ """
+
+ def __init__(self):
+ name = None
+ size = None
+ used = None
+ available = None
+ capacity = None
+ mount_point = None
+
+
+def get_disc_stats():
+ """ get stats on all filesystems
+
+ parses the output of cmd 'df', returns list of FilesystemStats
+ """
+
+ # call
+ code, out, err = call_and_capture(DF_CMD)
+
+ # warn if unexpected outcome
+ if code != 0:
+ warn('df returned non-zero exit code {0}!'.format(code))
+ if err:
+ for line in err:
+ warn('df produced output to stderr: "{0}"'.format(line))
+
+ # find columns in output that are just spaces
+ min_len = min(len(line) for line in out)
+ separator_cols = [idx for idx in range(min_len) if \
+ all(line[idx] == ' ' for line in out)]
+
+ # check columns and their header
+ if len(separator_cols) != 5:
+ raise ValueError('unepexpected number of separator columns: {0}'
+ .format(separator_cols)) # must eliminate neighbours?
+
+ title_line = out[0]
+ title = title_line[ : separator_cols[0]].strip()
+ if title != 'Filesystem':
+ warn('Unexpected column title: "{0}" != "Filesystem"!'
+ .format(title))
+ title = title_line[separator_cols[0] : separator_cols[1]].strip()
+ if title != '1024-blocks':
+ warn('Unexpected column title: "{0}" != "1024-blocks"!'
+ .format(title))
+ title = title_line[separator_cols[1] : separator_cols[2]].strip()
+ if title != 'Used':
+ warn('Unexpected column title: "{0}" != "Used"!'
+ .format(title))
+ title = title_line[separator_cols[2] : separator_cols[3]].strip()
+ if title != 'Available':
+ warn('Unexpected column title: "{0}" != "Available"!'
+ .format(title))
+ title = title_line[separator_cols[3] : separator_cols[4]].strip()
+ if title != 'Capacity':
+ warn('Unexpected column title: "{0}" != "Capacity"!'
+ .format(title))
+ title = title_line[separator_cols[4] : ].strip()
+ if title != 'Mounted on':
+ warn('Unexpected column title: "{0}" != "Mounted on"!'
+ .format(title))
+
+ # create result
+ result = []
+ for line in out[1:]:
+ stats = FilesystemStats()
+ stats.name = line[ : separator_cols[0]].strip()
+ stats.size = int(line[separator_cols[0] : separator_cols[1]].strip())
+ stats.used = int(line[separator_cols[1] : separator_cols[2]].strip())
+ stats.available = int(line[separator_cols[2] : separator_cols[3]]\
+ .strip())
+ stats.capacity = int(line[separator_cols[3] : separator_cols[4]]\
+ .strip()[:-1])
+ stats.mount_point = line[separator_cols[4] : ].strip()
+
+ # more checks: does match capacity
+ capacity = 100. * stats.used / stats.size
+ if abs(capacity - stats.capacity) > 5.:
+ warn('capacities for {0} deviate more than 5%: '
+ '{1} != {2:.2f}(={3}/{4})'.format(
+ stats.name, stats.capacity, capacity, stats.used, stats.size))
+
+ size = stats.used + stats.available
+ if float(abs(stats.size - size)) / float(max(stats.size, size)) > 0.1:
+ warn('size for {0} differs by more than 10% from used+available!'
+ .format(stats.name))
+
+ result.append(stats)
+
+ return result