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