Skip to content

Commit cec3dfd

Browse files
committed
WIP: Import results from perflogs
1 parent 001ac57 commit cec3dfd

File tree

3 files changed

+153
-3
lines changed

3 files changed

+153
-3
lines changed

reframe/frontend/argparse.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def add_argument(self, *flags, **kwargs):
196196

197197
if flags and opt_name is None:
198198
# A positional argument
199-
opt_name = flags[-1]
199+
opt_name, flags = flags[-1], flags[:-1]
200200

201201
if opt_name is None:
202202
raise ValueError('could not infer a dest name: no flags defined')
@@ -230,7 +230,8 @@ def add_argument(self, *flags, **kwargs):
230230
except KeyError:
231231
self._defaults.__dict__[opt_name] = None
232232

233-
if not flags:
233+
positional = kwargs.pop('positional', False)
234+
if not flags and not positional:
234235
return None
235236

236237
return self._holder.add_argument(*flags, **kwargs)

reframe/frontend/cli.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
import time
1515
import traceback
16+
import yaml
1617

1718
import reframe.core.config as config
1819
import reframe.core.exceptions as errors
@@ -465,6 +466,10 @@ def main():
465466
action_options.add_argument(
466467
'-V', '--version', action='version', version=osext.reframe_version()
467468
)
469+
action_options.add_argument(
470+
'--import-results', action='store', metavar='SPECFILE',
471+
help='Import results to the database'
472+
)
468473

469474
# Run options
470475
run_options.add_argument(
@@ -815,6 +820,7 @@ def main():
815820
action='store_true',
816821
help='Use a login shell for job scripts'
817822
)
823+
argparser.add_argument('args', metavar='ARGS', nargs='*', positional=True)
818824

819825
def restrict_logging():
820826
'''Restrict logging to errors only.
@@ -1037,6 +1043,21 @@ def restrict_logging():
10371043
)
10381044
sys.exit(0)
10391045

1046+
if options.import_results:
1047+
with exit_gracefully_on_error('failed to import results', printer):
1048+
with open(options.import_results) as fp:
1049+
spec = yaml.load(fp, yaml.Loader)
1050+
1051+
if spec['import']['from'] == 'perflog':
1052+
kwargs = spec['import']
1053+
del kwargs['from']
1054+
report = reporting.RunReport.create_from_perflog(*options.args,
1055+
**kwargs)
1056+
# report.save('foo.json', link_to_last=False)
1057+
uuid = report.store()
1058+
printer.info(f'Results imported successfully as session {uuid}')
1059+
sys.exit(0)
1060+
10401061
# Show configuration after everything is set up
10411062
if options.show_config:
10421063
# Restore logging level

reframe/frontend/reporting/__init__.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import os
1414
import re
1515
import socket
16+
import sys
1617
import time
1718
import uuid
1819
from collections.abc import Hashable
20+
from datetime import datetime
1921
from filelock import FileLock
2022

2123
import reframe as rfm
@@ -25,7 +27,7 @@
2527
from reframe.core.logging import getlogger, _format_time_rfc3339, time_function
2628
from reframe.core.runtime import runtime
2729
from reframe.core.warnings import suppress_deprecations
28-
from reframe.utility import nodelist_abbrev, OrderedSet
30+
from reframe.utility import nodelist_abbrev, nodelist_expand, OrderedSet
2931
from .storage import StorageBackend
3032
from .utility import Aggregator, parse_cmp_spec, parse_time_period, is_uuid
3133

@@ -249,6 +251,132 @@ def __getitem__(self, key):
249251
def __rfm_json_encode__(self):
250252
return self.__report
251253

254+
@classmethod
255+
def create_from_perflog(cls, *logfiles, format=None,
256+
merge_records=None, datefmt=None,
257+
ignore_lines=None, ignore_records=None):
258+
def _filter_record(rec):
259+
if ignore_records is None:
260+
return False
261+
else:
262+
return eval(ignore_records, {}, rec)
263+
264+
def _do_merge(dst, src):
265+
system = src.get('system')
266+
part = src.get('partition')
267+
pvar = src.get('pvar')
268+
pval = src.get('pval')
269+
pref = src.get('pref')
270+
plower = src.get('plower')
271+
pupper = src.get('pupper')
272+
punit = src.get('punit')
273+
if pvar is None:
274+
return dst
275+
276+
if system is not None and part is not None:
277+
pvar = f'{system}:{part}:{pvar}'
278+
279+
# Convert to numbers before inserting
280+
def _convert(x):
281+
if x is None:
282+
return x
283+
284+
if x == 'null':
285+
return None
286+
287+
return float(x)
288+
289+
pval = _convert(pval)
290+
pref = _convert(pref)
291+
pupper = _convert(pupper)
292+
plower = _convert(plower)
293+
dst['perfvalues'][pvar] = (pval, pref, plower, pupper, punit)
294+
dst.pop('pvar', None)
295+
dst.pop('pval', None)
296+
dst.pop('pref', None)
297+
dst.pop('plower', None)
298+
dst.pop('pupper', None)
299+
dst.pop('punit', None)
300+
return dst
301+
302+
patt = re.compile(format)
303+
report = RunReport()
304+
session_uuid = report['session_info']['uuid']
305+
run_index = 0
306+
test_index = 0
307+
t_report_start = sys.maxsize
308+
t_report_end = 0
309+
num_failures = 0
310+
testcases = []
311+
for filename in logfiles:
312+
records = {}
313+
with open(filename) as fp:
314+
for lineno, line in enumerate(fp, start=1):
315+
if lineno in ignore_lines:
316+
continue
317+
318+
m = patt.match(line)
319+
if not m:
320+
continue
321+
322+
rec = m.groupdict()
323+
if _filter_record(rec):
324+
continue
325+
326+
# Add parameters as separate fields
327+
if 'name' in rec:
328+
params = rec['name'].split()[1:]
329+
for spec in params:
330+
p, v = spec.split('=', maxsplit=1)
331+
rec[p[1:]] = v
332+
333+
# Groom the record
334+
if 'job_completion_time' in rec:
335+
key = 'job_completion_time'
336+
date = datetime.strptime(rec[key], datefmt)
337+
rec[key] = date.strftime(_DATETIME_FMT)
338+
ts = date.timestamp()
339+
rec[f'{key}_unix'] = ts
340+
t_report_start = min(t_report_start, ts)
341+
t_report_end = max(t_report_end, ts)
342+
343+
if 'job_nodelist' in rec:
344+
key = 'job_nodelist'
345+
rec[key] = nodelist_expand(rec[key])
346+
347+
rec['uuid'] = f'{session_uuid}:{run_index}:{test_index}'
348+
rec.setdefault('result', 'pass')
349+
if rec['result'] != 'pass':
350+
num_failures += 1
351+
352+
if not merge_records:
353+
key = lineno
354+
elif len(merge_records) == 1:
355+
key = rec[merge_records[0]]
356+
else:
357+
key = tuple(rec[k] for k in merge_records)
358+
359+
if key in records:
360+
records[key] = _do_merge(records[key], rec)
361+
else:
362+
rec['perfvalues'] = {}
363+
records[key] = _do_merge(rec, rec)
364+
test_index += 1
365+
366+
testcases += list(records.values())
367+
368+
report.update_timestamps(t_report_start, t_report_end)
369+
report._add_run({
370+
'num_cases': len(testcases),
371+
'num_failures': num_failures,
372+
'run_index': run_index,
373+
'testcases': testcases
374+
})
375+
return report
376+
377+
def _add_run(self, run):
378+
self.__report['runs'].append(run)
379+
252380
def update_session_info(self, session_info):
253381
# Remove timestamps
254382
for key, val in session_info.items():

0 commit comments

Comments
 (0)