1 /* 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 17 #include <err.h> 18 #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 <utility> 27 #include <vector> 28 29 #include <android-base/file.h> 30 #include <android-base/stringprintf.h> 31 #include <android-base/strings.h> 32 #include <benchmark/benchmark.h> 33 #include <tinyxml2.h> 34 #include "util.h" 35 36 static const std::vector<int> kCommonSizes{ 37 8, 38 64, 39 512, 40 1 * KB, 41 8 * KB, 42 16 * KB, 43 32 * KB, 44 64 * KB, 45 128 * KB, 46 }; 47 48 static const std::vector<int> kSmallSizes{ 49 // Increment by 1 50 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 51 // Increment by 8 52 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 53 // Increment by 16 54 160, 176, 192, 208, 224, 240, 256, 55 }; 56 57 static const std::vector<int> kMediumSizes{ 58 512, 59 1 * KB, 60 8 * KB, 61 16 * KB, 62 32 * KB, 63 64 * KB, 64 128 * KB, 65 }; 66 67 static const std::vector<int> kLargeSizes{ 68 256 * KB, 69 512 * KB, 70 1024 * KB, 71 2048 * KB, 72 }; 73 74 std::map<std::string, std::pair<benchmark_func_t, std::string>> g_str_to_func; 75 76 std::mutex g_map_lock; 77 78 static struct option g_long_options[] = 79 { 80 {"bionic_cpu", required_argument, 0, 'c'}, 81 {"bionic_xml", required_argument, 0, 'x'}, 82 {"bionic_iterations", required_argument, 0, 'i'}, 83 {"bionic_extra", required_argument, 0, 'a'}, 84 {"help", no_argument, 0, 'h'}, 85 {0, 0, 0, 0}, 86 }; 87 88 typedef std::vector<std::vector<int>> args_vector_t; 89 90 void Usage() { 91 printf("Usage:\n"); 92 printf("bionic_benchmarks [--bionic_cpu=<cpu_to_isolate>]\n"); 93 printf(" [--bionic_xml=<path_to_xml>]\n"); 94 printf(" [--bionic_iterations=<num_iter>]\n"); 95 printf(" [--bionic_extra=\"<fn_name> <arg1> <arg 2> ...\"]\n"); 96 printf(" [<Google benchmark flags>]\n"); 97 printf("Google benchmark flags:\n"); 98 99 int fake_argc = 2; 100 char argv0[] = "bionic_benchmarks"; 101 char argv1[] = "--help"; 102 char* fake_argv[3] {argv0, argv1, NULL}; 103 benchmark::Initialize(&fake_argc, fake_argv); 104 exit(1); 105 } 106 107 // This function removes any bionic benchmarks command line arguments by checking them 108 // against g_long_options. It fills new_argv with the filtered args. 109 void SanitizeOpts(int argc, char** argv, std::vector<char*>* new_argv) { 110 // TO THOSE ADDING OPTIONS: This currently doesn't support optional arguments. 111 (*new_argv)[0] = argv[0]; 112 for (int i = 1; i < argc; ++i) { 113 char* optarg = argv[i]; 114 size_t opt_idx = 0; 115 116 // Iterate through g_long_options until either we hit the end or we have a match. 117 for (opt_idx = 0; g_long_options[opt_idx].name && 118 strncmp(g_long_options[opt_idx].name, optarg + 2, 119 strlen(g_long_options[opt_idx].name)); ++opt_idx) { 120 } 121 122 if (!g_long_options[opt_idx].name) { 123 new_argv->push_back(optarg); 124 } else { 125 if (g_long_options[opt_idx].has_arg == required_argument) { 126 // If the arg was passed in with an =, it spans one char *. 127 // Otherwise, we skip a spot for the argument. 128 if (!strchr(optarg, '=')) { 129 i++; 130 } 131 } 132 } 133 } 134 new_argv->push_back(0); 135 } 136 137 bench_opts_t ParseOpts(int argc, char** argv) { 138 bench_opts_t opts; 139 int opt; 140 int option_index = 0; 141 142 opts.cpu_to_lock = LONG_MAX; 143 opts.num_iterations = 0; 144 145 // To make this parser handle the benchmark options silently: 146 extern int opterr; 147 opterr = 0; 148 149 while ((opt = getopt_long(argc, argv, "c:x:i:a:h", g_long_options, &option_index)) != -1) { 150 if (opt == -1) { 151 break; 152 } 153 switch (opt) { 154 case 'c': 155 if (*optarg) { 156 char* check_null; 157 opts.cpu_to_lock = strtol(optarg, &check_null, 10); 158 if (*check_null) { 159 errx(1, "ERROR: Args %s is not a valid integer.", optarg); 160 } 161 } else { 162 printf("ERROR: no argument specified for bionic_cpu\n"); 163 Usage(); 164 } 165 break; 166 case 'x': 167 if (*optarg) { 168 opts.xmlpath = optarg; 169 } else { 170 printf("ERROR: no argument specified for bionic_xml\n"); 171 Usage(); 172 } 173 break; 174 case 'a': 175 if (*optarg) { 176 opts.extra_benchmarks.push_back(optarg); 177 } else { 178 printf("ERROR: no argument specified for bionic_extra\n"); 179 Usage(); 180 } 181 break; 182 case 'i': 183 if (*optarg){ 184 char* check_null; 185 opts.num_iterations = strtol(optarg, &check_null, 10); 186 if (*check_null != '\0' or opts.num_iterations < 0) { 187 errx(1, "ERROR: Args %s is not a valid number of iterations.", optarg); 188 } 189 } else { 190 printf("ERROR: no argument specified for bionic_iterations\n"); 191 Usage(); 192 } 193 break; 194 case 'h': 195 Usage(); 196 break; 197 case '?': 198 break; 199 default: 200 exit(1); 201 } 202 } 203 return opts; 204 } 205 206 // This is a wrapper for every function call for per-benchmark cpu pinning. 207 void LockAndRun(benchmark::State& state, benchmark_func_t func_to_bench, long cpu_to_lock) { 208 if (cpu_to_lock != LONG_MAX) LockToCPU(cpu_to_lock); 209 // To avoid having to link against Google benchmarks in libutil, 210 // benchmarks are kept without parameter information, necessitating this cast. 211 reinterpret_cast<void(*) (benchmark::State&)>(func_to_bench)(state); 212 } 213 214 static constexpr char kOnebufManualStr[] = "AT_ONEBUF_MANUAL_ALIGN_"; 215 static constexpr char kTwobufManualStr[] = "AT_TWOBUF_MANUAL_ALIGN1_"; 216 217 static bool ParseOnebufManualStr(std::string& arg, std::vector<int>* values) { 218 // The format of this is: 219 // AT_ONEBUF_MANUAL_ALIGN_XX_SIZE_YY 220 // Where: 221 // XX is the alignment 222 // YY is the size 223 int align; 224 int size; 225 if (sscanf(arg.c_str(), "AT_ONEBUF_MANUAL_ALIGN_%d_SIZE_%d" , &align, &size) != 2) { 226 return false; 227 } 228 229 if (align != 0 && (align & (align - 1)) != 0) { 230 return false; 231 } 232 233 values->push_back(static_cast<int>(size)); 234 values->push_back(static_cast<int>(align)); 235 return true; 236 } 237 238 static bool ParseTwobufManualStr(std::string& arg, std::vector<int>* values) { 239 // The format of this is: 240 // AT_TWOBUF_MANUAL_ALIGN1_XX_ALIGN2_YY_SIZE_ZZ 241 // Where: 242 // XX is the alignment of the first argument 243 // YY is the alignment of the second argument 244 // ZZ is the size 245 int align1; 246 int align2; 247 int size; 248 if (sscanf(arg.c_str(), "AT_TWOBUF_MANUAL_ALIGN1_%d_ALIGN2_%d_SIZE_%d" , 249 &align1, &align2, &size) != 3) { 250 return false; 251 } 252 253 // Verify the alignments are powers of 2. 254 if ((align1 != 0 && (align1 & (align1 - 1)) != 0) 255 || (align2 != 0 && (align2 & (align2 - 1)) != 0)) { 256 return false; 257 } 258 259 values->push_back(static_cast<int>(size)); 260 values->push_back(static_cast<int>(align1)); 261 values->push_back(static_cast<int>(align2)); 262 return true; 263 } 264 265 args_vector_t* ResolveArgs(args_vector_t* to_populate, std::string args, 266 std::map<std::string, args_vector_t>& args_shorthand) { 267 // args is either a space-separated list of ints, a macro name, or 268 // special free form macro. 269 // To ease formatting in XML files, args is left and right trimmed. 270 if (args_shorthand.count(args)) { 271 return &args_shorthand[args]; 272 } 273 // Check for free form macro. 274 if (android::base::StartsWith(args, kOnebufManualStr)) { 275 std::vector<int> values; 276 if (!ParseOnebufManualStr(args, &values)) { 277 errx(1, "ERROR: Bad format of macro %s, should be AT_ONEBUF_MANUAL_ALIGN_XX_SIZE_YY", 278 args.c_str()); 279 } 280 to_populate->push_back(std::move(values)); 281 return to_populate; 282 } else if (android::base::StartsWith(args, kTwobufManualStr)) { 283 std::vector<int> values; 284 if (!ParseTwobufManualStr(args, &values)) { 285 errx(1, 286 "ERROR: Bad format of macro %s, should be AT_TWOBUF_MANUAL_ALIGN1_XX_ALIGNE2_YY_SIZE_ZZ", 287 args.c_str()); 288 } 289 to_populate->push_back(std::move(values)); 290 return to_populate; 291 } 292 293 to_populate->push_back(std::vector<int>()); 294 std::stringstream sstream(args); 295 std::string argstr; 296 while (sstream >> argstr) { 297 char* check_null; 298 int converted = static_cast<int>(strtol(argstr.c_str(), &check_null, 10)); 299 if (*check_null) { 300 errx(1, "ERROR: Args str %s contains an invalid macro or int.", args.c_str()); 301 } 302 (*to_populate)[0].push_back(converted); 303 } 304 return to_populate; 305 } 306 307 void RegisterGoogleBenchmarks(bench_opts_t primary_opts, bench_opts_t secondary_opts, 308 const std::string& fn_name, args_vector_t* run_args) { 309 if (g_str_to_func.find(fn_name) == g_str_to_func.end()) { 310 errx(1, "ERROR: No benchmark for function %s", fn_name.c_str()); 311 } 312 long iterations_to_use = primary_opts.num_iterations ? primary_opts.num_iterations : 313 secondary_opts.num_iterations; 314 int cpu_to_use = INT_MAX; 315 if (primary_opts.cpu_to_lock != INT_MAX) { 316 cpu_to_use = primary_opts.cpu_to_lock; 317 318 } else if (secondary_opts.cpu_to_lock != INT_MAX) { 319 cpu_to_use = secondary_opts.cpu_to_lock; 320 } 321 322 benchmark_func_t benchmark_function = g_str_to_func.at(fn_name).first; 323 for (const std::vector<int>& args : (*run_args)) { 324 auto registration = benchmark::RegisterBenchmark(fn_name.c_str(), LockAndRun, 325 benchmark_function, 326 cpu_to_use)->Args(args); 327 if (iterations_to_use > 0) { 328 registration->Iterations(iterations_to_use); 329 } 330 } 331 } 332 333 void RegisterCliBenchmarks(bench_opts_t cmdline_opts, 334 std::map<std::string, args_vector_t>& args_shorthand) { 335 // Register any of the extra benchmarks that were specified in the options. 336 args_vector_t arg_vector; 337 args_vector_t* run_args = &arg_vector; 338 for (const std::string& extra_fn : cmdline_opts.extra_benchmarks) { 339 android::base::Trim(extra_fn); 340 size_t first_space_pos = extra_fn.find(' '); 341 std::string fn_name = extra_fn.substr(0, first_space_pos); 342 std::string cmd_args; 343 if (first_space_pos != std::string::npos) { 344 cmd_args = extra_fn.substr(extra_fn.find(' ') + 1); 345 } else { 346 cmd_args = ""; 347 } 348 run_args = ResolveArgs(run_args, cmd_args, args_shorthand); 349 RegisterGoogleBenchmarks(bench_opts_t(), cmdline_opts, fn_name, run_args); 350 351 run_args = &arg_vector; 352 arg_vector.clear(); 353 } 354 } 355 356 int RegisterXmlBenchmarks(bench_opts_t cmdline_opts, 357 std::map<std::string, args_vector_t>& args_shorthand) { 358 // Structure of the XML file: 359 // - Element "fn" Function to benchmark. 360 // - - Element "iterations" Number of iterations to run. Leaving this blank uses 361 // Google benchmarks' convergence heuristics. 362 // - - Element "cpu" CPU to isolate to, if any. 363 // - - Element "args" Whitespace-separated list of per-function integer arguments, or 364 // one of the macros defined in util.h. 365 tinyxml2::XMLDocument doc; 366 if (doc.LoadFile(cmdline_opts.xmlpath.c_str()) != tinyxml2::XML_SUCCESS) { 367 doc.PrintError(); 368 return doc.ErrorID(); 369 } 370 371 // Read and register the functions. 372 tinyxml2::XMLNode* fn = doc.FirstChildElement("fn"); 373 while (fn) { 374 if (fn == fn->ToComment()) { 375 // Skip comments. 376 fn = fn->NextSibling(); 377 continue; 378 } 379 380 auto fn_elem = fn->FirstChildElement("name"); 381 if (!fn_elem) { 382 errx(1, "ERROR: Malformed XML entry: missing name element."); 383 } 384 std::string fn_name = fn_elem->GetText(); 385 if (fn_name.empty()) { 386 errx(1, "ERROR: Malformed XML entry: error parsing name text."); 387 } 388 auto* xml_args = fn->FirstChildElement("args"); 389 args_vector_t arg_vector; 390 args_vector_t* run_args = ResolveArgs(&arg_vector, 391 xml_args ? android::base::Trim(xml_args->GetText()) : "", 392 args_shorthand); 393 394 // XML values for CPU and iterations take precedence over those passed in via CLI. 395 bench_opts_t xml_opts{}; 396 auto* num_iterations_elem = fn->FirstChildElement("iterations"); 397 if (num_iterations_elem) { 398 int temp; 399 num_iterations_elem->QueryIntText(&temp); 400 xml_opts.num_iterations = temp; 401 } else { 402 xml_opts.num_iterations = 0; 403 } 404 auto* cpu_to_lock_elem = fn->FirstChildElement("cpu"); 405 if (cpu_to_lock_elem) { 406 int temp; 407 cpu_to_lock_elem->QueryIntText(&temp); 408 xml_opts.cpu_to_lock = temp; 409 } else { 410 xml_opts.cpu_to_lock = INT_MAX; 411 } 412 413 RegisterGoogleBenchmarks(xml_opts, cmdline_opts, fn_name, run_args); 414 415 fn = fn->NextSibling(); 416 } 417 return 0; 418 } 419 420 static void SetArgs(const std::vector<int>& sizes, args_vector_t* args) { 421 for (int size : sizes) { 422 args->push_back({size}); 423 } 424 } 425 426 static void SetArgs(const std::vector<int>& sizes, int align, args_vector_t* args) { 427 for (int size : sizes) { 428 args->push_back({size, align}); 429 } 430 } 431 432 433 static void SetArgs(const std::vector<int>& sizes, int align1, int align2, args_vector_t* args) { 434 for (int size : sizes) { 435 args->push_back({size, align1, align2}); 436 } 437 } 438 439 static args_vector_t GetArgs(const std::vector<int>& sizes) { 440 args_vector_t args; 441 SetArgs(sizes, &args); 442 return args; 443 } 444 445 static args_vector_t GetArgs(const std::vector<int>& sizes, int align) { 446 args_vector_t args; 447 SetArgs(sizes, align, &args); 448 return args; 449 } 450 451 static args_vector_t GetArgs(const std::vector<int>& sizes, int align1, int align2) { 452 args_vector_t args; 453 SetArgs(sizes, align1, align2, &args); 454 return args; 455 } 456 457 std::map<std::string, args_vector_t> GetShorthand() { 458 std::vector<int> all_sizes(kSmallSizes); 459 all_sizes.insert(all_sizes.end(), kMediumSizes.begin(), kMediumSizes.end()); 460 all_sizes.insert(all_sizes.end(), kLargeSizes.begin(), kLargeSizes.end()); 461 462 std::map<std::string, args_vector_t> args_shorthand { 463 {"AT_COMMON_SIZES", GetArgs(kCommonSizes)}, 464 {"AT_SMALL_SIZES", GetArgs(kSmallSizes)}, 465 {"AT_MEDIUM_SIZES", GetArgs(kMediumSizes)}, 466 {"AT_LARGE_SIZES", GetArgs(kLargeSizes)}, 467 {"AT_ALL_SIZES", GetArgs(all_sizes)}, 468 469 {"AT_ALIGNED_ONEBUF", GetArgs(kCommonSizes, 0)}, 470 {"AT_ALIGNED_ONEBUF_SMALL", GetArgs(kSmallSizes, 0)}, 471 {"AT_ALIGNED_ONEBUF_MEDIUM", GetArgs(kMediumSizes, 0)}, 472 {"AT_ALIGNED_ONEBUF_LARGE", GetArgs(kLargeSizes, 0)}, 473 {"AT_ALIGNED_ONEBUF_ALL", GetArgs(all_sizes, 0)}, 474 475 {"AT_ALIGNED_TWOBUF", GetArgs(kCommonSizes, 0, 0)}, 476 {"AT_ALIGNED_TWOBUF_SMALL", GetArgs(kSmallSizes, 0, 0)}, 477 {"AT_ALIGNED_TWOBUF_MEDIUM", GetArgs(kMediumSizes, 0, 0)}, 478 {"AT_ALIGNED_TWOBUF_LARGE", GetArgs(kLargeSizes, 0, 0)}, 479 {"AT_ALIGNED_TWOBUF_ALL", GetArgs(all_sizes, 0, 0)}, 480 481 // Do not exceed 512. that is about the largest number of properties 482 // that can be created with the current property area size. 483 {"NUM_PROPS", args_vector_t{ {1}, {4}, {16}, {64}, {128}, {256}, {512} }}, 484 485 {"MATH_COMMON", args_vector_t{ {0}, {1}, {2}, {3} }} 486 }; 487 488 args_vector_t args_onebuf; 489 args_vector_t args_twobuf; 490 for (int size : all_sizes) { 491 args_onebuf.push_back({size, 0}); 492 args_twobuf.push_back({size, 0, 0}); 493 // Skip alignments on zero sizes. 494 if (size == 0) { 495 continue; 496 } 497 for (int align1 = 1; align1 <= 32; align1 <<= 1) { 498 args_onebuf.push_back({size, align1}); 499 for (int align2 = 1; align2 <= 32; align2 <<= 1) { 500 args_twobuf.push_back({size, align1, align2}); 501 } 502 } 503 } 504 args_shorthand.emplace("AT_MANY_ALIGNED_ONEBUF", args_onebuf); 505 args_shorthand.emplace("AT_MANY_ALIGNED_TWOBUF", args_twobuf); 506 507 return args_shorthand; 508 } 509 510 static bool FileExists(const std::string& file) { 511 struct stat st; 512 return stat(file.c_str(), &st) != -1 && S_ISREG(st.st_mode); 513 } 514 515 void RegisterAllBenchmarks(const bench_opts_t& opts, 516 std::map<std::string, args_vector_t>& args_shorthand) { 517 for (auto& entry : g_str_to_func) { 518 auto& function_info = entry.second; 519 args_vector_t arg_vector; 520 args_vector_t* run_args = ResolveArgs(&arg_vector, function_info.second, 521 args_shorthand); 522 RegisterGoogleBenchmarks(bench_opts_t(), opts, entry.first, run_args); 523 } 524 } 525 526 int main(int argc, char** argv) { 527 std::map<std::string, args_vector_t> args_shorthand = GetShorthand(); 528 bench_opts_t opts = ParseOpts(argc, argv); 529 std::vector<char*> new_argv(argc); 530 SanitizeOpts(argc, argv, &new_argv); 531 532 if (opts.xmlpath.empty()) { 533 // Don't add the default xml file if a user is specifying the tests to run. 534 if (opts.extra_benchmarks.empty()) { 535 RegisterAllBenchmarks(opts, args_shorthand); 536 } 537 } else if (!FileExists(opts.xmlpath)) { 538 // See if this is a file in the suites directory. 539 std::string file(android::base::GetExecutableDirectory() + "/suites/" + opts.xmlpath); 540 if (opts.xmlpath[0] == '/' || !FileExists(file)) { 541 printf("Cannot find xml file %s: does not exist or is not a file.\n", opts.xmlpath.c_str()); 542 return 1; 543 } 544 opts.xmlpath = file; 545 } 546 547 if (!opts.xmlpath.empty()) { 548 if (int err = RegisterXmlBenchmarks(opts, args_shorthand)) { 549 return err; 550 } 551 } 552 RegisterCliBenchmarks(opts, args_shorthand); 553 554 // Set the thread priority to the maximum. 555 if (setpriority(PRIO_PROCESS, 0, -20)) { 556 perror("Failed to raise priority of process. Are you root?\n"); 557 } 558 559 int new_argc = new_argv.size(); 560 benchmark::Initialize(&new_argc, new_argv.data()); 561 benchmark::RunSpecifiedBenchmarks(); 562 } 563