created a wrapper around "df" command in file_helpers
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Tue, 12 Jan 2016 12:27:01 +0000 (13:27 +0100)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Tue, 12 Jan 2016 12:27:01 +0000 (13:27 +0100)
file_helpers.py

index 20c7f9c..06566d7 100644 (file)
@@ -4,6 +4,7 @@ Featuring::
 
 * 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
 
@@ -12,6 +13,10 @@ Featuring::
 
 import contextlib
 import os
+from call_helpers import call_and_capture
+from warnings import warn
+from math import floor, ceil
+
 
 
 @contextlib.contextmanager
@@ -29,3 +34,105 @@ def cd(path):
         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