blob: e61233c2aa1fcc035663a3e010bd3e06a14e8d69 [file] [log] [blame]
Anders Lewisa7b0f882017-07-24 20:01:13 -07001/*
2 * Copyright (C) 2017 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
Anders Lewisa98a5fb2017-08-09 16:52:19 -070017#include <err.h>
Anders Lewisa7b0f882017-07-24 20:01:13 -070018#include <getopt.h>
19#include <math.h>
20#include <sys/resource.h>
21
22#include <map>
23#include <mutex>
24#include <sstream>
25#include <string>
26#include <vector>
27
Christopher Ferrisd9d39be2017-08-23 18:03:51 -070028#include <android-base/file.h>
29#include <android-base/strings.h>
Anders Lewisa7b0f882017-07-24 20:01:13 -070030#include <benchmark/benchmark.h>
31#include <tinyxml2.h>
32#include "util.h"
33
Christopher Ferrisd9d39be2017-08-23 18:03:51 -070034#if defined(__ANDROID__)
35static constexpr const char* kDefaultSuite="full.xml";
36#else
37static constexpr const char* kDefaultSuite="host.xml";
38#endif
39
Anders Lewisa7b0f882017-07-24 20:01:13 -070040std::map<std::string, benchmark_func_t> g_str_to_func;
41
42std::mutex g_map_lock;
43
44static struct option g_long_options[] =
45{
46 {"bionic_cpu", required_argument, 0, 'c'},
47 {"bionic_xml", required_argument, 0, 'x'},
48 {"bionic_iterations", required_argument, 0, 'i'},
49 {"bionic_extra", required_argument, 0, 'a'},
50 {"help", no_argument, 0, 'h'},
51 {0, 0, 0, 0},
52};
53
54typedef std::vector<std::vector<int>> args_vector_t;
55
56void Usage() {
57 printf("Usage:\n");
Christopher Ferrisd9d39be2017-08-23 18:03:51 -070058 printf("bionic_benchmarks [--bionic_cpu=<cpu_to_isolate>]\n");
59 printf(" [--bionic_xml=<path_to_xml>]\n");
Anders Lewisa7b0f882017-07-24 20:01:13 -070060 printf(" [--bionic_iterations=<num_iter>]\n");
Christopher Ferrisd9d39be2017-08-23 18:03:51 -070061 printf(" [--bionic_extra=\"<fn_name> <arg1> <arg 2> ...\"]\n");
Anders Lewisa7b0f882017-07-24 20:01:13 -070062 printf(" [<Google benchmark flags>]\n");
63 printf("Google benchmark flags:\n");
64
65 int fake_argc = 2;
66 char argv0[] = "bionic_benchmarks";
67 char argv1[] = "--help";
68 char* fake_argv[3] {argv0, argv1, NULL};
69 benchmark::Initialize(&fake_argc, fake_argv);
Anders Lewisa98a5fb2017-08-09 16:52:19 -070070 exit(1);
Anders Lewisa7b0f882017-07-24 20:01:13 -070071}
72
73// This function removes any bionic benchmarks command line arguments by checking them
74// against g_long_options. It fills new_argv with the filtered args.
75void SanitizeOpts(int argc, char** argv, std::vector<char*>* new_argv) {
76 // TO THOSE ADDING OPTIONS: This currently doesn't support optional arguments.
77 (*new_argv)[0] = argv[0];
78 for (int i = 1; i < argc; ++i) {
79 char* optarg = argv[i];
80 size_t opt_idx = 0;
81
82 // Iterate through g_long_options until either we hit the end or we have a match.
83 for (opt_idx = 0; g_long_options[opt_idx].name &&
84 strncmp(g_long_options[opt_idx].name, optarg + 2,
85 strlen(g_long_options[opt_idx].name)); ++opt_idx) {
86 }
87
88 if (!g_long_options[opt_idx].name) {
89 new_argv->push_back(optarg);
90 } else {
91 if (g_long_options[opt_idx].has_arg == required_argument) {
92 // If the arg was passed in with an =, it spans one char *.
93 // Otherwise, we skip a spot for the argument.
94 if (!strchr(optarg, '=')) {
95 i++;
96 }
97 }
98 }
99 }
100 new_argv->push_back(0);
101}
102
103bench_opts_t ParseOpts(int argc, char** argv) {
104 bench_opts_t opts;
105 int opt;
106 int option_index = 0;
107
108 opts.cpu_to_lock = LONG_MAX;
109 opts.num_iterations = 0;
110
111 // To make this parser handle the benchmark options silently:
112 extern int opterr;
113 opterr = 0;
114
115 while ((opt = getopt_long(argc, argv, "c:x:i:a:h", g_long_options, &option_index)) != -1) {
116 if (opt == -1) {
117 break;
118 }
119 switch (opt) {
120 case 'c':
121 if (*optarg) {
122 char* check_null;
123 opts.cpu_to_lock = strtol(optarg, &check_null, 10);
124 if (*check_null) {
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700125 errx(1, "ERROR: Args %s is not a valid integer.", optarg);
Anders Lewisa7b0f882017-07-24 20:01:13 -0700126 }
127 } else {
128 printf("ERROR: no argument specified for bionic_cpu\n");
129 Usage();
130 }
131 break;
132 case 'x':
133 if (*optarg) {
134 opts.xmlpath = optarg;
135 } else {
136 printf("ERROR: no argument specified for bionic_xml\n");
137 Usage();
138 }
139 break;
140 case 'a':
141 if (*optarg) {
142 opts.extra_benchmarks.push_back(optarg);
143 } else {
144 printf("ERROR: no argument specified for bionic_extra\n");
145 Usage();
146 }
147 break;
148 case 'i':
149 if (*optarg){
150 char* check_null;
151 opts.num_iterations = strtol(optarg, &check_null, 10);
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700152 if (*check_null != '\0' or opts.num_iterations < 0) {
153 errx(1, "ERROR: Args %s is not a valid number of iterations.", optarg);
Anders Lewisa7b0f882017-07-24 20:01:13 -0700154 }
155 } else {
156 printf("ERROR: no argument specified for bionic_iterations\n");
157 Usage();
158 }
159 break;
160 case 'h':
161 Usage();
162 break;
163 case '?':
164 break;
165 default:
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700166 exit(1);
Anders Lewisa7b0f882017-07-24 20:01:13 -0700167 }
168 }
169 return opts;
170}
171
172// This is a wrapper for every function call for per-benchmark cpu pinning.
173void LockAndRun(benchmark::State& state, benchmark_func_t func_to_bench, long cpu_to_lock) {
174 if (cpu_to_lock != LONG_MAX) LockToCPU(cpu_to_lock);
175 // To avoid having to link against Google benchmarks in libutil,
176 // benchmarks are kept without parameter information, necessitating this cast.
177 reinterpret_cast<void(*) (benchmark::State&)>(func_to_bench)(state);
178}
179
180args_vector_t* ResolveArgs(args_vector_t* to_populate, std::string args,
181 std::map<std::string, args_vector_t>& args_shorthand) {
182 // args is either a space-separated list of ints or a macro name.
183 // To ease formatting in XML files, args is left and right trimmed.
184 if (args_shorthand.count(args)) {
185 return &args_shorthand[args];
186 }
187 to_populate->push_back(std::vector<int>());
188 std::stringstream sstream(args);
189 std::string argstr;
190 while (sstream >> argstr) {
191 char* check_null;
192 int converted = static_cast<int>(strtol(argstr.c_str(), &check_null, 10));
193 if (*check_null) {
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700194 errx(1, "ERROR: Args str %s contains an invalid macro or int.", args.c_str());
Anders Lewisa7b0f882017-07-24 20:01:13 -0700195 }
196 (*to_populate)[0].push_back(converted);
197 }
198 return to_populate;
199}
200
201void RegisterGoogleBenchmarks(bench_opts_t primary_opts, bench_opts_t secondary_opts,
202 std::string fn_name, args_vector_t* run_args) {
203 if (g_str_to_func.find(fn_name) == g_str_to_func.end()) {
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700204 errx(1, "ERROR: No benchmark for function %s", fn_name.c_str());
Anders Lewisa7b0f882017-07-24 20:01:13 -0700205 }
206 long iterations_to_use = primary_opts.num_iterations ? primary_opts.num_iterations :
207 secondary_opts.num_iterations;
208 int cpu_to_use = INT_MAX;
209 if (primary_opts.cpu_to_lock != INT_MAX) {
210 cpu_to_use = primary_opts.cpu_to_lock;
211
212 } else if (secondary_opts.cpu_to_lock != INT_MAX) {
213 cpu_to_use = secondary_opts.cpu_to_lock;
214 }
215
216 for (std::vector<int> args : (*run_args)) {
217 auto registration = benchmark::RegisterBenchmark(fn_name.c_str(), LockAndRun,
218 g_str_to_func.at(fn_name),
219 cpu_to_use)->Args(args);
220 if (iterations_to_use > 0) {
221 registration->Iterations(iterations_to_use);
222 }
223 }
224}
225
226void RegisterCliBenchmarks(bench_opts_t cmdline_opts,
227 std::map<std::string, args_vector_t>& args_shorthand) {
228 // Register any of the extra benchmarks that were specified in the options.
229 args_vector_t arg_vector;
230 args_vector_t* run_args = &arg_vector;
231 for (std::string extra_fn : cmdline_opts.extra_benchmarks) {
232 android::base::Trim(extra_fn);
233 size_t first_space_pos = extra_fn.find(" ");
234 std::string fn_name = extra_fn.substr(0, first_space_pos);
235 std::string cmd_args;
236 if (first_space_pos != std::string::npos) {
237 cmd_args = extra_fn.substr(extra_fn.find(" ") + 1);
238 } else {
239 cmd_args = "";
240 }
241 run_args = ResolveArgs(run_args, cmd_args, args_shorthand);
242 RegisterGoogleBenchmarks(bench_opts_t(), cmdline_opts, fn_name, run_args);
243
244 run_args = &arg_vector;
245 arg_vector.clear();
246 }
247}
248
249int RegisterXmlBenchmarks(bench_opts_t cmdline_opts,
250 std::map<std::string, args_vector_t>& args_shorthand) {
251 // Structure of the XML file:
252 // - Element "fn" Function to benchmark.
253 // - - Element "iterations" Number of iterations to run. Leaving this blank uses
254 // Google benchmarks' convergence heuristics.
255 // - - Element "cpu" CPU to isolate to, if any.
256 // - - Element "args" Whitespace-separated list of per-function integer arguments, or
257 // one of the macros defined in util.h.
258 tinyxml2::XMLDocument doc;
Elliott Hughesc2223f72017-08-08 11:23:27 -0700259 if (doc.LoadFile(cmdline_opts.xmlpath.c_str()) != tinyxml2::XML_SUCCESS) {
Anders Lewisa7b0f882017-07-24 20:01:13 -0700260 doc.PrintError();
261 return doc.ErrorID();
262 }
263
264 // Read and register the functions.
265 tinyxml2::XMLNode* fn = doc.FirstChildElement("fn");
Anders Lewisa7b0f882017-07-24 20:01:13 -0700266 while (fn) {
Christopher Ferrisd9d39be2017-08-23 18:03:51 -0700267 if (fn == fn->ToComment()) {
268 // Skip comments.
269 fn = fn->NextSibling();
270 continue;
271 }
272
Anders Lewisa7b0f882017-07-24 20:01:13 -0700273 auto fn_elem = fn->FirstChildElement("name");
274 if (!fn_elem) {
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700275 errx(1, "ERROR: Malformed XML entry: missing name element.");
Anders Lewisa7b0f882017-07-24 20:01:13 -0700276 }
277 std::string fn_name = fn_elem->GetText();
278 if (fn_name.empty()) {
Anders Lewisa98a5fb2017-08-09 16:52:19 -0700279 errx(1, "ERROR: Malformed XML entry: error parsing name text.");
Anders Lewisa7b0f882017-07-24 20:01:13 -0700280 }
281 auto* xml_args = fn->FirstChildElement("args");
Christopher Ferrisd9d39be2017-08-23 18:03:51 -0700282 args_vector_t arg_vector;
283 args_vector_t* run_args = ResolveArgs(&arg_vector,
284 xml_args ? android::base::Trim(xml_args->GetText()) : "",
285 args_shorthand);
Anders Lewisa7b0f882017-07-24 20:01:13 -0700286
287 // XML values for CPU and iterations take precedence over those passed in via CLI.
288 bench_opts_t xml_opts{};
289 auto* num_iterations_elem = fn->FirstChildElement("iterations");
290 if (num_iterations_elem) {
291 int temp;
292 num_iterations_elem->QueryIntText(&temp);
293 xml_opts.num_iterations = temp;
294 } else {
295 xml_opts.num_iterations = 0;
296 }
297 auto* cpu_to_lock_elem = fn->FirstChildElement("cpu");
298 if (cpu_to_lock_elem) {
299 int temp;
300 cpu_to_lock_elem->QueryIntText(&temp);
301 xml_opts.cpu_to_lock = temp;
302 } else {
303 xml_opts.cpu_to_lock = INT_MAX;
304 }
305
306 RegisterGoogleBenchmarks(xml_opts, cmdline_opts, fn_name, run_args);
307
308 fn = fn->NextSibling();
Anders Lewisa7b0f882017-07-24 20:01:13 -0700309 }
310 return 0;
311}
312
313std::map<std::string, args_vector_t> GetShorthand() {
314 std::map<std::string, args_vector_t> args_shorthand {
315 {"AT_ALIGNED_TWOBUF", args_vector_t{ {8, 0, 0},
Anders Lewisac4f4b42017-08-08 18:29:51 -0700316 {64, 0, 0},
317 {512, 0, 0},
318 {1 * KB, 0, 0},
319 {8 * KB, 0, 0},
320 {16 * KB, 0, 0},
321 {32 * KB, 0, 0},
322 {64 * KB, 0, 0} }},
Anders Lewisa7b0f882017-07-24 20:01:13 -0700323 {"AT_ALIGNED_ONEBUF", args_vector_t{ {(8), 0},
Anders Lewisac4f4b42017-08-08 18:29:51 -0700324 {(64), 0},
325 {(512), 0},
326 {(1*KB), 0},
327 {(8*KB), 0},
328 {(16*KB), 0},
329 {(32*KB), 0},
330 {(64*KB), 0}}},
Anders Lewisa7b0f882017-07-24 20:01:13 -0700331
332 {"AT_COMMON_SIZES", args_vector_t{ {8}, {64}, {512}, {1*KB}, {8*KB}, {16*KB},
333 {32*KB}, {64*KB}}},
334
335 // Do not exceed 512. that is about the largest number of properties
336 // that can be created with the current property area size.
337 {"NUM_PROPS", args_vector_t{ {1}, {4}, {16}, {64}, {128}, {256}, {512} }},
338
339 {"MATH_COMMON", args_vector_t{ {0}, {1}, {2}, {3} }}
340 };
341 for (int i = 1; i < 15; i++) {
342 int align = pow(2, i);
343 std::stringstream sstream;
344 sstream << "AT_" << align << "_ALIGN_TWOBUF";
345 args_shorthand.emplace(sstream.str(),
346 args_vector_t{ {8, align, align},
347 {64, align, align},
348 {512, align, align},
349 {1 * KB, align, align},
350 {8 * KB, align, align},
351 {16 * KB, align, align},
352 {32 * KB, align, align},
353 {64 * KB, align, align} });
354 sstream.str("");
355 sstream << "AT_" << align << "_ALIGN_ONEBUF";
356 args_shorthand.emplace(sstream.str(),
357 args_vector_t{ {(8), align},
358 {(64), align},
359 {(512), align},
360 {(1*KB), align},
361 {(8*KB), align},
362 {(16*KB), align},
363 {(32*KB), align},
364 {(64*KB), align} });
365 sstream.str("");
366 }
367 return args_shorthand;
368}
369
Christopher Ferrisd9d39be2017-08-23 18:03:51 -0700370static bool FileExists(const std::string& file) {
371 struct stat st;
372 return stat(file.c_str(), &st) != -1 && S_ISREG(st.st_mode);
373}
Anders Lewisa7b0f882017-07-24 20:01:13 -0700374
375int main(int argc, char** argv) {
376 std::map<std::string, args_vector_t> args_shorthand = GetShorthand();
377 bench_opts_t opts = ParseOpts(argc, argv);
378 std::vector<char*> new_argv(argc);
379 SanitizeOpts(argc, argv, &new_argv);
380
Christopher Ferrisd9d39be2017-08-23 18:03:51 -0700381 if (opts.xmlpath.empty()) {
382 // Don't add the default xml file if a user is specifying the tests to run.
383 if (opts.extra_benchmarks.empty()) {
384 // Try and use the default.
385 opts.xmlpath = android::base::GetExecutableDirectory() + "/suites/" + kDefaultSuite;
386 if (!FileExists(opts.xmlpath)) {
387 printf("Cannot find default xml file %s\n", kDefaultSuite);
388 return 1;
389 }
390 }
391 } else if (!FileExists(opts.xmlpath)) {
392 // See if this is a file in the suites directory.
393 std::string file(android::base::GetExecutableDirectory() + "/suites/" + opts.xmlpath);
394 if (opts.xmlpath[0] == '/' || !FileExists(file)) {
395 printf("Cannot find xml file %s: does not exist or is not a file.\n", opts.xmlpath.c_str());
396 return 1;
397 }
398 opts.xmlpath = file;
399 }
400
Anders Lewisa7b0f882017-07-24 20:01:13 -0700401 if (!opts.xmlpath.empty()) {
402 if (int err = RegisterXmlBenchmarks(opts, args_shorthand)) {
403 return err;
404 }
405 }
406 RegisterCliBenchmarks(opts, args_shorthand);
407
408 // Set the thread priority to the maximum.
409 if (setpriority(PRIO_PROCESS, 0, -20)) {
410 perror("Failed to raise priority of process. Are you root?\n");
411 }
412
413 int new_argc = new_argv.size();
414 benchmark::Initialize(&new_argc, new_argv.data());
415 benchmark::RunSpecifiedBenchmarks();
416}