From 8bda59fbe7e37074419943632548f8fd62709892 Mon Sep 17 00:00:00 2001 From: Micah David Cochran Date: Mon, 3 Jan 2022 14:57:01 -0600 Subject: [PATCH] made collect_os_info script in Python --- utils/collect_os_info.py | 158 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 utils/collect_os_info.py diff --git a/utils/collect_os_info.py b/utils/collect_os_info.py new file mode 100644 index 00000000..0f5bf04b --- /dev/null +++ b/utils/collect_os_info.py @@ -0,0 +1,158 @@ + +import os +from pathlib import Path +import subprocess +import sys +from typing import Optional, Union + +# external requirement +import psutil + +# from nominatim.version import NOMINATIM_VERSION +# from nominatim.db.connection import connect + + +class ReportSystemInformation: + """Generate a report about the host system including software versions, memory, + storage, and database configuration.""" + def __init__(self): + self._memory: int = psutil.virtual_memory().total + self.friendly_memory: str = self._friendly_memory_string(self._memory) + # psutil.cpu_count(logical=False) returns the number of CPU cores. + # For number of logical cores (Hypthreaded), call psutil.cpu_count() or os.cpu_count() + self.num_cpus: int = psutil.cpu_count(logical=False) + self.os_info: str = self._os_name_info() + +### These are commented out because they have not been tested. +# self.nominatim_ver: str = '{0[0]}.{0[1]}.{0[2]}-{0[3]}'.format(NOMINATIM_VERSION) +# self._pg_version = conn.server_version_tuple() +# self._postgis_version = conn.postgis_version_tuple() +# self.postgresql_ver: str = self._convert_version(self._pg_version) +# self.postgis_ver: str = self._convert_version(self._postgis_version) + + self.nominatim_ver: str = "" + self.postgresql_ver: str = "" + self.postgresql_config: str = "" + self.postgis_ver: str = "" + + # the below commands require calling the shell to gather information + self.disk_free: str = self._run_command(["df", "-h"]) + self.lsblk: str = self._run_command("lsblk") + # psutil.disk_partitions() <- this function is similar to the above, but it is cross platform + + # Note: `systemd-detect-virt` command only works on Linux, on other OSes + # should give a message: "Unknown (unable to find the 'systemd-detect-virt' command)" + self.container_vm_env: str = self._run_command("systemd-detect-virt") + + def _convert_version(self, ver_tup: tuple) -> str: + """converts tuple version (ver_tup) to a string representation""" + return ".".join(map(str,ver_tup)) + + def _friendly_memory_string(self, mem: int) -> str: + """Create a user friendly string for the amount of memory specified as mem""" + mem_magnitude = ('bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') + mag = 0 + # determine order of magnitude + while mem > 1000: + mem /= 1000 + mag += 1 + + return f"{mem:.1f} {mem_magnitude[mag]}" + + + def _run_command(self, cmd: Union[str, list]) -> str: + """Runs a command using the shell and returns the output from stdout""" + try: + if sys.version_info < (3, 7): + cap_out = subprocess.run(cmd, stdout=subprocess.PIPE) + else: + cap_out = subprocess.run(cmd, capture_output=True) + return cap_out.stdout.decode("utf-8") + except FileNotFoundError: + # non-Linux system should end up here + return f"Unknown (unable to find the '{cmd}' command)" + + + def _os_name_info(self) -> str: + """Obtain Operating System Name (and possibly the version)""" + + os_info = None + # man page os-release(5) details meaning of the fields + if Path("/etc/os-release").is_file(): + os_info = self._from_file_find_line_portion("/etc/os-release", "PRETTY_NAME", "=") + # alternative location + elif Path("/usr/lib/os-release").is_file(): + os_info = self._from_file_find_line_portion("/usr/lib/os-release", "PRETTY_NAME", "=") + + # fallback on Python's os name + if(os_info is None or os_info == ""): + os_info = os.name + + # if the above is insufficient, take a look at neofetch's approach to OS detection + return os_info + + + # Note: Intended to be used on informational files like /proc + def _from_file_find_line_portion(self, filename: str, start: str, sep: str, + fieldnum: int = 1) -> Optional[str]: + """open filename, finds the line starting with the 'start' string. + Splits the line using seperator and returns a "fieldnum" from the split.""" + with open(filename) as fh: + for line in fh: + if line.startswith(start): + result = line.split(sep)[fieldnum].strip() + return result + + def report(self, out = sys.stdout, err = sys.stderr) -> None: + """Generates the Markdown report. + + Optionally pass out or err parameters to redirect the output of stdout + and stderr to other file objects.""" + + # NOTE: This should be a report format. Any conversions or lookup has be + # done, do that action in the __init__() or another function. + message = """ +Use this information in your issue report at https://github.com/osm-search/Nominatim/issues +Copy and paste or redirect the output of the file: + $ ./collect_os_info.py > report.md +""" + report = f""" +**Software Environment:** +- Python version: {sys.version} +- Nominatim version: {self.nominatim_ver} +- PostgreSQL version: {self.postgresql_ver} +- PostGIS version: {self.postgis_ver} +- OS: {self.os_info} + + +**Hardware Configuration:** +- RAM: {self.friendly_memory} +- number of CPUs: {self.num_cpus} +- bare metal/AWS/other cloud service (per systemd-detect-virt(1)): {self.container_vm_env} +- type and size of disks: +**`df -h` - df - report file system disk space usage: ** +``` +{self.disk_free} +``` + +**lsblk - list block devices: ** +``` +{self.lsblk} +``` + + +**Postgresql Configuration:** +``` +{self.postgresql_config} +``` +**Notes** +Please add any notes about anything above anything above that is incorrect. + """ + + print(message, file = err) + print(report, file = out) + + +if __name__ == "__main__": + sys_info = ReportSystemInformation() + sys_info.report() \ No newline at end of file -- 2.39.5