1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * builtin-bench.c |
4 | * |
5 | * General benchmarking collections provided by perf |
6 | * |
7 | * Copyright (C) 2009, Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> |
8 | */ |
9 | |
10 | /* |
11 | * Available benchmark collection list: |
12 | * |
13 | * sched ... scheduler and IPC performance |
14 | * syscall ... System call performance |
15 | * mem ... memory access performance |
16 | * numa ... NUMA scheduling and MM performance |
17 | * futex ... Futex performance |
18 | * epoll ... Event poll performance |
19 | */ |
20 | #include <subcmd/parse-options.h> |
21 | #include "builtin.h" |
22 | #include "bench/bench.h" |
23 | |
24 | #include <locale.h> |
25 | #include <stdio.h> |
26 | #include <stdlib.h> |
27 | #include <string.h> |
28 | #include <sys/prctl.h> |
29 | #include <linux/zalloc.h> |
30 | |
31 | typedef int (*bench_fn_t)(int argc, const char **argv); |
32 | |
33 | struct bench { |
34 | const char *name; |
35 | const char *summary; |
36 | bench_fn_t fn; |
37 | }; |
38 | |
39 | #ifdef HAVE_LIBNUMA_SUPPORT |
40 | static struct bench numa_benchmarks[] = { |
41 | { "mem" , "Benchmark for NUMA workloads" , bench_numa }, |
42 | { "all" , "Run all NUMA benchmarks" , NULL }, |
43 | { NULL, NULL, NULL } |
44 | }; |
45 | #endif |
46 | |
47 | static struct bench sched_benchmarks[] = { |
48 | { "messaging" , "Benchmark for scheduling and IPC" , bench_sched_messaging }, |
49 | { "pipe" , "Benchmark for pipe() between two processes" , bench_sched_pipe }, |
50 | { "seccomp-notify" , "Benchmark for seccomp user notify" , bench_sched_seccomp_notify}, |
51 | { "all" , "Run all scheduler benchmarks" , NULL }, |
52 | { NULL, NULL, NULL } |
53 | }; |
54 | |
55 | static struct bench syscall_benchmarks[] = { |
56 | { "basic" , "Benchmark for basic getppid(2) calls" , bench_syscall_basic }, |
57 | { "getpgid" , "Benchmark for getpgid(2) calls" , bench_syscall_getpgid }, |
58 | { "fork" , "Benchmark for fork(2) calls" , bench_syscall_fork }, |
59 | { "execve" , "Benchmark for execve(2) calls" , bench_syscall_execve }, |
60 | { "all" , "Run all syscall benchmarks" , NULL }, |
61 | { NULL, NULL, NULL }, |
62 | }; |
63 | |
64 | static struct bench mem_benchmarks[] = { |
65 | { "memcpy" , "Benchmark for memcpy() functions" , bench_mem_memcpy }, |
66 | { "memset" , "Benchmark for memset() functions" , bench_mem_memset }, |
67 | { "find_bit" , "Benchmark for find_bit() functions" , bench_mem_find_bit }, |
68 | { "all" , "Run all memory access benchmarks" , NULL }, |
69 | { NULL, NULL, NULL } |
70 | }; |
71 | |
72 | static struct bench futex_benchmarks[] = { |
73 | { "hash" , "Benchmark for futex hash table" , bench_futex_hash }, |
74 | { "wake" , "Benchmark for futex wake calls" , bench_futex_wake }, |
75 | { "wake-parallel" , "Benchmark for parallel futex wake calls" , bench_futex_wake_parallel }, |
76 | { "requeue" , "Benchmark for futex requeue calls" , bench_futex_requeue }, |
77 | /* pi-futexes */ |
78 | { "lock-pi" , "Benchmark for futex lock_pi calls" , bench_futex_lock_pi }, |
79 | { "all" , "Run all futex benchmarks" , NULL }, |
80 | { NULL, NULL, NULL } |
81 | }; |
82 | |
83 | #ifdef HAVE_EVENTFD_SUPPORT |
84 | static struct bench epoll_benchmarks[] = { |
85 | { "wait" , "Benchmark epoll concurrent epoll_waits" , bench_epoll_wait }, |
86 | { "ctl" , "Benchmark epoll concurrent epoll_ctls" , bench_epoll_ctl }, |
87 | { "all" , "Run all futex benchmarks" , NULL }, |
88 | { NULL, NULL, NULL } |
89 | }; |
90 | #endif // HAVE_EVENTFD_SUPPORT |
91 | |
92 | static struct bench internals_benchmarks[] = { |
93 | { "synthesize" , "Benchmark perf event synthesis" , bench_synthesize }, |
94 | { "kallsyms-parse" , "Benchmark kallsyms parsing" , bench_kallsyms_parse }, |
95 | { "inject-build-id" , "Benchmark build-id injection" , bench_inject_build_id }, |
96 | { "evlist-open-close" , "Benchmark evlist open and close" , bench_evlist_open_close }, |
97 | { "pmu-scan" , "Benchmark sysfs PMU info scanning" , bench_pmu_scan }, |
98 | { NULL, NULL, NULL } |
99 | }; |
100 | |
101 | static struct bench breakpoint_benchmarks[] = { |
102 | { "thread" , "Benchmark thread start/finish with breakpoints" , bench_breakpoint_thread}, |
103 | { "enable" , "Benchmark breakpoint enable/disable" , bench_breakpoint_enable}, |
104 | { "all" , "Run all breakpoint benchmarks" , NULL}, |
105 | { NULL, NULL, NULL }, |
106 | }; |
107 | |
108 | static struct bench uprobe_benchmarks[] = { |
109 | { "baseline" , "Baseline libc usleep(1000) call" , bench_uprobe_baseline, }, |
110 | { "empty" , "Attach empty BPF prog to uprobe on usleep, system wide" , bench_uprobe_empty, }, |
111 | { "trace_printk" , "Attach trace_printk BPF prog to uprobe on usleep syswide" , bench_uprobe_trace_printk, }, |
112 | { NULL, NULL, NULL }, |
113 | }; |
114 | |
115 | struct collection { |
116 | const char *name; |
117 | const char *summary; |
118 | struct bench *benchmarks; |
119 | }; |
120 | |
121 | static struct collection collections[] = { |
122 | { "sched" , "Scheduler and IPC benchmarks" , sched_benchmarks }, |
123 | { "syscall" , "System call benchmarks" , syscall_benchmarks }, |
124 | { "mem" , "Memory access benchmarks" , mem_benchmarks }, |
125 | #ifdef HAVE_LIBNUMA_SUPPORT |
126 | { "numa" , "NUMA scheduling and MM benchmarks" , numa_benchmarks }, |
127 | #endif |
128 | {"futex" , "Futex stressing benchmarks" , futex_benchmarks }, |
129 | #ifdef HAVE_EVENTFD_SUPPORT |
130 | {"epoll" , "Epoll stressing benchmarks" , epoll_benchmarks }, |
131 | #endif |
132 | { "internals" , "Perf-internals benchmarks" , internals_benchmarks }, |
133 | { "breakpoint" , "Breakpoint benchmarks" , breakpoint_benchmarks }, |
134 | { "uprobe" , "uprobe benchmarks" , uprobe_benchmarks }, |
135 | { "all" , "All benchmarks" , NULL }, |
136 | { NULL, NULL, NULL } |
137 | }; |
138 | |
139 | /* Iterate over all benchmark collections: */ |
140 | #define for_each_collection(coll) \ |
141 | for (coll = collections; coll->name; coll++) |
142 | |
143 | /* Iterate over all benchmarks within a collection: */ |
144 | #define for_each_bench(coll, bench) \ |
145 | for (bench = coll->benchmarks; bench && bench->name; bench++) |
146 | |
147 | static void dump_benchmarks(struct collection *coll) |
148 | { |
149 | struct bench *bench; |
150 | |
151 | printf("\n # List of available benchmarks for collection '%s':\n\n" , coll->name); |
152 | |
153 | for_each_bench(coll, bench) |
154 | printf("%14s: %s\n" , bench->name, bench->summary); |
155 | |
156 | printf("\n" ); |
157 | } |
158 | |
159 | static const char *bench_format_str; |
160 | |
161 | /* Output/formatting style, exported to benchmark modules: */ |
162 | int bench_format = BENCH_FORMAT_DEFAULT; |
163 | unsigned int bench_repeat = 10; /* default number of times to repeat the run */ |
164 | |
165 | static const struct option bench_options[] = { |
166 | OPT_STRING('f', "format" , &bench_format_str, "default|simple" , "Specify the output formatting style" ), |
167 | OPT_UINTEGER('r', "repeat" , &bench_repeat, "Specify number of times to repeat the run" ), |
168 | OPT_END() |
169 | }; |
170 | |
171 | static const char * const bench_usage[] = { |
172 | "perf bench [<common options>] <collection> <benchmark> [<options>]" , |
173 | NULL |
174 | }; |
175 | |
176 | static void print_usage(void) |
177 | { |
178 | struct collection *coll; |
179 | int i; |
180 | |
181 | printf("Usage: \n" ); |
182 | for (i = 0; bench_usage[i]; i++) |
183 | printf("\t%s\n" , bench_usage[i]); |
184 | printf("\n" ); |
185 | |
186 | printf(" # List of all available benchmark collections:\n\n" ); |
187 | |
188 | for_each_collection(coll) |
189 | printf("%14s: %s\n" , coll->name, coll->summary); |
190 | printf("\n" ); |
191 | } |
192 | |
193 | static int bench_str2int(const char *str) |
194 | { |
195 | if (!str) |
196 | return BENCH_FORMAT_DEFAULT; |
197 | |
198 | if (!strcmp(str, BENCH_FORMAT_DEFAULT_STR)) |
199 | return BENCH_FORMAT_DEFAULT; |
200 | else if (!strcmp(str, BENCH_FORMAT_SIMPLE_STR)) |
201 | return BENCH_FORMAT_SIMPLE; |
202 | |
203 | return BENCH_FORMAT_UNKNOWN; |
204 | } |
205 | |
206 | /* |
207 | * Run a specific benchmark but first rename the running task's ->comm[] |
208 | * to something meaningful: |
209 | */ |
210 | static int run_bench(const char *coll_name, const char *bench_name, bench_fn_t fn, |
211 | int argc, const char **argv) |
212 | { |
213 | int size; |
214 | char *name; |
215 | int ret; |
216 | |
217 | size = strlen(coll_name) + 1 + strlen(bench_name) + 1; |
218 | |
219 | name = zalloc(size); |
220 | BUG_ON(!name); |
221 | |
222 | scnprintf(name, size, "%s-%s" , coll_name, bench_name); |
223 | |
224 | prctl(PR_SET_NAME, name); |
225 | argv[0] = name; |
226 | |
227 | ret = fn(argc, argv); |
228 | |
229 | free(name); |
230 | |
231 | return ret; |
232 | } |
233 | |
234 | static void run_collection(struct collection *coll) |
235 | { |
236 | struct bench *bench; |
237 | const char *argv[2]; |
238 | |
239 | argv[1] = NULL; |
240 | /* |
241 | * TODO: |
242 | * |
243 | * Preparing preset parameters for |
244 | * embedded, ordinary PC, HPC, etc... |
245 | * would be helpful. |
246 | */ |
247 | for_each_bench(coll, bench) { |
248 | if (!bench->fn) |
249 | break; |
250 | printf("# Running %s/%s benchmark...\n" , coll->name, bench->name); |
251 | |
252 | argv[1] = bench->name; |
253 | run_bench(coll_name: coll->name, bench_name: bench->name, fn: bench->fn, argc: 1, argv); |
254 | printf("\n" ); |
255 | } |
256 | } |
257 | |
258 | static void run_all_collections(void) |
259 | { |
260 | struct collection *coll; |
261 | |
262 | for_each_collection(coll) |
263 | run_collection(coll); |
264 | } |
265 | |
266 | int cmd_bench(int argc, const char **argv) |
267 | { |
268 | struct collection *coll; |
269 | int ret = 0; |
270 | |
271 | /* Unbuffered output */ |
272 | setvbuf(stdout, NULL, _IONBF, 0); |
273 | setlocale(LC_ALL, "" ); |
274 | |
275 | if (argc < 2) { |
276 | /* No collection specified. */ |
277 | print_usage(); |
278 | goto end; |
279 | } |
280 | |
281 | argc = parse_options(argc, argv, bench_options, bench_usage, |
282 | PARSE_OPT_STOP_AT_NON_OPTION); |
283 | |
284 | bench_format = bench_str2int(str: bench_format_str); |
285 | if (bench_format == BENCH_FORMAT_UNKNOWN) { |
286 | printf("Unknown format descriptor: '%s'\n" , bench_format_str); |
287 | goto end; |
288 | } |
289 | |
290 | if (bench_repeat == 0) { |
291 | printf("Invalid repeat option: Must specify a positive value\n" ); |
292 | goto end; |
293 | } |
294 | |
295 | if (argc < 1) { |
296 | print_usage(); |
297 | goto end; |
298 | } |
299 | |
300 | if (!strcmp(argv[0], "all" )) { |
301 | run_all_collections(); |
302 | goto end; |
303 | } |
304 | |
305 | for_each_collection(coll) { |
306 | struct bench *bench; |
307 | |
308 | if (strcmp(coll->name, argv[0])) |
309 | continue; |
310 | |
311 | if (argc < 2) { |
312 | /* No bench specified. */ |
313 | dump_benchmarks(coll); |
314 | goto end; |
315 | } |
316 | |
317 | if (!strcmp(argv[1], "all" )) { |
318 | run_collection(coll); |
319 | goto end; |
320 | } |
321 | |
322 | for_each_bench(coll, bench) { |
323 | if (strcmp(bench->name, argv[1])) |
324 | continue; |
325 | |
326 | if (bench_format == BENCH_FORMAT_DEFAULT) |
327 | printf("# Running '%s/%s' benchmark:\n" , coll->name, bench->name); |
328 | ret = run_bench(coll_name: coll->name, bench_name: bench->name, fn: bench->fn, argc: argc-1, argv: argv+1); |
329 | goto end; |
330 | } |
331 | |
332 | if (!strcmp(argv[1], "-h" ) || !strcmp(argv[1], "--help" )) { |
333 | dump_benchmarks(coll); |
334 | goto end; |
335 | } |
336 | |
337 | printf("Unknown benchmark: '%s' for collection '%s'\n" , argv[1], argv[0]); |
338 | ret = 1; |
339 | goto end; |
340 | } |
341 | |
342 | printf("Unknown collection: '%s'\n" , argv[0]); |
343 | ret = 1; |
344 | |
345 | end: |
346 | return ret; |
347 | } |
348 | |