blob: e42c2a6c036456e9eeb964ecb86b9c518fc95c84 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r"""
Utility functions common across dvsim.
"""
import logging as log
import os
import pprint
import re
import shlex
import subprocess
import sys
import time
from collections import OrderedDict
import hjson
import mistletoe
# For verbose logging
VERBOSE = 15
# Run a command and get the result. Exit with error if the command did not
# succeed. This is a simpler version of the run_cmd function below.
def run_cmd(cmd):
(status, output) = subprocess.getstatusoutput(cmd)
if status:
sys.stderr.write("cmd " + cmd + " returned with status " + str(status))
sys.exit(status)
return output
# Run a command with a specified timeout. If the command does not finish before
# the timeout, then it returns -1. Else it returns the command output. If the
# command fails, it throws an exception and returns the stderr.
def run_cmd_with_timeout(cmd, timeout=-1, exit_on_failure=1):
args = shlex.split(cmd)
p = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
# If timeout is set, poll for the process to finish until timeout
result = ""
status = -1
if timeout == -1:
p.wait()
else:
start = time.time()
while time.time() - start < timeout:
if p.poll() is not None:
break
time.sleep(.01)
# Capture output and status if cmd exited, else kill it
if p.poll() is not None:
result = p.communicate()[0]
status = p.returncode
else:
log.error("cmd \"%s\" timed out!", cmd)
p.kill()
if status != 0:
log.error("cmd \"%s\" exited with status %d", cmd, status)
if exit_on_failure == 1: sys.exit(status)
return (result, status)
# Parse hjson and return a dict
def parse_hjson(hjson_file):
hjson_cfg_dict = None
try:
log.debug("Parsing %s", hjson_file)
f = open(hjson_file, 'rU')
text = f.read()
hjson_cfg_dict = hjson.loads(text, use_decimal=True)
f.close()
except Exception as e:
log.fatal(
"Failed to parse \"%s\" possibly due to bad path or syntax error.\n%s",
hjson_file, e)
sys.exit(1)
return hjson_cfg_dict
def subst_wildcards(var, mdict, ignored_wildcards=[], ignore_error=False):
'''
If var has wildcards specified within {..}, find and substitute them.
'''
def subst(wildcard, mdict):
if wildcard in mdict.keys(): return mdict[wildcard]
else: return None
if "{eval_cmd}" in var:
idx = var.find("{eval_cmd}") + 11
subst_var = subst_wildcards(var[idx:], mdict, ignored_wildcards,
ignore_error)
# If var has wildcards that were ignored, then skip running the command
# for now, assume that it will be handled later.
match = re.findall(r"{([A-Za-z0-9\_]+)}", subst_var)
if len(match) == 0:
var = run_cmd(subst_var)
else:
match = re.findall(r"{([A-Za-z0-9\_]+)}", var)
if len(match) > 0:
subst_list = {}
for item in match:
if item not in ignored_wildcards:
log.debug("Found wildcard \"%s\" in \"%s\"", item, var)
found = subst(item, mdict)
if found is not None:
if type(found) is list:
subst_found = []
for element in found:
element = subst_wildcards(
element, mdict, ignored_wildcards,
ignore_error)
subst_found.append(element)
# Expand list into a str since list within list is
# not supported.
found = " ".join(subst_found)
elif type(found) is str:
found = subst_wildcards(found, mdict,
ignored_wildcards,
ignore_error)
elif type(found) is bool:
found = int(found)
subst_list[item] = found
else:
# Check if the wildcard exists as an environment variable
env_var = os.environ.get(item)
if env_var is not None: subst_list[item] = env_var
elif not ignore_error:
log.error(
"Substitution for the wildcard \"%s\" not found",
item)
sys.exit(1)
for item in subst_list:
var = var.replace("{" + item + "}", str(subst_list[item]))
return var
def find_and_substitute_wildcards(sub_dict,
full_dict,
ignored_wildcards=[],
ignore_error=False):
'''
Recursively find key values containing wildcards in sub_dict in full_dict
and return resolved sub_dict.
'''
for key in sub_dict.keys():
if type(sub_dict[key]) in [dict, OrderedDict]:
# Recursively call this funciton in sub-dicts
sub_dict[key] = find_and_substitute_wildcards(
sub_dict[key], full_dict, ignored_wildcards, ignore_error)
elif type(sub_dict[key]) is list:
sub_dict_key_values = list(sub_dict[key])
# Loop through the list of key's values and substitute each var
# in case it contains a wildcard
for i in range(len(sub_dict_key_values)):
if type(sub_dict_key_values[i]) in [dict, OrderedDict]:
# Recursively call this funciton in sub-dicts
sub_dict_key_values[i] = \
find_and_substitute_wildcards(sub_dict_key_values[i],
full_dict, ignored_wildcards, ignore_error)
elif type(sub_dict_key_values[i]) is str:
sub_dict_key_values[i] = subst_wildcards(
sub_dict_key_values[i], full_dict, ignored_wildcards,
ignore_error)
# Set the substituted key values back
sub_dict[key] = sub_dict_key_values
elif type(sub_dict[key]) is str:
sub_dict[key] = subst_wildcards(sub_dict[key], full_dict,
ignored_wildcards, ignore_error)
return sub_dict
def md_results_to_html(title, css_path, md_text):
'''Convert results in md format to html. Add a little bit of styling.
'''
html_text = "<!DOCTYPE html>\n"
html_text += "<html lang=\"en\">\n"
html_text += "<head>\n"
if title != "":
html_text += " <title>{}</title>\n".format(title)
if css_path != "":
html_text += " <link rel=\"stylesheet\" type=\"text/css\""
html_text += " href=\"{}\"/>\n".format(css_path)
html_text += "</head>\n"
html_text += "<body>\n"
html_text += "<div class=\"results\">\n"
html_text += mistletoe.markdown(md_text)
html_text += "</div>\n"
html_text += "</body>\n"
html_text += "</html>\n"
html_text = htmc_color_pc_cells(html_text)
return html_text
def htmc_color_pc_cells(text):
'''This function finds cells in a html table that contains a "%" sign. It then
uses the number in front if the % sign to color the cell based on the value
from a shade from red to green. These color styles are encoded in ./style.css
which is assumed to be accessible by the final webpage.
This function is now augmented to also take "E" or "W" as identifiers along
with "%". For example, '10 W' is indicative of 10 warnings, and will be color
coded with yellow. Likewise, "7 E" indicates 7 errors and will be color coded
with red. A value of 0 in both cases will be color coded with green.
Note that a space between the value and the indicators (%, E, W) is mandatory.
'''
# Replace <td> with <td class="color-class"> based on the fp
# value. "color-classes" are listed in ./style.css as follows: "cna"
# for NA value, "c0" to "c10" for fp value falling between 0.00-9.99,
# 10.00-19.99 ... 90.00-99.99, 100.0 respetively.
def color_cell(cell, cclass, indicator="%"):
op = cell.replace("<td", "<td class=\"" + cclass + "\"")
# Remove the indicator.
op = re.sub(r"\s*" + indicator + "\s*", "", op)
return op
# List of 'not applicable' identifiers.
na_list = ['--', 'NA', 'N.A.', 'N.A', 'N/A', 'na', 'n.a.', 'n.a', 'n/a']
na_list_patterns = '|'.join(na_list)
# List of floating point patterns: '0', '0.0' & '.0'
fp_patterns = "\d+|\d+\.\d+|\.\d+"
patterns = fp_patterns + '|' + na_list_patterns
indicators = "%|E|W"
match = re.findall(
r"(<td.*>\s*(" + patterns + ")\s+(" + indicators + ")\s*</td>)", text)
if len(match) > 0:
subst_list = {}
fp_nums = []
for item in match:
# item is a tuple - first is the full string indicating the table
# cell which we want to replace, second is the floating point value.
cell = item[0]
fp_num = item[1]
indicator = item[2]
# Skip if fp_num is already processed.
if (fp_num, indicator) in fp_nums: continue
fp_nums.append((fp_num, indicator))
if fp_num in na_list: subst = color_cell(cell, "cna", indicator)
else:
# Item is a fp num.
try:
fp = float(fp_num)
except ValueError:
log.error("Percentage item \"%s\" in cell \"%s\" is not an " + \
"integer or a floating point number", fp_num, cell)
continue
if indicator == "%":
# Item is a percentage.
if fp >= 0.0 and fp < 10.0: subst = color_cell(cell, "c0")
elif fp >= 10.0 and fp < 20.0:
subst = color_cell(cell, "c1")
elif fp >= 20.0 and fp < 30.0:
subst = color_cell(cell, "c2")
elif fp >= 30.0 and fp < 40.0:
subst = color_cell(cell, "c3")
elif fp >= 40.0 and fp < 50.0:
subst = color_cell(cell, "c4")
elif fp >= 50.0 and fp < 60.0:
subst = color_cell(cell, "c5")
elif fp >= 60.0 and fp < 70.0:
subst = color_cell(cell, "c6")
elif fp >= 70.0 and fp < 80.0:
subst = color_cell(cell, "c7")
elif fp >= 80.0 and fp < 90.0:
subst = color_cell(cell, "c8")
elif fp >= 90.0 and fp < 100.0:
subst = color_cell(cell, "c9")
elif fp >= 100.0:
subst = color_cell(cell, "c10")
else:
# Item is a error or a warning num.
# Use "c6" (yellow) for warnings and "c0" (red) for errors.
if fp == 0:
subst = color_cell(cell, "c10", indicator)
elif indicator == "W":
subst = color_cell(cell, "c6", indicator)
elif indicator == "E":
subst = color_cell(cell, "c0", indicator)
subst_list[cell] = subst
for item in subst_list:
text = text.replace(item, subst_list[item])
return text