From 17b15dfcebce7537e411ef69f6493c8ab1f49544 Mon Sep 17 00:00:00 2001 From: terminaldweller Date: Fri, 24 Mar 2023 17:14:29 +0330 Subject: fixed the help option, fixed some breakages, some operations are now async, updated the readme --- README.md | 35 +++++++++++++++---- poetry.lock | 4 +-- pyproject.toml | 2 +- virttop/virttop.py | 99 ++++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 15649cd..718c522 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # virttop a top like utility for libvirt - ![Image](virttop.png) ## How to get @@ -9,6 +8,23 @@ a top like utility for libvirt pip install virttop ``` +## Options +```sh +usage: virttop.py [-h] [--uri URI [URI ...]] [--config CONFIG] + [--active ACTIVE] [--logfile LOGFILE] + +options: + -h, --help show this help message and exit + --uri URI [URI ...], -u URI [URI ...] + A list of URIs to connect to seperated by commas + --config CONFIG, -c CONFIG + Path to the config file + --active ACTIVE, -a ACTIVE + Show active VMs only + --logfile LOGFILE, -l LOGFILE + Location of the log file +``` + ## Configfile The default location for the config file is '~/.virttop.toml'. @@ -28,9 +44,14 @@ selected_bg=36 ## Keybindings -`j` and `k` and arrow keys move up and down.
-`g` moves to the top of the list.
-`G` moved to the bottom of the list.
-`r` runs an inactive domain.
-`s` shuts down a running domain.
-`d` destroy a running domain.
+`j`,`k` and arrow keys move up and down. + +`g` moves to the top of the list. + +`G` moves to the bottom of the list. + +`r` runs an inactive domain. + +`s` shuts down a running domain. + +`d` destroys a running domain. diff --git a/poetry.lock b/poetry.lock index d7b6a32..bb5ab8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "libvirt-python" -version = "9.0.0" +version = "9.1.0" description = "The libvirt virtualization API python binding" category = "main" optional = false @@ -25,5 +25,5 @@ defusedxml = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] libvirt-python = [ - {file = "libvirt-python-9.0.0.tar.gz", hash = "sha256:49702d33fa8cbcae19fa727467a69f7ae2241b3091324085ca1cc752b2b414ce"}, + {file = "libvirt-python-9.1.0.tar.gz", hash = "sha256:6f98d235db975a33ff5e6fc09be566a00d22398039d411f34220384c05590ba4"}, ] diff --git a/pyproject.toml b/pyproject.toml index d36c74f..73afd96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "virttop" -version = "0.2.1" +version = "0.2.2" description = "A top like utility for libvirt" authors = ["terminaldweller "] license = "GPL-3.0" diff --git a/virttop/virttop.py b/virttop/virttop.py index 47bf53f..0794ea6 100755 --- a/virttop/virttop.py +++ b/virttop/virttop.py @@ -10,6 +10,8 @@ import asyncio import csv import curses import dataclasses +import functools +import logging import os import signal import sys @@ -35,7 +37,7 @@ def request_cred(credentials, sasl_user, sasl_pass): return 0 -def do_cleanup(): +def do_cleanup(stdscr): """Return the terminal to a sane state.""" curses.nocbreak() stdscr.keypad(False) @@ -45,9 +47,9 @@ def do_cleanup(): # pylint: disable=unused-argument -def sig_handler_sigint(signum, frame): +def sig_handler_sigint(signum, frame, stdscr): """Just to handle C-c cleanly""" - do_cleanup() + do_cleanup(stdscr) sys.exit(0) @@ -79,11 +81,11 @@ class Argparser: # pylint: disable=too-few-public-methods default=False, ) self.parser.add_argument( - "--debug", - "-d", - type=bool, - help="Turn on debug logging", - default=False, + "--logfile", + "-l", + type=str, + help="Location of the log file", + default="~/.virttop.log", ) self.args = self.parser.parse_args() @@ -176,7 +178,6 @@ def get_disk_info( # pylint: disable=too-many-locals -# pylint: disable=too-many-branches def ffs( offset: int, header_list: typing.Optional[typing.List[str]], @@ -318,8 +319,12 @@ def fill_virt_data_uri( if not found_the_pool: virt_data.memory_pool.append("N/A") - disk_info = get_disk_info(xml_doc) - image_name = disk_info["file"] + try: + disk_info = get_disk_info(xml_doc) + image_name = disk_info["file"] + except Exception as exception: + image_name = "X" + logging.exception(exception) if host.ID() >= 0: _, rd_bytes, _, wr_bytes, _ = dom.blockStats(image_name) virt_data.disk_reads.append(size_abr(rd_bytes, 1)) @@ -336,7 +341,8 @@ def fill_virt_data_uri( virt_data.disk_writes.append("-") virt_data.macs.append("-") virt_data.ips.append("-") - except: + except Exception as exception: + logging.exception(exception) pass @@ -402,16 +408,48 @@ def get_visible_rows(max_rows: int, sel: int) -> typing.Tuple[int, int]: return win_min_row, win_max_row -async def main_loop() -> None: +async def start_domain(dom): + """Start a domain.""" + try: + task = asyncio.create_task(dom.createWithFlags()) + await asyncio.sleep(0) + return task + except Exception as exception: + logging.exception(exception) + + +async def destroy_domain(dom): + """Destroy a domain gracefully.""" + try: + task = asyncio.create_task( + dom.destroyFlags(flags=libvirt.VIR_DOMAIN_DESTROY_GRACEFUL) + ) + await asyncio.sleep(0) + return task + except Exception as exception: + logging.exception(exception) + + +async def shutdown_domain(dom): + """Shutdown a domain.""" + try: + task = asyncio.create_task(dom.shutdownFlags()) + await asyncio.sleep(0) + return task + except Exception as exception: + logging.exception(exception) + + +async def main_loop(argparser, stdscr) -> None: """Main TUI loop.""" sel: int = 0 current_row: int = 0 current_visi: int = 0 vm_name_ordered_list: typing.List[str] = [] + task_list: typing.List[asyncio.Task] = [] - stdscr = curses_init() - signal.signal(signal.SIGINT, sig_handler_sigint) - argparser = Argparser() + sigint_handler = functools.partial(sig_handler_sigint, stdscr=stdscr) + signal.signal(signal.SIGINT, sigint_handler) config_data = read_config(argparser.args.config) arp_table = get_arp_table() init_color_pairs(config_data) @@ -525,29 +563,40 @@ async def main_loop() -> None: break elif char == ord("d"): dom = conn.lookupByName(vm_name_ordered_list[sel]) - dom.destroyFlags(flags=libvirt.VIR_DOMAIN_DESTROY_GRACEFUL) + logging.debug("shutting down domain %s", vm_name_ordered_list[sel]) + task_list.append(await destroy_domain(dom)) elif char == ord("s"): dom = conn.lookupByName(vm_name_ordered_list[sel]) - dom.shutdownFlags() + logging.debug("shutting down domain %s", vm_name_ordered_list[sel]) + task_list.append(await shutdown_domain(dom)) elif char == ord("r"): dom = conn.lookupByName(vm_name_ordered_list[sel]) - dom.createWithFlags() + logging.debug("shutting down domain %s", vm_name_ordered_list[sel]) + task_list.append(await start_domain(dom)) else: pass stdscr.refresh() + vm_name_ordered_list = [] def main() -> None: """Entry point.""" try: - asyncio.run(main_loop()) - except Exception as e: - print(e) - finally: - do_cleanup() + argparser = Argparser() + stdscr = curses_init() + logging.basicConfig( + filename=os.path.expanduser(argparser.args.logfile), + filemode="w", + format="%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s", + datefmt="%H:%M:%S", + level=logging.DEBUG, + ) + asyncio.run(main_loop(argparser, stdscr)) + except Exception as exception: + logging.exception(exception) + do_cleanup(stdscr) -# FIXME- the finally wipes the screen, effectively rendering the help option useless if __name__ == "__main__": main() -- cgit v1.2.3