/* * builtin-record.c * * Builtin record command: Record the profile of a workload * (or a CPU, or a PID) into the perf.data output file - for * later analysis via perf report. */ #include "builtin.h" #include "perf.h" #include "util/util.h" #include "util/parse-options.h" #include "util/parse-events.h" #include "util/string.h" #include #include #define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1) #define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask)) static int fd[MAX_NR_CPUS][MAX_COUNTERS]; static long default_interval = 100000; static int nr_cpus = 0; static unsigned int page_size; static unsigned int mmap_pages = 128; static int freq = 0; static int output; static const char *output_name = "perf.data"; static int group = 0; static unsigned int realtime_prio = 0; static int system_wide = 0; static pid_t target_pid = -1; static int inherit = 1; static int force = 0; static int append_file = 0; static int call_graph = 0; static int verbose = 0; static long samples; static struct timeval last_read; static struct timeval this_read; static u64 bytes_written; static struct pollfd event_array[MAX_NR_CPUS * MAX_COUNTERS]; static int nr_poll; static int nr_cpu; static int file_new = 1; static struct perf_file_header file_header; struct mmap_event { struct perf_event_header header; u32 pid; u32 tid; u64 start; u64 len; u64 pgoff; char filename[PATH_MAX]; }; struct comm_event { struct perf_event_header header; u32 pid; u32 tid; char comm[16]; }; struct mmap_data { int counter; void *base; unsigned int mask; unsigned int prev; }; static struct mmap_data mmap_array[MAX_NR_CPUS][MAX_COUNTERS]; static unsigned long mmap_read_head(struct mmap_data *md) { struct perf_counter_mmap_page *pc = md->base; long head; head = pc->data_head; rmb(); return head; } static void mmap_write_tail(struct mmap_data *md, unsigned long tail) { struct perf_counter_mmap_page *pc = md->base; /* * ensure all reads are done before we write the tail out. */ /* mb(); */ pc->data_tail = tail; } static void write_output(void *buf, size_t size) { while (size) { int ret = write(output, buf, size); if (ret < 0) die("failed to write"); size -= ret; buf += ret; bytes_written += ret; } } static void mmap_read(struct mmap_data *md) { unsigned int head = mmap_read_head(md); unsigned int old = md->prev; unsigned char *data = md->base + page_size; unsigned long size; void *buf; int diff; gettimeofday(&this_read, NULL); /* * If we're further behind than half the buffer, there's a chance * the writer will bite our tail and mess up the samples under us. * * If we somehow ended up ahead of the head, we got messed up. * * In either case, truncate and restart at head. */ diff = head - old; if (diff < 0) { struct timeval iv; unsigned long msecs; timersub(&this_read, &last_read, &iv); msecs = iv.tv_sec*1000 + iv.tv_usec/1000; fprintf(stderr, "WARNING: failed to keep up with mmap data." " Last read %lu msecs ago.\n", msecs); /* * head points to a known good entry, start there. */ old = head; } last_read = this_read; if (old != head) samples++; size = head - old; if ((old & md->mask) + size != (head & md->mask)) { buf = &data[old & md->mask]; size = md->mask + 1 - (old & md->mask); old += size; write_output(buf, size); } buf = &data[old & md->mask]; size = head - old; old += size; write_output(buf, size); md->prev = old; mmap_write_tail(md, old); } static volatile int done = 0; static volatile int signr = -1; static void sig_handler(int sig) { done = 1; signr = sig; } static void sig_atexit(void) { if (signr == -1) return; signal(signr, SIG_DFL); kill(getpid(), signr); } static void pid_synthesize_comm_event(pid_t pid, int full) { struct comm_event comm_ev; char filename[PATH_MAX]; char bf[BUFSIZ]; int fd; size_t size; char *field, *sep; DIR *tasks; struct dirent dirent, *next; snprintf(filename, sizeof(filename), "/proc/%d/stat", pid); fd = open(filename, O_RDONLY); if (fd < 0) { /* * We raced with a task exiting - just return: */ if (verbose) fprintf(stderr, "couldn't open %s\n", filename); return; } if (read(fd, bf, sizeof(bf)) < 0) { fprintf(stderr, "couldn't read %s\n", filename); exit(EXIT_FAILURE); } close(fd); /* 9027 (cat) R 6747 9027 6747 34816 9027 ... */ memset(&comm_ev, 0, sizeof(comm_ev)); field = strchr(bf, '('); if (field == NULL) goto out_failure; sep = strchr(++field, ')'); if (sep == NULL) goto out_failure; size = sep - field; memcpy(comm_ev.comm, field, size++); comm_ev.pid = pid; comm_ev.header.type = PERF_EVENT_COMM; size = ALIGN(size, sizeof(u64)); comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size); if (!full) { comm_ev.tid = pid; write_output(&comm_ev, comm_ev.header.size); return; } snprintf(filename, sizeof(filename), "/proc/%d/task", pid); tasks = opendir(filename); while (!readdir_r(tasks, &dirent, &next) && next) { char *end; pid = strtol(dirent.d_name, &end, 10); if (*end) continue; comm_ev.tid = pid; write_output(&comm_ev, comm_ev.header.size); } closedir(tasks); return; out_failure: fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n", filename); exit(EXIT_FAILURE); } static void pid_synthesize_mmap_samples(pid_t pid) { char filename[PATH_MAX]; FILE *fp; snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); fp = fopen(filename, "r"); if (fp == NULL) { /* * We raced with a task exiting - just return: */ if (verbose) fprintf(stderr, "couldn't open %s\n", filename); return; } while (1) { char bf[BUFSIZ], *pbf = bf; struct mmap_event mmap_ev = { .header.type = PERF_EVENT_MMAP, }; int n; size_t size; if (fgets(bf, sizeof(bf), fp) == NULL) break; /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ n = hex2u64(pbf, &mmap_ev.start); if (n < 0) continue; pbf += n + 1; n = hex2u64(pbf, &mmap_ev.len); if (n < 0) continue; pbf += n + 3; if (*pbf == 'x') { /* vm_exec */ char *execname = strchr(bf, '/'); if (execname == NULL) continue; size = strlen(execname); execname[size - 1] = '\0'; /* Remove \n */ memcpy(mmap_ev.filename, execname, size); size = ALIGN(size, sizeof(u64)); mmap_ev.len -= mmap_ev.start; mmap_ev.header.size = (sizeof(mmap_ev) - (sizeof(mmap_ev.filename) - size)); mmap_ev.pid = pid; mmap_ev.tid = pid; write_output(&mmap_ev, mmap_ev.header.size); } } fclose(fp); } static void synthesize_samples(void) { DIR *proc; struct dirent dirent, *next; proc = opendir("/proc"); while (!readdir_r(proc, &dirent, &next) && next) { char *end; pid_t pid; pid = strtol(dirent.d_name, &end, 10); if (*end) /* only interested in proper numerical dirents */ continue; pid_synthesize_comm_event(pid, 1); pid_synthesize_mmap_samples(pid); } closedir(proc); } static int group_fd; static void create_counter(int counter, int cpu, pid_t pid) { struct perf_counter_attr *attr = attrs + counter; int track = 1; attr->sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID; if (freq) { attr->sample_type |= PERF_SAMPLE_PERIOD; attr->freq = 1; attr->sample_freq = freq; } if (call_graph) attr->sample_type |= PERF_SAMPLE_CALLCHAIN; if (file_new) { file_header.sample_type = attr->sample_type; } else { if (file_header.sample_type != attr->sample_type) { fprintf(stderr, "incompatible append\n"); exit(-1); } } attr->mmap = track; attr->comm = track; attr->inherit = (cpu < 0) && inherit; attr->disabled = 1; track = 0; /* only the first counter needs these */ try_again: fd[nr_cpu][counter] = sys_perf_counter_open(attr, pid, cpu, group_fd, 0); if (fd[nr_cpu][counter] < 0) { int err = errno; if (err == EPERM) die("Permission error - are you root?\n"); /* * If it's cycles then fall back to hrtimer * based cpu-clock-tick sw counter, which * is always available even if no PMU support: */ if (attr->type == PERF_TYPE_HARDWARE && attr->config == PERF_COUNT_HW_CPU_CYCLES) { if (verbose) warning(" ... trying to fall back to cpu-clock-ticks\n"); attr->type = PERF_TYPE_SOFTWARE; attr->config = PERF_COUNT_SW_CPU_CLOCK; goto try_again; } printf("\n"); error("perfcounter syscall returned with %d (%s)\n", fd[nr_cpu][counter], strerror(err)); die("No CONFIG_PERF_COUNTERS=y kernel support configured?\n"); exit(-1); } assert(fd[nr_cpu][counter] >= 0); fcntl(fd[nr_cpu][counter], F_SETFL, O_NONBLOCK); /* * First counter acts as the group leader: */ if (group && group_fd == -1) group_fd = fd[nr_cpu][counter]; event_array[nr_poll].fd = fd[nr_cpu][counter]; event_array[nr_poll].events = POLLIN; nr_poll++; mmap_array[nr_cpu][counter].counter = counter; mmap_array[nr_cpu][counter].prev = 0; mmap_array[nr_cpu][counter].mask = mmap_pages*page_size - 1; mmap_array[nr_cpu][counter].base = mmap(NULL, (mmap_pages+1)*page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd[nr_cpu][counter], 0); if (mmap_array[nr_cpu][counter].base == MAP_FAILED) { error("failed to mmap with %d (%s)\n", errno, strerror(errno)); exit(-1); } ioctl(fd[nr_cpu][counter], PERF_COUNTER_IOC_ENABLE); } static void open_counters(int cpu, pid_t pid) { int counter; if (pid > 0) { pid_synthesize_comm_event(pid, 0); pid_synthesize_mmap_samples(pid); } group_fd = -1; for (counter = 0; counter < nr_counters; counter++) create_counter(counter, cpu, pid); nr_cpu++; } static void atexit_header(void) { file_header.data_size += bytes_written; if (pwrite(output, &file_header, sizeof(file_header), 0) == -1) perror("failed to write on file headers"); } static int __cmd_record(int argc, const char **argv) { int i, counter; struct stat st; pid_t pid; int flags; int ret; page_size = sysconf(_SC_PAGE_SIZE); nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); assert(nr_cpus <= MAX_NR_CPUS); assert(nr_cpus >= 0); atexit(sig_atexit); signal(SIGCHLD, sig_handler); signal(SIGINT, sig_handler); if (!stat(output_name, &st) && !force && !append_file) { fprintf(stderr, "Error, output file %s exists, use -A to append or -f to overwrite.\n", output_name); exit(-1); } flags = O_CREAT|O_RDWR; if (append_file) file_new = 0; else flags |= O_TRUNC; output = open(output_name, flags, S_IRUSR|S_IWUSR); if (output < 0) { perror("failed to create output file"); exit(-1); } if (!file_new) { if (read(output, &file_header, sizeof(file_header)) == -1) { perror("failed to read file headers"); exit(-1); } lseek(output, file_header.data_size, SEEK_CUR); } atexit(atexit_header); if (!system_wide) { open_counters(-1, target_pid != -1 ? target_pid : getpid()); } else for (i = 0; i < nr_cpus; i++) open_counters(i, target_pid); if (target_pid == -1 && argc) { pid = fork(); if (pid < 0) perror("failed to fork"); if (!pid) { if (execvp(argv[0], (char **)argv)) { perror(argv[0]); exit(-1); } } } if (realtime_prio) { struct sched_param param; param.sched_priority = realtime_prio; if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { printf("Could not set realtime priority.\n"); exit(-1); } } if (system_wide) synthesize_samples(); while (!done) { int hits = samples; for (i = 0; i < nr_cpu; i++) { for (counter = 0; counter < nr_counters; counter++) mmap_read(&mmap_array[i][counter]); } if (hits == samples) ret = poll(event_array, nr_poll, 100); } /* * Approximate RIP event size: 24 bytes. */ fprintf(stderr, "[ perf record: Captured and wrote %.3f MB %s (~%lld samples) ]\n", (double)bytes_written / 1024.0 / 1024.0, output_name, bytes_written / 24); return 0; } static const char * const record_usage[] = { "perf record [] []", "perf record [] -- []", NULL }; static const struct option options[] = { OPT_CALLBACK('e', "event", NULL, "event", "event selector. use 'perf list' to list available events", parse_events), OPT_INTEGER('p', "pid", &target_pid, "record events on existing pid"), OPT_INTEGER('r', "realtime", &realtime_prio, "collect data with this RT SCHED_FIFO priority"), OPT_BOOLEAN('a', "all-cpus", &system_wide, "system-wide collection from all CPUs"), OPT_BOOLEAN('A', "append", &append_file, "append to the output file to do incremental profiling"), OPT_BOOLEAN('f', "force", &force, "overwrite existing data file"), OPT_LONG('c', "count", &default_interval, "event period to sample"), OPT_STRING('o', "output", &output_name, "file", "output file name"), OPT_BOOLEAN('i', "inherit", &inherit, "child tasks inherit counters"), OPT_INTEGER('F', "freq", &freq, "profile at this frequency"), OPT_INTEGER('m', "mmap-pages", &mmap_pages, "number of mmap data pages"), OPT_BOOLEAN('g', "call-graph", &call_graph, "do call-graph (stack chain/backtrace) recording"), OPT_BOOLEAN('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), OPT_END() }; int cmd_record(int argc, const char **argv, const char *prefix) { int counter; argc = parse_options(argc, argv, options, record_usage, 0); if (!argc && target_pid == -1 && !system_wide) usage_with_options(record_usage, options); if (!nr_counters) { nr_counters = 1; attrs[0].type = PERF_TYPE_HARDWARE; attrs[0].config = PERF_COUNT_HW_CPU_CYCLES; } for (counter = 0; counter < nr_counters; counter++) { if (attrs[counter].sample_period) continue; attrs[counter].sample_period = default_interval; } return __cmd_record(argc, argv); }