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 "hwui/Typeface.h"
21 #include "protos/hwui.pb.h"
22 #include "Properties.h"
23
24 #include <benchmark/benchmark.h>
25 #include <../src/sysinfo.h>
26 #include <getopt.h>
27 #include <stdio.h>
28 #include <string>
29 #include <unistd.h>
30 #include <unordered_map>
31 #include <vector>
32 #include <pthread.h>
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <errno.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 )");
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++; dlen--;
98 }
99 } while (dlen > 0);
100 printf("\n");
101 }
102 }
103
moveToCpuSet(const char * cpusetName)104 static void moveToCpuSet(const char* cpusetName) {
105 if (access("/dev/cpuset/tasks", F_OK)) {
106 fprintf(stderr, "don't have access to cpusets, skipping...\n");
107 return;
108 }
109 static const int BUF_SIZE = 100;
110 char buffer[BUF_SIZE];
111
112 if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
113 fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
114 return;
115 }
116 int fd = open(buffer, O_WRONLY | O_CLOEXEC);
117 if (fd == -1) {
118 fprintf(stderr, "Error opening file %d\n", errno);
119 return;
120 }
121 pid_t pid = getpid();
122
123 int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long) pid);
124 if (towrite >= BUF_SIZE) {
125 fprintf(stderr, "Buffer wasn't large enough?\n");
126 } else {
127 if (write(fd, buffer, towrite) != towrite) {
128 fprintf(stderr, "Failed to write, errno=%d", errno);
129 }
130 }
131 close(fd);
132 }
133
setBenchmarkFormat(const char * format)134 static bool setBenchmarkFormat(const char* format) {
135 if (!strcmp(format, "tabular")) {
136 gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
137 } else if (!strcmp(format, "json")) {
138 gBenchmarkReporter.reset(new benchmark::JSONReporter());
139 } else if (!strcmp(format, "csv")) {
140 gBenchmarkReporter.reset(new benchmark::CSVReporter());
141 } else {
142 fprintf(stderr, "Unknown format '%s'", format);
143 return false;
144 }
145 return true;
146 }
147
148 // For options that only exist in long-form. Anything in the
149 // 0-255 range is reserved for short options (which just use their ASCII value)
150 namespace LongOpts {
151 enum {
152 Reserved = 255,
153 List,
154 WaitForGpu,
155 ReportFrametime,
156 CpuSet,
157 BenchmarkFormat,
158 Onscreen,
159 Offscreen,
160 };
161 }
162
163 static const struct option LONG_OPTIONS[] = {
164 { "frames", required_argument, nullptr, 'f' },
165 { "repeat", required_argument, nullptr, 'r' },
166 { "help", no_argument, nullptr, 'h' },
167 { "list", no_argument, nullptr, LongOpts::List },
168 { "wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu },
169 { "report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime },
170 { "cpuset", required_argument, nullptr, LongOpts::CpuSet },
171 { "benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat },
172 { "onscreen", no_argument, nullptr, LongOpts::Onscreen },
173 { "offscreen", no_argument, nullptr, LongOpts::Offscreen },
174 { 0, 0, 0, 0 }
175 };
176
177 static const char* SHORT_OPTIONS = "c:r:h";
178
parseOptions(int argc,char * argv[])179 void parseOptions(int argc, char* argv[]) {
180 int c;
181 bool error = false;
182 opterr = 0;
183
184 while (true) {
185
186 /* getopt_long stores the option index here. */
187 int option_index = 0;
188
189 c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index);
190
191 if (c == -1)
192 break;
193
194 switch (c) {
195 case 0:
196 // Option set a flag, don't need to do anything
197 // (although none of the current LONG_OPTIONS do this...)
198 break;
199
200 case LongOpts::List:
201 listTests();
202 exit(EXIT_SUCCESS);
203 break;
204
205 case 'c':
206 gOpts.count = atoi(optarg);
207 if (!gOpts.count) {
208 fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
209 error = true;
210 }
211 break;
212
213 case 'r':
214 gRepeatCount = atoi(optarg);
215 if (!gRepeatCount) {
216 fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
217 error = true;
218 } else {
219 gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX);
220 }
221 break;
222
223 case LongOpts::ReportFrametime:
224 if (optarg) {
225 gOpts.reportFrametimeWeight = atoi(optarg);
226 if (!gOpts.reportFrametimeWeight) {
227 fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg);
228 error = true;
229 }
230 } else {
231 gOpts.reportFrametimeWeight = 10;
232 }
233 break;
234
235 case LongOpts::WaitForGpu:
236 Properties::waitForGpuCompletion = true;
237 break;
238
239 case LongOpts::CpuSet:
240 if (!optarg) {
241 error = true;
242 break;
243 }
244 moveToCpuSet(optarg);
245 break;
246
247 case LongOpts::BenchmarkFormat:
248 if (!optarg) {
249 error = true;
250 break;
251 }
252 if (!setBenchmarkFormat(optarg)) {
253 error = true;
254 }
255 break;
256
257 case LongOpts::Onscreen:
258 gOpts.renderOffscreen = false;
259 break;
260
261 case LongOpts::Offscreen:
262 gOpts.renderOffscreen = true;
263 break;
264
265 case 'h':
266 printHelp();
267 exit(EXIT_SUCCESS);
268 break;
269
270 case '?':
271 fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
272 // fall-through
273 default:
274 error = true;
275 break;
276 }
277 }
278
279 if (error) {
280 fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
281 exit(EXIT_FAILURE);
282 }
283
284 /* Print any remaining command line arguments (not options). */
285 if (optind < argc) {
286 do {
287 const char* test = argv[optind++];
288 auto pos = TestScene::testMap().find(test);
289 if (pos == TestScene::testMap().end()) {
290 fprintf(stderr, "Unknown test '%s'\n", test);
291 exit(EXIT_FAILURE);
292 } else {
293 gRunTests.push_back(pos->second);
294 }
295 } while (optind < argc);
296 } else {
297 for (auto& iter : TestScene::testMap()) {
298 gRunTests.push_back(iter.second);
299 }
300 }
301 }
302
main(int argc,char * argv[])303 int main(int argc, char* argv[]) {
304 // set defaults
305 gOpts.count = 150;
306
307 Typeface::setRobotoTypefaceForTest();
308
309 parseOptions(argc, argv);
310 if (!gBenchmarkReporter && gOpts.renderOffscreen) {
311 gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
312 }
313
314 if (gBenchmarkReporter) {
315 size_t name_field_width = 10;
316 for (auto&& test : gRunTests) {
317 name_field_width = std::max<size_t>(name_field_width, test.name.size());
318 }
319 // _50th, _90th, etc...
320 name_field_width += 5;
321
322 benchmark::BenchmarkReporter::Context context;
323 context.num_cpus = benchmark::NumCPUs();
324 context.mhz_per_cpu = benchmark::CyclesPerSecond() / 1000000.0f;
325 context.cpu_scaling_enabled = benchmark::CpuScalingEnabled();
326 context.name_field_width = name_field_width;
327 gBenchmarkReporter->ReportContext(context);
328 }
329
330 for (int i = 0; i < gRepeatCount; i++) {
331 for (auto&& test : gRunTests) {
332 run(test, gOpts, gBenchmarkReporter.get());
333 }
334 }
335
336 if (gBenchmarkReporter) {
337 gBenchmarkReporter->Finalize();
338 }
339
340 LeakChecker::checkForLeaks();
341 return 0;
342 }
343