diff --git a/bin/pylsblk b/bin/pylsblk new file mode 100644 index 0000000..2cd0117 --- /dev/null +++ b/bin/pylsblk @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import os +import yaml + +def get_device_info(device_name): + info = { + 'device': device_name, + 'sector_size': 512, + 'size': 0, + 'mount': None, + 'model': None, + 'tran': 'sata', + 'rota': True, + 'parent': None + } + + sector_size_file = f'/sys/class/block/{device_name}/queue/hw_sector_size' + if os.path.exists(sector_size_file): + with open(sector_size_file) as sector_size_fd: + info['sector_size'] = int(sector_size_fd.read().strip()) * 512 + + # Get device size + size_file = f'/sys/class/block/{device_name}/size' + if os.path.exists(size_file): + with open(size_file) as size_fd: + info['size'] = int(size_fd.read().strip()) * info['sector_size'] + + # Get mount point + with open('/proc/mounts') as mounts_fd: + for line in mounts_fd: + parts = line.strip().split() + if len(parts) >= 2 and parts[0] == f'/dev/{device_name}': + info['mount'] = parts[1] + + # Get device model + model_file = f'/sys/class/block/{device_name}/device/model' + if os.path.exists(model_file): + with open(model_file) as model_fd: + info['model'] = model_fd.read().strip() + + # Get device transport + transport_file = f'/sys/class/block/{device_name}/device/transport' + if os.path.exists(transport_file): + with open(transport_file) as transport_fd: + info['tran'] = transport_fd.read().strip() + + # Get device rotation + rotational_file = f'/sys/class/block/{device_name}/queue/rotational' + if os.path.exists(rotational_file): + with open(rotational_file) as rotational_fd: + info['rota'] = int(rotational_fd.read().strip()) + + return info + + +def main(): + bdev_names = [bdev_name for bdev_name in os.listdir('/sys/class/block/') + if not bdev_name.startswith('loop')] + bdevs = [get_device_info(bdev_name) + for bdev_name in bdev_names] + + # Get correct lineage + for parent in bdevs: + for child in bdevs: + if parent['device'] in child['device'] and \ + parent['device'] != child['device']: + child['parent'] = parent['device'] + child['model'] = parent['model'] + child['rota'] = parent['rota'] + child['tran'] = parent['tran'] + + # Print bdev info + print(yaml.dump(bdevs)) + +if __name__ == '__main__': + main() diff --git a/jarvis_util/introspect/system_info.py b/jarvis_util/introspect/system_info.py index 455fd59..bf2c15a 100644 --- a/jarvis_util/introspect/system_info.py +++ b/jarvis_util/introspect/system_info.py @@ -11,6 +11,7 @@ from jarvis_util.serialize.yaml_file import YamlFile import jarvis_util.util.small_df as sdf import json +import yaml import shlex import ipaddress import copy @@ -155,6 +156,44 @@ def wait(self): print(self.df) +class PyLsblk(Exec): + """ + List all block devices in the system per-node. PyLsblk will return + a YAML output + + A table is stored per-host: + parent: the parent device of the partition (e.g., /dev/sda or NaN) + device: the name of the partition (e.g., /dev/sda1) + size: total size of the partition (bytes) + mount: where the partition is mounted (if anywhere) + model: the exact model of the device + tran: the transport of the device (e.g., /dev/nvme) + rota: whether or not the device is rotational + host: the host this record corresponds to + """ + + def __init__(self, exec_info): + cmd = 'pylsblk' + super().__init__(cmd, exec_info.mod(collect_output=True)) + self.exec_async = exec_info.exec_async + self.df = None + if not self.exec_async: + self.wait() + + def wait(self): + super().wait() + total = [] + for host, stdout in self.stdout.items(): + lsblk_data = yaml.load(stdout, Loader=yaml.FullLoader) + for dev in lsblk_data: + if dev['tran'] == 'pcie': + dev['tran'] = 'nvme' + dev['host'] = host + total.append(dev) + self.df = sdf.SmallDf(rows=total) + print(self.df) + + class Blkid(Exec): """ List all filesystems (even those unmounted) and their properties @@ -162,7 +201,7 @@ class Blkid(Exec): Stores a per-host table with the following: device: the device (or partition) which stores the data (e.g., /dev/sda) fs_type: the type of filesystem (e.g., ext4) - uuid: filesystem-levle uuid from the FS metadata + uuid: filesystem-level uuid from the FS metadata partuuid: the partition-lable UUID for the partition partlabel: semantic partition type information label: semantic label given by users @@ -341,7 +380,7 @@ def build(self, exec_info, introspect=True): Build a resource graph. :param exec_info: Where to collect resource information - :param introspect: Whether to introspect system info, or rely solely + :param introspect: Whether to pylsblk system info, or rely solely on admin-defined settings :return: self """ @@ -542,7 +581,7 @@ def _introspect(self, exec_info): :param exec_info: Where to collect resource information :return: None """ - self.lsblk = Lsblk(exec_info.mod(hide_output=True)) + self.lsblk = PyLsblk(exec_info.mod(hide_output=True)) self.blkid = Blkid(exec_info.mod(hide_output=True)) self.list_fs = ListFses(exec_info.mod(hide_output=True)) self.fi_info = FiInfo(exec_info.mod(hide_output=True)) diff --git a/setup.py b/setup.py index a1bdd4e..ac2d270 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ setuptools.setup( name="jarvis_util", packages=setuptools.find_packages(), + scripts=['bin/pylsblk'], version="0.0.1", author="Luke Logan", author_email="llogan@hawk.iit.edu",