Skip to content

Commit 3502487

Browse files
authored
Merge pull request #38 from redhat-performance/feat/RPOPC-1306-specjbb-multi-metric
RPOPC-1306: Add multi-metric support for SpecJBB benchmark
2 parents e606e11 + 367fda1 commit 3502487

2 files changed

Lines changed: 189 additions & 1 deletion

File tree

src/chronicler/processors/specjbb_processor.py

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

1919
from .base_processor import BaseProcessor, ProcessorError
2020
from .timestamp_utils import validate_iso8601_timestamp
21-
from ..schema import Run, TimeSeriesPoint, create_run_key, create_sequence_key
21+
from ..schema import Run, TimeSeriesPoint, PrimaryMetric, StatisticalSummary, create_run_key, create_sequence_key
2222

2323
logger = logging.getLogger(__name__)
2424

@@ -340,3 +340,59 @@ def _build_run_object(
340340
configuration=config,
341341
timeseries=timeseries if timeseries else None
342342
)
343+
344+
def _extract_primary_metrics(
345+
self, runs: Dict[str, Any],
346+
overall_stats: Optional[StatisticalSummary]
347+
) -> Optional[List[PrimaryMetric]]:
348+
"""
349+
Extract Critical-jOPS and Max-jOPS as coequal primary metrics.
350+
351+
SpecJBB is a multi-metric benchmark reporting two key throughput metrics:
352+
- Critical-jOPS: overall_score_bops from detailed .txt file (if available)
353+
- Max-jOPS: peak_throughput_bops from CSV warehouse configurations
354+
355+
Both metrics are important for Java performance characterization.
356+
357+
Returns list of PrimaryMetric objects for available metrics.
358+
"""
359+
if not runs:
360+
return None
361+
362+
# Get first run to extract metrics
363+
first_run = list(runs.values())[0]
364+
365+
# Handle both dict and Run dataclass objects
366+
metrics = None
367+
if isinstance(first_run, dict) and 'metrics' in first_run:
368+
metrics = first_run['metrics']
369+
elif hasattr(first_run, 'metrics') and first_run.metrics:
370+
metrics = first_run.metrics
371+
372+
if not metrics:
373+
return None
374+
375+
# Build list of primary metrics (only include metrics with data)
376+
primary_metrics = []
377+
378+
# Critical-jOPS from overall_score_bops (from .txt file)
379+
if 'overall_score_bops' in metrics and metrics['overall_score_bops'] is not None:
380+
primary_metrics.append(
381+
PrimaryMetric(
382+
name='Critical-jOPS',
383+
value=float(metrics['overall_score_bops']),
384+
unit='Bops'
385+
)
386+
)
387+
388+
# Max-jOPS from peak_throughput_bops (from CSV)
389+
if 'peak_throughput_bops' in metrics and metrics['peak_throughput_bops'] is not None:
390+
primary_metrics.append(
391+
PrimaryMetric(
392+
name='Max-jOPS',
393+
value=float(metrics['peak_throughput_bops']),
394+
unit='Bops'
395+
)
396+
)
397+
398+
return primary_metrics if primary_metrics else None

tests/test_specjbb_integration.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
Integration test for SpecJBB multi-metric processing.
3+
4+
Demonstrates end-to-end processing of SpecJBB results with both metrics.
5+
"""
6+
7+
import pytest
8+
from pathlib import Path
9+
from unittest.mock import patch
10+
11+
from chronicler.processors.specjbb_processor import SpecJBBProcessor
12+
13+
pytestmark = pytest.mark.integration
14+
15+
16+
def test_specjbb_processor_multi_metric_extraction(result_dir):
17+
"""
18+
Processor-level integration test: SpecJBB CSV → parsed runs → Results with 2 primary_metrics.
19+
20+
This demonstrates the complete processing flow for RPOPC-1306.
21+
SpecJBB reports Critical-jOPS (overall_score_bops) and Max-jOPS (peak_throughput_bops).
22+
"""
23+
# Create realistic SpecJBB CSV with warehouse configurations
24+
csv_content = """Warehouses,Bops,Numb_JVMs,Start_Date,End_Date
25+
2,50000,1,2026-06-01T10:00:00Z,2026-06-01T10:05:00Z
26+
4,95000,1,2026-06-01T10:05:00Z,2026-06-01T10:10:00Z
27+
6,135000,1,2026-06-01T10:10:00Z,2026-06-01T10:15:00Z
28+
8,165000,1,2026-06-01T10:15:00Z,2026-06-01T10:20:00Z
29+
10,180000,1,2026-06-01T10:20:00Z,2026-06-01T10:25:00Z
30+
12,185000,1,2026-06-01T10:25:00Z,2026-06-01T10:30:00Z
31+
14,182000,1,2026-06-01T10:30:00Z,2026-06-01T10:35:00Z
32+
16,178000,1,2026-06-01T10:35:00Z,2026-06-01T10:40:00Z"""
33+
34+
csv_path = result_dir / "results_specjbb.csv"
35+
csv_path.write_text(csv_content)
36+
37+
# Create .txt file with overall score (Critical-jOPS)
38+
txt_content = """SPECjbb2005 Result Report
39+
40+
Test Configuration:
41+
Number of JVMs: 1
42+
43+
Results:
44+
SPECjbb2005 bops = 170000, SPECjbb2005 bops/JVM = 170000
45+
46+
Throughput 170000
47+
"""
48+
49+
txt_path = result_dir / "SPECjbb2005.001.txt"
50+
txt_path.write_text(txt_content)
51+
52+
# Create dummy zip
53+
dummy_zip = result_dir / "results_specjbb.zip"
54+
dummy_zip.write_bytes(b"")
55+
56+
# Process with SpecJBB processor
57+
processor = SpecJBBProcessor(str(result_dir))
58+
extracted_result = {"files": {}, "extracted_path": str(result_dir)}
59+
60+
with patch.object(processor.archive_handler, "extract_result_archive") as mock_extract:
61+
mock_extract.return_value = extracted_result
62+
results = processor.build_results()
63+
64+
# Verify structure
65+
assert results is not None
66+
assert results.status == "PASS"
67+
assert results.total_runs == 1
68+
assert results.runs is not None
69+
assert "run_0" in results.runs
70+
71+
# Verify primary_metrics
72+
assert results.primary_metrics is not None
73+
assert len(results.primary_metrics) == 2, "Should have exactly 2 primary metrics"
74+
75+
metrics_by_name = {m.name: m for m in results.primary_metrics}
76+
77+
# Verify both metrics present
78+
assert "Critical-jOPS" in metrics_by_name, "Missing Critical-jOPS metric"
79+
assert "Max-jOPS" in metrics_by_name, "Missing Max-jOPS metric"
80+
81+
# Verify units
82+
assert metrics_by_name["Critical-jOPS"].unit == "Bops"
83+
assert metrics_by_name["Max-jOPS"].unit == "Bops"
84+
85+
# Verify values
86+
# Critical-jOPS = overall_score_bops = 170000 (from .txt file)
87+
assert metrics_by_name["Critical-jOPS"].value == 170000
88+
89+
# Max-jOPS = peak_throughput_bops = 185000 (from CSV, warehouses=12)
90+
assert metrics_by_name["Max-jOPS"].value == 185000
91+
92+
93+
def test_specjbb_processor_multi_metric_without_txt_file(result_dir):
94+
"""
95+
Test primary_metrics extraction when .txt file is missing.
96+
97+
Should still extract Max-jOPS from CSV peak throughput.
98+
Critical-jOPS should be omitted if overall_score is not available.
99+
"""
100+
# Create CSV without .txt file
101+
csv_content = """Warehouses,Bops,Numb_JVMs,Start_Date,End_Date
102+
2,50000,1,2026-06-01T10:00:00Z,2026-06-01T10:05:00Z
103+
4,95000,1,2026-06-01T10:05:00Z,2026-06-01T10:10:00Z
104+
6,135000,1,2026-06-01T10:10:00Z,2026-06-01T10:15:00Z"""
105+
106+
csv_path = result_dir / "results_specjbb.csv"
107+
csv_path.write_text(csv_content)
108+
109+
# Create dummy zip
110+
dummy_zip = result_dir / "results_specjbb.zip"
111+
dummy_zip.write_bytes(b"")
112+
113+
# Process with SpecJBB processor
114+
processor = SpecJBBProcessor(str(result_dir))
115+
extracted_result = {"files": {}, "extracted_path": str(result_dir)}
116+
117+
with patch.object(processor.archive_handler, "extract_result_archive") as mock_extract:
118+
mock_extract.return_value = extracted_result
119+
results = processor.build_results()
120+
121+
# Verify primary_metrics
122+
assert results.primary_metrics is not None
123+
assert len(results.primary_metrics) == 1, "Should have only Max-jOPS when .txt is missing"
124+
125+
metrics_by_name = {m.name: m for m in results.primary_metrics}
126+
127+
# Verify only Max-jOPS is present
128+
assert "Max-jOPS" in metrics_by_name, "Missing Max-jOPS metric"
129+
assert "Critical-jOPS" not in metrics_by_name, "Critical-jOPS should be omitted without .txt file"
130+
131+
# Verify Max-jOPS value
132+
assert metrics_by_name["Max-jOPS"].value == 135000

0 commit comments

Comments
 (0)