| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import logging as log |
| import sys |
| |
| try: |
| import enlighten |
| ENLIGHTEN_EXISTS = True |
| except ImportError: |
| ENLIGHTEN_EXISTS = False |
| |
| |
| class StatusPrinter: |
| """Dummy Status Printer class for interactive mode. |
| |
| When interactive mode is set, dvsim does not print the status. By |
| instantiating this dummy class (printing nothing), outer interface stays |
| same. |
| """ |
| |
| def __init__(self): |
| pass |
| |
| def print_header(self, msg): |
| pass |
| |
| def init_target(self, target, msg): |
| pass |
| |
| def update_target(self, target, hms, msg, perc, running): |
| pass |
| |
| def exit(self): |
| pass |
| |
| |
| class TtyStatusPrinter(StatusPrinter): |
| '''Abstraction for printing the current target status onto the console. |
| |
| Targets are ASIC tool flow steps such as build, run, cov etc. These steps |
| are sequenced by the Scheduler. There may be multiple jobs running in |
| parallel in each target. This class provides a mechanism to peridically |
| print the completion status of each target onto the terminal. Messages |
| printed by this class are rather static in nature - all the necessary |
| computations of how the jobs are progressing need to be handled externally. |
| |
| The following are the 'fields' accepted by this class: |
| hms: Elapsed time in hh:mm:ss. |
| target: The tool flow step. |
| msg: The completion status message (set externally). |
| perc: Percentage of completion. |
| running: What jobs are currently still running. |
| ''' |
| |
| # Print elapsed time in bold. |
| hms_fmt = ''.join(['\033[1m', u'{hms:9s}', '\033[0m']) |
| header_fmt = hms_fmt + u' [{target:^13s}]: [{msg}]' |
| status_fmt = header_fmt + u' {perc:3.0f}% {running}' |
| |
| def __init__(self): |
| # Once a target is complete, we no longer need to update it - we can |
| # just skip it. Maintaining this here provides a way to print the status |
| # one last time when it reaches 100%. It is much easier to do that here |
| # than in the Scheduler class. |
| super().__init__() |
| self.target_done = {} |
| |
| def print_header(self, msg): |
| '''Initilize / print the header bar. |
| |
| The header bar contains an introductory message such as the legend of |
| what Q, D, ... mean.''' |
| |
| log.info(self.header_fmt.format(hms="", target="legend", msg=msg)) |
| |
| def init_target(self, target, msg): |
| '''Initialize the status bar for each target.''' |
| |
| self.target_done[target] = False |
| |
| def _trunc_running(self, running: str) -> str: |
| """Truncates the list of running items to 30 character string.""" |
| return running[:28] + (running[28:] and "..") |
| |
| def update_target(self, target, hms, msg, perc, running): |
| '''Periodically update the status bar for each target.''' |
| |
| if self.target_done[target]: |
| return |
| |
| log.info( |
| self.status_fmt.format(hms=hms, |
| target=target, |
| msg=msg, |
| perc=perc, |
| running=self._trunc_running(running))) |
| if perc == 100: |
| self.target_done[target] = True |
| |
| def exit(self): |
| '''Do cleanup activities before exitting.''' |
| |
| pass |
| |
| |
| class EnlightenStatusPrinter(TtyStatusPrinter): |
| '''Abstraction for printing status using Enlighten. |
| |
| Enlighten is a third party progress bar tool. Documentation: |
| https://python-enlighten.readthedocs.io/en/stable/ |
| |
| Though it offers very fancy progress bar visualization, we stick to a |
| simple status bar 'pinned' to the bottom of the screen for each target |
| that displays statically, a pre-prepared message. We avoid the progress bar |
| visualization since it requires enlighten to perform some computations the |
| Scheduler already does. It also helps keep the overhead to a minimum. |
| |
| Enlighten does not work if the output of dvsim is redirected to a file, for |
| example - it needs to be attached to a TTY enabled stream. |
| ''' |
| |
| def __init__(self): |
| super().__init__() |
| |
| # Initialize the status_bars for header and the targets . |
| self.manager = enlighten.get_manager() |
| self.status_header = None |
| self.status_target = {} |
| |
| def print_header(self, msg): |
| self.status_header = self.manager.status_bar( |
| status_format=self.header_fmt, |
| hms="", |
| target="legend", |
| msg= |
| "Q: queued, D: dispatched, P: passed, F: failed, K: killed, T: total" |
| ) |
| |
| def init_target(self, target, msg): |
| super().init_target(target, msg) |
| self.status_target[target] = self.manager.status_bar( |
| status_format=self.status_fmt, |
| hms="", |
| target=target, |
| msg=msg, |
| perc=0.0, |
| running="") |
| |
| def update_target(self, target, hms, msg, perc, running): |
| if self.target_done[target]: |
| return |
| |
| self.status_target[target].update(hms=hms, |
| msg=msg, |
| perc=perc, |
| running=self._trunc_running(running)) |
| if perc == 100: |
| self.target_done[target] = True |
| |
| def exit(self): |
| self.status_header.close() |
| for target in self.status_target: |
| self.status_target[target].close() |
| |
| |
| def get_status_printer(interactive): |
| """Factory method that returns a status printer instance. |
| |
| If ENLIGHTEN_EXISTS (enlighten is installed) and stdout is a TTY, then |
| return an instance of EnlightenStatusPrinter, else return an instance of |
| StatusPrinter. |
| """ |
| if interactive: |
| return StatusPrinter() |
| |
| if ENLIGHTEN_EXISTS and sys.stdout.isatty(): |
| return EnlightenStatusPrinter() |
| |
| return TtyStatusPrinter() |