Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/scripts/performance/perfcmp
1566 views
#!/usr/bin/env python
"""Compare 2 perf runs.

To use, specify the local directories that contain
the run information::

    $ ./perfcmp /results/2016-01-01-1111/ /results/2016-01-01-2222/

"""
import os
import json
import argparse

from colorama import Fore, Style
from tabulate import tabulate


class RunComparison(object):

    MEMORY_FIELDS = ['average_memory', 'max_memory']
    TIME_FIELDS = ['total_time']
    # Fields that aren't memory or time fields, they require
    # no special formatting.
    OTHER_FIELDS = ['average_cpu']

    def __init__(self, old_summary, new_summary):
        self.old_summary = old_summary
        self.new_summary = new_summary

    def iter_field_names(self):
        for field in self.TIME_FIELDS + self.MEMORY_FIELDS + self.OTHER_FIELDS:
            yield field

    def old(self, field):
        value = self.old_summary[field]
        return self._format(field, value)

    def old_suffix(self, field):
        value = self.old_summary[field]
        return self._format_suffix(field, value)

    def new_suffix(self, field):
        value = self.new_summary[field]
        return self._format_suffix(field, value)

    def _format_suffix(self, field, value):
        if field in self.TIME_FIELDS:
            return 'sec'
        elif field in self.OTHER_FIELDS:
            return ''
        else:
            # The suffix depends on the actual value.
            return self._human_readable_size(value)[1]

    def old_stddev(self, field):
        real_field = 'std_dev_%s' % field
        return self.old(real_field)

    def new(self, field):
        value = self.new_summary[field]
        return self._format(field, value)

    def new_stddev(self, field):
        real_field = 'std_dev_%s' % field
        return self.new(real_field)

    def _format(self, field, value):
        if field.startswith('std_dev_'):
            field = field[len('std_dev_'):]
        if field in self.MEMORY_FIELDS:
            return self._human_readable_size(value)[0]
        elif field in self.TIME_FIELDS:
            return '%-3.2f' % value
        else:
            return '%.2f' % value

    def _human_readable_size(self, value):
        hummanize_suffixes = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB')
        base = 1024
        bytes_int = float(value)

        if bytes_int == 1:
            return '1 Byte'
        elif bytes_int < base:
            return '%d Bytes' % bytes_int

        for i, suffix in enumerate(hummanize_suffixes):
            unit = base ** (i+2)
            if round((bytes_int / unit) * base) < base:
                return ['%.2f' % (base * bytes_int / unit), suffix]

    def diff_percent(self, field):
        diff_percent = (
            (self.new_summary[field] - self.old_summary[field]) /
            float(self.old_summary[field])) * 100
        return diff_percent


def compare_runs(old_dir, new_dir):
    for dirname in os.listdir(old_dir):
        old_run_dir = os.path.join(old_dir, dirname)
        new_run_dir = os.path.join(new_dir, dirname)
        if not os.path.isdir(old_run_dir):
            continue
        old_summary = get_summary(old_run_dir)
        new_summary = get_summary(new_run_dir)
        comp = RunComparison(old_summary, new_summary)
        header = [Style.BRIGHT + dirname + Style.RESET_ALL,
                  Style.BRIGHT + 'old' + Style.RESET_ALL,
                  # Numeric suffix (MiB, GiB, sec).
                  '',
                  'std_dev',
                  Style.BRIGHT + 'new' + Style.RESET_ALL,
                  # Numeric suffix (MiB, GiB, sec).
                  '',
                  'std_dev',
                  Style.BRIGHT + 'delta' + Style.RESET_ALL]
        rows = []
        for field in comp.iter_field_names():
            row = [field, comp.old(field), comp.old_suffix(field),
                   comp.old_stddev(field), comp.new(field),
                   comp.new_suffix(field), comp.new_stddev(field)]
            diff_percent = comp.diff_percent(field)
            diff_percent_str = '%.2f%%' % diff_percent
            if diff_percent < 0:
                diff_percent_str = (
                    Fore.GREEN + diff_percent_str + Style.RESET_ALL)
            else:
                diff_percent_str = (
                    Fore.RED + diff_percent_str + Style.RESET_ALL)
            row.append(diff_percent_str)
            rows.append(row)
        print(tabulate(rows, headers=header, tablefmt='plain'))
        print('')


def get_summary(benchmark_dir):
    summary_json = os.path.join(benchmark_dir, 'summary.json')
    with open(summary_json) as f:
        return json.load(f)


def main():
    parser = argparse.ArgumentParser(description='__doc__')
    parser.add_argument('oldrunid', help='Path to old run idir')
    parser.add_argument('newrunid', help='Local to new run dir')
    args = parser.parse_args()
    compare_runs(args.oldrunid, args.newrunid)


if __name__ == '__main__':
    main()