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 
38 using namespace android;
39 using namespace android::uirenderer;
40 using namespace android::uirenderer::test;
41 
42 static int gRepeatCount = 1;
43 static std::vector<TestScene::Info> gRunTests;
44 static TestScene::Options gOpts;
45 std::unique_ptr<benchmark::BenchmarkReporter> gBenchmarkReporter;
46 
47 void run(const TestScene::Info& info, const TestScene::Options& opts,
48          benchmark::BenchmarkReporter* reporter);
49 
printHelp()50 static void printHelp() {
51     printf(R"(
52 USAGE: hwuimacro [OPTIONS] <TESTNAME>
53 
54 OPTIONS:
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 
listTests()74 static 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 
moveToCpuSet(const char * cpusetName)105 static 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 
setBenchmarkFormat(const char * format)135 static 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 
setRenderer(const char * renderer)149 static 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)
165 namespace LongOpts {
166 enum {
167     Reserved = 255,
168     List,
169     WaitForGpu,
170     ReportFrametime,
171     CpuSet,
172     BenchmarkFormat,
173     Onscreen,
174     Offscreen,
175     Renderer,
176 };
177 }
178 
179 static 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 
193 static const char* SHORT_OPTIONS = "c:r:h";
194 
parseOptions(int argc,char * argv[])195 void 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 
main(int argc,char * argv[])327 int 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