1 // SPDX-License-Identifier: GPL-2.0
11 #include <linux/kernel.h>
12 #include <linux/time64.h>
13 #include <linux/list.h>
14 #include <linux/err.h>
15 #include <internal/lib.h>
16 #include <subcmd/parse-options.h>
19 #include "util/data.h"
20 #include "util/stat.h"
21 #include "util/debug.h"
22 #include "util/event.h"
23 #include "util/symbol.h"
24 #include "util/session.h"
25 #include "util/build-id.h"
26 #include "util/synthetic-events.h"
28 #define MMAP_DEV_MAJOR 8
29 #define DSO_MMAP_RATIO 4
31 static unsigned int iterations
= 100;
32 static unsigned int nr_mmaps
= 100;
33 static unsigned int nr_samples
= 100; /* samples per mmap */
35 static u64 bench_sample_type
;
36 static u16 bench_id_hdr_size
;
46 struct list_head list
;
52 static struct bench_dso
*dsos
;
54 extern int cmd_inject(int argc
, const char *argv
[]);
56 static const struct option options
[] = {
57 OPT_UINTEGER('i', "iterations", &iterations
,
58 "Number of iterations used to compute average (default: 100)"),
59 OPT_UINTEGER('m', "nr-mmaps", &nr_mmaps
,
60 "Number of mmap events for each iteration (default: 100)"),
61 OPT_UINTEGER('n', "nr-samples", &nr_samples
,
62 "Number of sample events per mmap event (default: 100)"),
63 OPT_INCR('v', "verbose", &verbose
,
64 "be more verbose (show iteration count, DSO name, etc)"),
68 static const char *const bench_usage
[] = {
69 "perf bench internals inject-build-id <options>",
74 * Helper for collect_dso that adds the given file as a dso to dso_list
75 * if it contains a build-id. Stops after collecting 4 times more than
76 * we need (for MMAP2 events).
78 static int add_dso(const char *fpath
, const struct stat
*sb __maybe_unused
,
79 int typeflag
, struct FTW
*ftwbuf __maybe_unused
)
81 struct bench_dso
*dso
= &dsos
[nr_dsos
];
84 if (typeflag
== FTW_D
|| typeflag
== FTW_SL
)
87 if (filename__read_build_id(fpath
, &bid
) < 0)
90 dso
->name
= realpath(fpath
, NULL
);
91 if (dso
->name
== NULL
)
95 pr_debug2(" Adding DSO: %s\n", fpath
);
97 /* stop if we collected enough DSOs */
98 if ((unsigned int)nr_dsos
== DSO_MMAP_RATIO
* nr_mmaps
)
104 static void collect_dso(void)
106 dsos
= calloc(nr_mmaps
* DSO_MMAP_RATIO
, sizeof(*dsos
));
108 printf(" Memory allocation failed\n");
112 if (nftw("/usr/lib/", add_dso
, 10, FTW_PHYS
) < 0)
115 pr_debug(" Collected %d DSOs\n", nr_dsos
);
118 static void release_dso(void)
122 for (i
= 0; i
< nr_dsos
; i
++) {
123 struct bench_dso
*dso
= &dsos
[i
];
130 /* Fake address used by mmap and sample events */
131 static u64
dso_map_addr(struct bench_dso
*dso
)
133 return 0x400000ULL
+ dso
->ino
* 8192ULL;
136 static u32
synthesize_attr(struct bench_data
*data
)
138 union perf_event event
;
140 memset(&event
, 0, sizeof(event
.attr
) + sizeof(u64
));
142 event
.header
.type
= PERF_RECORD_HEADER_ATTR
;
143 event
.header
.size
= sizeof(event
.attr
) + sizeof(u64
);
145 event
.attr
.attr
.type
= PERF_TYPE_SOFTWARE
;
146 event
.attr
.attr
.config
= PERF_COUNT_SW_TASK_CLOCK
;
147 event
.attr
.attr
.exclude_kernel
= 1;
148 event
.attr
.attr
.sample_id_all
= 1;
149 event
.attr
.attr
.sample_type
= bench_sample_type
;
151 return writen(data
->input_pipe
[1], &event
, event
.header
.size
);
154 static u32
synthesize_fork(struct bench_data
*data
)
156 union perf_event event
;
158 memset(&event
, 0, sizeof(event
.fork
) + bench_id_hdr_size
);
160 event
.header
.type
= PERF_RECORD_FORK
;
161 event
.header
.misc
= PERF_RECORD_MISC_FORK_EXEC
;
162 event
.header
.size
= sizeof(event
.fork
) + bench_id_hdr_size
;
166 event
.fork
.pid
= data
->pid
;
167 event
.fork
.tid
= data
->pid
;
169 return writen(data
->input_pipe
[1], &event
, event
.header
.size
);
172 static u32
synthesize_mmap(struct bench_data
*data
, struct bench_dso
*dso
,
175 union perf_event event
;
176 size_t len
= offsetof(struct perf_record_mmap2
, filename
);
177 u64
*id_hdr_ptr
= (void *)&event
;
180 len
+= roundup(strlen(dso
->name
) + 1, 8) + bench_id_hdr_size
;
182 memset(&event
, 0, min(len
, sizeof(event
.mmap2
)));
184 event
.header
.type
= PERF_RECORD_MMAP2
;
185 event
.header
.misc
= PERF_RECORD_MISC_USER
;
186 event
.header
.size
= len
;
188 event
.mmap2
.pid
= data
->pid
;
189 event
.mmap2
.tid
= data
->pid
;
190 event
.mmap2
.maj
= MMAP_DEV_MAJOR
;
191 event
.mmap2
.ino
= dso
->ino
;
193 strcpy(event
.mmap2
.filename
, dso
->name
);
195 event
.mmap2
.start
= dso_map_addr(dso
);
196 event
.mmap2
.len
= 4096;
197 event
.mmap2
.prot
= PROT_EXEC
;
199 if (len
> sizeof(event
.mmap2
)) {
200 /* write mmap2 event first */
201 writen(data
->input_pipe
[1], &event
, len
- bench_id_hdr_size
);
202 /* zero-fill sample id header */
203 memset(id_hdr_ptr
, 0, bench_id_hdr_size
);
204 /* put timestamp in the right position */
205 ts_idx
= (bench_id_hdr_size
/ sizeof(u64
)) - 2;
206 id_hdr_ptr
[ts_idx
] = timestamp
;
207 writen(data
->input_pipe
[1], id_hdr_ptr
, bench_id_hdr_size
);
209 ts_idx
= (len
/ sizeof(u64
)) - 2;
210 id_hdr_ptr
[ts_idx
] = timestamp
;
211 writen(data
->input_pipe
[1], &event
, len
);
216 static u32
synthesize_sample(struct bench_data
*data
, struct bench_dso
*dso
,
219 union perf_event event
;
220 struct perf_sample sample
= {
223 .ip
= dso_map_addr(dso
),
227 event
.header
.type
= PERF_RECORD_SAMPLE
;
228 event
.header
.misc
= PERF_RECORD_MISC_USER
;
229 event
.header
.size
= perf_event__sample_event_size(&sample
, bench_sample_type
, 0);
231 perf_event__synthesize_sample(&event
, bench_sample_type
, 0, &sample
);
233 return writen(data
->input_pipe
[1], &event
, event
.header
.size
);
236 static u32
synthesize_flush(struct bench_data
*data
)
238 struct perf_event_header header
= {
239 .size
= sizeof(header
),
240 .type
= PERF_RECORD_FINISHED_ROUND
,
243 return writen(data
->input_pipe
[1], &header
, header
.size
);
246 static void *data_reader(void *arg
)
248 struct bench_data
*data
= arg
;
253 flag
= fcntl(data
->output_pipe
[0], F_GETFL
);
254 fcntl(data
->output_pipe
[0], F_SETFL
, flag
| O_NONBLOCK
);
256 /* read out data from child */
258 n
= read(data
->output_pipe
[0], buf
, sizeof(buf
));
264 if (errno
!= EINTR
&& errno
!= EAGAIN
)
270 close(data
->output_pipe
[0]);
274 static int setup_injection(struct bench_data
*data
, bool build_id_all
)
280 if (pipe(ready_pipe
) < 0)
283 if (pipe(data
->input_pipe
) < 0)
286 if (pipe(data
->output_pipe
) < 0)
293 if (data
->pid
== 0) {
294 const char **inject_argv
;
297 close(data
->input_pipe
[1]);
298 close(data
->output_pipe
[0]);
299 close(ready_pipe
[0]);
301 dup2(data
->input_pipe
[0], STDIN_FILENO
);
302 close(data
->input_pipe
[0]);
303 dup2(data
->output_pipe
[1], STDOUT_FILENO
);
304 close(data
->output_pipe
[1]);
306 dev_null_fd
= open("/dev/null", O_WRONLY
);
310 dup2(dev_null_fd
, STDERR_FILENO
);
315 inject_argv
= calloc(inject_argc
+ 1, sizeof(*inject_argv
));
316 if (inject_argv
== NULL
)
319 inject_argv
[0] = strdup("inject");
320 inject_argv
[1] = strdup("-b");
322 inject_argv
[2] = strdup("--buildid-all");
324 /* signal that we're ready to go */
325 close(ready_pipe
[1]);
327 cmd_inject(inject_argc
, inject_argv
);
332 pthread_create(&data
->th
, NULL
, data_reader
, data
);
334 close(ready_pipe
[1]);
335 close(data
->input_pipe
[0]);
336 close(data
->output_pipe
[1]);
338 /* wait for child ready */
339 if (read(ready_pipe
[0], &buf
, 1) < 0)
341 close(ready_pipe
[0]);
346 static int inject_build_id(struct bench_data
*data
, u64
*max_rss
)
350 struct rusage rusage
;
353 /* this makes the child to run */
354 if (perf_header__write_pipe(data
->input_pipe
[1]) < 0)
357 len
+= synthesize_attr(data
);
358 len
+= synthesize_fork(data
);
360 for (i
= 0; i
< nr_mmaps
; i
++) {
361 int idx
= rand() % (nr_dsos
- 1);
362 struct bench_dso
*dso
= &dsos
[idx
];
363 u64 timestamp
= rand() % 1000000;
365 pr_debug2(" [%d] injecting: %s\n", i
+1, dso
->name
);
366 len
+= synthesize_mmap(data
, dso
, timestamp
);
368 for (k
= 0; k
< nr_samples
; k
++)
369 len
+= synthesize_sample(data
, dso
, timestamp
+ k
* 1000);
371 if ((i
+ 1) % 10 == 0)
372 len
+= synthesize_flush(data
);
375 /* tihs makes the child to finish */
376 close(data
->input_pipe
[1]);
378 wait4(data
->pid
, &status
, 0, &rusage
);
379 *max_rss
= rusage
.ru_maxrss
;
381 pr_debug(" Child %d exited with %d\n", data
->pid
, status
);
386 static void do_inject_loop(struct bench_data
*data
, bool build_id_all
)
389 struct stats time_stats
, mem_stats
;
390 double time_average
, time_stddev
;
391 double mem_average
, mem_stddev
;
393 init_stats(&time_stats
);
394 init_stats(&mem_stats
);
396 pr_debug(" Build-id%s injection benchmark\n", build_id_all
? "-all" : "");
398 for (i
= 0; i
< iterations
; i
++) {
399 struct timeval start
, end
, diff
;
400 u64 runtime_us
, max_rss
;
402 pr_debug(" Iteration #%d\n", i
+1);
404 if (setup_injection(data
, build_id_all
) < 0) {
405 printf(" Build-id injection setup failed\n");
409 gettimeofday(&start
, NULL
);
410 if (inject_build_id(data
, &max_rss
) < 0) {
411 printf(" Build-id injection failed\n");
415 gettimeofday(&end
, NULL
);
416 timersub(&end
, &start
, &diff
);
417 runtime_us
= diff
.tv_sec
* USEC_PER_SEC
+ diff
.tv_usec
;
418 update_stats(&time_stats
, runtime_us
);
419 update_stats(&mem_stats
, max_rss
);
421 pthread_join(data
->th
, NULL
);
424 time_average
= avg_stats(&time_stats
) / USEC_PER_MSEC
;
425 time_stddev
= stddev_stats(&time_stats
) / USEC_PER_MSEC
;
426 printf(" Average build-id%s injection took: %.3f msec (+- %.3f msec)\n",
427 build_id_all
? "-all" : "", time_average
, time_stddev
);
429 /* each iteration, it processes MMAP2 + BUILD_ID + nr_samples * SAMPLE */
430 time_average
= avg_stats(&time_stats
) / (nr_mmaps
* (nr_samples
+ 2));
431 time_stddev
= stddev_stats(&time_stats
) / (nr_mmaps
* (nr_samples
+ 2));
432 printf(" Average time per event: %.3f usec (+- %.3f usec)\n",
433 time_average
, time_stddev
);
435 mem_average
= avg_stats(&mem_stats
);
436 mem_stddev
= stddev_stats(&mem_stats
);
437 printf(" Average memory usage: %.0f KB (+- %.0f KB)\n",
438 mem_average
, mem_stddev
);
441 static int do_inject_loops(struct bench_data
*data
)
447 bench_sample_type
= PERF_SAMPLE_IDENTIFIER
| PERF_SAMPLE_IP
;
448 bench_sample_type
|= PERF_SAMPLE_TID
| PERF_SAMPLE_TIME
;
449 bench_id_hdr_size
= 32;
453 printf(" Cannot collect DSOs for injection\n");
457 do_inject_loop(data
, false);
458 do_inject_loop(data
, true);
464 int bench_inject_build_id(int argc
, const char **argv
)
466 struct bench_data data
;
468 argc
= parse_options(argc
, argv
, options
, bench_usage
, 0);
470 usage_with_options(bench_usage
, options
);
474 return do_inject_loops(&data
);