Srikrishna Iyer | df0936b | 2021-02-14 16:36:02 -0800 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | |
| 5 | import logging as log |
| 6 | import sys |
| 7 | |
| 8 | try: |
| 9 | import enlighten |
| 10 | ENLIGHTEN_EXISTS = True |
| 11 | except ImportError: |
| 12 | ENLIGHTEN_EXISTS = False |
| 13 | |
| 14 | |
| 15 | class StatusPrinter: |
| 16 | '''Abstraction for printing the current target status onto the console. |
| 17 | |
| 18 | Targets are ASIC tool flow steps such as build, run, cov etc. These steps |
| 19 | are sequenced by the Scheduler. There may be multiple jobs running in |
| 20 | parallel in each target. This class provides a mechanism to peridically |
| 21 | print the completion status of each target onto the terminal. Messages |
| 22 | printed by this class are rather static in nature - all the necessary |
| 23 | computations of how the jobs are progressing need to be handled externally. |
| 24 | |
| 25 | The following are the 'fields' accepted by this class: |
| 26 | hms: Elapsed time in hh:mm:ss. |
| 27 | target: The tool flow step. |
| 28 | msg: The completion status message (set externally). |
| 29 | perc: Percentage of completion. |
| 30 | ''' |
| 31 | |
| 32 | # Print elapsed time in bold. |
| 33 | hms_fmt = ''.join(['\033[1m', u'{hms:9s}', '\033[0m']) |
| 34 | header_fmt = hms_fmt + u' [{target:^13s}]: [{msg}]' |
| 35 | status_fmt = header_fmt + u' {perc:3.0f}%' |
| 36 | |
| 37 | def __init__(self): |
| 38 | # Once a target is complete, we no longer need to update it - we can |
| 39 | # just skip it. Maintaining this here provides a way to print the status |
| 40 | # one last time when it reaches 100%. It is much easier to do that here |
| 41 | # than in the Scheduler class. |
| 42 | self.target_done = {} |
| 43 | |
| 44 | def print_header(self, msg): |
| 45 | '''Initilize / print the header bar. |
| 46 | |
| 47 | The header bar contains an introductory message such as the legend of |
| 48 | what Q, D, ... mean.''' |
| 49 | |
| 50 | log.info(self.header_fmt.format(hms="", target="legend", msg=msg)) |
| 51 | |
| 52 | def init_target(self, target, msg): |
| 53 | '''Initialize the status bar for each target.''' |
| 54 | |
| 55 | self.target_done[target] = False |
| 56 | |
| 57 | def update_target(self, target, hms, msg, perc): |
| 58 | '''Periodically update the status bar for each target.''' |
| 59 | |
| 60 | if self.target_done[target]: |
| 61 | return |
| 62 | |
| 63 | log.info( |
| 64 | self.status_fmt.format(hms=hms, target=target, msg=msg, perc=perc)) |
| 65 | if perc == 100: |
| 66 | self.target_done[target] = True |
| 67 | |
| 68 | def exit(self): |
| 69 | '''Do cleanup activities before exitting.''' |
| 70 | |
| 71 | pass |
| 72 | |
| 73 | |
| 74 | class EnlightenStatusPrinter(StatusPrinter): |
| 75 | '''Abstraction for printing status using Enlighten. |
| 76 | |
| 77 | Enlighten is a third party progress bar tool. Documentation: |
| 78 | https://python-enlighten.readthedocs.io/en/stable/ |
| 79 | |
| 80 | Though it offers very fancy progress bar visualization, we stick to a |
| 81 | simple status bar 'pinned' to the bottom of the screen for each target |
| 82 | that displays statically, a pre-prepared message. We avoid the progress bar |
| 83 | visualization since it requires enlighten to perform some computations the |
| 84 | Scheduler already does. It also helps keep the overhead to a minimum. |
| 85 | |
| 86 | Enlighten does not work if the output of dvsim is redirected to a file, for |
| 87 | example - it needs to be attached to a TTY enabled stream. |
| 88 | ''' |
| 89 | def __init__(self): |
| 90 | super().__init__() |
| 91 | |
| 92 | # Initialize the status_bars for header and the targets . |
| 93 | self.manager = enlighten.get_manager() |
| 94 | self.status_header = None |
| 95 | self.status_target = {} |
| 96 | |
| 97 | def print_header(self, msg): |
| 98 | self.status_header = self.manager.status_bar( |
| 99 | status_format=self.header_fmt, |
| 100 | hms="", |
| 101 | target="legend", |
| 102 | msg= |
| 103 | "Q: queued, D: dispatched, P: passed, F: failed, K: killed, T: total" |
| 104 | ) |
| 105 | |
| 106 | def init_target(self, target, msg): |
| 107 | super().init_target(target, msg) |
| 108 | self.status_target[target] = self.manager.status_bar( |
| 109 | status_format=self.status_fmt, |
| 110 | hms="", |
| 111 | target=target, |
| 112 | msg=msg, |
| 113 | perc=0.0) |
| 114 | |
| 115 | def update_target(self, target, hms, msg, perc): |
| 116 | if self.target_done[target]: |
| 117 | return |
| 118 | |
| 119 | self.status_target[target].update(hms=hms, msg=msg, perc=perc) |
| 120 | if perc == 100: |
| 121 | self.target_done[target] = True |
| 122 | |
| 123 | def exit(self): |
| 124 | self.status_header.close() |
| 125 | for target in self.status_target: |
| 126 | self.status_target[target].close() |
| 127 | |
| 128 | |
| 129 | def get_status_printer(): |
| 130 | """Factory method that returns a status printer instance. |
| 131 | |
| 132 | If ENLIGHTEN_EXISTS (enlighten is installed) and stdout is a TTY, then |
| 133 | return an instance of EnlightenStatusPrinter, else return an instance of |
| 134 | StatusPrinter. |
| 135 | """ |
| 136 | if ENLIGHTEN_EXISTS and sys.stdout.isatty(): |
| 137 | return EnlightenStatusPrinter() |
| 138 | else: |
| 139 | return StatusPrinter() |