1/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 *
7 * Classes for writing out bench results in various formats.
8 */
9
10#ifndef SkPictureResultsWriter_DEFINED
11#define SkPictureResultsWriter_DEFINED
12
13
14#include "PictureRenderer.h"
15#include "BenchLogger.h"
16#include "ResultsWriter.h"
17#include "SkJSONCPP.h"
18#include "SkStream.h"
19#include "SkString.h"
20#include "SkTArray.h"
21#include "TimerData.h"
22
23/**
24 * Base class for writing picture bench results.
25 */
26class PictureResultsWriter : SkNoncopyable {
27public:
28    enum TileFlags {kPurging, kAvg};
29
30    PictureResultsWriter() {}
31    virtual ~PictureResultsWriter() {}
32
33    virtual void bench(const char name[], int32_t x, int32_t y) = 0;
34    virtual void logRenderer(sk_tools::PictureRenderer *pr) = 0;
35    virtual void tileMeta(int x, int y, int tx, int ty) = 0;
36    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) = 0;
37    virtual void tileData(
38            TimerData* data,
39            const char format[],
40            const TimerData::Result result,
41            uint32_t timerTypes,
42            int numInnerLoops = 1) = 0;
43   virtual void end() = 0;
44};
45
46/**
47 * This class allows bench data to be piped into multiple
48 * PictureResultWriter classes. It does not own any classes
49 * passed to it, so the owner is required to manage any classes
50 * passed to PictureResultsMultiWriter */
51class PictureResultsMultiWriter : public PictureResultsWriter {
52public:
53    PictureResultsMultiWriter()
54        : fWriters() {}
55    void add(PictureResultsWriter* newWriter) {
56        fWriters.push_back(newWriter);
57    }
58    virtual ~PictureResultsMultiWriter() {}
59    virtual void bench(const char name[], int32_t x, int32_t y) SK_OVERRIDE {
60        for(int i=0; i<fWriters.count(); ++i) {
61            fWriters[i]->bench(name, x, y);
62        }
63    }
64    virtual void logRenderer(sk_tools::PictureRenderer *pr) SK_OVERRIDE {
65        for(int i=0; i<fWriters.count(); ++i) {
66            fWriters[i]->logRenderer(pr);
67        }
68    }
69    virtual void tileMeta(int x, int y, int tx, int ty) SK_OVERRIDE {
70        for(int i=0; i<fWriters.count(); ++i) {
71            fWriters[i]->tileMeta(x, y, tx, ty);
72        }
73    }
74    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) SK_OVERRIDE {
75        for(int i=0; i<fWriters.count(); ++i) {
76            fWriters[i]->addTileFlag(flag);
77        }
78    }
79    virtual void tileData(
80            TimerData* data,
81            const char format[],
82            const TimerData::Result result,
83            uint32_t timerTypes,
84            int numInnerLoops = 1) SK_OVERRIDE {
85        for(int i=0; i<fWriters.count(); ++i) {
86            fWriters[i]->tileData(data, format, result, timerTypes,
87                                 numInnerLoops);
88        }
89    }
90   virtual void end() SK_OVERRIDE {
91        for(int i=0; i<fWriters.count(); ++i) {
92            fWriters[i]->end();
93        }
94   }
95private:
96    SkTArray<PictureResultsWriter*> fWriters;
97};
98
99/**
100 * Writes to BenchLogger to mimic original behavior
101 */
102class PictureResultsLoggerWriter : public PictureResultsWriter {
103private:
104    void logProgress(const char str[]) {
105        if(fLogger != NULL) {
106            fLogger->logProgress(str);
107        }
108    }
109public:
110    PictureResultsLoggerWriter(BenchLogger* log)
111          : fLogger(log), fCurrentLine() {}
112    virtual void bench(const char name[], int32_t x, int32_t y) SK_OVERRIDE {
113        SkString result;
114        result.printf("running bench [%i %i] %s ", x, y, name);
115        this->logProgress(result.c_str());
116    }
117    virtual void logRenderer(sk_tools::PictureRenderer* renderer) SK_OVERRIDE {
118        fCurrentLine = renderer->getConfigName();
119    }
120    virtual void tileMeta(int x, int y, int tx, int ty) SK_OVERRIDE {
121        fCurrentLine.appendf(": tile [%i,%i] out of [%i,%i]", x, y, tx, ty);
122    }
123    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) SK_OVERRIDE {
124        if(flag == PictureResultsWriter::kPurging) {
125            fCurrentLine.append(" <withPurging>");
126        } else if(flag == PictureResultsWriter::kAvg) {
127            fCurrentLine.append(" <averaged>");
128        }
129    }
130    virtual void tileData(
131            TimerData* data,
132            const char format[],
133            const TimerData::Result result,
134            uint32_t timerTypes,
135            int numInnerLoops = 1) SK_OVERRIDE {
136        SkString results = data->getResult(format, result,
137                fCurrentLine.c_str(), timerTypes, numInnerLoops);
138        results.append("\n");
139        this->logProgress(results.c_str());
140    }
141    virtual void end() {}
142private:
143    BenchLogger* fLogger;
144    SkString fCurrentLine;
145};
146
147/**
148 * This PictureResultsWriter collects data in a JSON node
149 *
150 * The format is something like
151 * {
152 *      benches: [
153 *          {
154 *              name: "Name_of_test"
155 *              tilesets: [
156 *                  {
157 *                      name: "Name of the configuration"
158 *                      tiles: [
159 *                          {
160 *                              flags: {
161 *                                  purging: true //Flags for the current tile
162 *                                              // are put here
163 *                              }
164 *                              data: {
165 *                                  wsecs: [....] //Actual data ends up here
166 *                              }
167 *                          }
168 *                      ]
169 *                  }
170 *              ]
171 *          }
172 *      ]
173 * }*/
174
175class PictureJSONResultsWriter : public PictureResultsWriter {
176public:
177    PictureJSONResultsWriter(const char filename[],
178                             const char builderName[],
179                             int buildNumber,
180                             int timestamp,
181                             const char gitHash[],
182                             int gitNumber)
183        : fStream(filename) {
184        fBuilderName = SkString(builderName);
185        fBuildNumber = buildNumber;
186        fTimestamp = timestamp;
187        fGitHash = SkString(gitHash);
188        fGitNumber = gitNumber;
189        fBuilderData = this->makeBuilderJson();
190    }
191
192    virtual void bench(const char name[], int32_t x, int32_t y) SK_OVERRIDE {
193        fBenchName = SkString(name);
194    }
195    virtual void logRenderer(sk_tools::PictureRenderer* pr) SK_OVERRIDE {
196        fParams = pr->getJSONConfig();
197        fConfigString = pr->getConfigName();
198    }
199    // Apparently tiles aren't used, so tileMeta is empty
200    virtual void tileMeta(int x, int y, int tx, int ty) SK_OVERRIDE {}
201    // Flags aren't used, so addTileFlag is empty
202    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) SK_OVERRIDE {}
203    virtual void tileData(
204            TimerData* data,
205            const char format[],
206            const TimerData::Result result,
207            uint32_t timerTypes,
208            int numInnerLoops = 1) SK_OVERRIDE {
209        Json::Value newData = data->getJSON(timerTypes, result, numInnerLoops);
210        Json::Value combinedParams(fBuilderData);
211        for(Json::ValueIterator iter = fParams.begin(); iter != fParams.end();
212                iter++) {
213            combinedParams[iter.key().asString()]= *iter;
214        }
215        // For each set of timer data
216        for(Json::ValueIterator iter = newData.begin(); iter != newData.end();
217                iter++) {
218            Json::Value data;
219            data["buildNumber"] = fBuildNumber;
220            data["timestamp"] = fTimestamp;
221            data["gitHash"] = fGitHash.c_str();
222            data["gitNumber"] = fGitNumber;
223            data["isTrybot"] = fBuilderName.endsWith("Trybot");
224
225            data["params"] = combinedParams;
226            data["params"]["benchName"] = fBenchName.c_str();
227
228            // Not including skpSize because that's deprecated?
229            data["key"] = this->makeKey(iter.key().asString().c_str()).c_str();
230            // Get the data
231            SkTArray<double> times;
232            Json::Value val = *iter;
233            for(Json::ValueIterator vals = val.begin(); vals != val.end();
234                    vals++) {
235                times.push_back((*vals).asDouble());
236            }
237            qsort(static_cast<void*>(times.begin()), times.count(),
238                    sizeof(double), PictureJSONResultsWriter::CompareDoubles);
239            data["value"] = times[static_cast<int>(times.count() * 0.25f)];
240            data["params"]["measurementType"] = iter.key().asString();
241            fStream.writeText(Json::FastWriter().write(data).c_str());
242        }
243    }
244    virtual void end() SK_OVERRIDE {
245       fStream.flush();
246    }
247private:
248    Json::Value makeBuilderJson() const {
249        static const int kNumKeys = 6;
250        static const char* kKeys[kNumKeys] = {
251            "role", "os", "model", "gpu", "arch", "configuration"};
252        Json::Value builderData;
253
254        if (!fBuilderName.isEmpty()) {
255            SkTArray<SkString> splitBuilder;
256            SkStrSplit(fBuilderName.c_str(), "-", &splitBuilder);
257            SkASSERT(splitBuilder.count() >= kNumKeys);
258            for (int i = 0; i < kNumKeys && i < splitBuilder.count(); ++i) {
259                builderData[kKeys[i]] = splitBuilder[i].c_str();
260            }
261            builderData["builderName"] = fBuilderName.c_str();
262            if (kNumKeys < splitBuilder.count()) {
263                SkString extras;
264                for (int i = kNumKeys; i < splitBuilder.count(); ++i) {
265                    extras.append(splitBuilder[i]);
266                    if (i != splitBuilder.count() - 1) {
267                        extras.append("-");
268                    }
269                }
270                builderData["badParams"] = extras.c_str();
271            }
272        }
273        return builderData;
274    }
275
276    static int CompareDoubles(const void* p1, const void* p2) {
277        if(*static_cast<const double*>(p1) < *static_cast<const double*>(p2)) {
278            return -1;
279        } else if(*static_cast<const double*>(p1) ==
280                *static_cast<const double*>(p2)) {
281            return 0;
282        } else {
283            return 1;
284        }
285    }
286    SkString makeKey(const char measurementType[]) const {
287        SkString tmp(fBuilderName);
288        tmp.append("_");
289        tmp.append(fBenchName);
290        tmp.append("_");
291        tmp.append(fConfigString);
292        tmp.append("_");
293        tmp.append(measurementType);
294        return tmp;
295    }
296
297    SkFILEWStream   fStream;
298    Json::Value     fBuilderData;
299    SkString        fBenchName;
300    Json::Value     fParams;
301
302    SkString        fConfigString;
303    SkString        fBuilderName;
304    int             fBuildNumber;
305    int             fTimestamp;
306    SkString        fGitHash;
307    int             fGitNumber;
308};
309
310#endif
311