linux/Documentation/perf_counter/builtin-record.c
Ingo Molnar 5242519b02 perf stat: Convert to Git option parsing
Remove getopt usage and use Git's much more advanced and more compact
command option library.

Extend the event parser library with the extensions that were in
perf-stat before.

Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
Cc: Marcelo Tosatti <mtosatti@redhat.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
LKML-Reference: <new-submission>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-05-26 11:59:34 +02:00

446 lines
10 KiB
C

#include "perf.h"
#include "util/util.h"
#include "util/parse-options.h"
#include "util/parse-events.h"
#include <sched.h>
#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
static int default_interval = 100000;
static int event_count[MAX_COUNTERS];
static int fd[MAX_NR_CPUS][MAX_COUNTERS];
static int nr_cpus = 0;
static unsigned int page_size;
static unsigned int mmap_pages = 16;
static int output;
static const char *output_name = "output.perf";
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 nmi = 1;
const unsigned int default_count[] = {
1000000,
1000000,
10000,
10000,
1000000,
10000,
};
struct mmap_data {
int counter;
void *base;
unsigned int mask;
unsigned int prev;
};
static unsigned int mmap_read_head(struct mmap_data *md)
{
struct perf_counter_mmap_page *pc = md->base;
int head;
head = pc->data_head;
rmb();
return head;
}
static long events;
static struct timeval last_read, this_read;
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 screw up the events 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 > md->mask / 2 || 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)
events++;
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;
while (size) {
int ret = write(output, buf, size);
if (ret < 0) {
perror("failed to write");
exit(-1);
}
size -= ret;
buf += ret;
}
}
buf = &data[old & md->mask];
size = head - old;
old += size;
while (size) {
int ret = write(output, buf, size);
if (ret < 0) {
perror("failed to write");
exit(-1);
}
size -= ret;
buf += ret;
}
md->prev = old;
}
static volatile int done = 0;
static void sig_handler(int sig)
{
done = 1;
}
static struct pollfd event_array[MAX_NR_CPUS * MAX_COUNTERS];
static struct mmap_data mmap_array[MAX_NR_CPUS][MAX_COUNTERS];
static int nr_poll;
static int nr_cpu;
struct mmap_event {
struct perf_event_header header;
__u32 pid, tid;
__u64 start;
__u64 len;
__u64 pgoff;
char filename[PATH_MAX];
};
struct comm_event {
struct perf_event_header header;
__u32 pid,tid;
char comm[16];
};
static pid_t pid_synthesize_comm_event(pid_t pid)
{
char filename[PATH_MAX];
char bf[BUFSIZ];
struct comm_event comm_ev;
size_t size;
int fd;
snprintf(filename, sizeof(filename), "/proc/%d/stat", pid);
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "couldn't open %s\n", filename);
exit(EXIT_FAILURE);
}
if (read(fd, bf, sizeof(bf)) < 0) {
fprintf(stderr, "couldn't read %s\n", filename);
exit(EXIT_FAILURE);
}
close(fd);
pid_t spid, ppid;
char state;
char comm[18];
memset(&comm_ev, 0, sizeof(comm_ev));
int nr = sscanf(bf, "%d %s %c %d %d ",
&spid, comm, &state, &ppid, &comm_ev.pid);
if (nr != 5) {
fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n",
filename);
exit(EXIT_FAILURE);
}
comm_ev.header.type = PERF_EVENT_COMM;
comm_ev.tid = pid;
size = strlen(comm);
comm[--size] = '\0'; /* Remove the ')' at the end */
--size; /* Remove the '(' at the begin */
memcpy(comm_ev.comm, comm + 1, size);
size = ALIGN(size, sizeof(uint64_t));
comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size);
int ret = write(output, &comm_ev, comm_ev.header.size);
if (ret < 0) {
perror("failed to write");
exit(-1);
}
return comm_ev.pid;
}
static void pid_synthesize_mmap_events(pid_t pid, pid_t pgid)
{
char filename[PATH_MAX];
FILE *fp;
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "couldn't open %s\n", filename);
exit(EXIT_FAILURE);
}
while (1) {
char bf[BUFSIZ];
unsigned char vm_read, vm_write, vm_exec, vm_mayshare;
struct mmap_event mmap_ev = {
.header.type = PERF_EVENT_MMAP,
};
unsigned long ino;
int major, minor;
size_t size;
if (fgets(bf, sizeof(bf), fp) == NULL)
break;
/* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
sscanf(bf, "%llx-%llx %c%c%c%c %llx %x:%x %lu",
&mmap_ev.start, &mmap_ev.len,
&vm_read, &vm_write, &vm_exec, &vm_mayshare,
&mmap_ev.pgoff, &major, &minor, &ino);
if (vm_exec == 'x') {
char *execname = strrchr(bf, ' ');
if (execname == NULL || execname[1] != '/')
continue;
execname += 1;
size = strlen(execname);
execname[size - 1] = '\0'; /* Remove \n */
memcpy(mmap_ev.filename, execname, size);
size = ALIGN(size, sizeof(uint64_t));
mmap_ev.len -= mmap_ev.start;
mmap_ev.header.size = (sizeof(mmap_ev) -
(sizeof(mmap_ev.filename) - size));
mmap_ev.pid = pgid;
mmap_ev.tid = pid;
if (write(output, &mmap_ev, mmap_ev.header.size) < 0) {
perror("failed to write");
exit(-1);
}
}
}
fclose(fp);
}
static void open_counters(int cpu, pid_t pid)
{
struct perf_counter_hw_event hw_event;
int counter, group_fd;
int track = 1;
if (pid > 0) {
pid_t pgid = pid_synthesize_comm_event(pid);
pid_synthesize_mmap_events(pid, pgid);
}
group_fd = -1;
for (counter = 0; counter < nr_counters; counter++) {
memset(&hw_event, 0, sizeof(hw_event));
hw_event.config = event_id[counter];
hw_event.irq_period = event_count[counter];
hw_event.record_type = PERF_RECORD_IP | PERF_RECORD_TID;
hw_event.nmi = nmi;
hw_event.mmap = track;
hw_event.comm = track;
hw_event.inherit = (cpu < 0) && inherit;
track = 0; // only the first counter needs these
fd[nr_cpu][counter] =
sys_perf_counter_open(&hw_event, pid, cpu, group_fd, 0);
if (fd[nr_cpu][counter] < 0) {
int err = errno;
printf("kerneltop error: syscall returned with %d (%s)\n",
fd[nr_cpu][counter], strerror(err));
if (err == EPERM)
printf("Are you root?\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, MAP_SHARED, fd[nr_cpu][counter], 0);
if (mmap_array[nr_cpu][counter].base == MAP_FAILED) {
printf("kerneltop error: failed to mmap with %d (%s)\n",
errno, strerror(errno));
exit(-1);
}
}
nr_cpu++;
}
static int __cmd_record(int argc, const char **argv)
{
int i, counter;
pid_t pid;
int ret;
page_size = sysconf(_SC_PAGE_SIZE);
nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
assert(nr_cpus <= MAX_NR_CPUS);
assert(nr_cpus >= 0);
output = open(output_name, O_CREAT|O_RDWR, S_IRWXU);
if (output < 0) {
perror("failed to create output file");
exit(-1);
}
if (!system_wide) {
open_counters(-1, target_pid != -1 ? target_pid : 0);
} else for (i = 0; i < nr_cpus; i++)
open_counters(i, target_pid);
signal(SIGCHLD, sig_handler);
signal(SIGINT, sig_handler);
if (target_pid == -1) {
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, &param)) {
printf("Could not set realtime priority.\n");
exit(-1);
}
}
/*
* TODO: store the current /proc/$/maps information somewhere
*/
while (!done) {
int hits = events;
for (i = 0; i < nr_cpu; i++) {
for (counter = 0; counter < nr_counters; counter++)
mmap_read(&mmap_array[i][counter]);
}
if (hits == events)
ret = poll(event_array, nr_poll, 100);
}
return 0;
}
static const char * const record_usage[] = {
"perf record [<options>] <command>",
NULL
};
static char events_help_msg[EVENTS_HELP_MAX];
static const struct option options[] = {
OPT_CALLBACK('e', "event", NULL, "event",
events_help_msg, parse_events),
OPT_INTEGER('c', "count", &default_interval,
"event period to sample"),
OPT_INTEGER('m', "mmap-pages", &mmap_pages,
"number of mmap data pages"),
OPT_STRING('o', "output", &output_name, "file",
"output file name"),
OPT_BOOLEAN('i', "inherit", &inherit,
"child tasks inherit counters"),
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_END()
};
int cmd_record(int argc, const char **argv, const char *prefix)
{
int counter;
create_events_help(events_help_msg);
argc = parse_options(argc, argv, options, record_usage, 0);
if (!argc)
usage_with_options(record_usage, options);
if (!nr_counters) {
nr_counters = 1;
event_id[0] = 0;
}
for (counter = 0; counter < nr_counters; counter++) {
if (event_count[counter])
continue;
event_count[counter] = default_interval;
}
return __cmd_record(argc, argv);
}