diff options
Diffstat (limited to '')
| -rw-r--r-- | README.md | 35 | ||||
| -rw-r--r-- | poetry.lock | 4 | ||||
| -rw-r--r-- | pyproject.toml | 2 | ||||
| -rwxr-xr-x | virttop/virttop.py | 99 | 
4 files changed, 105 insertions, 35 deletions
| @@ -1,7 +1,6 @@  # virttop  a top like utility for libvirt -    ## 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.</br> -`g` moves to the top of the list.</br> -`G` moved to the bottom of the list.</br> -`r` runs an inactive domain.</br> -`s` shuts down a running domain.</br> -`d` destroy a running domain.</br> +`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 <devi@terminaldweller.com>"]  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() | 
