Repository: reframe-hpc/reframe

Issue:
Add support for selecting the A or B column variant in performance comparisons
In performance comparisons, When listing columns not in the group-by list the amount of total columns of the final matrix can grow significantly, since for non-grouped-by attribute, two columns will be listed. While there are cases, that both columns are useful, it would be nice in the column spec of the comparison options to be able to select the A or B variant of the column.

Discussion:

Solution patch:
diff --git a/docs/manpage.rst b/docs/manpage.rst
index 7002d6a1a..f92305bb7 100644
--- a/docs/manpage.rst
+++ b/docs/manpage.rst
@@ -1483,9 +1483,15 @@ The selection of the final columns of the results table is specified by the same
 
 However, for performance comparisons, ReFrame will generate two columns for every attribute in the subspec that is not also a group-by attribute, suffixed with ``_A`` and ``_B``.
 These columns contain the aggregated values of the corresponding attributes.
-Note that only the aggregation of ``pval`` (i.e. the test case performance)  can be controlled (see :ref:`testcase-grouping`)
+Note that only the aggregation of ``pval`` (i.e. the test case performance)  can be controlled (see :ref:`testcase-grouping`).
 All other attributes are aggregated by joining their unique values.
 
+It possible to select only one of the A/B variants of the extra columns by adding the ``_A`` or ``_B`` suffix to the column name in the ``<cols>`` subspec, e.g., ``+result_A`` will only show the result of the first group of test cases.
+
+.. versionchanged:: 4.8
+
+   Support for selecting A/B column variants in performance comparisons.
+
 Examples
 --------
 
diff --git a/reframe/frontend/reporting/__init__.py b/reframe/frontend/reporting/__init__.py
index 8632f9ab5..91a7818f8 100644
--- a/reframe/frontend/reporting/__init__.py
+++ b/reframe/frontend/reporting/__init__.py
@@ -680,11 +680,28 @@ def _aggregate_perf(grouped_testcases, aggr_fn, cols):
 def compare_testcase_data(base_testcases, target_testcases, base_fn, target_fn,
                           groups=None, columns=None):
     groups = groups or []
-    columns = columns or []
-    grouped_base = _group_testcases(base_testcases, groups, columns)
-    grouped_target = _group_testcases(target_testcases, groups, columns)
-    pbase = _aggregate_perf(grouped_base, base_fn, columns)
-    ptarget = _aggregate_perf(grouped_target, target_fn, columns)
+
+    # Clean up columns and store those for which we want explicitly the A or B
+    # variants
+    cols = []
+    variants_A = set()
+    variants_B = set()
+    for c in columns:
+        if c.endswith('_A'):
+            variants_A.add(c[:-2])
+            cols.append(c[:-2])
+        elif c.endswith('_B'):
+            variants_B.add(c[:-2])
+            cols.append(c[:-2])
+        else:
+            variants_A.add(c)
+            variants_B.add(c)
+            cols.append(c)
+
+    grouped_base = _group_testcases(base_testcases, groups, cols)
+    grouped_target = _group_testcases(target_testcases, groups, cols)
+    pbase = _aggregate_perf(grouped_base, base_fn, cols)
+    ptarget = _aggregate_perf(grouped_target, target_fn, cols)
 
     # For visual purposes if `name` is in `groups`, consider also its
     # derivative `basename` to be in, so as to avoid duplicate columns
@@ -692,13 +709,17 @@ def compare_testcase_data(base_testcases, target_testcases, base_fn, target_fn,
         groups.append('basename')
 
     # Build the final table data
-    extra_cols = set(columns) - set(groups) - {'pdiff'}
+    extra_cols = set(cols) - set(groups) - {'pdiff'}
 
     # Header line
     header = []
-    for c in columns:
+    for c in cols:
         if c in extra_cols:
-            header += [f'{c}_A', f'{c}_B']
+            if c in variants_A:
+                header.append(f'{c}_A')
+
+            if c in variants_B:
+                header.append(f'{c}_B')
         else:
             header.append(c)
 
@@ -706,7 +727,7 @@ def compare_testcase_data(base_testcases, target_testcases, base_fn, target_fn,
     for key, aggr_data in pbase.items():
         pdiff = None
         line = []
-        for c in columns:
+        for c in cols:
             base = aggr_data.get(c)
             try:
                 target = ptarget[key][c]
@@ -729,8 +750,11 @@ def compare_testcase_data(base_testcases, target_testcases, base_fn, target_fn,
             elif c == 'pdiff':
                 line.append('n/a' if pdiff is None else pdiff)
             elif c in extra_cols:
-                line.append('n/a' if base is None else base)
-                line.append('n/a' if target is None else target)
+                if c in variants_A:
+                    line.append('n/a' if base is None else base)
+
+                if c in variants_B:
+                    line.append('n/a' if target is None else target)
             else:
                 line.append('n/a' if base is None else base)
 


Test patch:
diff --git a/unittests/test_cli.py b/unittests/test_cli.py
index 87eaed30d..6598bdd8f 100644
--- a/unittests/test_cli.py
+++ b/unittests/test_cli.py
@@ -1446,6 +1446,13 @@ def assert_no_crash(returncode, stdout, stderr, exitcode=0):
         )
     )
 
+    # Select only specific variants of extra cols
+    assert_no_crash(
+        *run_reframe2(
+            action='--performance-compare=now-1m:now/now-1d:now/mean:/+result_A+job_nodelist_B'  # noqa: E501
+        )
+    )
+
     # Check disjoint sets of groups and columns
     assert_no_crash(
         *run_reframe2(