Skip to content

Commit 92aa97c

Browse files
committed
WIP: Import results from perflogs
1 parent 2a41433 commit 92aa97c

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
@@ -464,6 +465,10 @@ def main():
464465
action_options.add_argument(
465466
'-V', '--version', action='version', version=osext.reframe_version()
466467
)
468+
action_options.add_argument(
469+
'--import-results', action='store', metavar='SPECFILE',
470+
help='Import results to the database'
471+
)
467472

468473
# Run options
469474
run_options.add_argument(
@@ -809,6 +814,7 @@ def main():
809814
action='store_true',
810815
help='Use a login shell for job scripts'
811816
)
817+
argparser.add_argument('args', metavar='ARGS', nargs='*', positional=True)
812818

813819
def restrict_logging():
814820
'''Restrict logging to errors only.
@@ -1034,6 +1040,21 @@ def restrict_logging():
10341040
)
10351041
sys.exit(0)
10361042

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

reframe/frontend/reporting/__init__.py

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

2224
import reframe as rfm
@@ -26,7 +28,7 @@
2628
from reframe.core.logging import getlogger, _format_time_rfc3339, time_function
2729
from reframe.core.runtime import runtime
2830
from reframe.core.warnings import suppress_deprecations
29-
from reframe.utility import nodelist_abbrev, OrderedSet
31+
from reframe.utility import nodelist_abbrev, nodelist_expand, OrderedSet
3032
from .storage import StorageBackend
3133
from .utility import Aggregator, parse_cmp_spec, parse_query_spec
3234

@@ -270,6 +272,132 @@ def __getitem__(self, key):
270272
def __rfm_json_encode__(self):
271273
return self.__report
272274

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

0 commit comments

Comments
 (0)