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 <fnmatch.h>
27 #include <getopt.h>
28 #include <pthread.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <string>
32 #include <unordered_map>
33 #include <vector>
34
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39
40 using namespace android;
41 using namespace android::uirenderer;
42 using namespace android::uirenderer::test;
43
44 static std::vector<TestScene::Info> gRunTests;
45 static TestScene::Options gOpts;
46 static bool gRunLeakCheck = true;
47 std::unique_ptr<benchmark::BenchmarkReporter> gBenchmarkReporter;
48
49 void run(const TestScene::Info& info, const TestScene::Options& opts,
50 benchmark::BenchmarkReporter* reporter);
51
printHelp()52 static void printHelp() {
53 printf(R"(
54 USAGE: hwuimacro [OPTIONS] <TESTNAME>
55
56 OPTIONS:
57 -c, --count=NUM NUM loops a test should run (example, number of frames)
58 -r, --runs=NUM Repeat the test(s) NUM times
59 -h, --help Display this help
60 --list List all tests
61 --wait-for-gpu Set this to wait for the GPU before producing the
62 next frame. Note that without locked clocks this will
63 pathologically bad performance due to large idle time
64 --report-frametime[=weight] If set, the test will print to stdout the
65 moving average frametime. Weight is optional, default is 10
66 --cpuset=name Adds the test to the specified cpuset before running
67 Not supported on all devices and needs root
68 --offscreen Render tests off device screen. This option is on by default
69 --onscreen Render tests on device screen. By default tests
70 are offscreen rendered
71 --benchmark_format Set output format. Possible values are tabular, json, csv
72 --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
73 --skip-leak-check Skips the memory leak check
74 --report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
75 )");
76 }
77
listTests()78 static void listTests() {
79 printf("Tests: \n");
80 for (auto&& test : TestScene::testMap()) {
81 auto&& info = test.second;
82 const char* col1 = info.name.c_str();
83 int dlen = info.description.length();
84 const char* col2 = info.description.c_str();
85 // World's best line breaking algorithm.
86 do {
87 int toPrint = dlen;
88 if (toPrint > 50) {
89 char* found = (char*)memrchr(col2, ' ', 50);
90 if (found) {
91 toPrint = found - col2;
92 } else {
93 toPrint = 50;
94 }
95 }
96 printf("%-20s %.*s\n", col1, toPrint, col2);
97 col1 = "";
98 col2 += toPrint;
99 dlen -= toPrint;
100 while (*col2 == ' ') {
101 col2++;
102 dlen--;
103 }
104 } while (dlen > 0);
105 printf("\n");
106 }
107 }
108
moveToCpuSet(const char * cpusetName)109 static void moveToCpuSet(const char* cpusetName) {
110 if (access("/dev/cpuset/tasks", F_OK)) {
111 fprintf(stderr, "don't have access to cpusets, skipping...\n");
112 return;
113 }
114 static const int BUF_SIZE = 100;
115 char buffer[BUF_SIZE];
116
117 if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
118 fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
119 return;
120 }
121 int fd = open(buffer, O_WRONLY | O_CLOEXEC);
122 if (fd == -1) {
123 fprintf(stderr, "Error opening file %d\n", errno);
124 return;
125 }
126 pid_t pid = getpid();
127
128 int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long)pid);
129 if (towrite >= BUF_SIZE) {
130 fprintf(stderr, "Buffer wasn't large enough?\n");
131 } else {
132 if (write(fd, buffer, towrite) != towrite) {
133 fprintf(stderr, "Failed to write, errno=%d", errno);
134 }
135 }
136 close(fd);
137 }
138
setBenchmarkFormat(const char * format)139 static bool setBenchmarkFormat(const char* format) {
140 if (!strcmp(format, "tabular")) {
141 gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
142 } else if (!strcmp(format, "json")) {
143 gBenchmarkReporter.reset(new benchmark::JSONReporter());
144 } else {
145 fprintf(stderr, "Unknown format '%s'\n", format);
146 return false;
147 }
148 return true;
149 }
150
setRenderer(const char * renderer)151 static bool setRenderer(const char* renderer) {
152 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'\n", 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 SkipLeakCheck,
177 ReportGpuMemory,
178 };
179 }
180
181 static const struct option LONG_OPTIONS[] = {
182 {"frames", required_argument, nullptr, 'f'},
183 {"repeat", required_argument, nullptr, 'r'},
184 {"help", no_argument, nullptr, 'h'},
185 {"list", no_argument, nullptr, LongOpts::List},
186 {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
187 {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
188 {"cpuset", required_argument, nullptr, LongOpts::CpuSet},
189 {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
190 {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
191 {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
192 {"renderer", required_argument, nullptr, LongOpts::Renderer},
193 {"skip-leak-check", no_argument, nullptr, LongOpts::SkipLeakCheck},
194 {"report-gpu-memory", optional_argument, nullptr, LongOpts::ReportGpuMemory},
195 {0, 0, 0, 0}};
196
197 static const char* SHORT_OPTIONS = "c:r:h";
198
parseOptions(int argc,char * argv[])199 void parseOptions(int argc, char* argv[]) {
200 int c;
201 bool error = false;
202 opterr = 0;
203
204 while (true) {
205 /* getopt_long stores the option index here. */
206 int option_index = 0;
207
208 c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index);
209
210 if (c == -1) break;
211
212 switch (c) {
213 case 0:
214 // Option set a flag, don't need to do anything
215 // (although none of the current LONG_OPTIONS do this...)
216 break;
217
218 case LongOpts::List:
219 listTests();
220 exit(EXIT_SUCCESS);
221 break;
222
223 case 'c':
224 gOpts.frameCount = atoi(optarg);
225 if (!gOpts.frameCount) {
226 fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
227 error = true;
228 }
229 break;
230
231 case 'r':
232 gOpts.repeatCount = atoi(optarg);
233 if (!gOpts.repeatCount) {
234 fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
235 error = true;
236 } else {
237 gOpts.repeatCount = (gOpts.repeatCount > 0 ? gOpts.repeatCount : INT_MAX);
238 }
239 break;
240
241 case LongOpts::ReportFrametime:
242 if (optarg) {
243 gOpts.reportFrametimeWeight = atoi(optarg);
244 if (!gOpts.reportFrametimeWeight) {
245 fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg);
246 error = true;
247 }
248 } else {
249 gOpts.reportFrametimeWeight = 10;
250 }
251 break;
252
253 case LongOpts::WaitForGpu:
254 Properties::waitForGpuCompletion = true;
255 break;
256
257 case LongOpts::CpuSet:
258 if (!optarg) {
259 error = true;
260 break;
261 }
262 moveToCpuSet(optarg);
263 break;
264
265 case LongOpts::BenchmarkFormat:
266 if (!optarg) {
267 error = true;
268 break;
269 }
270 if (!setBenchmarkFormat(optarg)) {
271 error = true;
272 }
273 break;
274
275 case LongOpts::Renderer:
276 if (!optarg) {
277 error = true;
278 break;
279 }
280 if (!setRenderer(optarg)) {
281 error = true;
282 }
283 break;
284
285 case LongOpts::Onscreen:
286 gOpts.renderOffscreen = false;
287 break;
288
289 case LongOpts::Offscreen:
290 gOpts.renderOffscreen = true;
291 break;
292
293 case LongOpts::SkipLeakCheck:
294 gRunLeakCheck = false;
295 break;
296
297 case LongOpts::ReportGpuMemory:
298 gOpts.reportGpuMemoryUsage = true;
299 if (optarg) {
300 if (!strcmp("verbose", optarg)) {
301 gOpts.reportGpuMemoryUsageVerbose = true;
302 } else {
303 fprintf(stderr, "Invalid report gpu memory option '%s'\n", optarg);
304 error = true;
305 }
306 }
307 break;
308
309 case 'h':
310 printHelp();
311 exit(EXIT_SUCCESS);
312 break;
313
314 case '?':
315 fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
316 [[fallthrough]];
317 default:
318 error = true;
319 break;
320 }
321 }
322
323 if (error) {
324 fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
325 exit(EXIT_FAILURE);
326 }
327
328 /* Print any remaining command line arguments (not options). */
329 if (optind < argc) {
330 do {
331 const char* test = argv[optind++];
332 if (strchr(test, '*')) {
333 // Glob match
334 for (auto& iter : TestScene::testMap()) {
335 if (!fnmatch(test, iter.first.c_str(), 0)) {
336 gRunTests.push_back(iter.second);
337 }
338 }
339 } else {
340 auto pos = TestScene::testMap().find(test);
341 if (pos == TestScene::testMap().end()) {
342 fprintf(stderr, "Unknown test '%s'\n", test);
343 exit(EXIT_FAILURE);
344 } else {
345 gRunTests.push_back(pos->second);
346 }
347 }
348 } while (optind < argc);
349 } else {
350 for (auto& iter : TestScene::testMap()) {
351 gRunTests.push_back(iter.second);
352 }
353 }
354 }
355
main(int argc,char * argv[])356 int main(int argc, char* argv[]) {
357 Typeface::setRobotoTypefaceForTest();
358
359 parseOptions(argc, argv);
360 if (!gBenchmarkReporter && gOpts.renderOffscreen) {
361 gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
362 }
363
364 if (gBenchmarkReporter) {
365 size_t name_field_width = 10;
366 for (auto&& test : gRunTests) {
367 name_field_width = std::max<size_t>(name_field_width, test.name.size());
368 }
369 // _50th, _90th, etc...
370 name_field_width += 5;
371
372 benchmark::BenchmarkReporter::Context context;
373 context.name_field_width = name_field_width;
374 gBenchmarkReporter->ReportContext(context);
375 }
376
377 for (auto&& test : gRunTests) {
378 run(test, gOpts, gBenchmarkReporter.get());
379 }
380
381 if (gBenchmarkReporter) {
382 gBenchmarkReporter->Finalize();
383 }
384
385 renderthread::RenderProxy::trimMemory(100);
386 HardwareBitmapUploader::terminate();
387
388 if (gRunLeakCheck) {
389 LeakChecker::checkForLeaks();
390 }
391 return 0;
392 }
393