1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <benchmark/benchmark.h>
17 #include <err.h>
18 #include <getopt.h>
19 #include <cinttypes>
20 #include <cmath>
21 #include <sys/resource.h>
22 #include <sys/stat.h>
23
24 #include <map>
25 #include <mutex>
26 #include <sstream>
27 #include <string>
28 #include <utility>
29 #include <regex>
30 #include <vector>
31
32 #include "util.h"
33
34 extern "C" {
35 #include "cJSON.h"
36 }
37
38 static const std::vector<int> commonArgs {
39 8,
40 16,
41 32,
42 64,
43 512,
44 1 * K,
45 8 * K,
46 16 * K,
47 32 * K,
48 64 * K,
49 128 * K,
50 };
51
52 std::map<std::string, std::pair<BenchmarkFunc, std::string>> g_allBenchmarks;
53 std::mutex g_benchmarkLock;
54 std::map<std::string, std::pair<BenchmarkFunc, ApplyBenchmarkFunc>> g_applyBenchmarks;
55
56 using args_vector=std::vector<std::vector<int64_t>>;
57
58 static struct option g_benchmarkLongOptions[] = {
59 {"musl_cpu", required_argument, nullptr, 'c'},
60 {"musl_iterations", required_argument, nullptr, 'i'},
61 {"musl_json", required_argument, nullptr, 'j'},
62 {"help", no_argument, nullptr, 'h'},
63 {nullptr, 0, nullptr, 0},
64 };
65
PrintUsageAndExit()66 void PrintUsageAndExit()
67 {
68 printf("Usage:\n");
69 printf("musl_benchmarks [--musl_cpu=<cpu_to_isolate>]\n");
70 printf(" [--musl_iterations=<num_iter>]\n");
71 printf(" [--musl_json=<path_to_json>]\n");
72 printf(" [<original benchmark flags>]\n");
73 printf("benchmark flags:\n");
74
75 int argc = 2;
76 char argv0[] = "musl_benchmark";
77 char argv1[] = "--help";
78 char *argv[3]{argv0, argv1, nullptr};
79 benchmark::Initialize(&argc, argv);
80 exit(1);
81 }
82
ShiftOptions(int argc,char ** argv,std::vector<char * > * argvAfterShift)83 void ShiftOptions(int argc, char **argv, std::vector<char *> *argvAfterShift)
84 {
85 (*argvAfterShift)[0] = argv[0];
86 for (int i = 1; i < argc; ++i) {
87 char *optarg = argv[i];
88 size_t index = 0;
89 // Find if musl defined this arg.
90 while (g_benchmarkLongOptions[index].name && strncmp(g_benchmarkLongOptions[index].name, optarg + 2,
91 strlen(g_benchmarkLongOptions[index].name))) {
92 ++index;
93 }
94 // Not defined.
95 if (!g_benchmarkLongOptions[index].name) {
96 argvAfterShift->push_back(optarg);
97 } else if ((g_benchmarkLongOptions[index].has_arg == required_argument) && !strchr(optarg, '=')) {
98 i++;
99 }
100 }
101 argvAfterShift->push_back(nullptr);
102 }
103
ParseOptions(int argc,char ** argv)104 bench_opts_t ParseOptions(int argc, char **argv)
105 {
106 bench_opts_t opts;
107 int opt;
108
109 while ((opt = getopt_long(argc, argv, "c:i:j:h", g_benchmarkLongOptions, nullptr)) != -1) {
110 int decimal = 10;
111 switch (opt) {
112 case 'c':
113 if (*optarg) {
114 char *errorCheck;
115 opts.cpuNum = strtol(optarg, &errorCheck, decimal);
116 if (*errorCheck) {
117 errx(1, "ERROR: Args %s is not a valid integer.", optarg);
118 }
119 } else {
120 printf("ERROR: no argument specified for musl_cpu.\n");
121 PrintUsageAndExit();
122 }
123 break;
124 case 'i':
125 if (*optarg) {
126 char *errorCheck;
127 opts.iterNum = strtol(optarg, &errorCheck, decimal);
128 if (*errorCheck != '\0' or opts.iterNum < 0) {
129 errx(1, "ERROR: Args %s is not a valid number of iterations.", optarg);
130 }
131 } else {
132 printf("ERROR: no argument specified for musl_iterations.\n");
133 PrintUsageAndExit();
134 }
135 break;
136 case 'j':
137 if (*optarg) {
138 opts.jsonPath = optarg;
139 } else {
140 printf("ERROR: no argument specified for musl_json\n");
141 PrintUsageAndExit();
142 }
143 break;
144 case 'h':
145 PrintUsageAndExit();
146 break;
147 case '?':
148 break;
149 default:
150 exit(1);
151 }
152 }
153 return opts;
154 }
155
LockAndRun(benchmark::State & state,BenchmarkFunc func,int cpuNum)156 void LockAndRun(benchmark::State &state, BenchmarkFunc func, int cpuNum)
157 {
158 #if not defined __APPLE__
159 if (cpuNum >= 0) {
160 cpu_set_t cpuset;
161 CPU_ZERO(&cpuset);
162 CPU_SET(cpuNum, &cpuset);
163
164 if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
165 printf("lock CPU failed, ERROR:%s\n", strerror(errno));
166 }
167 }
168 #endif
169
170 reinterpret_cast<void (*)(benchmark::State &)>(func)(state);
171 }
172
ResolveArgs(args_vector * argsVector,std::string args,std::map<std::string,args_vector> & presetArgs)173 args_vector *ResolveArgs(args_vector *argsVector, std::string args,
174 std::map<std::string, args_vector> &presetArgs)
175 {
176 // Get it from preset args.
177 if (presetArgs.count(args)) {
178 return &presetArgs[args];
179 }
180
181 // Convert string to int.
182 argsVector->push_back(std::vector<int64_t>());
183 std::stringstream sstream(args);
184 std::string argstr;
185 while (sstream >> argstr) {
186 char *errorCheck;
187 int converted = static_cast<int>(strtol(argstr.c_str(), &errorCheck, 10));
188 if (*errorCheck) {
189 errx(1, "ERROR: Args str %s contains an invalid macro or int.", args.c_str());
190 }
191 (*argsVector)[0].push_back(converted);
192 }
193 return argsVector;
194 }
195
MatchFuncNameInJson(const std::string & str,const std::string & pattern)196 bool MatchFuncNameInJson(const std::string& str, const std::string& pattern)
197 {
198 size_t jsonwildcard = pattern.find("*");
199 if (jsonwildcard == std::string::npos) {
200 return str == pattern;
201 }
202 std::string prefix = pattern.substr(0, jsonwildcard);
203 std::string jsonFuncName = prefix + ".*";
204 std::regex re(jsonFuncName, std::regex::icase);
205 return std::regex_match(str, re);
206 }
207
GetArgs(const std::vector<int> & sizes)208 static args_vector GetArgs(const std::vector<int> &sizes)
209 {
210 args_vector args;
211 for (int size : sizes) {
212 args.push_back({size});
213 }
214 return args;
215 }
216
GetArgs(const std::vector<int> & sizes,int value)217 static args_vector GetArgs(const std::vector<int> &sizes, int value)
218 {
219 args_vector args;
220 for (int size : sizes) {
221 args.push_back({size, value});
222 }
223 return args;
224 }
225
GetArgs(const std::vector<int> & sizes,int value1,int value2)226 static args_vector GetArgs(const std::vector<int> &sizes, int value1, int value2)
227 {
228 args_vector args;
229 for (int size : sizes) {
230 args.push_back({size, value1, value2});
231 }
232 return args;
233 }
234
GetPresetArgs()235 std::map<std::string, args_vector> GetPresetArgs()
236 {
237 std::map<std::string, args_vector> presetArgs {
238 {"COMMON_ARGS", GetArgs(commonArgs)},
239 {"ALIGNED_ONEBUF", GetArgs(commonArgs, 0)},
240 {"ALIGNED_TWOBUF", GetArgs(commonArgs, 0, 0)},
241 {"BENCHMARK_5", args_vector{{0}, {1}, {2}, {3}, {4}}},
242 {"BENCHMARK_8", args_vector{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}}},
243 {"BENCHMARK_22", args_vector{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13},
244 {14}, {15}, {16}, {17}, {18}, {19}, {20}, {21}}},
245 };
246
247 return presetArgs;
248 }
249
RegisterSingleBenchmark(bench_opts_t optsFromJson,bench_opts_t optsFromCommandLine,const std::string & funcName,args_vector * runArgs)250 void RegisterSingleBenchmark(bench_opts_t optsFromJson, bench_opts_t optsFromCommandLine,
251 const std::string &funcName, args_vector *runArgs)
252 {
253 bool isApplyUseCase = false;
254 if (g_allBenchmarks.find(funcName) != g_allBenchmarks.end()) {
255 isApplyUseCase = false;
256 } else if (g_applyBenchmarks.find(funcName) != g_applyBenchmarks.end()) {
257 isApplyUseCase = true;
258 } else {
259 errx(1, "ERROR: No benchmark for function %s", funcName.c_str());
260 }
261
262 long iterNum = optsFromCommandLine.iterNum ? optsFromCommandLine.iterNum : optsFromJson.iterNum;
263 int cpuNum = -1;
264 if (optsFromCommandLine.cpuNum >= 0) {
265 cpuNum = optsFromCommandLine.cpuNum;
266 } else if (optsFromJson.cpuNum >= 0) {
267 cpuNum = optsFromJson.cpuNum;
268 }
269
270 if (isApplyUseCase) {
271 BenchmarkFunc func = g_applyBenchmarks.at(funcName).first;
272 auto registration = benchmark::RegisterBenchmark(funcName.c_str(), LockAndRun, func,
273 cpuNum)->Apply(g_applyBenchmarks.at(funcName).second);
274
275 if (iterNum > 0) {
276 registration->Iterations(iterNum);
277 }
278 } else {
279 BenchmarkFunc func = g_allBenchmarks.at(funcName).first;
280 for (const std::vector<int64_t> &args : (*runArgs)) {
281 // It will call LockAndRun(func, opts.cpuNum).
282 auto registration = benchmark::RegisterBenchmark(funcName.c_str(), LockAndRun, func, cpuNum)->Args(args);
283 if (iterNum > 0) {
284 registration->Iterations(iterNum);
285 }
286 }
287 }
288 }
289
RegisterAllBenchmarks(const bench_opts_t & opts,std::map<std::string,args_vector> & presetArgs)290 void RegisterAllBenchmarks(const bench_opts_t &opts, std::map<std::string, args_vector> &presetArgs)
291 {
292 for (auto &entry : g_allBenchmarks) {
293 auto &funcInfo = entry.second;
294 args_vector argVector;
295 args_vector *runArgs = ResolveArgs(&argVector, funcInfo.second, presetArgs);
296 RegisterSingleBenchmark(bench_opts_t(), opts, entry.first, runArgs);
297 }
298
299 for (auto &entry : g_applyBenchmarks) {
300 args_vector *runArgs = nullptr;
301 RegisterSingleBenchmark(bench_opts_t(), opts, entry.first, runArgs);
302 }
303 }
304
Trim(const std::string & str)305 std::string Trim(const std::string& str)
306 {
307 size_t first = str.find_first_not_of(' ');
308 if (std::string::npos == first) {
309 return "";
310 }
311 size_t last = str.find_last_not_of(' ');
312 return str.substr(first, (last - first + 1));
313 }
314
RegisterJsonBenchmarks(const bench_opts_t & opts,std::map<std::string,args_vector> & presetArgs)315 int RegisterJsonBenchmarks(const bench_opts_t &opts, std::map<std::string, args_vector> &presetArgs)
316 {
317 char *file = nullptr;
318 cJSON *json = nullptr;
319 // Read JSON string from file
320 file = ReadJsonFile(opts.jsonPath.c_str());
321 if (file == nullptr) {
322 printf("fail to read file or no data read.\n");
323 return JOSN_ERROR_FILE_READ_FAILED;
324 }
325
326 // Load JSON data
327 json = cJSON_Parse(file);
328 if (json == nullptr) {
329 printf("JSON parsing failed, incorrect JSON format.\n");
330 return JOSN_ERROR_JSON_FORMAT;
331 }
332
333 // Parsing Fields
334 cJSON *item = cJSON_GetObjectItem(json, "InterfaceUsecases");
335 if (item) {
336 int arraySize = cJSON_GetArraySize(item); // Get the size of the array
337
338 // Parsing each member in an array and register the functions.
339 for (int i = 0; i < arraySize; i++) {
340 cJSON *arrayItem = cJSON_GetArrayItem(item, i); // Extract array subscript object
341 if (arrayItem == nullptr) {
342 continue;
343 }
344
345 // Parsing data
346 cJSON *obj = cJSON_GetObjectItem(arrayItem, "name");
347 std::string fnName;
348 if (obj != nullptr) {
349 fnName = std::string(obj->valuestring);
350 } else {
351 printf("missing name element or error parsing name text\n");
352 }
353
354 obj = cJSON_GetObjectItem(arrayItem, "args");
355 std::string jsonArgs;
356 args_vector argVector;
357 args_vector *runArgs = nullptr;
358 if (obj != nullptr) {
359 jsonArgs = std::string(obj->valuestring);
360 runArgs = ResolveArgs(&argVector, Trim(jsonArgs), presetArgs);
361 } else {
362 runArgs = ResolveArgs(&argVector, "", presetArgs);
363 }
364
365 bench_opts_t jsonOpts{};
366 obj = cJSON_GetObjectItem(arrayItem, "iterations");
367 if (obj != nullptr) {
368 jsonOpts.iterNum = obj->valueint;
369 }
370
371 obj = cJSON_GetObjectItem(arrayItem, "cpu");
372 if (obj != nullptr) {
373 jsonOpts.cpuNum = obj->valueint;
374 }
375
376 std::vector<std::string> matchedFuncNames;
377 for (const auto& muslbenchmark : g_allBenchmarks) {
378 if (MatchFuncNameInJson(muslbenchmark.first, fnName)) {
379 matchedFuncNames.push_back(muslbenchmark.first);
380 }
381 }
382
383 for (const auto& useapply : g_applyBenchmarks) {
384 if (MatchFuncNameInJson(useapply.first, fnName)) {
385 matchedFuncNames.push_back(useapply.first);
386 }
387 }
388
389 if (matchedFuncNames.empty()) {
390 errx(1, "JSONERROR: No benchmark found for function like %s in matchedFuncNames", fnName.c_str());
391 }
392
393 for (const auto& matchedFuncName : matchedFuncNames) {
394 fnName = matchedFuncName;
395 RegisterSingleBenchmark(jsonOpts, opts, fnName, runArgs);
396 }
397 }
398 }
399
400 if (json != nullptr) {
401 cJSON_Delete(json);
402 }
403 return JSON_SUCCESS;
404 }
405
IsRegularFileExists(const std::string & file)406 static bool IsRegularFileExists(const std::string& file)
407 {
408 struct stat st;
409 return stat(file.c_str(), &st) != -1 && S_ISREG(st.st_mode);
410 }
411
main(int argc,char ** argv)412 int main(int argc, char **argv)
413 {
414 std::map<std::string, args_vector> presetArgs = GetPresetArgs();
415 bench_opts_t opts = ParseOptions(argc, argv);
416 std::vector<char *> argvAfterShift(argc);
417 ShiftOptions(argc, argv, &argvAfterShift);
418
419 if (opts.jsonPath.empty()) {
420 RegisterAllBenchmarks(opts, presetArgs);
421 } else if (!IsRegularFileExists(opts.jsonPath)) {
422 std::string file("suites" + opts.jsonPath);
423 if (opts.jsonPath[0] == '/' || !IsRegularFileExists(file)) {
424 printf("Cannot find json file %s: does not exist or is not a file.\n", opts.jsonPath.c_str());
425 return 1;
426 }
427 opts.jsonPath = file;
428 }
429
430 if (!opts.jsonPath.empty()) {
431 if (int err = RegisterJsonBenchmarks(opts, presetArgs)) {
432 return err;
433 }
434 }
435
436 if (setpriority(PRIO_PROCESS, 0, -20)) {
437 perror("Set priority of process failed.\n");
438 }
439 int argcAfterShift = argvAfterShift.size();
440 benchmark::Initialize(&argcAfterShift, argvAfterShift.data());
441 benchmark::RunSpecifiedBenchmarks();
442 }
443