summaryrefslogtreecommitdiff
path: root/buildscripts/resmokelib/testing/hooks/combine_benchrun_embedded_results.py
blob: b70e57d874131312bb2d0e51535027a547a23421 (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
"""Module for generating the test results file fed into the perf plugin."""

from __future__ import absolute_import
from __future__ import division

import collections
import datetime
import glob
import json
import os
import re

from buildscripts.resmokelib import config as _config
from buildscripts.resmokelib.testing.hooks import combine_benchmark_results as cbr


class CombineBenchrunEmbeddedResults(cbr.CombineBenchmarkResults):
    """CombineBenchrunEmbeddedResults class.

    The CombineBenchrunEmbeddedResults hook combines test results from
    individual benchmark embedded files to a single file. This is useful for
    generating the json file to feed into the Evergreen performance
    visualization plugin.
    """

    DESCRIPTION = "Combine JSON results from embedded benchrun"

    def __init__(self, hook_logger, fixture):
        """Initialize CombineBenchrunEmbeddedResults."""
        cbr.CombineBenchmarkResults.__init__(self, hook_logger, fixture)
        self.report_root = _config.BENCHRUN_REPORT_ROOT

    def before_test(self, test, test_report):
        """Remove any existing mongoebench reports for this test."""
        for bm_report in self._test_result_files(test):
            os.remove(bm_report)

    def after_test(self, test, test_report):
        """Update test report."""
        for bm_report in self._test_result_files(test):
            test_name = test.short_name()
            thread_count = self._parse_report_name(bm_report)
            with open(bm_report, "r") as report_file:
                report_dict = json.load(report_file)
                if test_name not in self.benchmark_reports:
                    self.benchmark_reports[test_name] = _BenchrunEmbeddedThreadsReport()
                self.benchmark_reports[test_name].add_report(thread_count, report_dict)

    def before_suite(self, test_report):
        """Set suite start time."""
        self.create_time = datetime.datetime.now()
        # Remove any existing perf reports.
        if self.report_file and os.path.isfile(self.report_file):
            os.remove(self.report_file)

    def _generate_perf_plugin_report(self):
        """Format the data to look like a perf plugin report."""
        perf_report = {
            "start": self._strftime(self.create_time),
            "end": self._strftime(self.end_time),
            "errors": [],  # There are no errors if we have gotten this far.
            "results": []
        }

        for name, report in self.benchmark_reports.items():
            test_report = {"name": name, "results": report.generate_perf_plugin_dict()}

            perf_report["results"].append(test_report)

        return perf_report

    def _test_result_files(self, test):
        """Return a list of existing test result files based on the test.short_name()."""
        return glob.glob(
            os.path.join(self.report_root, test.short_name(), "**", "mongoebench[.]*[.]json"))

    def _parse_report_name(self, report_path):
        """Parse mongoebench report path and return thread_count.

        The format of the mongoebench report file name is defined in
        ../testing/testcases/benchrun_embedded_test.py
        as self.report_root/<test_name>/thread<num threads>/mongoebench.<iteration num>.json
        """
        _, report_subpath = report_path.split(self.report_root + os.sep)
        _, thread_name, _ = report_subpath.split(os.sep)
        return re.findall(r"\d+", thread_name)[0]


class _BenchrunEmbeddedThreadsReport(object):
    """_BenchrunEmbeddedThreadsReport class.

    Class representation of a report for all thread levels of a single
    benchmark test. Each report is designed to correspond to one graph
    in the Evergreen perf plugin.

    A raw mongoebench report looks like the following:
    {
        "note" : "values per second",
        "errCount" : { "$numberLong" : "0" },
        "trapped" : "error: not implemented",
        "insertLatencyAverageMicros" : 389.4926654182272,
        "totalOps" : { "$numberLong" : "12816" },
        "totalOps/s" : 2563.095938304905,
        "findOne" : 0,
        "insert" : 2563.095938304905,
        "delete" : 0,
        "update" : 0,
        "query" : 0,
        "command" : 0,
        "findOnes" : { "$numberLong" : "0" },
        "inserts" : { "$numberLong" : "12816" },
        "deletes" : { "$numberLong" : "0" },
        "updates" : { "$numberLong" : "0" },
        "queries" : { "$numberLong" : "0" },
        "commands" : { "$numberLong" : "0" }
    }
    """

    def __init__(self):
        # list of benchmark runs for each thread.
        self.thread_benchmark_map = collections.defaultdict(list)

    def add_report(self, thread_count, report):
        """Add to report."""
        self.thread_benchmark_map[thread_count].append(report)

    def generate_perf_plugin_dict(self):
        """Generate perf plugin data points of the following format.

        "1": {
          "error_values": [
            0,
            0,
            0
          ],
          "ops_per_sec": 9552.108279243452,
          "ops_per_sec_values": [
            9574.812658450564,
            9522.642340821469,
            9536.252775275878
          ]
        },
        """

        res = {}
        for thread_count, reports in self.thread_benchmark_map.items():
            thread_report = {"error_values": [], "ops_per_sec_values": []}

            for report in reports:
                thread_report["error_values"].append(report["errCount"]["$numberLong"])
                thread_report["ops_per_sec_values"].append(report["totalOps/s"])
            thread_report["ops_per_sec"] = sum(thread_report["ops_per_sec_values"]) / len(reports)

            res[thread_count] = thread_report

        return res