Source code for insights.parsers.df

"""
Disk free space commands
========================

Module for the processing of output from the ``df`` command.  The base class
``DiskFree`` provides all of the functionality for all classes.
Data is avaliable as rows of the output contained in one ``Record`` object
for each line of output.

Parsers contained in this module are:

DiskFree_LI - command ``df -li``
--------------------------------

DiskFree_ALP - command ``df -alP``
----------------------------------

DiskFree_AL - command ``df -al``
--------------------------------

"""
from collections import namedtuple
from insights.core import CommandParser
from insights.core.exceptions import ParseException
from insights.core.plugins import parser
from insights.specs import Specs

Record = namedtuple("Record", ['filesystem', 'total', 'used', 'available', 'capacity', 'mounted_on'])
"""namedtuple: Represents the information parsed from ``df`` command output."""


[docs] def parse_df_lines(df_content): """Parse contents of each line in ``df`` output. Parse each line of ``df`` output ensuring that wrapped lines are reassembled prior to parsing, and that mount names containing spaces are maintained. Parameters: df_content (list): Lines of df output to be parsed. Returns: list: A list of ``Record`` ``namedtuple``'s. One for each line of the ``df`` output with columns as the key values. The fields of ``Record`` provide information about the file system attributes as determined by the arguments to the ``df`` command. So, for example, if ``df`` is given the ``-alP``, the values are in terms of 1024 blocks. If ``-li`` is given, then the values are in terms of inodes:: - filesystem: Name of the filesystem - total (str): total number of resources on the filesystem - used (str): number of the resources used on the filesystem - available (str): number of the resource available on the filesystem - capacity (str): percentage of the resource used on the filesystem - mounted_on: mount point of the filesystem """ df_ls = {} df_out = [] is_sep = False columns = Record._fields for line in df_content[1:]: # [1:] -> Skip the header # Stop at 5 splits to avoid splitting spaces in path line_splits = line.rstrip().split(None, 5) if len(line_splits) >= 6: for i, name in enumerate(columns): df_ls[name] = line_splits[i] is_sep = False elif len(line_splits) == 1: # First line of the separated line df_ls[columns[0]] = line_splits[0] is_sep = True elif is_sep and len(line_splits) >= 5: # Re-split to avoid this kind of "Mounted on": "VMware Tools" line_splits = line.split(None, 4) # Last line of the separated line for i, name in enumerate(columns[1:]): df_ls[name] = line_splits[i] is_sep = False elif not line_splits: # Skip empty lines (might in sosreport) continue else: raise ParseException("Could not parse line '{l}'".format(l=line)) # Only add this record if we've got a line and it's not separated if df_ls and not is_sep: rec = Record(**df_ls) df_out.append(rec) df_ls = {} return df_out
[docs] class DiskFree(CommandParser): """ Class to provide methods used by all ``df`` command classes. Attributes: data (list of Record): List of ``Record`` objects for each line of command output. filesystems (dict of list): Dictionary with each entry being a list of ``Record`` objects, for all lines in the command output. The dictionary is keyed by the ``filesystem`` value of the Record. mounts (dict): Dictionary with each entry being a ``Record`` object corresponding to the ``mounted_on`` key. Raises: ParseException: When there are lines cannot be parsed or the ``block size`` cannot be recognized. """ def __init__(self, context): super(DiskFree, self).__init__(context) self.filesystems = {} self.mounts = {} for datum in self.data: if datum.filesystem not in self.filesystems: self.filesystems[datum.filesystem] = [] self.filesystems[datum.filesystem].append(datum) self.mounts[datum.mounted_on] = datum def __len__(self): return len(self.data) def __iter__(self): for row in self.data: yield row
[docs] def parse_content(self, content): def _digital_block_size(_block_size): """ # man df SIZE may be (or may be an integer optionally followed by) one of following: KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y. """ units = { '': 1, 'B': 1, 'K': 1024, 'KB': 1000, 'M': 1024 * 1024, 'MB': 1000 * 1000, 'G': 1024 * 1024 * 1024, 'GB': 1000 * 1000 * 1000, 'T': 1024 * 1024 * 1024 * 1024, 'TB': 1000 * 1000 * 1000 * 1000, 'P': 1024 * 1024 * 1024 * 1024 * 1024, 'PB': 1000 * 1000 * 1000 * 1000 * 1000, 'E': 1024 * 1024 * 1024 * 1024 * 1024 * 1024, 'EB': 1000 * 1000 * 1000 * 1000 * 1000 * 1000, 'Z': 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, 'ZB': 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, 'Y': 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, 'YB': 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, } suffix = _block_size[-2:].lstrip('0123456789') suffix_up = suffix.upper() if suffix_up in units: return units[suffix_up] * int(_block_size.rstrip('kKMGTPEZYB')) raise ParseException("Unknown block size: '{0}'".format(suffix)) bad_lines = ["no such file or directory"] content = [l for l in content if bad_lines[0] not in l.lower()] self.block_size = self.raw_block_size = None # Get the block_size when there is such column header = content[0] if 'blocks' in header: block_size = [i.split('-')[0] for i in header.split() if 'blocks' in i][0] self.raw_block_size = block_size self.block_size = _digital_block_size(block_size) self.data = parse_df_lines(content)
@property def filesystem_names(self): """list: Returns list of unique filesystem names.""" return sorted(self.filesystems.keys())
[docs] def get_filesystem(self, name): """str: Returns list of Record objects for filesystem ``name``.""" return self.filesystems.get(name, [])
@property def mount_names(self): """list: Returns list of unique mount point names.""" return sorted(self.mounts.keys())
[docs] def get_mount(self, name): """Record: Returns Record obj for mount point ``name``.""" return self.mounts.get(name)
[docs] def get_dir(self, path): """ Record: returns the Record object that contains the given path. This finds the most specific mount path that contains the given path. """ try: longest = max(m for m in self.mounts if path.startswith(m)) return self.mounts[longest] except ValueError: return None
[docs] @parser(Specs.df__li) class DiskFree_LI(DiskFree): """Parse lines from the output of the ``df -li`` command. Typical content of the ``df -li`` command output looks like:: Filesystem Inodes IUsed IFree IUse% Mounted on devtmpfs 242224 359 241865 1% /dev tmpfs 246028 1 246027 1% /dev/shm tmpfs 246028 491 245537 1% /run tmpfs 246028 17 246011 1% /sys/fs/cgroup /dev/sda2 8911872 58130 8853742 1% / /dev/sdb1 26213888 19662 26194226 1% /opt/data /dev/sda1 524288 306 523982 1% /boot tmpfs 246028 5 246023 1% /run/user/0 Attributes: data (list): A list of the ``df`` information with one ``Record`` object for each line of command output. Mapping of input columns to output fields is:: Input column Output Field ------------ ------------ Filesystem filesystem Inodes total IUsed used IFree available IUse% capacity Mounted on mounted_on Examples: >>> len(df_li) 8 >>> len(df_li.filesystem_names) 5 >>> df_li.get_filesystem('/dev/sda1')[0].mounted_on == '/boot' True >>> '/opt/data' in df_li.mount_names True >>> df_li.get_mount('/sys/fs/cgroup').available == '246011' True >>> [d.mounted_on for d in df_li if 'sda' in d.filesystem] == ['/', '/boot'] True >>> df_li.data[0].filesystem == 'devtmpfs' True >>> df_li.data[0].capacity == '1%' True """ pass
[docs] @parser(Specs.df__alP) class DiskFree_ALP(DiskFree): """Parse lines from the output of the ``df -alP`` command. Typical content of the ``df -alP`` command looks like:: Filesystem 1024-blocks Used Available Capacity Mounted on sysfs 0 0 0 - /sys proc 0 0 0 - /proc devtmpfs 968896 0 968896 0% /dev securityfs 0 0 0 - /sys/kernel/security tmpfs 984112 0 984112 0% /dev/shm devpts 0 0 0 - /dev/pts tmpfs 984112 8660 975452 1% /run tmpfs 984112 0 984112 0% /sys/fs/cgroup cgroup 0 0 0 - /sys/fs/cgroup/systemd cgroup 0 0 0 - /sys/fs/cgroup/pids cgroup 0 0 0 - /sys/fs/cgroup/rdma configfs 0 0 0 - /sys/kernel/config /dev/sda2 17813504 2127172 15686332 12% / selinuxfs 0 0 0 - /sys/fs/selinux systemd-1 - - - - /proc/sys/fs/binfmt_misc debugfs 0 0 0 - /sys/kernel/debug mqueue 0 0 0 - /dev/mqueue hugetlbfs 0 0 0 - /dev/hugepages /dev/sdb1 52402180 1088148 51314032 3% /V M T o o l s /dev/sda1 1038336 185676 852660 18% /boot Attributes: data (list): A list of the ``df`` information with one ``Record`` object for each line of command output. Mapping of input columns to output fields is:: Input column Output Field ------------ ------------ Filesystem filesystem 1024-blocks total Used used Available available Capacity capacity Mounted on mounted_on raw_block_size (str): The unit of display values. block_size (int): The unit of display values, which is converted to integer. Examples: >>> len(df_alP) 20 >>> len(df_alP.filesystem_names) 16 >>> df_alP.raw_block_size '1024' >>> df_alP.block_size 1024 >>> df_alP.get_filesystem('/dev/sda2')[0].mounted_on == '/' True >>> '/V M T o o l s' in df_alP.mount_names True >>> df_alP.get_mount('/boot').available '852660' >>> int(int(df_alP.get_mount('/boot').available) * df_alP.block_size / 1024) # to KB 852660 >>> int(int(df_alP.get_mount('/boot').available) * df_alP.block_size / 1024 / 1024) # to MB 832 >>> [d.mounted_on for d in df_alP if 'sda' in d.filesystem] == ['/', '/boot'] True >>> df_alP.data[0].filesystem == 'sysfs' True """ pass
[docs] @parser(Specs.df__al) class DiskFree_AL(DiskFree): """Parse lines from the output of the ``df -al`` command. Typical content of the ``df -al`` command looks like:: Filesystem 1K-blocks Used Available Use% Mounted on sysfs 0 0 0 - /sys proc 0 0 0 - /proc devtmpfs 968896 0 968896 0% /dev securityfs 0 0 0 - /sys/kernel/security tmpfs 984112 0 984112 0% /dev/shm devpts 0 0 0 - /dev/pts tmpfs 984112 8660 975452 1% /run tmpfs 984112 0 984112 0% /sys/fs/cgroup cgroup 0 0 0 - /sys/fs/cgroup/systemd cgroup 0 0 0 - /sys/fs/cgroup/pids cgroup 0 0 0 - /sys/fs/cgroup/rdma configfs 0 0 0 - /sys/kernel/config /dev/sda2 17813504 2127172 15686332 12% / selinuxfs 0 0 0 - /sys/fs/selinux systemd-1 - - - - /proc/sys/fs/binfmt_misc debugfs 0 0 0 - /sys/kernel/debug mqueue 0 0 0 - /dev/mqueue hugetlbfs 0 0 0 - /dev/hugepages /dev/sdb1 52402180 1088148 51314032 3% /V M T o o l s /dev/sda1 1038336 185676 852660 18% /boot Attributes: data (list): A list of the ``df`` information with one ``Record`` object for each line of command output. Mapping of input columns to output fields is:: Input column Output Field ------------ ------------ Filesystem filesystem 1K-blocks total Used used Available available Use% capacity Mounted on mounted_on raw_block_size (str): The unit of display values. block_size (int): The unit of display values, which is converted to integer. Examples: >>> len(df_al) 20 >>> len(df_al.filesystem_names) 16 >>> df_al.raw_block_size '1K' >>> df_al.block_size 1024 >>> df_al.get_filesystem('/dev/sda2')[0].mounted_on == '/' True >>> '/V M T o o l s' in df_al.mount_names True >>> df_al.get_mount('/').used '2127172' >>> int(int(df_al.get_mount('/').used) * df_alP.block_size / 1024) # to KB 2127172 >>> int(int(df_al.get_mount('/').used) * df_alP.block_size / 1024 / 1024) # to MB 2077 >>> [d.mounted_on for d in df_al if 'sda' in d.filesystem] == ['/', '/boot'] True >>> df_al.data[0].filesystem == 'sysfs' True """ pass