-
Notifications
You must be signed in to change notification settings - Fork 4
fix: add failure buckets data model back in #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,19 @@ | |
|
|
||
| """Class describing simulation results.""" | ||
|
|
||
| import collections | ||
| import re | ||
| from collections.abc import Sequence | ||
| from collections.abc import Mapping, Sequence | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from pydantic import BaseModel, ConfigDict | ||
|
|
||
| from dvsim.job.data import CompletedJobStatus | ||
| from dvsim.testplan import Result | ||
|
|
||
| if TYPE_CHECKING: | ||
| from dvsim.job.data import CompletedJobStatus | ||
|
|
||
| __all__ = () | ||
|
|
||
| _REGEX_REMOVE = [ | ||
| # Remove UVM time. | ||
| re.compile(r"@\s+[\d.]+\s+[np]s: "), | ||
|
|
@@ -66,6 +72,78 @@ | |
| ] | ||
|
|
||
|
|
||
| def _bucketize(fail_msg: str) -> str: | ||
| """Generalise error messages to create common error buckets.""" | ||
| bucket = fail_msg | ||
| # Remove stuff. | ||
| for regex in _REGEX_REMOVE: | ||
| bucket = regex.sub("", bucket) | ||
| # Strip stuff. | ||
| for regex in _REGEX_STRIP: | ||
| bucket = regex.sub(r"\g<1>", bucket) | ||
| # Replace with '*'. | ||
| for regex in _REGEX_STAR: | ||
| bucket = regex.sub("*", bucket) | ||
|
|
||
| return bucket | ||
|
|
||
|
|
||
| class JobFailureOverview(BaseModel): | ||
| """Overview of the Job failure.""" | ||
|
|
||
| model_config = ConfigDict(frozen=True, extra="forbid") | ||
|
|
||
| name: str | ||
| """Name of the job.""" | ||
|
|
||
| seed: int | None | ||
| """Test seed.""" | ||
|
|
||
| line: int | None | ||
| """Line number within the log if there is one.""" | ||
|
|
||
| log_context: Sequence[str] | ||
| """Context within the log.""" | ||
|
|
||
|
|
||
| class BucketedFailures(BaseModel): | ||
| """Bucketed failed runs. | ||
|
|
||
| The runs are grouped into failure buckets based on the error messages they | ||
| reported. This makes it easier to see the classes of errors. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(frozen=True, extra="forbid") | ||
|
|
||
| buckets: Mapping[str, Sequence["JobFailureOverview"]] | ||
| """Mapping of common error message strings to the full job failure summary.""" | ||
|
|
||
| @staticmethod | ||
| def from_job_status(results: Sequence["CompletedJobStatus"]) -> "BucketedFailures": | ||
| """Construct from CompletedJobStatus objects.""" | ||
| buckets = {} | ||
|
|
||
| for job_status in results: | ||
| if job_status.status in ["F", "K"]: | ||
| bucket = _bucketize(job_status.fail_msg.message) | ||
|
|
||
| if bucket not in buckets: | ||
| buckets[bucket] = [] | ||
|
|
||
| buckets[bucket].append( | ||
| JobFailureOverview( | ||
| name=job_status.full_name, | ||
| seed=job_status.seed, | ||
| line=job_status.fail_msg.line_number, | ||
| log_context=job_status.fail_msg.context, | ||
| ), | ||
| ) | ||
|
|
||
| return BucketedFailures( | ||
| buckets=buckets, | ||
| ) | ||
|
|
||
|
|
||
| class SimResults: | ||
| """An object wrapping up a table of results for some tests. | ||
|
|
||
|
|
@@ -76,30 +154,22 @@ class SimResults: | |
| holding all failing tests with the same signature. | ||
| """ | ||
|
|
||
| def __init__(self, results: Sequence[CompletedJobStatus]) -> None: | ||
| def __init__(self, results: Sequence["CompletedJobStatus"]) -> None: | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quoted type required here to prevent circular imports. Once we have a few more data classes, it might be worth refactoring the file structure a bit so that we can have types without quotes. Circular imports due to typing is generally a good sign that things that should be together are not. |
||
| self.table = [] | ||
| self.buckets = collections.defaultdict(list) | ||
| self.buckets: Mapping[str, JobFailureOverview] = {} | ||
|
|
||
| self._name_to_row = {} | ||
|
|
||
| for job_status in results: | ||
| self._add_item(job_status=job_status) | ||
|
|
||
| def _add_item(self, job_status: CompletedJobStatus) -> None: | ||
| def _add_item(self, job_status: "CompletedJobStatus") -> None: | ||
| """Recursively add a single item to the table of results.""" | ||
| if job_status.status in ["F", "K"]: | ||
| bucket = self._bucketize(job_status.fail_msg.message) | ||
| self.buckets[bucket].append( | ||
| ( | ||
| job_status, | ||
| job_status.fail_msg.line_number, | ||
| job_status.fail_msg.context, | ||
| ), | ||
| ) | ||
|
|
||
| # Runs get added to the table directly | ||
| if job_status.target == "run": | ||
| self._add_run(job_status) | ||
|
|
||
| def _add_run(self, job_status: CompletedJobStatus) -> None: | ||
| def _add_run(self, job_status: "CompletedJobStatus") -> None: | ||
| """Add an entry to table for item.""" | ||
| row = self._name_to_row.get(job_status.name) | ||
| if row is None: | ||
|
|
@@ -119,16 +189,3 @@ def _add_run(self, job_status: CompletedJobStatus) -> None: | |
| if job_status.status == "P": | ||
| row.passing += 1 | ||
| row.total += 1 | ||
|
|
||
| def _bucketize(self, fail_msg): | ||
| bucket = fail_msg | ||
| # Remove stuff. | ||
| for regex in _REGEX_REMOVE: | ||
| bucket = regex.sub("", bucket) | ||
| # Strip stuff. | ||
| for regex in _REGEX_STRIP: | ||
| bucket = regex.sub(r"\g<1>", bucket) | ||
| # Replace with '*'. | ||
| for regex in _REGEX_STAR: | ||
| bucket = regex.sub("*", bucket) | ||
| return bucket | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return type needs quoting as the type is the class itself. The reference for which doesn't exist until it's been fully defined, at least as far as the class attributes and method prototypes.