Edit on GitHub

sysmon_pytk._common

Shared functions used throughout the application.

  1# SPDX-FileCopyrightText: © 2024 Stacey Adams <stacey.belle.rose@gmail.com>
  2# SPDX-License-Identifier: MIT
  3
  4"""
  5Shared functions used throughout the application.
  6"""
  7
  8from __future__ import annotations
  9
 10import platform
 11import re
 12import subprocess  # nosec B404
 13import time
 14from socket import AF_INET
 15
 16import psutil
 17
 18DISK_ALERT_LEVEL = 80
 19"""Percent of disk usage at which the meter shows red."""
 20
 21DISK_WARN_LEVEL = 60
 22"""Percent of disk usage at which the meter shows yellow."""
 23
 24REFRESH_INTERVAL = 750
 25"""Number of milliseconds between widget refreshes."""
 26
 27INTERNAL_PAD = 12
 28"""Standard widget padding, in pixels."""
 29
 30BYTE_SYMBOLS = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
 31
 32# SPDX-SnippetBegin
 33# SPDX-SnippetName bytes2human function
 34# http://code.google.com/p/pyftpdlib/source/browse/trunk/test/bench.py
 35# SPDX-FileCopyrightText: © 2007-2013 Giampaolo Rodola' <g.rodola@gmail.com>
 36# SPDX-License-Identifier: MIT
 37
 38
 39def bytes2human(num: int, precision: int = 1) -> str:
 40    """
 41    Convert a byte count to a human-readable format, with a given precision.
 42
 43    Parameters
 44    ----------
 45    num : int
 46        The number to format.
 47    precision : int, optional
 48        The number of decimals to use for display.
 49
 50    Returns
 51    -------
 52    str
 53        The number in human-readable format.
 54
 55    Examples
 56    --------
 57    >>> bytes2human(10000)
 58    '9.8KiB'
 59    >>> bytes2human(100001221)
 60    '95.4MiB'
 61    """
 62    # originally taken from pyftpdlib, then rewritten
 63    prefix = {}
 64    for idx, symb in enumerate(BYTE_SYMBOLS[1:]):
 65        prefix[symb] = 1 << (idx+1)*10
 66    for symbol in reversed(BYTE_SYMBOLS[1:]):
 67        if num >= prefix[symbol]:
 68            value = float(num) / prefix[symbol]
 69            return f"{value:.{precision}f}{symbol}"
 70    return f"{num:.{precision}f}{BYTE_SYMBOLS[0]}"
 71
 72# SPDX-SnippetEnd
 73
 74
 75def bytes2whole(num: int) -> str:
 76    """
 77    Convert a byte count to a human-readable format, with no decimal.
 78
 79    Parameters
 80    ----------
 81    num : int
 82        The number to format.
 83
 84    Returns
 85    -------
 86    str
 87        The number in human-readable format.
 88
 89    Examples
 90    --------
 91    >>> bytes2whole(10000)
 92    '10KiB'
 93    >>> bytes2whole(100001221)
 94    '95MiB'
 95    """
 96    return bytes2human(num, precision=0)
 97
 98
 99def digits(numstr: str) -> list[int]:
100    """
101    Return a list of numbers in a string.
102
103    Parameters
104    ----------
105    numstr : str
106        A string containing various numbers.
107
108    Returns
109    -------
110    List[int]
111        A list of integers extracted from the string.
112
113    Examples
114    --------
115    >>> digits("")
116    []
117    >>> digits("8.5MiB")
118    [8, 5]
119    >> digits("13. Notes vol. 22, pp. 585-588, 1996.")
120    [13, 22, 585, 588, 1996]
121    """
122    return [int(s) for s in re.findall(r"\d+", numstr)]
123
124
125def cpu_temp() -> float:
126    """
127    Return the CPU temperature in degrees Celsius.
128
129    Returns
130    -------
131    float
132        The current CPU temperature.
133
134    Examples
135    --------
136    >>> cpu_temp()
137    41.0
138    """
139    temps = psutil.sensors_temperatures()
140    key = "coretemp" if "coretemp" in temps else next(iter(temps))
141    return temps[key][0].current
142
143
144def net_addr() -> str:
145    """
146    Get the first non-loopback network address (IPv4).
147
148    Returns
149    -------
150    str
151        The discovered network address.
152    """
153    addresses = psutil.net_if_addrs()
154    for nic, addr_list in addresses.items():
155        if nic != "lo":
156            addr = [addr.address for addr in addr_list if addr.family == AF_INET]
157            return addr[0] if len(addr) > 0 else ""
158    return ""
159
160
161def system_uptime() -> str:
162    """
163    Return the system uptime in a human-readable format.
164
165    Returns
166    -------
167    str
168        The current system uptime in a human-readable format.
169
170    Examples
171    --------
172    >>> system_uptime()  # for a system just rebooted
173    "47 sec"
174    >>> system_uptime()  # five minutes later
175    "5m 47s"
176    >>> system_uptime()  # 2 hours later
177    "2h 5m 47s"
178    >>> system_uptime()  # 8 days later
179    "8d 2h 5m"
180    """
181    uptime = time.time() - psutil.boot_time()
182    uptime_minutes, uptime_seconds = divmod(uptime, 60)
183    uptime_hours, uptime_minutes = divmod(uptime_minutes, 60)
184    uptime_days, uptime_hours = divmod(uptime_hours, 24)
185    if uptime_days:
186        return f"{uptime_days:.0f}d {uptime_hours:.0f}h {uptime_minutes:.0f}m"
187    if uptime_hours:
188        return f"{uptime_hours:.0f}h {uptime_minutes:.0f}m {uptime_seconds:.0f}s"
189    if uptime_minutes:
190        return f"{uptime_minutes:.0f}m {uptime_seconds:.0f}s"
191    return f"{uptime_seconds:.0f} sec"
192
193
194def disk_usage(mountpoint: str) -> str:
195    """
196    Return the disk usage of the provided mount point in a human-readable format.
197
198    Parameters
199    ----------
200    mountpoint: str
201        a filesystem path belonging to the desired mount point.
202
203    Returns
204    -------
205    str
206        A human-readable string of the disk usage.
207
208    Example
209    -------
210    >>> disk_usage("/")
211    "32GiB/199GiB 17%"
212    """
213    diskinfo = psutil.disk_usage(mountpoint)
214    used_fmt = bytes2whole(diskinfo.used)
215    total_fmt = bytes2whole(diskinfo.total)
216    # if formatted numbers are less then 10, use single decimal place rather than zero decimals
217    if digits(used_fmt)[0] < 10:  # noqa: PLR2004
218        used_fmt = bytes2human(diskinfo.used)
219    if digits(total_fmt)[0] < 10:  # noqa: PLR2004
220        total_fmt = bytes2human(diskinfo.total)
221    return f"{used_fmt}/{total_fmt} {round(diskinfo.percent)}%"
222
223
224def get_processor_name() -> str:
225    """
226    Get the full processor name of the computer.
227    """
228    if platform.system() == "Windows":
229        return _get_processor_name_windows()
230    if platform.system() == "Darwin":
231        return _get_processor_name_darwin()
232    if platform.system() == "Linux":
233        return _get_processor_name_linux()
234    return ""
235
236
237def _get_processor_name_windows() -> str:
238    return platform.processor()
239
240
241def _get_processor_name_darwin() -> str:
242    return subprocess.check_output(  # nosec B603
243        ["/usr/sbin/sysctl", "-n", "machdep.cpu.brand_string"]
244    ).decode("utf-8").strip()
245
246
247def _get_processor_name_linux() -> str:
248    all_info = subprocess.check_output(  # nosec B603
249        ["/usr/bin/cat", "/proc/cpuinfo"]
250    ).decode().strip()
251    for line in all_info.split("\n"):
252        if "model name" in line:
253            return re.sub(r".*model name.*:", "", line, count=1)
254    return ""
DISK_ALERT_LEVEL = 80

Percent of disk usage at which the meter shows red.

DISK_WARN_LEVEL = 60

Percent of disk usage at which the meter shows yellow.

REFRESH_INTERVAL = 750

Number of milliseconds between widget refreshes.

INTERNAL_PAD = 12

Standard widget padding, in pixels.

def bytes2human(num: int, precision: int = 1) -> str:
40def bytes2human(num: int, precision: int = 1) -> str:
41    """
42    Convert a byte count to a human-readable format, with a given precision.
43
44    Parameters
45    ----------
46    num : int
47        The number to format.
48    precision : int, optional
49        The number of decimals to use for display.
50
51    Returns
52    -------
53    str
54        The number in human-readable format.
55
56    Examples
57    --------
58    >>> bytes2human(10000)
59    '9.8KiB'
60    >>> bytes2human(100001221)
61    '95.4MiB'
62    """
63    # originally taken from pyftpdlib, then rewritten
64    prefix = {}
65    for idx, symb in enumerate(BYTE_SYMBOLS[1:]):
66        prefix[symb] = 1 << (idx+1)*10
67    for symbol in reversed(BYTE_SYMBOLS[1:]):
68        if num >= prefix[symbol]:
69            value = float(num) / prefix[symbol]
70            return f"{value:.{precision}f}{symbol}"
71    return f"{num:.{precision}f}{BYTE_SYMBOLS[0]}"

Convert a byte count to a human-readable format, with a given precision.

Parameters
  • num (int): The number to format.
  • precision (int, optional): The number of decimals to use for display.
Returns
  • str: The number in human-readable format.
Examples
>>> bytes2human(10000)
'9.8KiB'
>>> bytes2human(100001221)
'95.4MiB'
def bytes2whole(num: int) -> str:
76def bytes2whole(num: int) -> str:
77    """
78    Convert a byte count to a human-readable format, with no decimal.
79
80    Parameters
81    ----------
82    num : int
83        The number to format.
84
85    Returns
86    -------
87    str
88        The number in human-readable format.
89
90    Examples
91    --------
92    >>> bytes2whole(10000)
93    '10KiB'
94    >>> bytes2whole(100001221)
95    '95MiB'
96    """
97    return bytes2human(num, precision=0)

Convert a byte count to a human-readable format, with no decimal.

Parameters
  • num (int): The number to format.
Returns
  • str: The number in human-readable format.
Examples
>>> bytes2whole(10000)
'10KiB'
>>> bytes2whole(100001221)
'95MiB'
def digits(numstr: str) -> list[int]:
100def digits(numstr: str) -> list[int]:
101    """
102    Return a list of numbers in a string.
103
104    Parameters
105    ----------
106    numstr : str
107        A string containing various numbers.
108
109    Returns
110    -------
111    List[int]
112        A list of integers extracted from the string.
113
114    Examples
115    --------
116    >>> digits("")
117    []
118    >>> digits("8.5MiB")
119    [8, 5]
120    >> digits("13. Notes vol. 22, pp. 585-588, 1996.")
121    [13, 22, 585, 588, 1996]
122    """
123    return [int(s) for s in re.findall(r"\d+", numstr)]

Return a list of numbers in a string.

Parameters
  • numstr (str): A string containing various numbers.
Returns
  • List[int]: A list of integers extracted from the string.
Examples
>>> digits("")
[]
>>> digits("8.5MiB")
[8, 5]
>> digits("13. Notes vol. 22, pp. 585-588, 1996.")
[13, 22, 585, 588, 1996]
def cpu_temp() -> float:
126def cpu_temp() -> float:
127    """
128    Return the CPU temperature in degrees Celsius.
129
130    Returns
131    -------
132    float
133        The current CPU temperature.
134
135    Examples
136    --------
137    >>> cpu_temp()
138    41.0
139    """
140    temps = psutil.sensors_temperatures()
141    key = "coretemp" if "coretemp" in temps else next(iter(temps))
142    return temps[key][0].current

Return the CPU temperature in degrees Celsius.

Returns
  • float: The current CPU temperature.
Examples
>>> cpu_temp()
41.0
def net_addr() -> str:
145def net_addr() -> str:
146    """
147    Get the first non-loopback network address (IPv4).
148
149    Returns
150    -------
151    str
152        The discovered network address.
153    """
154    addresses = psutil.net_if_addrs()
155    for nic, addr_list in addresses.items():
156        if nic != "lo":
157            addr = [addr.address for addr in addr_list if addr.family == AF_INET]
158            return addr[0] if len(addr) > 0 else ""
159    return ""

Get the first non-loopback network address (IPv4).

Returns
  • str: The discovered network address.
def system_uptime() -> str:
162def system_uptime() -> str:
163    """
164    Return the system uptime in a human-readable format.
165
166    Returns
167    -------
168    str
169        The current system uptime in a human-readable format.
170
171    Examples
172    --------
173    >>> system_uptime()  # for a system just rebooted
174    "47 sec"
175    >>> system_uptime()  # five minutes later
176    "5m 47s"
177    >>> system_uptime()  # 2 hours later
178    "2h 5m 47s"
179    >>> system_uptime()  # 8 days later
180    "8d 2h 5m"
181    """
182    uptime = time.time() - psutil.boot_time()
183    uptime_minutes, uptime_seconds = divmod(uptime, 60)
184    uptime_hours, uptime_minutes = divmod(uptime_minutes, 60)
185    uptime_days, uptime_hours = divmod(uptime_hours, 24)
186    if uptime_days:
187        return f"{uptime_days:.0f}d {uptime_hours:.0f}h {uptime_minutes:.0f}m"
188    if uptime_hours:
189        return f"{uptime_hours:.0f}h {uptime_minutes:.0f}m {uptime_seconds:.0f}s"
190    if uptime_minutes:
191        return f"{uptime_minutes:.0f}m {uptime_seconds:.0f}s"
192    return f"{uptime_seconds:.0f} sec"

Return the system uptime in a human-readable format.

Returns
  • str: The current system uptime in a human-readable format.
Examples
>>> system_uptime()  # for a system just rebooted
"47 sec"
>>> system_uptime()  # five minutes later
"5m 47s"
>>> system_uptime()  # 2 hours later
"2h 5m 47s"
>>> system_uptime()  # 8 days later
"8d 2h 5m"
def disk_usage(mountpoint: str) -> str:
195def disk_usage(mountpoint: str) -> str:
196    """
197    Return the disk usage of the provided mount point in a human-readable format.
198
199    Parameters
200    ----------
201    mountpoint: str
202        a filesystem path belonging to the desired mount point.
203
204    Returns
205    -------
206    str
207        A human-readable string of the disk usage.
208
209    Example
210    -------
211    >>> disk_usage("/")
212    "32GiB/199GiB 17%"
213    """
214    diskinfo = psutil.disk_usage(mountpoint)
215    used_fmt = bytes2whole(diskinfo.used)
216    total_fmt = bytes2whole(diskinfo.total)
217    # if formatted numbers are less then 10, use single decimal place rather than zero decimals
218    if digits(used_fmt)[0] < 10:  # noqa: PLR2004
219        used_fmt = bytes2human(diskinfo.used)
220    if digits(total_fmt)[0] < 10:  # noqa: PLR2004
221        total_fmt = bytes2human(diskinfo.total)
222    return f"{used_fmt}/{total_fmt} {round(diskinfo.percent)}%"

Return the disk usage of the provided mount point in a human-readable format.

Parameters
  • mountpoint (str): a filesystem path belonging to the desired mount point.
Returns
  • str: A human-readable string of the disk usage.
Example
>>> disk_usage("/")
"32GiB/199GiB 17%"
def get_processor_name() -> str:
225def get_processor_name() -> str:
226    """
227    Get the full processor name of the computer.
228    """
229    if platform.system() == "Windows":
230        return _get_processor_name_windows()
231    if platform.system() == "Darwin":
232        return _get_processor_name_darwin()
233    if platform.system() == "Linux":
234        return _get_processor_name_linux()
235    return ""

Get the full processor name of the computer.