Skip to content

Commit f89dc31

Browse files
committed
fix: add failure buckets data model back in
With previous refactoring the failure bucket collection stage was accidentally dropped. With this commit the data is restored to the JSON report. With follow up commits it will be added back to the report itself. Signed-off-by: James McCorrie <[email protected]>
1 parent 6b8f0dc commit f89dc31

File tree

3 files changed

+96
-31
lines changed

3 files changed

+96
-31
lines changed

src/dvsim/flow/sim.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from dvsim.modes import BuildMode, Mode, RunMode, find_mode
2727
from dvsim.regression import Regression
2828
from dvsim.report.data import FlowResults, IPMeta, Testpoint, TestResult, TestStage, ToolMeta
29-
from dvsim.sim_results import SimResults
29+
from dvsim.sim_results import BucketedFailures, SimResults
3030
from dvsim.test import Test
3131
from dvsim.testplan import Testplan
3232
from dvsim.tool.utils import get_sim_tool_plugin
@@ -697,13 +697,16 @@ def make_test_result(tr) -> TestResult | None:
697697
raw_metrics=coverage,
698698
)
699699

700+
failures = BucketedFailures.from_job_status(results=run_results)
701+
700702
# --- Final result ---
701703
return FlowResults(
702704
block=block,
703705
tool=tool,
704706
timestamp=timestamp,
705707
stages=stages,
706708
coverage=coverage_model,
709+
failed_jobs=failures,
707710
passed=total_passed,
708711
total=total_runs,
709712
percent=100.0 * total_passed / total_runs if total_runs else 0.0,

src/dvsim/report/data.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from pydantic import BaseModel, ConfigDict
1212

13+
from dvsim.sim_results import BucketedFailures
14+
1315
__all__ = (
1416
"IPMeta",
1517
"ResultsSummary",
@@ -181,6 +183,9 @@ class FlowResults(BaseModel):
181183
coverage: CoverageMetrics | None
182184
"""Coverage metrics."""
183185

186+
failed_jobs: BucketedFailures
187+
"""Bucketed failed job overview."""
188+
184189
passed: int
185190
"""Number of tests passed."""
186191
total: int

src/dvsim/sim_results.py

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44

55
"""Class describing simulation results."""
66

7-
import collections
87
import re
9-
from collections.abc import Sequence
8+
from collections.abc import Mapping, Sequence
9+
from typing import TYPE_CHECKING
10+
11+
from pydantic import BaseModel, ConfigDict
1012

11-
from dvsim.job.data import CompletedJobStatus
1213
from dvsim.testplan import Result
1314

15+
if TYPE_CHECKING:
16+
from dvsim.job.data import CompletedJobStatus
17+
18+
__all__ = ()
19+
1420
_REGEX_REMOVE = [
1521
# Remove UVM time.
1622
re.compile(r"@\s+[\d.]+\s+[np]s: "),
@@ -66,6 +72,78 @@
6672
]
6773

6874

75+
def _bucketize(fail_msg: str) -> str:
76+
"""Generalise error messages to create common error buckets."""
77+
bucket = fail_msg
78+
# Remove stuff.
79+
for regex in _REGEX_REMOVE:
80+
bucket = regex.sub("", bucket)
81+
# Strip stuff.
82+
for regex in _REGEX_STRIP:
83+
bucket = regex.sub(r"\g<1>", bucket)
84+
# Replace with '*'.
85+
for regex in _REGEX_STAR:
86+
bucket = regex.sub("*", bucket)
87+
88+
return bucket
89+
90+
91+
class JobFailureOverview(BaseModel):
92+
"""Overview of the Job failure."""
93+
94+
model_config = ConfigDict(frozen=True, extra="forbid")
95+
96+
name: str
97+
"""Name of the job."""
98+
99+
seed: int | None
100+
"""Test seed."""
101+
102+
line: int | None
103+
"""Line number within the log if there is one."""
104+
105+
log_context: Sequence[str]
106+
"""Context within the log."""
107+
108+
109+
class BucketedFailures(BaseModel):
110+
"""Bucketed failed runs.
111+
112+
The runs are grouped into failure buckets based on the error messages they
113+
reported. This makes it easier to see the classes of errors.
114+
"""
115+
116+
model_config = ConfigDict(frozen=True, extra="forbid")
117+
118+
buckets: Mapping[str, Sequence["JobFailureOverview"]]
119+
"""Mapping of common error message strings to the full job failure summary."""
120+
121+
@staticmethod
122+
def from_job_status(results: Sequence["CompletedJobStatus"]) -> "BucketedFailures":
123+
"""Construct from CompletedJobStatus objects."""
124+
buckets = {}
125+
126+
for job_status in results:
127+
if job_status.status in ["F", "K"]:
128+
bucket = _bucketize(job_status.fail_msg.message)
129+
130+
if bucket not in buckets:
131+
buckets[bucket] = []
132+
133+
buckets[bucket].append(
134+
JobFailureOverview(
135+
name=job_status.full_name,
136+
seed=job_status.seed,
137+
line=job_status.fail_msg.line_number,
138+
log_context=job_status.fail_msg.context,
139+
),
140+
)
141+
142+
return BucketedFailures(
143+
buckets=buckets,
144+
)
145+
146+
69147
class SimResults:
70148
"""An object wrapping up a table of results for some tests.
71149
@@ -76,30 +154,22 @@ class SimResults:
76154
holding all failing tests with the same signature.
77155
"""
78156

79-
def __init__(self, results: Sequence[CompletedJobStatus]) -> None:
157+
def __init__(self, results: Sequence["CompletedJobStatus"]) -> None:
80158
self.table = []
81-
self.buckets = collections.defaultdict(list)
159+
self.buckets: Mapping[str, JobFailureOverview] = {}
160+
82161
self._name_to_row = {}
162+
83163
for job_status in results:
84164
self._add_item(job_status=job_status)
85165

86-
def _add_item(self, job_status: CompletedJobStatus) -> None:
166+
def _add_item(self, job_status: "CompletedJobStatus") -> None:
87167
"""Recursively add a single item to the table of results."""
88-
if job_status.status in ["F", "K"]:
89-
bucket = self._bucketize(job_status.fail_msg.message)
90-
self.buckets[bucket].append(
91-
(
92-
job_status,
93-
job_status.fail_msg.line_number,
94-
job_status.fail_msg.context,
95-
),
96-
)
97-
98168
# Runs get added to the table directly
99169
if job_status.target == "run":
100170
self._add_run(job_status)
101171

102-
def _add_run(self, job_status: CompletedJobStatus) -> None:
172+
def _add_run(self, job_status: "CompletedJobStatus") -> None:
103173
"""Add an entry to table for item."""
104174
row = self._name_to_row.get(job_status.name)
105175
if row is None:
@@ -119,16 +189,3 @@ def _add_run(self, job_status: CompletedJobStatus) -> None:
119189
if job_status.status == "P":
120190
row.passing += 1
121191
row.total += 1
122-
123-
def _bucketize(self, fail_msg):
124-
bucket = fail_msg
125-
# Remove stuff.
126-
for regex in _REGEX_REMOVE:
127-
bucket = regex.sub("", bucket)
128-
# Strip stuff.
129-
for regex in _REGEX_STRIP:
130-
bucket = regex.sub(r"\g<1>", bucket)
131-
# Replace with '*'.
132-
for regex in _REGEX_STAR:
133-
bucket = regex.sub("*", bucket)
134-
return bucket

0 commit comments

Comments
 (0)