1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Collection of host system information including software versions, memory,
9 storage, and database configuration.
14 from pathlib import Path
15 from typing import List, Optional, Union
18 from psycopg2.extensions import make_dsn
20 from ..config import Configuration
21 from ..db.connection import connect, server_version_tuple, execute_scalar
22 from ..version import NOMINATIM_VERSION
25 def friendly_memory_string(mem: float) -> str:
26 """Create a user friendly string for the amount of memory specified as mem"""
27 mem_magnitude = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
29 # determine order of magnitude
34 return f"{mem:.1f} {mem_magnitude[mag]}"
37 def run_command(cmd: Union[str, List[str]]) -> str:
38 """Runs a command using the shell and returns the output from stdout"""
40 if sys.version_info < (3, 7):
41 cap_out = subprocess.run(cmd, stdout=subprocess.PIPE, check=False)
43 cap_out = subprocess.run(cmd, capture_output=True, check=False)
44 return cap_out.stdout.decode("utf-8")
45 except FileNotFoundError:
46 # non-Linux system should end up here
47 return f"Unknown (unable to find the '{cmd}' command)"
50 def os_name_info() -> str:
51 """Obtain Operating System Name (and possibly the version)"""
53 # man page os-release(5) details meaning of the fields
54 if Path("/etc/os-release").is_file():
55 os_info = from_file_find_line_portion(
56 "/etc/os-release", "PRETTY_NAME", "=")
57 # alternative location
58 elif Path("/usr/lib/os-release").is_file():
59 os_info = from_file_find_line_portion(
60 "/usr/lib/os-release", "PRETTY_NAME", "="
63 # fallback on Python's os name
64 if os_info is None or os_info == "":
67 # if the above is insufficient, take a look at neofetch's approach to OS detection
71 # Note: Intended to be used on informational files like /proc
72 def from_file_find_line_portion(
73 filename: str, start: str, sep: str, fieldnum: int = 1
75 """open filename, finds the line starting with the 'start' string.
76 Splits the line using separator and returns a "fieldnum" from the split."""
77 with open(filename, encoding='utf8') as file:
80 if line.startswith(start):
81 result = line.split(sep)[fieldnum].strip()
85 def get_postgresql_config(version: int) -> str:
86 """Retrieve postgres configuration file"""
88 with open(f"/etc/postgresql/{version}/main/postgresql.conf", encoding='utf8') as file:
89 db_config = file.read()
93 return f"**Could not read '/etc/postgresql/{version}/main/postgresql.conf'**"
96 def report_system_information(config: Configuration) -> None:
97 """Generate a report about the host system including software versions, memory,
98 storage, and database configuration."""
100 with connect(make_dsn(config.get_libpq_dsn(), dbname='postgres')) as conn:
101 postgresql_ver: str = '.'.join(map(str, server_version_tuple(conn)))
103 with conn.cursor() as cur:
104 cur.execute("SELECT datname FROM pg_catalog.pg_database WHERE datname=%s",
105 (config.get_database_params()['dbname'], ))
106 nominatim_db_exists = cur.rowcount > 0
108 if nominatim_db_exists:
109 with connect(config.get_libpq_dsn()) as conn:
110 postgis_ver: str = execute_scalar(conn, 'SELECT postgis_lib_version()')
112 postgis_ver = "Unable to connect to database"
114 postgresql_config: str = get_postgresql_config(int(float(postgresql_ver)))
116 # Note: psutil.disk_partitions() is similar to run_command("lsblk")
118 # Note: run_command("systemd-detect-virt") only works on Linux, on other OSes
119 # should give a message: "Unknown (unable to find the 'systemd-detect-virt' command)"
121 # Generates the Markdown report.
125 Use this information in your issue report at https://github.com/osm-search/Nominatim/issues
126 Redirect the output to a file:
127 $ ./collect_os_info.py > report.md
130 **Software Environment:**
131 - Python version: {sys.version}
132 - Nominatim version: {NOMINATIM_VERSION!s}
133 - PostgreSQL version: {postgresql_ver}
134 - PostGIS version: {postgis_ver}
135 - OS: {os_name_info()}
138 **Hardware Configuration:**
139 - RAM: {friendly_memory_string(psutil.virtual_memory().total)}
140 - number of CPUs: {psutil.cpu_count(logical=False)}
141 - bare metal/AWS/other cloud service (per systemd-detect-virt(1)): {run_command("systemd-detect-virt")}
142 - type and size of disks:
143 **`df -h` - df - report file system disk space usage: **
145 {run_command(["df", "-h"])}
148 **lsblk - list block devices: **
150 {run_command("lsblk")}
154 **Postgresql Configuration:**
159 Please add any notes about anything above anything above that is incorrect.