1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "tests/common/LeakChecker.h"
18#include "tests/common/TestScene.h"
19
20#include "Properties.h"
21#include "hwui/Typeface.h"
22#include "protos/hwui.pb.h"
23
24#include <benchmark/benchmark.h>
25#include <getopt.h>
26#include <pthread.h>
27#include <stdio.h>
28#include <unistd.h>
29#include <string>
30#include <unordered_map>
31#include <vector>
32
33#include <errno.h>
34#include <fcntl.h>
35#include <sys/stat.h>
36#include <sys/types.h>
37
38using namespace android;
39using namespace android::uirenderer;
40using namespace android::uirenderer::test;
41
42static int gRepeatCount = 1;
43static std::vector<TestScene::Info> gRunTests;
44static TestScene::Options gOpts;
45std::unique_ptr<benchmark::BenchmarkReporter> gBenchmarkReporter;
46
47void run(const TestScene::Info& info, const TestScene::Options& opts,
48         benchmark::BenchmarkReporter* reporter);
49
50static void printHelp() {
51    printf(R"(
52USAGE: hwuimacro [OPTIONS] <TESTNAME>
53
54OPTIONS:
55  -c, --count=NUM      NUM loops a test should run (example, number of frames)
56  -r, --runs=NUM       Repeat the test(s) NUM times
57  -h, --help           Display this help
58  --list               List all tests
59  --wait-for-gpu       Set this to wait for the GPU before producing the
60                       next frame. Note that without locked clocks this will
61                       pathologically bad performance due to large idle time
62  --report-frametime[=weight] If set, the test will print to stdout the
63                       moving average frametime. Weight is optional, default is 10
64  --cpuset=name        Adds the test to the specified cpuset before running
65                       Not supported on all devices and needs root
66  --offscreen          Render tests off device screen. This option is on by default
67  --onscreen           Render tests on device screen. By default tests
68                       are offscreen rendered
69  --benchmark_format   Set output format. Possible values are tabular, json, csv
70  --renderer=TYPE      Sets the render pipeline to use. May be opengl, skiagl, or skiavk
71)");
72}
73
74static void listTests() {
75    printf("Tests: \n");
76    for (auto&& test : TestScene::testMap()) {
77        auto&& info = test.second;
78        const char* col1 = info.name.c_str();
79        int dlen = info.description.length();
80        const char* col2 = info.description.c_str();
81        // World's best line breaking algorithm.
82        do {
83            int toPrint = dlen;
84            if (toPrint > 50) {
85                char* found = (char*)memrchr(col2, ' ', 50);
86                if (found) {
87                    toPrint = found - col2;
88                } else {
89                    toPrint = 50;
90                }
91            }
92            printf("%-20s %.*s\n", col1, toPrint, col2);
93            col1 = "";
94            col2 += toPrint;
95            dlen -= toPrint;
96            while (*col2 == ' ') {
97                col2++;
98                dlen--;
99            }
100        } while (dlen > 0);
101        printf("\n");
102    }
103}
104
105static void moveToCpuSet(const char* cpusetName) {
106    if (access("/dev/cpuset/tasks", F_OK)) {
107        fprintf(stderr, "don't have access to cpusets, skipping...\n");
108        return;
109    }
110    static const int BUF_SIZE = 100;
111    char buffer[BUF_SIZE];
112
113    if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
114        fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
115        return;
116    }
117    int fd = open(buffer, O_WRONLY | O_CLOEXEC);
118    if (fd == -1) {
119        fprintf(stderr, "Error opening file %d\n", errno);
120        return;
121    }
122    pid_t pid = getpid();
123
124    int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long)pid);
125    if (towrite >= BUF_SIZE) {
126        fprintf(stderr, "Buffer wasn't large enough?\n");
127    } else {
128        if (write(fd, buffer, towrite) != towrite) {
129            fprintf(stderr, "Failed to write, errno=%d", errno);
130        }
131    }
132    close(fd);
133}
134
135static bool setBenchmarkFormat(const char* format) {
136    if (!strcmp(format, "tabular")) {
137        gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
138    } else if (!strcmp(format, "json")) {
139        gBenchmarkReporter.reset(new benchmark::JSONReporter());
140    } else if (!strcmp(format, "csv")) {
141        gBenchmarkReporter.reset(new benchmark::CSVReporter());
142    } else {
143        fprintf(stderr, "Unknown format '%s'", format);
144        return false;
145    }
146    return true;
147}
148
149static bool setRenderer(const char* renderer) {
150    if (!strcmp(renderer, "opengl")) {
151        Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL);
152    } else if (!strcmp(renderer, "skiagl")) {
153        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
154    } else if (!strcmp(renderer, "skiavk")) {
155        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
156    } else {
157        fprintf(stderr, "Unknown format '%s'", renderer);
158        return false;
159    }
160    return true;
161}
162
163// For options that only exist in long-form. Anything in the
164// 0-255 range is reserved for short options (which just use their ASCII value)
165namespace LongOpts {
166enum {
167    Reserved = 255,
168    List,
169    WaitForGpu,
170    ReportFrametime,
171    CpuSet,
172    BenchmarkFormat,
173    Onscreen,
174    Offscreen,
175    Renderer,
176};
177}
178
179static const struct option LONG_OPTIONS[] = {
180        {"frames", required_argument, nullptr, 'f'},
181        {"repeat", required_argument, nullptr, 'r'},
182        {"help", no_argument, nullptr, 'h'},
183        {"list", no_argument, nullptr, LongOpts::List},
184        {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
185        {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
186        {"cpuset", required_argument, nullptr, LongOpts::CpuSet},
187        {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
188        {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
189        {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
190        {"renderer", required_argument, nullptr, LongOpts::Renderer},
191        {0, 0, 0, 0}};
192
193static const char* SHORT_OPTIONS = "c:r:h";
194
195void parseOptions(int argc, char* argv[]) {
196    int c;
197    bool error = false;
198    opterr = 0;
199
200    while (true) {
201        /* getopt_long stores the option index here. */
202        int option_index = 0;
203
204        c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index);
205
206        if (c == -1) break;
207
208        switch (c) {
209            case 0:
210                // Option set a flag, don't need to do anything
211                // (although none of the current LONG_OPTIONS do this...)
212                break;
213
214            case LongOpts::List:
215                listTests();
216                exit(EXIT_SUCCESS);
217                break;
218
219            case 'c':
220                gOpts.count = atoi(optarg);
221                if (!gOpts.count) {
222                    fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
223                    error = true;
224                }
225                break;
226
227            case 'r':
228                gRepeatCount = atoi(optarg);
229                if (!gRepeatCount) {
230                    fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
231                    error = true;
232                } else {
233                    gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX);
234                }
235                break;
236
237            case LongOpts::ReportFrametime:
238                if (optarg) {
239                    gOpts.reportFrametimeWeight = atoi(optarg);
240                    if (!gOpts.reportFrametimeWeight) {
241                        fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg);
242                        error = true;
243                    }
244                } else {
245                    gOpts.reportFrametimeWeight = 10;
246                }
247                break;
248
249            case LongOpts::WaitForGpu:
250                Properties::waitForGpuCompletion = true;
251                break;
252
253            case LongOpts::CpuSet:
254                if (!optarg) {
255                    error = true;
256                    break;
257                }
258                moveToCpuSet(optarg);
259                break;
260
261            case LongOpts::BenchmarkFormat:
262                if (!optarg) {
263                    error = true;
264                    break;
265                }
266                if (!setBenchmarkFormat(optarg)) {
267                    error = true;
268                }
269                break;
270
271            case LongOpts::Renderer:
272                if (!optarg) {
273                    error = true;
274                    break;
275                }
276                if (!setRenderer(optarg)) {
277                    error = true;
278                }
279                break;
280
281            case LongOpts::Onscreen:
282                gOpts.renderOffscreen = false;
283                break;
284
285            case LongOpts::Offscreen:
286                gOpts.renderOffscreen = true;
287                break;
288
289            case 'h':
290                printHelp();
291                exit(EXIT_SUCCESS);
292                break;
293
294            case '?':
295                fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
296            // fall-through
297            default:
298                error = true;
299                break;
300        }
301    }
302
303    if (error) {
304        fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
305        exit(EXIT_FAILURE);
306    }
307
308    /* Print any remaining command line arguments (not options). */
309    if (optind < argc) {
310        do {
311            const char* test = argv[optind++];
312            auto pos = TestScene::testMap().find(test);
313            if (pos == TestScene::testMap().end()) {
314                fprintf(stderr, "Unknown test '%s'\n", test);
315                exit(EXIT_FAILURE);
316            } else {
317                gRunTests.push_back(pos->second);
318            }
319        } while (optind < argc);
320    } else {
321        for (auto& iter : TestScene::testMap()) {
322            gRunTests.push_back(iter.second);
323        }
324    }
325}
326
327int main(int argc, char* argv[]) {
328    // set defaults
329    gOpts.count = 150;
330
331    Typeface::setRobotoTypefaceForTest();
332
333    parseOptions(argc, argv);
334    if (!gBenchmarkReporter && gOpts.renderOffscreen) {
335        gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
336    }
337
338    if (gBenchmarkReporter) {
339        size_t name_field_width = 10;
340        for (auto&& test : gRunTests) {
341            name_field_width = std::max<size_t>(name_field_width, test.name.size());
342        }
343        // _50th, _90th, etc...
344        name_field_width += 5;
345
346        benchmark::BenchmarkReporter::Context context;
347        context.name_field_width = name_field_width;
348        gBenchmarkReporter->ReportContext(context);
349    }
350
351    for (int i = 0; i < gRepeatCount; i++) {
352        for (auto&& test : gRunTests) {
353            run(test, gOpts, gBenchmarkReporter.get());
354        }
355    }
356
357    if (gBenchmarkReporter) {
358        gBenchmarkReporter->Finalize();
359    }
360
361    LeakChecker::checkForLeaks();
362    return 0;
363}
364