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 "HardwareBitmapUploader.h"
23 #include "renderthread/RenderProxy.h"
24 
25 #include <benchmark/benchmark.h>
26 #include <getopt.h>
27 #include <pthread.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string>
31 #include <unordered_map>
32 #include <vector>
33 
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 
39 using namespace android;
40 using namespace android::uirenderer;
41 using namespace android::uirenderer::test;
42 
43 static int gRepeatCount = 1;
44 static std::vector<TestScene::Info> gRunTests;
45 static TestScene::Options gOpts;
46 std::unique_ptr<benchmark::BenchmarkReporter> gBenchmarkReporter;
47 
48 void run(const TestScene::Info& info, const TestScene::Options& opts,
49          benchmark::BenchmarkReporter* reporter);
50 
printHelp()51 static void printHelp() {
52     printf(R"(
53 USAGE: hwuimacro [OPTIONS] <TESTNAME>
54 
55 OPTIONS:
56   -c, --count=NUM      NUM loops a test should run (example, number of frames)
57   -r, --runs=NUM       Repeat the test(s) NUM times
58   -h, --help           Display this help
59   --list               List all tests
60   --wait-for-gpu       Set this to wait for the GPU before producing the
61                        next frame. Note that without locked clocks this will
62                        pathologically bad performance due to large idle time
63   --report-frametime[=weight] If set, the test will print to stdout the
64                        moving average frametime. Weight is optional, default is 10
65   --cpuset=name        Adds the test to the specified cpuset before running
66                        Not supported on all devices and needs root
67   --offscreen          Render tests off device screen. This option is on by default
68   --onscreen           Render tests on device screen. By default tests
69                        are offscreen rendered
70   --benchmark_format   Set output format. Possible values are tabular, json, csv
71   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
72   --render-ahead=NUM   Sets how far to render-ahead. Must be 0 (default), 1, or 2.
73 )");
74 }
75 
listTests()76 static void listTests() {
77     printf("Tests: \n");
78     for (auto&& test : TestScene::testMap()) {
79         auto&& info = test.second;
80         const char* col1 = info.name.c_str();
81         int dlen = info.description.length();
82         const char* col2 = info.description.c_str();
83         // World's best line breaking algorithm.
84         do {
85             int toPrint = dlen;
86             if (toPrint > 50) {
87                 char* found = (char*)memrchr(col2, ' ', 50);
88                 if (found) {
89                     toPrint = found - col2;
90                 } else {
91                     toPrint = 50;
92                 }
93             }
94             printf("%-20s %.*s\n", col1, toPrint, col2);
95             col1 = "";
96             col2 += toPrint;
97             dlen -= toPrint;
98             while (*col2 == ' ') {
99                 col2++;
100                 dlen--;
101             }
102         } while (dlen > 0);
103         printf("\n");
104     }
105 }
106 
moveToCpuSet(const char * cpusetName)107 static void moveToCpuSet(const char* cpusetName) {
108     if (access("/dev/cpuset/tasks", F_OK)) {
109         fprintf(stderr, "don't have access to cpusets, skipping...\n");
110         return;
111     }
112     static const int BUF_SIZE = 100;
113     char buffer[BUF_SIZE];
114 
115     if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
116         fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
117         return;
118     }
119     int fd = open(buffer, O_WRONLY | O_CLOEXEC);
120     if (fd == -1) {
121         fprintf(stderr, "Error opening file %d\n", errno);
122         return;
123     }
124     pid_t pid = getpid();
125 
126     int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long)pid);
127     if (towrite >= BUF_SIZE) {
128         fprintf(stderr, "Buffer wasn't large enough?\n");
129     } else {
130         if (write(fd, buffer, towrite) != towrite) {
131             fprintf(stderr, "Failed to write, errno=%d", errno);
132         }
133     }
134     close(fd);
135 }
136 
setBenchmarkFormat(const char * format)137 static bool setBenchmarkFormat(const char* format) {
138     if (!strcmp(format, "tabular")) {
139         gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
140     } else if (!strcmp(format, "json")) {
141         gBenchmarkReporter.reset(new benchmark::JSONReporter());
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, "skiagl")) {
151         Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
152     } else if (!strcmp(renderer, "skiavk")) {
153         Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
154     } else {
155         fprintf(stderr, "Unknown format '%s'", renderer);
156         return false;
157     }
158     return true;
159 }
160 
161 // For options that only exist in long-form. Anything in the
162 // 0-255 range is reserved for short options (which just use their ASCII value)
163 namespace LongOpts {
164 enum {
165     Reserved = 255,
166     List,
167     WaitForGpu,
168     ReportFrametime,
169     CpuSet,
170     BenchmarkFormat,
171     Onscreen,
172     Offscreen,
173     Renderer,
174     RenderAhead,
175 };
176 }
177 
178 static const struct option LONG_OPTIONS[] = {
179         {"frames", required_argument, nullptr, 'f'},
180         {"repeat", required_argument, nullptr, 'r'},
181         {"help", no_argument, nullptr, 'h'},
182         {"list", no_argument, nullptr, LongOpts::List},
183         {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
184         {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
185         {"cpuset", required_argument, nullptr, LongOpts::CpuSet},
186         {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
187         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
188         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
189         {"renderer", required_argument, nullptr, LongOpts::Renderer},
190         {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead},
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 LongOpts::RenderAhead:
290                 if (!optarg) {
291                     error = true;
292                 }
293                 gOpts.renderAhead = atoi(optarg);
294                 if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) {
295                     error = true;
296                 }
297                 break;
298 
299             case 'h':
300                 printHelp();
301                 exit(EXIT_SUCCESS);
302                 break;
303 
304             case '?':
305                 fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
306                 [[fallthrough]];
307             default:
308                 error = true;
309                 break;
310         }
311     }
312 
313     if (error) {
314         fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
315         exit(EXIT_FAILURE);
316     }
317 
318     /* Print any remaining command line arguments (not options). */
319     if (optind < argc) {
320         do {
321             const char* test = argv[optind++];
322             auto pos = TestScene::testMap().find(test);
323             if (pos == TestScene::testMap().end()) {
324                 fprintf(stderr, "Unknown test '%s'\n", test);
325                 exit(EXIT_FAILURE);
326             } else {
327                 gRunTests.push_back(pos->second);
328             }
329         } while (optind < argc);
330     } else {
331         for (auto& iter : TestScene::testMap()) {
332             gRunTests.push_back(iter.second);
333         }
334     }
335 }
336 
main(int argc,char * argv[])337 int main(int argc, char* argv[]) {
338     // set defaults
339     gOpts.count = 150;
340 
341     Typeface::setRobotoTypefaceForTest();
342 
343     parseOptions(argc, argv);
344     if (!gBenchmarkReporter && gOpts.renderOffscreen) {
345         gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
346     }
347 
348     if (gBenchmarkReporter) {
349         size_t name_field_width = 10;
350         for (auto&& test : gRunTests) {
351             name_field_width = std::max<size_t>(name_field_width, test.name.size());
352         }
353         // _50th, _90th, etc...
354         name_field_width += 5;
355 
356         benchmark::BenchmarkReporter::Context context;
357         context.name_field_width = name_field_width;
358         gBenchmarkReporter->ReportContext(context);
359     }
360 
361     for (int i = 0; i < gRepeatCount; i++) {
362         for (auto&& test : gRunTests) {
363             run(test, gOpts, gBenchmarkReporter.get());
364         }
365     }
366 
367     if (gBenchmarkReporter) {
368         gBenchmarkReporter->Finalize();
369     }
370 
371     renderthread::RenderProxy::trimMemory(100);
372     HardwareBitmapUploader::terminate();
373 
374     LeakChecker::checkForLeaks();
375     return 0;
376 }
377