• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Usage()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.
SanitizeOpts(int argc,char ** argv,std::vector<char * > * new_argv)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 
ParseOpts(int argc,char ** argv)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.
LockAndRun(benchmark::State & state,benchmark_func_t func_to_bench,long cpu_to_lock)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 
ParseOnebufManualStr(std::string & arg,std::vector<int> * values)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 
ParseTwobufManualStr(std::string & arg,std::vector<int> * values)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 
ResolveArgs(args_vector_t * to_populate,std::string args,std::map<std::string,args_vector_t> & args_shorthand)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 
RegisterGoogleBenchmarks(bench_opts_t primary_opts,bench_opts_t secondary_opts,const std::string & fn_name,args_vector_t * run_args)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 
RegisterCliBenchmarks(bench_opts_t cmdline_opts,std::map<std::string,args_vector_t> & args_shorthand)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 
RegisterXmlBenchmarks(bench_opts_t cmdline_opts,std::map<std::string,args_vector_t> & args_shorthand)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 
SetArgs(const std::vector<int> & sizes,args_vector_t * args)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 
SetArgs(const std::vector<int> & sizes,int align,args_vector_t * args)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 
SetArgs(const std::vector<int> & sizes,int align1,int align2,args_vector_t * args)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 
GetArgs(const std::vector<int> & sizes)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 
GetArgs(const std::vector<int> & sizes,int align)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 
GetArgs(const std::vector<int> & sizes,int align1,int align2)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 
GetShorthand()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 
FileExists(const std::string & file)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 
RegisterAllBenchmarks(const bench_opts_t & opts,std::map<std::string,args_vector_t> & args_shorthand)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 
main(int argc,char ** argv)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