sysmon_pytk.cli_sysmon
Command line system monitor.
Usage
usage: cli_sysmon [-h] [-v] [-r TIME] [-l {en,es,de,nb_NO}] [-d | -t | -b | -x]
System monitor: display CPU usage/temperature, memory usage, disk usage
Options
| General Option | Details |
|---|---|
-h, -?, --help |
show this help message and exit |
-v, --version |
show program's version number and exit |
-r TIME, --refresh TIME |
time between screen refreshes (in seconds, default=1.0) |
-l {en,es,de,nb_NO},--language {en,es,de,nb_NO} |
the language to use for display |
Display Details
| Display Option (choose one) | Details |
|---|---|
-d, --disk |
show disk details (default) |
-t, --temperature |
show temperature details |
-b, --both |
show both disk and temperature details |
-x, --no-details |
show no details, only the header |
Notes
By default, this program will use the same language as that selected for the
GUI application. To override it, use the -l option. To quit, press
Ctrl-C.
1#!/usr/bin/env python3 2 3# SPDX-FileCopyrightText: © 2024 Stacey Adams <stacey.belle.rose@gmail.com> 4# SPDX-License-Identifier: MIT 5 6""" 7Command line system monitor. 8 9.. include:: ../docs/CLI_USAGE.md 10""" 11 12import gettext 13import sys 14import time 15from socket import gethostname 16from typing import NoReturn 17 18import psutil 19from blessings import Terminal 20 21from . import _common, about 22from .app_locale import LANGUAGES, get_translator 23 24_ = get_translator() 25 26# set up argparse translation 27gettext.gettext = get_translator(domain="argparse") 28 29# Don't move this to the top of the import list! gettext must be imported first 30# and gettext.gettext must be reassigned for argparse translations to work. 31 32import argparse # noqa: E402 # pylint: disable=C0411,C0413 33 34REFRESH_SLEEP = 1.0 35TOTAL_BLOCKS = 50 36 37term = Terminal() 38 39# ruff: noqa: T201 40 41 42def _get_usage_color(percent: float) -> str: 43 if percent > _common.DISK_ALERT_LEVEL: 44 return term.bright_red 45 if percent > _common.DISK_WARN_LEVEL: 46 return term.bright_yellow 47 return term.bright_green 48 49 50def _cpu_usage() -> str: 51 percent = psutil.cpu_percent(interval=None) 52 label = _("CPU Usage") + ": " 53 details = f"{percent}%" 54 hilite = _get_usage_color(percent) 55 return label + hilite + details + term.normal + " "*5 56 57 58def _cpu_temp() -> str: 59 temperature = _common.cpu_temp() 60 label = _("Temperature") + ": " 61 details = f"{temperature}°C" 62 hilite = _get_usage_color(temperature) 63 return label + hilite + details + term.normal + " "*5 64 65 66def _mem_usage() -> str: 67 meminfo = psutil.virtual_memory() 68 used = _common.bytes2human(meminfo.total - meminfo.available) 69 total = _common.bytes2human(meminfo.total) 70 label = _("RAM Usage") + ": " 71 details = f"{used}/{total} {round(meminfo.percent)}%" 72 hilite = _get_usage_color(meminfo.percent) 73 return label + hilite + details + term.normal + " "*5 74 75 76def _swap_usage() -> str: 77 meminfo = psutil.swap_memory() 78 used = _common.bytes2human(meminfo.used) 79 total = _common.bytes2human(meminfo.total) 80 label = _("Swap Memory") + ": " 81 details = f"{used}/{total} {round(meminfo.percent)}%" 82 hilite = _get_usage_color(meminfo.percent) 83 return label + hilite + details + term.normal + " "*5 84 85 86def _disk_usage(mountpoint: str) -> str: 87 usage = psutil.disk_usage(mountpoint) 88 used = _common.bytes2human(usage.used) 89 total = _common.bytes2human(usage.total) 90 details = f"{used}/{total} {round(usage.percent)}%" 91 hilite = _get_usage_color(usage.percent) 92 return hilite + details + term.normal + " "*5 93 94 95def _disk_usage_rjust(mountpoint: str, width: int) -> str: 96 usage = psutil.disk_usage(mountpoint) 97 used = _common.bytes2human(usage.used) 98 total = _common.bytes2human(usage.total) 99 details = f"{used}/{total} {round(usage.percent)}%" 100 hilite = _get_usage_color(usage.percent) 101 return hilite + details.rjust(width) + term.normal 102 103 104def _disk_usage_bar(mountpoint: str) -> str: 105 usage = psutil.disk_usage(mountpoint) 106 used_blocks = int(usage.percent * TOTAL_BLOCKS / 100) 107 empty_blocks = TOTAL_BLOCKS - used_blocks 108 details = "█" * used_blocks + "━" * empty_blocks 109 hilite = _get_usage_color(usage.percent) 110 return hilite + details + term.normal + " "*5 111 112 113def _disk_details(*, starting_line: int) -> None: 114 print( 115 term.move(starting_line, 1) + term.black_on_bright_white 116 + _("Disk Usage").upper() + term.normal 117 ) 118 for idx, part in enumerate(psutil.disk_partitions()): 119 line = 2*idx + starting_line + 1 120 if line + 1 > term.height: 121 break 122 mountpoint = part.mountpoint 123 print(term.move(line, 0) + " "*(term.width-1)) 124 print(term.move(line, 1) + mountpoint + term.el) 125 print(term.move(line + 1, 1) + _disk_usage_bar(mountpoint)) 126 print(term.move(line + 1, TOTAL_BLOCKS + 3) + _disk_usage(mountpoint) + term.el) 127 print(term.clear_eos) 128 129 130def _temp_details(*, starting_line: int) -> None: 131 print( 132 term.move(starting_line, 1) + term.black_on_bright_white 133 + _("Temperature Sensors").upper() + term.normal 134 ) 135 temps = psutil.sensors_temperatures() 136 line = starting_line + 1 137 for name, entries in temps.items(): 138 if line + len(entries) + 1 > term.height: 139 break 140 print(term.move(line, 0) + " " + term.magenta + name + term.normal + term.el) 141 line += 1 142 for entry in entries: 143 tag = (entry.label or name).ljust(20) + " " 144 display = _("{current}°C (high = {high}°C, critical = {critical}°C)").format( 145 current=entry.current, high=entry.high, critical=entry.critical 146 ) 147 print(term.move(line, 0) + " "*10 + tag + display + term.el) 148 line += 1 149 print(term.clear_eos) 150 151 152def _blank_below_line(line: int, start_col: int, width: int) -> None: 153 """ 154 Blank a rectangular section of the screen with spaces. 155 """ 156 for row in range(line, term.height-1): 157 print(term.move(row, start_col) + " "*width) 158 159 160def _disk_details_half(*, starting_line: int) -> None: 161 allowed_width = term.width//2 - 1 162 print( 163 term.move(starting_line, 1) + term.black_on_bright_white 164 + _("Disk Usage").upper() + term.normal 165 ) 166 partitions = psutil.disk_partitions() 167 for idx, part in enumerate(partitions): 168 line = 2*idx + starting_line + 1 169 if line + 1 > term.height: 170 break 171 mountpoint = part.mountpoint 172 print(term.move(line, 1) + mountpoint.ljust(allowed_width)) 173 print(term.move(line + 1, 1) + _disk_usage_rjust(mountpoint, allowed_width)) 174 _blank_below_line(2*len(partitions) + starting_line + 1, 1, allowed_width) 175 176 177def _temp_details_half(*, starting_line: int) -> None: 178 start_col = term.width//2 + 1 179 print( 180 term.move(starting_line, start_col) + term.black_on_bright_white 181 + _("Temperature Sensors").upper() + term.normal 182 ) 183 temps = psutil.sensors_temperatures() 184 line = starting_line + 1 185 for name, entries in temps.items(): 186 if line + len(entries) + 1 > term.height: 187 break 188 print(term.move(line, start_col) + term.magenta + name + term.normal + term.el) 189 line += 1 190 for entry in entries: 191 label_width = term.width//2 - 7 192 print( 193 term.move(line, start_col) + " "*5 194 + (entry.label or name).ljust(label_width)[:label_width] 195 ) 196 print(term.move(line, term.width-11) + f"{entry.current}°C".rjust(10)) 197 line += 1 198 _blank_below_line(line, start_col, term.width-start_col) 199 200 201def _monitor_system(args: argparse.Namespace) -> NoReturn: 202 """ 203 Monitor the system usage. 204 """ 205 app_title = _("System Monitor").upper() 206 while True: 207 print(term.move(0, 1) + term.black_on_bright_white + app_title + term.normal) 208 print(term.move(1, 1) + _("Hostname: {}").format(gethostname()) + term.el) 209 print(term.move(1, 31) + _("IP Address: {}").format(_common.net_addr()) + term.el) 210 print(term.move(2, 1) + _("Processes: {}").format(len(psutil.pids())) + term.el) 211 print(term.move(2, 31) + _("Uptime: {}").format(_common.system_uptime()) + term.el) 212 print(term.move(4, 1) + _cpu_usage()) 213 print(term.move(5, 1) + _cpu_temp()) 214 print(term.move(4, 31) + _mem_usage()) 215 print(term.move(5, 31) + _swap_usage()) 216 start = 7 217 if args.details == "d": 218 _disk_details(starting_line=start) 219 elif args.details == "t": 220 _temp_details(starting_line=start) 221 elif args.details == "b": 222 _disk_details_half(starting_line=start) 223 _temp_details_half(starting_line=start) 224 msg = _("<Ctrl-C> to quit") 225 print( 226 term.move(term.height-1, term.width-len(msg)-1) 227 + term.cyan + msg + term.normal + term.move(0, 0) 228 ) 229 time.sleep(args.refresh) 230 231 232def _get_parser() -> argparse.Namespace: 233 app_desc = _( 234 "System monitor: display CPU usage/temperature, memory usage, disk usage" 235 ) 236 epilog = _( 237 "By default, this program will use the same language as that selected " 238 "for the GUI application. To override it, use the '-l' option. To " 239 "quit, press <Ctrl-C>." 240 ) 241 parser = argparse.ArgumentParser( 242 description=app_desc, epilog=epilog, add_help=False 243 ) 244 options = parser.add_argument_group(_("Options")) 245 options.add_argument( 246 "-h", "-?", "--help", action="help", 247 help=_("show this help message and exit") 248 ) 249 options.add_argument( 250 "-v", "--version", action="version", 251 version=f"{about.__app_name__} {about.__version__}", 252 help=_("show program's version number and exit") 253 ) 254 options.add_argument( 255 "-r", "--refresh", type=float, default=REFRESH_SLEEP, metavar="TIME", 256 help=_("time between screen refreshes (in seconds, default=%(default)s)") 257 ) 258 options.add_argument( 259 "-l", "--language", choices=LANGUAGES.values(), 260 help=_("the language to use for display") 261 ) 262 detail_options = parser.add_argument_group(_("Display Details")) 263 details = detail_options.add_mutually_exclusive_group() 264 details.add_argument( 265 "-d", "--disk", action="store_const", dest="details", const="d", 266 help=_("show disk details (default)") 267 ) 268 details.add_argument( 269 "-t", "--temperature", action="store_const", dest="details", const="t", 270 help=_("show temperature details") 271 ) 272 details.add_argument( 273 "-b", "--both", action="store_const", dest="details", const="b", 274 help=_("show both disk and temperature details") 275 ) 276 details.add_argument( 277 "-x", "--no-details", action="store_const", dest="details", const="", 278 help=_("show no details, only the header") 279 ) 280 parser.set_defaults(details="d") 281 return parser.parse_args() 282 283 284def monitor() -> NoReturn: 285 """ 286 Entry point for CLI monitor. 287 """ 288 args = _get_parser() 289 global _ # noqa: PLW0603 # pylint: disable=global-statement 290 _ = get_translator(forced_lang=args.language) 291 print(term.enter_fullscreen() + term.clear + term.civis) 292 try: 293 _monitor_system(args) 294 except KeyboardInterrupt: 295 print(term.cnorm + term.clear + term.exit_fullscreen()) 296 sys.exit(0) 297 298 299if __name__ == "__main__": 300 monitor()
def
monitor() -> NoReturn:
285def monitor() -> NoReturn: 286 """ 287 Entry point for CLI monitor. 288 """ 289 args = _get_parser() 290 global _ # noqa: PLW0603 # pylint: disable=global-statement 291 _ = get_translator(forced_lang=args.language) 292 print(term.enter_fullscreen() + term.clear + term.civis) 293 try: 294 _monitor_system(args) 295 except KeyboardInterrupt: 296 print(term.cnorm + term.clear + term.exit_fullscreen()) 297 sys.exit(0)
Entry point for CLI monitor.