Skip to content
Snippets Groups Projects
collect_benchmarks.py 5.97 KiB
Newer Older
#!/usr/bin/env python3

"""
Combine the json files from the individual benchmark tests into
a final master json file combining all benchmarks.

Benchmark results are expected to be all json files in the results
directory.
"""

## Our master definition file, the benchmark project directory
MASTER_FILE=r'benchmarks/benchmarks.json'

## Our results directory
RESULTS_PATH=r'results'

## Output json file with all benchmark results
OUTPUT_FILE=r'results/summary.json'

import argparse
import json
from pathlib import Path

## Exceptions for this module
class Error(Exception):
    '''Base class for exceptions in this module.'''
    pass
class FileNotFoundError(Error):
    '''File does not exist.

    Attributes:
        file: the file name
        message: error message
    '''
    def __init__(self, file):
        self.file = file
        self.message = 'No such file or directory: {}'.format(file)

class InvalidDefinitionError(Error):
    '''Raised for missing keys in the definitions.

    Attributes:
        key: the missing key
        file: the definition file
        message: error message
    '''
    def __init__(self, key, file):
        self.key = key
        self.file = file
        self.message = "key '{}' not found in '{}'".format(key, file)

class InvalidResultError(Error):
    '''Raised for invalid benchmark result value.

    Attributes:
        key: the missing key
        value: the invalid value
        file: the benchmark definition file
        message: error message
    '''
    def __init__(self, key, value, file):
        self.key = key
        self.value = value
        self.file = file
        self.message = "value '{}' for key '{}' invalid in benchmark file '{}'".format(
                value, key, file)
    
def collect_benchmarks():
    '''Collect all benchmark results and write results to a single file.'''
    print("Collecting all benchmark results")

    ## load the test definition for this benchmark
    results = _load_master()

    ## collect the test results
    results['benchmarks'] = _load_benchmarks()
    
    ## calculate aggregate test statistics
    results = _aggregate_results(results)

    ## save results to output file
    _save(results)

    ## Summarize results
    for bm in results['benchmarks']:
        _print_benchmark(bm)
    _print_summary(results)

def _load_master():
    '''Load master definition.'''
    master_file = Path(MASTER_FILE)
    if not master_file.exists():
        raise FileNotFoundError(master_file)
    print('  --> Loading master definition from:', master_file)
    results = None
    with master_file.open() as f:
        results = json.load(f)
    ## ensure this is a valid benchmark file
    for key in ('name', 'title', 'description'):
        if not key in results:
            raise InvalidDefinitionError('target', master_file)
    return results

def _load_benchmarks():
    '''Load all benchmark results from the results folder.'''
    print('  --> Collecting all benchmarks')
    rootdir = Path(RESULTS_PATH)
    results = []
    for file in rootdir.glob('*.json'):
        print('    --> Loading file:', file, '... ', end='')
        with open(file) as f:
            bm = json.load(f)
            ## skip files that don't include test results
            if not 'tests' in bm:
                print('skipped (does not contain benchmark results).')
                continue
            ## check if these are valid benchmark results,
            ## raise exception otherwise
            for key in ('name', 'title', 'description', 'target', 'n_tests',
                    'n_pass', 'n_fail', 'n_error', 'maximum', 'sum', 'value',
                    'result'):
                if not key in bm:
                    raise InvalidDefinitionError(key, file)
            if bm['result'] not in ('pass', 'fail', 'error'):
                raise InvalidResultError('result', bm['result'], file)
            ## Append to our test results
            results.append(bm)
            print('done')
    return results

def _aggregate_results(results):
    '''Aggregate benchmark results.'''
    print('  --> Aggregating benchmark statistics')
    results['n_benchmarks'] = len(results['benchmarks'])
    results['n_pass'] = len([1 for t in results['benchmarks'] if t['result'] == 'pass'])
    results['n_fail'] = len([1 for t in results['benchmarks'] if t['result'] == 'fail'])
    results['n_error'] = len([1 for t in results['benchmarks'] if t['result'] == 'error'])
    if results['n_error'] > 0:
        results['result'] = 'error'
    elif results['n_fail'] == 0:
        results['result'] = 'pass'
    else:
        results['result'] = 'fail'
    return results

def _save(results):
    '''Save aggregated benchmark results'''
    ofile = Path(OUTPUT_FILE)
    print('  --> Saving results to:', ofile)
    with ofile.open('w') as f:
        json.dump(results, f, indent=4)

def _print_benchmark(bm):
    '''Print benchmark summary to the terminal.'''
    print('====================================================================')
    print('  Summary for:', bm['title'])
    print('  Pass: {}, Fail: {}, Error: {} out of {} total tests'.format(
        bm['n_pass'], bm['n_fail'], bm['n_error'],
        bm['n_tests']))
    print('  Weighted sum: {} / {}'.format(bm['sum'], bm['maximum']))
    print('  kBenchmark value: {} (target: {})'.format(
        bm['value'], bm['target']))
    print('  ===> status:', bm['result'])

def _print_summary(results):
    '''Print master benchmark summary to the terminal.'''
    print('====================================================================')
    print('MASTER BENCHMARK SUMMARY FOR:', results['title'].upper())
    print('Pass: {}, Fail: {}, Error: {} out of {} total benchmarks'.format(
        results['n_pass'], results['n_fail'], results['n_error'],
        results['n_benchmarks']))
    print('===> status:', results['result'])
    print('====================================================================')


if __name__ == "__main__":
    try:
        collect_benchmarks()
    except Error as e:
        print()
        print('ERROR', e.message)