| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| r''' | |
| This class holds a dict of message buckets according to the format defined | |
| upon construction. It is meant to hold all message buckets of a build / tool | |
| run, and provides convenience functions that streamline result aggregation | |
| and printout in the Dvsim flow classes. | |
| ''' | |
| import hjson | |
| import copy | |
| import logging as log | |
| from pathlib import Path | |
| from typing import Dict, List | |
| from utils import print_msg_list | |
| from MsgBucket import MsgBucket | |
| class MsgBuckets(): | |
| def __init__(self, | |
| bucket_cfgs: List[Dict]) -> None: | |
| self.buckets = {f"{b['category']}_{b['severity']}": | |
| MsgBucket(b['category'], b['severity'], b['label']) | |
| for b in bucket_cfgs} | |
| def clear(self): | |
| '''Clear all signatures in all buckets''' | |
| for b in self.buckets.values(): | |
| b.clear() | |
| def get_labels(self, | |
| severity_filter: List[str] = []) -> List[str]: | |
| ''' | |
| Returns all bucket labels as a list. | |
| If severity_filter is not empty, only the buckets with the listed | |
| severities will be returned. | |
| ''' | |
| for s in severity_filter: | |
| if not MsgBucket.severity_is_known(s): | |
| RuntimeError(f'Unknown severity {s}') | |
| labels = [] | |
| for key, b in self.buckets.items(): | |
| if not severity_filter or b.severity in severity_filter: | |
| labels.append(b.label) | |
| return labels | |
| def get_keys(self, | |
| severity_filter: List[str] = []) -> List[str]: | |
| ''' | |
| Returns all bucket keys as a list. | |
| If severity_filter is not empty, only the buckets with the listed | |
| severities will be returned. | |
| ''' | |
| for s in severity_filter: | |
| if not MsgBucket.severity_is_known(s): | |
| RuntimeError(f'Unknown severity {s}') | |
| keys = [] | |
| for key, b in self.buckets.items(): | |
| if not severity_filter or b.severity in severity_filter: | |
| keys.append(key) | |
| return keys | |
| def get_counts(self, | |
| keys: List[str] = None, | |
| severity_filter: List[str] = []) -> List[int]: | |
| ''' | |
| Get bucket count totals as a list of integers. | |
| The bucket keys can be supplied externally, in which case the | |
| severity_filter does not apply. If a specific bucket key does not | |
| exist, a 0 count will be returned for that specific key. | |
| If severity_filter is not empty, only the buckets with the listed | |
| severities will be returned. | |
| ''' | |
| if not keys: | |
| keys = self.get_keys(severity_filter) | |
| counts = [] | |
| for k in keys: | |
| c = self.buckets[k].count() if k in self.buckets else 0 | |
| counts.append(c) | |
| return counts | |
| def get_counts_md(self, | |
| keys: List[str] = None, | |
| severity_filter: List[str] = [], | |
| colmap: bool = True) -> List[str]: | |
| ''' | |
| Get bucket count totals as a list of strings with optional colormap. | |
| The bucket keys can be supplied externally, in which case the | |
| severity_filter does not apply. If a specific bucket key does not | |
| exist, a "--" string will be returned for that specific key. | |
| If severity_filter is not empty, only the buckets with the listed | |
| severities will be returned. | |
| ''' | |
| if not keys: | |
| keys = self.get_keys(severity_filter) | |
| counts = [] | |
| for k in keys: | |
| c = self.buckets[k].count_md(colmap) if k in self.buckets else '--' | |
| counts.append(c) | |
| return counts | |
| def has_signatures(self, | |
| severity_filter: List[str] = []) -> bool: | |
| ''' | |
| Checks whether there are any signatures with specified severities. | |
| If severity_filter is empty, the method returns true if any of the | |
| buckets contains a nonzero amount of signatures. | |
| ''' | |
| return any(self.get_counts(severity_filter=severity_filter)) | |
| def print_signatures_md(self, | |
| severity_filter: List[str] = [], | |
| max_per_bucket: int = -1) -> str: | |
| ''' | |
| Render signatures into a string buffer. | |
| The bucket labels are used as subtitles in this printout. | |
| If severity_filter is not empty, only the buckets with the listed | |
| severities will be returned. | |
| The number of messages printed per bucket can be limited by | |
| setting max_per_bucket to a nonnegative value. | |
| ''' | |
| msgs = '' | |
| keys = self.get_keys(severity_filter) | |
| for k in keys: | |
| msgs += print_msg_list(f'#### {self.buckets[k].label}', | |
| self.buckets[k].signatures, | |
| max_per_bucket) | |
| return msgs | |
| def merge(self, other) -> None: | |
| ''' | |
| Merge other MsgBuckets object into this one. | |
| This will append signatures to the corresponding bucket if it exists. | |
| If the bucket does not yet exist it will be created. | |
| ''' | |
| for k, b in other.buckets.items(): | |
| if k in self.buckets: | |
| self.buckets[k].merge(b) | |
| else: | |
| self.buckets.update({k: copy.deepcopy(b)}) | |
| def __add__(self, other): | |
| ''' | |
| Merges two MsgBucket objects into one. | |
| Buckets will be uniquified, and signatures in buckets with the same | |
| category and severity will be merged. | |
| ''' | |
| mb = copy.deepcopy(self) | |
| mb.merge(other) | |
| return mb | |
| # TODO(#9079): remove the method below once the log parsing has been | |
| # merged into the Dvsim core code. | |
| def load_hjson(self, result_path: Path) -> None: | |
| '''Clear internal data structure and initialize with values in Hjson''' | |
| self.clear() | |
| try: | |
| with result_path.open() as results_file: | |
| results_dict = hjson.load(results_file, use_decimal=True) | |
| except IOError as err: | |
| log.warning("%s", err) | |
| if 'flow_error' not in self.buckets: | |
| self.buckets.update({'flow_error': MsgBucket('flow', | |
| 'error')}) | |
| self.buckets['flow_error'].signatures.append('IOError: %s' % err) | |
| return | |
| for k, signatures in results_dict.items(): | |
| if not isinstance(signatures, List): | |
| raise RuntimeError(f'Signatures in {k} must be a ' | |
| 'list of strings') | |
| self.buckets[k].signatures.extend(signatures) |