Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 #include <stdlib.h>
0003 #include <stddef.h>
0004 #include <ftw.h>
0005 #include <fcntl.h>
0006 #include <errno.h>
0007 #include <unistd.h>
0008 #include <pthread.h>
0009 #include <sys/mman.h>
0010 #include <sys/wait.h>
0011 #include <linux/kernel.h>
0012 #include <linux/time64.h>
0013 #include <linux/list.h>
0014 #include <linux/err.h>
0015 #include <internal/lib.h>
0016 #include <subcmd/parse-options.h>
0017 
0018 #include "bench.h"
0019 #include "util/data.h"
0020 #include "util/stat.h"
0021 #include "util/debug.h"
0022 #include "util/event.h"
0023 #include "util/symbol.h"
0024 #include "util/session.h"
0025 #include "util/build-id.h"
0026 #include "util/synthetic-events.h"
0027 
0028 #define MMAP_DEV_MAJOR  8
0029 #define DSO_MMAP_RATIO  4
0030 
0031 static unsigned int iterations = 100;
0032 static unsigned int nr_mmaps   = 100;
0033 static unsigned int nr_samples = 100;  /* samples per mmap */
0034 
0035 static u64 bench_sample_type;
0036 static u16 bench_id_hdr_size;
0037 
0038 struct bench_data {
0039     int         pid;
0040     int         input_pipe[2];
0041     int         output_pipe[2];
0042     pthread_t       th;
0043 };
0044 
0045 struct bench_dso {
0046     struct list_head    list;
0047     char            *name;
0048     int         ino;
0049 };
0050 
0051 static int nr_dsos;
0052 static struct bench_dso *dsos;
0053 
0054 extern int cmd_inject(int argc, const char *argv[]);
0055 
0056 static const struct option options[] = {
0057     OPT_UINTEGER('i', "iterations", &iterations,
0058              "Number of iterations used to compute average (default: 100)"),
0059     OPT_UINTEGER('m', "nr-mmaps", &nr_mmaps,
0060              "Number of mmap events for each iteration (default: 100)"),
0061     OPT_UINTEGER('n', "nr-samples", &nr_samples,
0062              "Number of sample events per mmap event (default: 100)"),
0063     OPT_INCR('v', "verbose", &verbose,
0064          "be more verbose (show iteration count, DSO name, etc)"),
0065     OPT_END()
0066 };
0067 
0068 static const char *const bench_usage[] = {
0069     "perf bench internals inject-build-id <options>",
0070     NULL
0071 };
0072 
0073 /*
0074  * Helper for collect_dso that adds the given file as a dso to dso_list
0075  * if it contains a build-id.  Stops after collecting 4 times more than
0076  * we need (for MMAP2 events).
0077  */
0078 static int add_dso(const char *fpath, const struct stat *sb __maybe_unused,
0079            int typeflag, struct FTW *ftwbuf __maybe_unused)
0080 {
0081     struct bench_dso *dso = &dsos[nr_dsos];
0082     struct build_id bid;
0083 
0084     if (typeflag == FTW_D || typeflag == FTW_SL)
0085         return 0;
0086 
0087     if (filename__read_build_id(fpath, &bid) < 0)
0088         return 0;
0089 
0090     dso->name = realpath(fpath, NULL);
0091     if (dso->name == NULL)
0092         return -1;
0093 
0094     dso->ino = nr_dsos++;
0095     pr_debug2("  Adding DSO: %s\n", fpath);
0096 
0097     /* stop if we collected enough DSOs */
0098     if ((unsigned int)nr_dsos == DSO_MMAP_RATIO * nr_mmaps)
0099         return 1;
0100 
0101     return 0;
0102 }
0103 
0104 static void collect_dso(void)
0105 {
0106     dsos = calloc(nr_mmaps * DSO_MMAP_RATIO, sizeof(*dsos));
0107     if (dsos == NULL) {
0108         printf("  Memory allocation failed\n");
0109         exit(1);
0110     }
0111 
0112     if (nftw("/usr/lib/", add_dso, 10, FTW_PHYS) < 0)
0113         return;
0114 
0115     pr_debug("  Collected %d DSOs\n", nr_dsos);
0116 }
0117 
0118 static void release_dso(void)
0119 {
0120     int i;
0121 
0122     for (i = 0; i < nr_dsos; i++) {
0123         struct bench_dso *dso = &dsos[i];
0124 
0125         free(dso->name);
0126     }
0127     free(dsos);
0128 }
0129 
0130 /* Fake address used by mmap and sample events */
0131 static u64 dso_map_addr(struct bench_dso *dso)
0132 {
0133     return 0x400000ULL + dso->ino * 8192ULL;
0134 }
0135 
0136 static ssize_t synthesize_attr(struct bench_data *data)
0137 {
0138     union perf_event event;
0139 
0140     memset(&event, 0, sizeof(event.attr) + sizeof(u64));
0141 
0142     event.header.type = PERF_RECORD_HEADER_ATTR;
0143     event.header.size = sizeof(event.attr) + sizeof(u64);
0144 
0145     event.attr.attr.type = PERF_TYPE_SOFTWARE;
0146     event.attr.attr.config = PERF_COUNT_SW_TASK_CLOCK;
0147     event.attr.attr.exclude_kernel = 1;
0148     event.attr.attr.sample_id_all = 1;
0149     event.attr.attr.sample_type = bench_sample_type;
0150 
0151     return writen(data->input_pipe[1], &event, event.header.size);
0152 }
0153 
0154 static ssize_t synthesize_fork(struct bench_data *data)
0155 {
0156     union perf_event event;
0157 
0158     memset(&event, 0, sizeof(event.fork) + bench_id_hdr_size);
0159 
0160     event.header.type = PERF_RECORD_FORK;
0161     event.header.misc = PERF_RECORD_MISC_FORK_EXEC;
0162     event.header.size = sizeof(event.fork) + bench_id_hdr_size;
0163 
0164     event.fork.ppid = 1;
0165     event.fork.ptid = 1;
0166     event.fork.pid = data->pid;
0167     event.fork.tid = data->pid;
0168 
0169     return writen(data->input_pipe[1], &event, event.header.size);
0170 }
0171 
0172 static ssize_t synthesize_mmap(struct bench_data *data, struct bench_dso *dso, u64 timestamp)
0173 {
0174     union perf_event event;
0175     size_t len = offsetof(struct perf_record_mmap2, filename);
0176     u64 *id_hdr_ptr = (void *)&event;
0177     int ts_idx;
0178 
0179     len += roundup(strlen(dso->name) + 1, 8) + bench_id_hdr_size;
0180 
0181     memset(&event, 0, min(len, sizeof(event.mmap2)));
0182 
0183     event.header.type = PERF_RECORD_MMAP2;
0184     event.header.misc = PERF_RECORD_MISC_USER;
0185     event.header.size = len;
0186 
0187     event.mmap2.pid = data->pid;
0188     event.mmap2.tid = data->pid;
0189     event.mmap2.maj = MMAP_DEV_MAJOR;
0190     event.mmap2.ino = dso->ino;
0191 
0192     strcpy(event.mmap2.filename, dso->name);
0193 
0194     event.mmap2.start = dso_map_addr(dso);
0195     event.mmap2.len = 4096;
0196     event.mmap2.prot = PROT_EXEC;
0197 
0198     if (len > sizeof(event.mmap2)) {
0199         /* write mmap2 event first */
0200         if (writen(data->input_pipe[1], &event, len - bench_id_hdr_size) < 0)
0201             return -1;
0202         /* zero-fill sample id header */
0203         memset(id_hdr_ptr, 0, bench_id_hdr_size);
0204         /* put timestamp in the right position */
0205         ts_idx = (bench_id_hdr_size / sizeof(u64)) - 2;
0206         id_hdr_ptr[ts_idx] = timestamp;
0207         if (writen(data->input_pipe[1], id_hdr_ptr, bench_id_hdr_size) < 0)
0208             return -1;
0209 
0210         return len;
0211     }
0212 
0213     ts_idx = (len / sizeof(u64)) - 2;
0214     id_hdr_ptr[ts_idx] = timestamp;
0215     return writen(data->input_pipe[1], &event, len);
0216 }
0217 
0218 static ssize_t synthesize_sample(struct bench_data *data, struct bench_dso *dso, u64 timestamp)
0219 {
0220     union perf_event event;
0221     struct perf_sample sample = {
0222         .tid = data->pid,
0223         .pid = data->pid,
0224         .ip = dso_map_addr(dso),
0225         .time = timestamp,
0226     };
0227 
0228     event.header.type = PERF_RECORD_SAMPLE;
0229     event.header.misc = PERF_RECORD_MISC_USER;
0230     event.header.size = perf_event__sample_event_size(&sample, bench_sample_type, 0);
0231 
0232     perf_event__synthesize_sample(&event, bench_sample_type, 0, &sample);
0233 
0234     return writen(data->input_pipe[1], &event, event.header.size);
0235 }
0236 
0237 static ssize_t synthesize_flush(struct bench_data *data)
0238 {
0239     struct perf_event_header header = {
0240         .size = sizeof(header),
0241         .type = PERF_RECORD_FINISHED_ROUND,
0242     };
0243 
0244     return writen(data->input_pipe[1], &header, header.size);
0245 }
0246 
0247 static void *data_reader(void *arg)
0248 {
0249     struct bench_data *data = arg;
0250     char buf[8192];
0251     int flag;
0252     int n;
0253 
0254     flag = fcntl(data->output_pipe[0], F_GETFL);
0255     fcntl(data->output_pipe[0], F_SETFL, flag | O_NONBLOCK);
0256 
0257     /* read out data from child */
0258     while (true) {
0259         n = read(data->output_pipe[0], buf, sizeof(buf));
0260         if (n > 0)
0261             continue;
0262         if (n == 0)
0263             break;
0264 
0265         if (errno != EINTR && errno != EAGAIN)
0266             break;
0267 
0268         usleep(100);
0269     }
0270 
0271     close(data->output_pipe[0]);
0272     return NULL;
0273 }
0274 
0275 static int setup_injection(struct bench_data *data, bool build_id_all)
0276 {
0277     int ready_pipe[2];
0278     int dev_null_fd;
0279     char buf;
0280 
0281     if (pipe(ready_pipe) < 0)
0282         return -1;
0283 
0284     if (pipe(data->input_pipe) < 0)
0285         return -1;
0286 
0287     if (pipe(data->output_pipe) < 0)
0288         return -1;
0289 
0290     data->pid = fork();
0291     if (data->pid < 0)
0292         return -1;
0293 
0294     if (data->pid == 0) {
0295         const char **inject_argv;
0296         int inject_argc = 2;
0297 
0298         close(data->input_pipe[1]);
0299         close(data->output_pipe[0]);
0300         close(ready_pipe[0]);
0301 
0302         dup2(data->input_pipe[0], STDIN_FILENO);
0303         close(data->input_pipe[0]);
0304         dup2(data->output_pipe[1], STDOUT_FILENO);
0305         close(data->output_pipe[1]);
0306 
0307         dev_null_fd = open("/dev/null", O_WRONLY);
0308         if (dev_null_fd < 0)
0309             exit(1);
0310 
0311         dup2(dev_null_fd, STDERR_FILENO);
0312 
0313         if (build_id_all)
0314             inject_argc++;
0315 
0316         inject_argv = calloc(inject_argc + 1, sizeof(*inject_argv));
0317         if (inject_argv == NULL)
0318             exit(1);
0319 
0320         inject_argv[0] = strdup("inject");
0321         inject_argv[1] = strdup("-b");
0322         if (build_id_all)
0323             inject_argv[2] = strdup("--buildid-all");
0324 
0325         /* signal that we're ready to go */
0326         close(ready_pipe[1]);
0327 
0328         cmd_inject(inject_argc, inject_argv);
0329 
0330         exit(0);
0331     }
0332 
0333     pthread_create(&data->th, NULL, data_reader, data);
0334 
0335     close(ready_pipe[1]);
0336     close(data->input_pipe[0]);
0337     close(data->output_pipe[1]);
0338 
0339     /* wait for child ready */
0340     if (read(ready_pipe[0], &buf, 1) < 0)
0341         return -1;
0342     close(ready_pipe[0]);
0343 
0344     return 0;
0345 }
0346 
0347 static int inject_build_id(struct bench_data *data, u64 *max_rss)
0348 {
0349     int status;
0350     unsigned int i, k;
0351     struct rusage rusage;
0352 
0353     /* this makes the child to run */
0354     if (perf_header__write_pipe(data->input_pipe[1]) < 0)
0355         return -1;
0356 
0357     if (synthesize_attr(data) < 0)
0358         return -1;
0359 
0360     if (synthesize_fork(data) < 0)
0361         return -1;
0362 
0363     for (i = 0; i < nr_mmaps; i++) {
0364         int idx = rand() % (nr_dsos - 1);
0365         struct bench_dso *dso = &dsos[idx];
0366         u64 timestamp = rand() % 1000000;
0367 
0368         pr_debug2("   [%d] injecting: %s\n", i+1, dso->name);
0369         if (synthesize_mmap(data, dso, timestamp) < 0)
0370             return -1;
0371 
0372         for (k = 0; k < nr_samples; k++) {
0373             if (synthesize_sample(data, dso, timestamp + k * 1000) < 0)
0374                 return -1;
0375         }
0376 
0377         if ((i + 1) % 10 == 0) {
0378             if (synthesize_flush(data) < 0)
0379                 return -1;
0380         }
0381     }
0382 
0383     /* this makes the child to finish */
0384     close(data->input_pipe[1]);
0385 
0386     wait4(data->pid, &status, 0, &rusage);
0387     *max_rss = rusage.ru_maxrss;
0388 
0389     pr_debug("   Child %d exited with %d\n", data->pid, status);
0390 
0391     return 0;
0392 }
0393 
0394 static void do_inject_loop(struct bench_data *data, bool build_id_all)
0395 {
0396     unsigned int i;
0397     struct stats time_stats, mem_stats;
0398     double time_average, time_stddev;
0399     double mem_average, mem_stddev;
0400 
0401     init_stats(&time_stats);
0402     init_stats(&mem_stats);
0403 
0404     pr_debug("  Build-id%s injection benchmark\n", build_id_all ? "-all" : "");
0405 
0406     for (i = 0; i < iterations; i++) {
0407         struct timeval start, end, diff;
0408         u64 runtime_us, max_rss;
0409 
0410         pr_debug("  Iteration #%d\n", i+1);
0411 
0412         if (setup_injection(data, build_id_all) < 0) {
0413             printf("  Build-id injection setup failed\n");
0414             break;
0415         }
0416 
0417         gettimeofday(&start, NULL);
0418         if (inject_build_id(data, &max_rss) < 0) {
0419             printf("  Build-id injection failed\n");
0420             break;
0421         }
0422 
0423         gettimeofday(&end, NULL);
0424         timersub(&end, &start, &diff);
0425         runtime_us = diff.tv_sec * USEC_PER_SEC + diff.tv_usec;
0426         update_stats(&time_stats, runtime_us);
0427         update_stats(&mem_stats, max_rss);
0428 
0429         pthread_join(data->th, NULL);
0430     }
0431 
0432     time_average = avg_stats(&time_stats) / USEC_PER_MSEC;
0433     time_stddev = stddev_stats(&time_stats) / USEC_PER_MSEC;
0434     printf("  Average build-id%s injection took: %.3f msec (+- %.3f msec)\n",
0435            build_id_all ? "-all" : "", time_average, time_stddev);
0436 
0437     /* each iteration, it processes MMAP2 + BUILD_ID + nr_samples * SAMPLE */
0438     time_average = avg_stats(&time_stats) / (nr_mmaps * (nr_samples + 2));
0439     time_stddev = stddev_stats(&time_stats) / (nr_mmaps * (nr_samples + 2));
0440     printf("  Average time per event: %.3f usec (+- %.3f usec)\n",
0441         time_average, time_stddev);
0442 
0443     mem_average = avg_stats(&mem_stats);
0444     mem_stddev = stddev_stats(&mem_stats);
0445     printf("  Average memory usage: %.0f KB (+- %.0f KB)\n",
0446         mem_average, mem_stddev);
0447 }
0448 
0449 static int do_inject_loops(struct bench_data *data)
0450 {
0451 
0452     srand(time(NULL));
0453     symbol__init(NULL);
0454 
0455     bench_sample_type  = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP;
0456     bench_sample_type |= PERF_SAMPLE_TID | PERF_SAMPLE_TIME;
0457     bench_id_hdr_size  = 32;
0458 
0459     collect_dso();
0460     if (nr_dsos == 0) {
0461         printf("  Cannot collect DSOs for injection\n");
0462         return -1;
0463     }
0464 
0465     do_inject_loop(data, false);
0466     do_inject_loop(data, true);
0467 
0468     release_dso();
0469     return 0;
0470 }
0471 
0472 int bench_inject_build_id(int argc, const char **argv)
0473 {
0474     struct bench_data data;
0475 
0476     argc = parse_options(argc, argv, options, bench_usage, 0);
0477     if (argc) {
0478         usage_with_options(bench_usage, options);
0479         exit(EXIT_FAILURE);
0480     }
0481 
0482     return do_inject_loops(&data);
0483 }
0484