blob: e9bf7f96d3b87ae366cd4c7e7e74b5fcf2c669a4 [file] [log] [blame]
# 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()