|
13 | 13 | import os
|
14 | 14 | import re
|
15 | 15 | import socket
|
| 16 | +import sys |
16 | 17 | import time
|
17 | 18 | import uuid
|
18 | 19 | from collections import UserDict
|
19 | 20 | from collections.abc import Hashable
|
| 21 | +from datetime import datetime |
20 | 22 | from filelock import FileLock
|
21 | 23 |
|
22 | 24 | import reframe as rfm
|
|
26 | 28 | from reframe.core.logging import getlogger, _format_time_rfc3339, time_function
|
27 | 29 | from reframe.core.runtime import runtime
|
28 | 30 | 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 |
30 | 32 | from .storage import StorageBackend
|
31 | 33 | from .utility import Aggregator, parse_cmp_spec, parse_query_spec
|
32 | 34 |
|
@@ -270,6 +272,132 @@ def __getitem__(self, key):
|
270 | 272 | def __rfm_json_encode__(self):
|
271 | 273 | return self.__report
|
272 | 274 |
|
| 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 | + |
273 | 401 | def update_session_info(self, session_info):
|
274 | 402 | # Remove timestamps
|
275 | 403 | for key, val in session_info.items():
|
|
0 commit comments