Skip to content

Commit 56473c1

Browse files
authored
Merge pull request #39 from redhat-performance/feat/RPOPC-1307-passmark-multi-metric
RPOPC-1307: Add multi-metric support for Passmark benchmark
2 parents 3502487 + 27b1c97 commit 56473c1

2 files changed

Lines changed: 214 additions & 1 deletion

File tree

src/chronicler/processors/passmark_processor.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from .base_processor import BaseProcessor, ProcessorError
1818
from .run_utils import run_data_timeseries_to_objects, timeseries_summary_from_metric
19-
from ..schema import Run, create_run_key, create_sequence_key
19+
from ..schema import Run, PrimaryMetric, StatisticalSummary, create_run_key, create_sequence_key
2020
from ..utils.parser_utils import read_file_content
2121

2222
logger = logging.getLogger(__name__)
@@ -270,3 +270,59 @@ def _build_run_object(self, run_data: Dict[str, Any]) -> Run:
270270
timeseries=timeseries if timeseries else None,
271271
timeseries_summary=ts_summary
272272
)
273+
274+
def _extract_primary_metrics(
275+
self, runs: Dict[str, Any],
276+
overall_stats: Optional[StatisticalSummary]
277+
) -> Optional[List[PrimaryMetric]]:
278+
"""
279+
Extract CPU Mark and Memory Mark as coequal primary metrics.
280+
281+
Passmark is a multi-metric benchmark reporting two key performance scores:
282+
- CPU Mark: SUMM_CPU_mean from aggregated CPU benchmark results
283+
- Memory Mark: SUMM_ME_mean from aggregated memory benchmark results
284+
285+
Both metrics are important for system characterization.
286+
287+
Returns list of PrimaryMetric objects for both metrics.
288+
"""
289+
if not runs:
290+
return None
291+
292+
# Get first run to extract metrics
293+
first_run = list(runs.values())[0]
294+
295+
# Handle both dict and Run dataclass objects
296+
metrics = None
297+
if isinstance(first_run, dict) and 'metrics' in first_run:
298+
metrics = first_run['metrics']
299+
elif hasattr(first_run, 'metrics') and first_run.metrics:
300+
metrics = first_run.metrics
301+
302+
if not metrics:
303+
return None
304+
305+
# Build list of primary metrics
306+
primary_metrics = []
307+
308+
# CPU Mark from SUMM_CPU_mean
309+
if 'SUMM_CPU_mean' in metrics and metrics['SUMM_CPU_mean'] is not None:
310+
primary_metrics.append(
311+
PrimaryMetric(
312+
name='CPU Mark',
313+
value=float(metrics['SUMM_CPU_mean']),
314+
unit='score'
315+
)
316+
)
317+
318+
# Memory Mark from SUMM_ME_mean
319+
if 'SUMM_ME_mean' in metrics and metrics['SUMM_ME_mean'] is not None:
320+
primary_metrics.append(
321+
PrimaryMetric(
322+
name='Memory Mark',
323+
value=float(metrics['SUMM_ME_mean']),
324+
unit='score'
325+
)
326+
)
327+
328+
return primary_metrics if primary_metrics else None

tests/test_passmark_integration.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
Integration test for Passmark multi-metric processing.
3+
4+
Demonstrates end-to-end processing of Passmark results with both CPU and Memory marks.
5+
"""
6+
7+
import pytest
8+
from pathlib import Path
9+
from unittest.mock import patch
10+
11+
from chronicler.processors.passmark_processor import PassmarkProcessor
12+
13+
pytestmark = pytest.mark.integration
14+
15+
16+
def test_passmark_processor_multi_metric_extraction(result_dir):
17+
"""
18+
Processor-level integration test: Passmark YML → parsed runs → Results with 2 primary_metrics.
19+
20+
This demonstrates the complete processing flow for RPOPC-1307.
21+
Passmark reports CPU Mark (SUMM_CPU) and Memory Mark (SUMM_ME).
22+
"""
23+
# Create realistic Passmark YML with CPU and Memory summary scores
24+
yml_content = """BaselineInfo:
25+
WebDBID: -1
26+
TimeStamp: 20260502203522
27+
Version:
28+
Major: 11
29+
Minor: 0
30+
Build: 1002
31+
Results:
32+
NumTestProcesses: 16
33+
CPU_INTEGER_MATH: 73090.879000000001
34+
CPU_FLOATINGPOINT_MATH: 52092.075564261577
35+
CPU_ENCRYPTION: 16095.081709056702
36+
ME_ALLOC_S: 7821.9859399764991
37+
ME_READ_S: 26985.984375
38+
ME_WRITE: 11951.0166015625
39+
SUMM_CPU: 24685.407296119931
40+
SUMM_ME: 2106.6563041301797
41+
SystemInformation:
42+
OSName: Red Hat Enterprise Linux 9.8 (Plow)
43+
Processor: Intel Xeon Platinum 8488C
44+
"""
45+
46+
yml_path = result_dir / "results_all_1.yml"
47+
yml_path.write_text(yml_content)
48+
49+
# Create dummy zip
50+
dummy_zip = result_dir / "results_passmark.zip"
51+
dummy_zip.write_bytes(b"")
52+
53+
# Process with Passmark processor
54+
processor = PassmarkProcessor(str(result_dir))
55+
extracted_result = {"files": {}, "extracted_path": str(result_dir)}
56+
57+
with patch.object(processor.archive_handler, "extract_result_archive") as mock_extract:
58+
mock_extract.return_value = extracted_result
59+
results = processor.build_results()
60+
61+
# Verify structure
62+
assert results is not None
63+
assert results.status == "PASS"
64+
assert results.total_runs == 1
65+
assert results.runs is not None
66+
assert "run_0" in results.runs
67+
68+
# Verify primary_metrics
69+
assert results.primary_metrics is not None
70+
assert len(results.primary_metrics) == 2, "Should have exactly 2 primary metrics"
71+
72+
metrics_by_name = {m.name: m for m in results.primary_metrics}
73+
74+
# Verify both metrics present
75+
assert "CPU Mark" in metrics_by_name, "Missing CPU Mark metric"
76+
assert "Memory Mark" in metrics_by_name, "Missing Memory Mark metric"
77+
78+
# Verify units
79+
assert metrics_by_name["CPU Mark"].unit == "score"
80+
assert metrics_by_name["Memory Mark"].unit == "score"
81+
82+
# Verify values (from SUMM_CPU and SUMM_ME in yml)
83+
# CPU Mark = SUMM_CPU = 24685.407296119931
84+
assert metrics_by_name["CPU Mark"].value == pytest.approx(24685.407296119931)
85+
86+
# Memory Mark = SUMM_ME = 2106.6563041301797
87+
assert metrics_by_name["Memory Mark"].value == pytest.approx(2106.6563041301797)
88+
89+
90+
def test_passmark_processor_multi_metric_with_multiple_iterations(result_dir):
91+
"""
92+
Test primary_metrics extraction with multiple iterations.
93+
94+
Should extract mean values from aggregated metrics (SUMM_CPU_mean, SUMM_ME_mean).
95+
"""
96+
# Create multiple YML files (simulating multiple iterations)
97+
yml_content_1 = """BaselineInfo:
98+
WebDBID: -1
99+
TimeStamp: 20260502203522
100+
Version:
101+
Major: 11
102+
Results:
103+
NumTestProcesses: 16
104+
CPU_INTEGER_MATH: 70000.0
105+
SUMM_CPU: 24000.0
106+
SUMM_ME: 2000.0
107+
SystemInformation:
108+
OSName: Red Hat Enterprise Linux 9.8
109+
"""
110+
111+
yml_content_2 = """BaselineInfo:
112+
WebDBID: -1
113+
TimeStamp: 20260502204522
114+
Version:
115+
Major: 11
116+
Results:
117+
NumTestProcesses: 16
118+
CPU_INTEGER_MATH: 75000.0
119+
SUMM_CPU: 25000.0
120+
SUMM_ME: 2200.0
121+
SystemInformation:
122+
OSName: Red Hat Enterprise Linux 9.8
123+
"""
124+
125+
yml_path_1 = result_dir / "results_all_1.yml"
126+
yml_path_1.write_text(yml_content_1)
127+
yml_path_2 = result_dir / "results_all_2.yml"
128+
yml_path_2.write_text(yml_content_2)
129+
130+
# Create dummy zip
131+
dummy_zip = result_dir / "results_passmark.zip"
132+
dummy_zip.write_bytes(b"")
133+
134+
# Process with Passmark processor
135+
processor = PassmarkProcessor(str(result_dir))
136+
extracted_result = {"files": {}, "extracted_path": str(result_dir)}
137+
138+
with patch.object(processor.archive_handler, "extract_result_archive") as mock_extract:
139+
mock_extract.return_value = extracted_result
140+
results = processor.build_results()
141+
142+
# Verify primary_metrics
143+
assert results.primary_metrics is not None
144+
assert len(results.primary_metrics) == 2, "Should have exactly 2 primary metrics"
145+
146+
metrics_by_name = {m.name: m for m in results.primary_metrics}
147+
148+
# Verify both metrics present
149+
assert "CPU Mark" in metrics_by_name
150+
assert "Memory Mark" in metrics_by_name
151+
152+
# Verify mean values
153+
# CPU Mark mean = (24000 + 25000) / 2 = 24500
154+
assert metrics_by_name["CPU Mark"].value == pytest.approx(24500.0)
155+
156+
# Memory Mark mean = (2000 + 2200) / 2 = 2100
157+
assert metrics_by_name["Memory Mark"].value == pytest.approx(2100.0)

0 commit comments

Comments
 (0)