diff options
Diffstat (limited to 'tools/perf/builtin-trace.c')
-rw-r--r-- | tools/perf/builtin-trace.c | 717 |
1 files changed, 532 insertions, 185 deletions
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index 0c5e4f72f2b..574a215e800 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -5,6 +5,53 @@ #include "util/symbol.h" #include "util/thread.h" #include "util/header.h" +#include "util/exec_cmd.h" +#include "util/trace-event.h" +#include "util/session.h" + +static char const *script_name; +static char const *generate_script_lang; + +static int default_start_script(const char *script __unused, + int argc __unused, + const char **argv __unused) +{ + return 0; +} + +static int default_stop_script(void) +{ + return 0; +} + +static int default_generate_script(const char *outfile __unused) +{ + return 0; +} + +static struct scripting_ops default_scripting_ops = { + .start_script = default_start_script, + .stop_script = default_stop_script, + .process_event = print_event, + .generate_script = default_generate_script, +}; + +static struct scripting_ops *scripting_ops; + +static void setup_scripting(void) +{ + /* make sure PERF_EXEC_PATH is set for scripts */ + perf_set_argv_exec_path(perf_exec_path()); + + setup_perl_scripting(); + + scripting_ops = &default_scripting_ops; +} + +static int cleanup_scripting(void) +{ + return scripting_ops->stop_script(); +} #include "util/parse-options.h" @@ -12,252 +59,460 @@ #include "util/debug.h" #include "util/trace-event.h" +#include "util/exec_cmd.h" -static char const *input_name = "perf.data"; -static int input; -static unsigned long page_size; -static unsigned long mmap_window = 32; +static char const *input_name = "perf.data"; -static unsigned long total = 0; -static unsigned long total_comm = 0; +static int process_sample_event(event_t *event, struct perf_session *session) +{ + struct sample_data data; + struct thread *thread; -static struct rb_root threads; -static struct thread *last_match; + memset(&data, 0, sizeof(data)); + data.time = -1; + data.cpu = -1; + data.period = 1; -static struct perf_header *header; -static u64 sample_type; + event__parse_sample(event, session->sample_type, &data); + dump_printf("(IP, %d): %d/%d: %p period: %Ld\n", + event->header.misc, + data.pid, data.tid, + (void *)(long)data.ip, + (long long)data.period); -static int -process_comm_event(event_t *event, unsigned long offset, unsigned long head) -{ - struct thread *thread; + thread = perf_session__findnew(session, event->ip.pid); + if (thread == NULL) { + pr_debug("problem processing %d event, skipping it.\n", + event->header.type); + return -1; + } - thread = threads__findnew(event->comm.pid, &threads, &last_match); + if (session->sample_type & PERF_SAMPLE_RAW) { + /* + * FIXME: better resolve from pid from the struct trace_entry + * field, although it should be the same than this perf + * event pid + */ + scripting_ops->process_event(data.cpu, data.raw_data, + data.raw_size, + data.time, thread->comm); + } - dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n", - (void *)(offset + head), - (void *)(long)(event->header.size), - event->comm.comm, event->comm.pid); + session->events_stats.total += data.period; + return 0; +} - if (thread == NULL || - thread__set_comm(thread, event->comm.comm)) { - dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); +static int sample_type_check(struct perf_session *session) +{ + if (!(session->sample_type & PERF_SAMPLE_RAW)) { + fprintf(stderr, + "No trace sample to read. Did you call perf record " + "without -R?"); return -1; } - total_comm++; return 0; } -static int -process_sample_event(event_t *event, unsigned long offset, unsigned long head) +static struct perf_event_ops event_ops = { + .process_sample_event = process_sample_event, + .process_comm_event = event__process_comm, + .sample_type_check = sample_type_check, +}; + +static int __cmd_trace(struct perf_session *session) { - char level; - int show = 0; - struct dso *dso = NULL; - struct thread *thread; - u64 ip = event->ip.ip; - u64 timestamp = -1; - u32 cpu = -1; - u64 period = 1; - void *more_data = event->ip.__more_data; - int cpumode; - - thread = threads__findnew(event->ip.pid, &threads, &last_match); - - if (sample_type & PERF_SAMPLE_TIME) { - timestamp = *(u64 *)more_data; - more_data += sizeof(u64); - } + return perf_session__process_events(session, &event_ops); +} - if (sample_type & PERF_SAMPLE_CPU) { - cpu = *(u32 *)more_data; - more_data += sizeof(u32); - more_data += sizeof(u32); /* reserved */ - } +struct script_spec { + struct list_head node; + struct scripting_ops *ops; + char spec[0]; +}; + +LIST_HEAD(script_specs); - if (sample_type & PERF_SAMPLE_PERIOD) { - period = *(u64 *)more_data; - more_data += sizeof(u64); +static struct script_spec *script_spec__new(const char *spec, + struct scripting_ops *ops) +{ + struct script_spec *s = malloc(sizeof(*s) + strlen(spec) + 1); + + if (s != NULL) { + strcpy(s->spec, spec); + s->ops = ops; } - dump_printf("%p [%p]: PERF_RECORD_SAMPLE (IP, %d): %d/%d: %p period: %Ld\n", - (void *)(offset + head), - (void *)(long)(event->header.size), - event->header.misc, - event->ip.pid, event->ip.tid, - (void *)(long)ip, - (long long)period); + return s; +} - dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); +static void script_spec__delete(struct script_spec *s) +{ + free(s->spec); + free(s); +} - if (thread == NULL) { - eprintf("problem processing %d event, skipping it.\n", - event->header.type); - return -1; - } +static void script_spec__add(struct script_spec *s) +{ + list_add_tail(&s->node, &script_specs); +} - cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; +static struct script_spec *script_spec__find(const char *spec) +{ + struct script_spec *s; - if (cpumode == PERF_RECORD_MISC_KERNEL) { - show = SHOW_KERNEL; - level = 'k'; + list_for_each_entry(s, &script_specs, node) + if (strcasecmp(s->spec, spec) == 0) + return s; + return NULL; +} - dso = kernel_dso; +static struct script_spec *script_spec__findnew(const char *spec, + struct scripting_ops *ops) +{ + struct script_spec *s = script_spec__find(spec); - dump_printf(" ...... dso: %s\n", dso->name); + if (s) + return s; - } else if (cpumode == PERF_RECORD_MISC_USER) { + s = script_spec__new(spec, ops); + if (!s) + goto out_delete_spec; - show = SHOW_USER; - level = '.'; + script_spec__add(s); - } else { - show = SHOW_HV; - level = 'H'; + return s; - dso = hypervisor_dso; +out_delete_spec: + script_spec__delete(s); - dump_printf(" ...... dso: [hypervisor]\n"); - } + return NULL; +} - if (sample_type & PERF_SAMPLE_RAW) { - struct { - u32 size; - char data[0]; - } *raw = more_data; +int script_spec_register(const char *spec, struct scripting_ops *ops) +{ + struct script_spec *s; - /* - * FIXME: better resolve from pid from the struct trace_entry - * field, although it should be the same than this perf - * event pid - */ - print_event(cpu, raw->data, raw->size, timestamp, thread->comm); - } - total += period; + s = script_spec__find(spec); + if (s) + return -1; + + s = script_spec__findnew(spec, ops); + if (!s) + return -1; return 0; } -static int -process_event(event_t *event, unsigned long offset, unsigned long head) +static struct scripting_ops *script_spec__lookup(const char *spec) { - trace_event(event); + struct script_spec *s = script_spec__find(spec); + if (!s) + return NULL; - switch (event->header.type) { - case PERF_RECORD_MMAP ... PERF_RECORD_LOST: - return 0; + return s->ops; +} - case PERF_RECORD_COMM: - return process_comm_event(event, offset, head); +static void list_available_languages(void) +{ + struct script_spec *s; - case PERF_RECORD_EXIT ... PERF_RECORD_READ: - return 0; + fprintf(stderr, "\n"); + fprintf(stderr, "Scripting language extensions (used in " + "perf trace -s [spec:]script.[spec]):\n\n"); - case PERF_RECORD_SAMPLE: - return process_sample_event(event, offset, head); + list_for_each_entry(s, &script_specs, node) + fprintf(stderr, " %-42s [%s]\n", s->spec, s->ops->name); - case PERF_RECORD_MAX: - default: - return -1; + fprintf(stderr, "\n"); +} + +static int parse_scriptname(const struct option *opt __used, + const char *str, int unset __used) +{ + char spec[PATH_MAX]; + const char *script, *ext; + int len; + + if (strcmp(str, "list") == 0) { + list_available_languages(); + return 0; + } + + script = strchr(str, ':'); + if (script) { + len = script - str; + if (len >= PATH_MAX) { + fprintf(stderr, "invalid language specifier"); + return -1; + } + strncpy(spec, str, len); + spec[len] = '\0'; + scripting_ops = script_spec__lookup(spec); + if (!scripting_ops) { + fprintf(stderr, "invalid language specifier"); + return -1; + } + script++; + } else { + script = str; + ext = strchr(script, '.'); + if (!ext) { + fprintf(stderr, "invalid script extension"); + return -1; + } + scripting_ops = script_spec__lookup(++ext); + if (!scripting_ops) { + fprintf(stderr, "invalid script extension"); + return -1; + } } + script_name = strdup(script); + return 0; } -static int __cmd_trace(void) +#define for_each_lang(scripts_dir, lang_dirent, lang_next) \ + while (!readdir_r(scripts_dir, &lang_dirent, &lang_next) && \ + lang_next) \ + if (lang_dirent.d_type == DT_DIR && \ + (strcmp(lang_dirent.d_name, ".")) && \ + (strcmp(lang_dirent.d_name, ".."))) + +#define for_each_script(lang_dir, script_dirent, script_next) \ + while (!readdir_r(lang_dir, &script_dirent, &script_next) && \ + script_next) \ + if (script_dirent.d_type != DT_DIR) + + +#define RECORD_SUFFIX "-record" +#define REPORT_SUFFIX "-report" + +struct script_desc { + struct list_head node; + char *name; + char *half_liner; + char *args; +}; + +LIST_HEAD(script_descs); + +static struct script_desc *script_desc__new(const char *name) { - int ret, rc = EXIT_FAILURE; - unsigned long offset = 0; - unsigned long head = 0; - struct stat perf_stat; - event_t *event; - uint32_t size; - char *buf; - - trace_report(); - register_idle_thread(&threads, &last_match); - - input = open(input_name, O_RDONLY); - if (input < 0) { - perror("failed to open file"); - exit(-1); - } + struct script_desc *s = zalloc(sizeof(*s)); - ret = fstat(input, &perf_stat); - if (ret < 0) { - perror("failed to stat file"); - exit(-1); - } + if (s != NULL) + s->name = strdup(name); - if (!perf_stat.st_size) { - fprintf(stderr, "zero-sized file, nothing to do!\n"); - exit(0); - } - header = perf_header__read(input); - head = header->data_offset; - sample_type = perf_header__sample_type(header); + return s; +} + +static void script_desc__delete(struct script_desc *s) +{ + free(s->name); + free(s); +} + +static void script_desc__add(struct script_desc *s) +{ + list_add_tail(&s->node, &script_descs); +} + +static struct script_desc *script_desc__find(const char *name) +{ + struct script_desc *s; + + list_for_each_entry(s, &script_descs, node) + if (strcasecmp(s->name, name) == 0) + return s; + return NULL; +} + +static struct script_desc *script_desc__findnew(const char *name) +{ + struct script_desc *s = script_desc__find(name); + + if (s) + return s; + + s = script_desc__new(name); + if (!s) + goto out_delete_desc; + + script_desc__add(s); - if (!(sample_type & PERF_SAMPLE_RAW)) - die("No trace sample to read. Did you call perf record " - "without -R?"); + return s; - if (load_kernel() < 0) { - perror("failed to load kernel symbols"); - return EXIT_FAILURE; +out_delete_desc: + script_desc__delete(s); + + return NULL; +} + +static char *ends_with(char *str, const char *suffix) +{ + size_t suffix_len = strlen(suffix); + char *p = str; + + if (strlen(str) > suffix_len) { + p = str + strlen(str) - suffix_len; + if (!strncmp(p, suffix, suffix_len)) + return p; } -remap: - buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ, - MAP_SHARED, input, offset); - if (buf == MAP_FAILED) { - perror("failed to mmap file"); - exit(-1); + return NULL; +} + +static char *ltrim(char *str) +{ + int len = strlen(str); + + while (len && isspace(*str)) { + len--; + str++; } -more: - event = (event_t *)(buf + head); + return str; +} - if (head + event->header.size >= page_size * mmap_window) { - unsigned long shift = page_size * (head / page_size); - int res; +static int read_script_info(struct script_desc *desc, const char *filename) +{ + char line[BUFSIZ], *p; + FILE *fp; - res = munmap(buf, page_size * mmap_window); - assert(res == 0); + fp = fopen(filename, "r"); + if (!fp) + return -1; - offset += shift; - head -= shift; - goto remap; + while (fgets(line, sizeof(line), fp)) { + p = ltrim(line); + if (strlen(p) == 0) + continue; + if (*p != '#') + continue; + p++; + if (strlen(p) && *p == '!') + continue; + + p = ltrim(p); + if (strlen(p) && p[strlen(p) - 1] == '\n') + p[strlen(p) - 1] = '\0'; + + if (!strncmp(p, "description:", strlen("description:"))) { + p += strlen("description:"); + desc->half_liner = strdup(ltrim(p)); + continue; + } + + if (!strncmp(p, "args:", strlen("args:"))) { + p += strlen("args:"); + desc->args = strdup(ltrim(p)); + continue; + } } - size = event->header.size; + fclose(fp); - if (!size || process_event(event, offset, head) < 0) { - - /* - * assume we lost track of the stream, check alignment, and - * increment a single u64 in the hope to catch on again 'soon'. - */ + return 0; +} - if (unlikely(head & 7)) - head &= ~7ULL; +static int list_available_scripts(const struct option *opt __used, + const char *s __used, int unset __used) +{ + struct dirent *script_next, *lang_next, script_dirent, lang_dirent; + char scripts_path[MAXPATHLEN]; + DIR *scripts_dir, *lang_dir; + char script_path[MAXPATHLEN]; + char lang_path[MAXPATHLEN]; + struct script_desc *desc; + char first_half[BUFSIZ]; + char *script_root; + char *str; + + snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path()); + + scripts_dir = opendir(scripts_path); + if (!scripts_dir) + return -1; - size = 8; + for_each_lang(scripts_dir, lang_dirent, lang_next) { + snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, + lang_dirent.d_name); + lang_dir = opendir(lang_path); + if (!lang_dir) + continue; + + for_each_script(lang_dir, script_dirent, script_next) { + script_root = strdup(script_dirent.d_name); + str = ends_with(script_root, REPORT_SUFFIX); + if (str) { + *str = '\0'; + desc = script_desc__findnew(script_root); + snprintf(script_path, MAXPATHLEN, "%s/%s", + lang_path, script_dirent.d_name); + read_script_info(desc, script_path); + } + free(script_root); + } } - head += size; + fprintf(stdout, "List of available trace scripts:\n"); + list_for_each_entry(desc, &script_descs, node) { + sprintf(first_half, "%s %s", desc->name, + desc->args ? desc->args : ""); + fprintf(stdout, " %-36s %s\n", first_half, + desc->half_liner ? desc->half_liner : ""); + } - if (offset + head < (unsigned long)perf_stat.st_size) - goto more; + exit(0); +} - rc = EXIT_SUCCESS; - close(input); +static char *get_script_path(const char *script_root, const char *suffix) +{ + struct dirent *script_next, *lang_next, script_dirent, lang_dirent; + char scripts_path[MAXPATHLEN]; + char script_path[MAXPATHLEN]; + DIR *scripts_dir, *lang_dir; + char lang_path[MAXPATHLEN]; + char *str, *__script_root; + char *path = NULL; + + snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path()); + + scripts_dir = opendir(scripts_path); + if (!scripts_dir) + return NULL; + + for_each_lang(scripts_dir, lang_dirent, lang_next) { + snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, + lang_dirent.d_name); + lang_dir = opendir(lang_path); + if (!lang_dir) + continue; + + for_each_script(lang_dir, script_dirent, script_next) { + __script_root = strdup(script_dirent.d_name); + str = ends_with(__script_root, suffix); + if (str) { + *str = '\0'; + if (strcmp(__script_root, script_root)) + continue; + snprintf(script_path, MAXPATHLEN, "%s/%s", + lang_path, script_dirent.d_name); + path = strdup(script_path); + free(__script_root); + break; + } + free(__script_root); + } + } - return rc; + return path; } -static const char * const annotate_usage[] = { +static const char * const trace_usage[] = { "perf trace [<options>] <command>", NULL }; @@ -267,25 +522,117 @@ static const struct option options[] = { "dump raw trace in ASCII"), OPT_BOOLEAN('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), + OPT_BOOLEAN('L', "Latency", &latency_format, + "show latency attributes (irqs/preemption disabled, etc)"), + OPT_CALLBACK_NOOPT('l', "list", NULL, NULL, "list available scripts", + list_available_scripts), + OPT_CALLBACK('s', "script", NULL, "name", + "script file name (lang:script name, script name, or *)", + parse_scriptname), + OPT_STRING('g', "gen-script", &generate_script_lang, "lang", + "generate perf-trace.xx script in specified language"), + OPT_END() }; int cmd_trace(int argc, const char **argv, const char *prefix __used) { - symbol__init(); - page_size = getpagesize(); + struct perf_session *session; + const char *suffix = NULL; + const char **__argv; + char *script_path; + int i, err; + + if (argc >= 2 && strncmp(argv[1], "rec", strlen("rec")) == 0) { + if (argc < 3) { + fprintf(stderr, + "Please specify a record script\n"); + return -1; + } + suffix = RECORD_SUFFIX; + } - argc = parse_options(argc, argv, options, annotate_usage, 0); - if (argc) { - /* - * Special case: if there's an argument left then assume tha - * it's a symbol filter: - */ - if (argc > 1) - usage_with_options(annotate_usage, options); + if (argc >= 2 && strncmp(argv[1], "rep", strlen("rep")) == 0) { + if (argc < 3) { + fprintf(stderr, + "Please specify a report script\n"); + return -1; + } + suffix = REPORT_SUFFIX; } + if (suffix) { + script_path = get_script_path(argv[2], suffix); + if (!script_path) { + fprintf(stderr, "script not found\n"); + return -1; + } + + __argv = malloc((argc + 1) * sizeof(const char *)); + __argv[0] = "/bin/sh"; + __argv[1] = script_path; + for (i = 3; i < argc; i++) + __argv[i - 1] = argv[i]; + __argv[argc - 1] = NULL; + + execvp("/bin/sh", (char **)__argv); + exit(-1); + } + + setup_scripting(); + + argc = parse_options(argc, argv, options, trace_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (symbol__init() < 0) + return -1; setup_pager(); - return __cmd_trace(); + session = perf_session__new(input_name, O_RDONLY, 0); + if (session == NULL) + return -ENOMEM; + + if (generate_script_lang) { + struct stat perf_stat; + + int input = open(input_name, O_RDONLY); + if (input < 0) { + perror("failed to open file"); + exit(-1); + } + + err = fstat(input, &perf_stat); + if (err < 0) { + perror("failed to stat file"); + exit(-1); + } + + if (!perf_stat.st_size) { + fprintf(stderr, "zero-sized file, nothing to do!\n"); + exit(0); + } + + scripting_ops = script_spec__lookup(generate_script_lang); + if (!scripting_ops) { + fprintf(stderr, "invalid language specifier"); + return -1; + } + + perf_header__read(&session->header, input); + err = scripting_ops->generate_script("perf-trace"); + goto out; + } + + if (script_name) { + err = scripting_ops->start_script(script_name, argc, argv); + if (err) + goto out; + } + + err = __cmd_trace(session); + + perf_session__delete(session); + cleanup_scripting(); +out: + return err; } |