summaryrefslogtreecommitdiffstats
path: root/tools/pcmk_simtimes.in
blob: c8b0af60f58763bc6e24f7ddd7ac92bc8ea4b06f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!@PYTHON@
""" Timing comparisons for crm_simulate profiling output
"""

__copyright__ = "Copyright 2019-2023 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"

import io
import re
import sys
import errno
import argparse
import os

# These imports allow running from a source checkout after running `make`.
# Note that while this doesn't necessarily mean it will successfully run tests,
# but being able to see --help output can be useful.
if os.path.exists("@abs_top_srcdir@/python"):
    sys.path.insert(0, "@abs_top_srcdir@/python")

if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
    sys.path.insert(0, "@abs_top_builddir@/python")

from pacemaker.exitstatus import ExitStatus

DESC = """Compare timings from crm_simulate profiling output"""

BEFORE_HELP = """Output of "crm_simulate --profile cts/scheduler --repeat <N>" from earlier Pacemaker build"""

# line like: * Testing cts/scheduler/xml/1360.xml ... 0.07 secs
PATTERN = r"""^\s*\*\s+Testing\s+.*/([^/]+)\.xml\s+\.+\s+([.0-9]+)\s+secs\s*$"""

def parse_args(argv=sys.argv):
    """ Parse command-line arguments """

    parser = argparse.ArgumentParser(description=DESC)

    parser.add_argument('-V', '--verbose', action='count',
                        help='Increase verbosity')

    parser.add_argument('-p', '--threshold-percent', type=float, default=0,
                        help="Don't show tests with less than this percentage difference in times")

    parser.add_argument('-s', '--threshold-seconds', type=float, default=0,
                        help="Don't show tests with less than this seconds difference in times")

    parser.add_argument('-S', '--sort', choices=['test', 'before', 'after', 'diff', 'percent'],
                        default='test', help="Sort results by this column")

    parser.add_argument('-r', '--reverse', action='store_true',
                        help="Sort results in descending order")

    parser.add_argument('before_file', metavar='BEFORE',
                        type=argparse.FileType('r'),
                        help=BEFORE_HELP)

    parser.add_argument('after_file', metavar='AFTER',
                        type=argparse.FileType('r'),
                        help='Output of same command from later Pacemaker build')

    return parser.parse_args(argv[1:])


def extract_times(infile):
    """ Extract test names and times into hash table from file """

    result = {}
    for line in infile:
        match = re.search(PATTERN, line)
        if match is not None:
            result[match.group(1)] = match.group(2)
    return result


def compare_test(test, before, after, args):
    """ Compare one test's timings """

    try:
        before_time = float(before[test])
    except KeyError:
        if args.verbose > 0:
            print("No previous test " + test + " to compare")
        return None

    after_time = float(after[test])

    time_diff = after_time - before_time
    time_diff_percent = (time_diff / before_time) * 100

    if ((abs(time_diff) >= args.threshold_seconds)
        and (abs(time_diff_percent) >= args.threshold_percent)):
        return { 'test': test,
                 'before': before_time,
                 'after': after_time,
                 'diff': time_diff,
                 'percent': time_diff_percent
               }
    return None

def sort_diff(result):
    """ Sort two test results by time difference """

    global sort_field

    return result[sort_field]


def print_results(results, sort_reverse):
    """ Output the comparison results """

    if results == []:
        return

    # Sort and print test differences
    results.sort(reverse=sort_reverse, key=sort_diff)
    for result in results:
        print("%-40s %6.2fs vs %6.2fs (%+.2fs = %+6.2f%%)" % (result['test'],
              result['before'], result['after'], result['diff'],
              result['percent']))

    # Print average differences
    diff_total = sum(d['diff'] for d in results)
    percent_total = sum(d['percent'] for d in results)
    nresults = len(results)
    print("\nAverages: %+.2fs %+6.2f%%" % ((diff_total / nresults),
          (percent_total / nresults)))


if __name__ == "__main__":

    global sort_field

    try:
        args = parse_args()
        if args.verbose is None:
            args.verbose = 0

        before = extract_times(args.before_file)
        after = extract_times(args.after_file)
        sort_field = args.sort

        # Build a list of test differences
        results = []
        for test in after.keys():
            result = compare_test(test, before, after, args)
            if result is not None:
                results = results + [ result ]

        print_results(results, sort_reverse=args.reverse)

    except KeyboardInterrupt:
        pass
    except IOError as e:
        if e.errno != errno.EPIPE:
            raise

    sys.exit(ExitStatus.OK)

# vim: set filetype=python expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=120: