1#    Copyright 2015-2017 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15
16"""Parse the results from a Workload Automation run and show it in a
17"pretty" table
18
19"""
20
21import os
22import collections, csv, re
23import pandas as pd
24from matplotlib import pyplot as plt
25
26class Result(pd.DataFrame):
27    """A DataFrame-like class for storing benchmark results"""
28    def __init__(self, *args, **kwargs):
29        super(Result, self).__init__(*args, **kwargs)
30        self.ax = None
31
32    def init_fig(self):
33        _, self.ax = plt.subplots()
34
35    def enlarge_axis(self, data):
36        """Make sure that the axis don't clobber some of the data"""
37
38        (_, _, plot_y_min, plot_y_max) = plt.axis()
39
40        concat_data = pd.concat(data[s] for s in data)
41        data_min = min(concat_data)
42        data_max = max(concat_data)
43
44        # A good margin can be 10% of the data range
45        margin = (data_max - data_min) / 10
46        if margin < 1:
47            margin = 1
48
49        update_axis = False
50
51        if data_min <= plot_y_min:
52            plot_y_min = data_min - margin
53            update_axis = True
54
55        if data_max >= plot_y_max:
56            plot_y_max = data_max + margin
57            update_axis = True
58
59        if update_axis:
60            self.ax.set_ylim(plot_y_min, plot_y_max)
61
62    def plot_results_benchmark(self, benchmark, title=None):
63        """Plot the results of the execution of a given benchmark
64
65        A title is added to the plot if title is not supplied
66        """
67
68        if title is None:
69            title = benchmark.replace('_', ' ')
70            title = title.title()
71
72        self[benchmark].plot(ax=self.ax, kind="bar", title=title)
73        plt.legend(bbox_to_anchor=(1.05, .5), loc=6)
74
75    def plot_results(self):
76        for bench in self.columns.levels[0]:
77            self.plot_results_benchmark(bench)
78
79def get_run_number(metric):
80    found = False
81    run_number = None
82
83    if re.match("Overall_Score|score|FPS", metric):
84        found = True
85
86        match = re.search(r"(.+)[ _](\d+)", metric)
87        if match:
88            run_number = int(match.group(2))
89            if match.group(1) == "Overall_Score":
90                run_number -= 1
91        else:
92            run_number = 0
93
94    return (found, run_number)
95
96def get_results(path=".", name=None):
97    """Return a pd.DataFrame with the results
98
99    The DataFrame's rows are the scores.  The first column is the
100    benchmark name and the second the id within it.  For benchmarks
101    that have a score result, that's what's used.  For benchmarks with
102    FPS_* result, that's the score.  E.g. glbenchmarks "score" is it's
103    fps.
104
105    An optional name argument can be passed.  If supplied, it overrides
106    the name in the results file.
107
108    """
109
110    bench_dict = collections.OrderedDict()
111
112    if os.path.isdir(path):
113        path = os.path.join(path, "results.csv")
114
115    with open(path) as fin:
116        results = csv.reader(fin)
117
118        for row in results:
119            (is_result, run_number) = get_run_number(row[3])
120
121            if is_result:
122                if name:
123                    run_id = name
124                else:
125                    run_id = re.sub(r"_\d+", r"", row[0])
126
127                bench = row[1]
128                try:
129                    result = int(row[4])
130                except ValueError:
131                    result = float(row[4])
132
133                if bench in bench_dict:
134                    if run_id in bench_dict[bench]:
135                        if run_number not in bench_dict[bench][run_id]:
136                            bench_dict[bench][run_id][run_number] = result
137                    else:
138                        bench_dict[bench][run_id] = {run_number: result}
139                else:
140                    bench_dict[bench] = {run_id: {run_number: result}}
141
142    bench_dfrs = {}
143    for bench, run_id_dict in bench_dict.iteritems():
144        bench_dfrs[bench] = pd.DataFrame(run_id_dict)
145
146    return Result(pd.concat(bench_dfrs.values(), axis=1,
147                            keys=bench_dfrs.keys()))
148
149def combine_results(data):
150    """Combine two DataFrame results into one
151
152    The data should be an array of results like the ones returned by
153    get_results() or have the same structure.  The returned DataFrame
154    has two column indexes.  The first one is the benchmark and the
155    second one is the key for the result.
156
157    """
158
159    res_dict = {}
160    for benchmark in data[0].columns.levels[0]:
161        concat_objs = [d[benchmark] for d in data]
162        res_dict[benchmark] = pd.concat(concat_objs, axis=1)
163
164    combined = pd.concat(res_dict.values(), axis=1, keys=res_dict.keys())
165
166    return Result(combined)
167