Ejemplo n.º 1
0
def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--add-note",
                        nargs=3,
                        help="Development only. --add-note N commit seed \
                        Adds N fake metrics to the given commit using the random seed."
                        )
    parser.add_argument("--ci", action='store_true',
                        help="Use ci results. You must fetch these with:\n    " \
                            + "$ git fetch https://gitlab.haskell.org/ghc/ghc-performance-notes.git refs/notes/perf:refs/notes/ci/perf")

    group = parser.add_argument_group(
        title='Filtering',
        description="Select which subset of performance metrics to dump")
    group.add_argument(
        "--test-env",
        help=
        "The given test environment to be compared. Use 'local' for locally run results. If using --ci, see .gitlab-ci file for TEST_ENV settings."
    )
    group.add_argument(
        "--test-name",
        help="Filters for tests matching the given regular expression.")
    group.add_argument("--metric",
                       help="Test metric (one of " + str(testing_metrics()) +
                       ").")
    group.add_argument("--way",
                       help="Test way (one of " + str(testing_metrics()) +
                       ").")

    group = parser.add_argument_group(
        title='Plotting', description="Plot historical performance metrics")
    group.add_argument(
        "--chart",
        nargs='?',
        default=None,
        action='store',
        const='./PerformanceChart.html',
        help=
        'Create a chart of the results an save it to the given file. Default to "./PerformanceChart.html".'
    )
    group.add_argument("--zero-y",
                       action='store_true',
                       help='When charting, include 0 in y axis')

    parser.add_argument(
        "commits",
        nargs='+',
        help=
        "Either a list of commits or a single commit range (e.g. HEAD~10..HEAD)."
    )
    args = parser.parse_args()

    env = 'local'
    name = re.compile('.*')
    CommitAndStat = NamedTuple('CommitAndStat', [('commit', GitHash),
                                                 ('stat', PerfStat)])
    metrics = []  # type: List[CommitAndStat]
    singleton_commit = len(args.commits) == 1

    #
    # Main logic of the program when called from the command-line.
    #

    ref = NoteNamespace('perf')
    if args.ci:
        ref = NoteNamespace('ci/perf')

    commits = args.commits
    if args.commits:
        # Commit range
        if len(commits) == 1 and ".." in commits[0]:
            commits = list(reversed(commit_log(commits[0])))
        for c in commits:
            metrics += [
                CommitAndStat(c, stat) for stat in get_perf_stats(c, ref)
            ]

    if args.metric:
        metrics = [test for test in metrics if test.stat.metric == args.metric]

    if args.way:
        metrics = [test for test in metrics if test.stat.way == args.way]

    if args.test_env:
        if '"' in args.test_env:
            raise Exception('test_env should not contain quotation marks')
        metrics = [
            test for test in metrics if test.stat.test_env == args.test_env
        ]

    if args.test_name:
        nameRe = re.compile(args.test_name)
        metrics = [test for test in metrics if nameRe.search(test.stat.test)]

    if args.add_note:

        def note_gen(n, commit, delta=''):
            note = []
            # Generates simple fake data. Likely not comprehensive enough to catch all edge cases.
            if not delta:
                note.extend([
                    PerfStat('local', 'T' + str(i * 100), 'some_way',
                             'some_field', str(i * 1000))
                    for i in range(1,
                                   int(int(n) / 2) + 1)
                ])
                note.extend([
                    PerfStat('non-local', 'W' + str(i * 100), 'other_way',
                             'other_field', str(i * 100))
                    for i in range(int(int(n) / 2) + 1,
                                   int(n) + 1)
                ])
            if delta:
                hu = abs(hash(delta))
                hv = abs(hash(hu))
                u = int(hu % 100)
                v = int(hv % 10)
                note.extend([
                    PerfStat('local', 'T' + str(i * 100), 'some_way',
                             'some_field', str(i * u))
                    for i in range(1,
                                   int(int(n) / 2) + 1)
                ])
                note.extend([
                    PerfStat('non-local', 'W' + str(i * 100), 'other_way',
                             'other_field', str(i * v))
                    for i in range(int(int(n) / 2) + 1,
                                   int(n) + 1)
                ])

            append_perf_stat(note, commit)

        note_gen(args.add_note[0], args.add_note[1], args.add_note[2])

    #
    # Chart
    #
    def metricAt(commit, testName, testMetric):
        values2 = [float(t.stat.value) for t in metrics if t.commit == commit \
                                                       and t.stat.test == testName \
                                                       and t.stat.metric == testMetric]
        if values2 == []:
            return None
        else:
            return (sum(values2) / len(values2))

    testSeries = list(
        set([(test.stat.test_env, test.stat.test, test.stat.metric,
              test.stat.way) for test in metrics]))

    #
    # Use Chart.js to visualize the data.
    #

    if args.chart:
        commitMsgs = dict([(h, get_commit_message(h)) for h in commits])
        chartData = {
                'type': 'line',
                'data': {
                    'labels': [commitMsgs[h].split("\n")[0] + " (" + \
                                    (h[:8] if is_commit_hash(h) else h) + \
                                ")" for h in commits],
                    'datasets': [{
                        'label': name + "(" + way + ") " + metric + " - " + env,
                        'data': [get_commit_metric_value_str_or_none(ref, commit, env, name, metric, way) \
                                        for commit in commits],

                        'fill': 'false',
                        'spanGaps': 'true',
                        'lineTension': 0,
                        'backgroundColor': hash_rgb_str((env, name, metric, way)),
                        'borderColor': hash_rgb_str((env, name, metric, way))
                    } for (env, name, metric, way) in testSeries]
                },
                'options': {
                    'scales': {
                        'yAxes': [{
                            'ticks': { 'beginAtZero': True }
                        }]
                    }
                }
            }

        # Try use local Chart.js file else use online version.
        tooltipjsFilePath = sys.path[0] + "/js/tooltip.js"
        chartjsFilePath = sys.path[0] + "/js/Chart-2.8.0.min.js"
        tooltipjsTag = None
        try:
            tooltipjsFile = open(tooltipjsFilePath, "r")
            tooltipjsTag = '<script>' + tooltipjsFile.read() + '</script>'
            tooltipjsFile.close()
        except:
            print("Failed to load custom tooltip: " + chartjsFilePath + ".")
            tooltipjsTag = None
        try:
            chartjsFile = open(chartjsFilePath, "r")
            chartjsTag = '<script>' + chartjsFile.read() + '</script>'
            chartjsFile.close()
        except:
            print("Failed to load " + chartjsFilePath +
                  ", reverting to online Chart.js.")
            chartjsTag = '<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>'

        file = open(args.chart, "w+t")
        print(\
            "<html>" + \
                '<head>\n' + \
                    (tooltipjsTag if tooltipjsTag is not None else '') + \
                    chartjsTag + \
                '</head>' + \
                '<body style="padding: 20px"><canvas id="myChart"></canvas><script>' + \
                    "var ctx = document.getElementById('myChart').getContext('2d');" + \
                    "var commitMsgs = " + json.dumps(commitMsgs, indent=2) + ";" + \
                    "var chartData = " + json.dumps(chartData, indent=2) + ";" + \
                    (("var chart = new Chart(ctx, setCustomTooltip(chartData, commitMsgs));") \
                        if tooltipjsTag is not None else \
                     ("var chart = new Chart(ctx, chartData);")) + \
                '</script></body>' + \
            "</html>"\
            , file=file)
        file.close()
        exit(0)

    #
    # Print the data in tablular format
    #

    #                  T1234                 T1234
    #              max_bytes             max_bytes
    #                 normal                normal
    # commit   x86_64-darwin       i386-linux-deb9
    # --------------------------------------------
    # HEAD              9123                  9123
    # HEAD~1           10023                 10023
    # HEAD~2           21234                 21234
    # HEAD~3           20000                 20000
    def strMetric(x):
        return '{:.2f}'.format(x.value) if x != None else ""

    # Data is in colum major format, so transpose and pass to print_table.
    T = TypeVar('T')

    def transpose(xss: List[List[T]]) -> List[List[T]]:
        return list(map(list, zip(*xss)))

    headerCols = [ ["","","","Commit"] ] \
                + [ [name, metric, way, env] for (env, name, metric, way) in testSeries ]
    dataCols = [ commits ] \
                + [ [strMetric(get_commit_metric(ref, commit, env, name, metric, way)) \
                        for commit in commits ] \
                        for (env, name, metric, way) in testSeries ]
    print_table(transpose(headerCols), transpose(dataCols))
Ejemplo n.º 2
0
        help=
        'Create a chart of the results an save it to the given file. Default to "./PerformanceChart.html".'
    )
    parser.add_argument("--ci", action='store_true',
                        help="Use ci results. You must fetch these with:\n    " \
                            + "$ git fetch https://gitlab.haskell.org/ghc/ghc-performance-notes.git refs/notes/perf:refs/notes/ci/perf")
    parser.add_argument(
        "--test-env",
        help=
        "The given test environment to be compared. Use 'local' for localy run results. If using --ci, see .gitlab-ci file for TEST_ENV settings."
    )
    parser.add_argument(
        "--test-name",
        help="Filters for tests matching the given regular expression.")
    parser.add_argument("--metric",
                        help="Test metric (one of " + str(testing_metrics()) +
                        ").")
    parser.add_argument("--way",
                        help="Test way (one of " + str(testing_metrics()) +
                        ").")
    parser.add_argument(
        "commits",
        nargs=argparse.REMAINDER,
        help=
        "Either a list of commits or a single commit range (e.g. HEAD~10..HEAD)."
    )
    args = parser.parse_args()

    env = 'local'
    name = re.compile('.*')
    # metrics is a tuple (str commit, PerfStat stat)