diff options
Diffstat (limited to 'tools/perf/util/trace-event-info.c')
-rw-r--r-- | tools/perf/util/trace-event-info.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c new file mode 100644 index 000000000..892c323b4 --- /dev/null +++ b/tools/perf/util/trace-event-info.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008,2009, Steven Rostedt <srostedt@redhat.com> + */ +#include <dirent.h> +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <stdbool.h> +#include <linux/list.h> +#include <linux/kernel.h> +#include <linux/zalloc.h> +#include <internal/lib.h> // page_size +#include <sys/param.h> + +#include "trace-event.h" +#include "tracepoint.h" +#include <api/fs/tracing_path.h> +#include "evsel.h" +#include "debug.h" + +#define VERSION "0.6" +#define MAX_EVENT_LENGTH 512 + +static int output_fd; + +struct tracepoint_path { + char *system; + char *name; + struct tracepoint_path *next; +}; + +int bigendian(void) +{ + unsigned char str[] = { 0x1, 0x2, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0}; + unsigned int *ptr; + + ptr = (unsigned int *)(void *)str; + return *ptr == 0x01020304; +} + +/* unfortunately, you can not stat debugfs or proc files for size */ +static int record_file(const char *file, ssize_t hdr_sz) +{ + unsigned long long size = 0; + char buf[BUFSIZ], *sizep; + off_t hdr_pos = lseek(output_fd, 0, SEEK_CUR); + int r, fd; + int err = -EIO; + + fd = open(file, O_RDONLY); + if (fd < 0) { + pr_debug("Can't read '%s'", file); + return -errno; + } + + /* put in zeros for file size, then fill true size later */ + if (hdr_sz) { + if (write(output_fd, &size, hdr_sz) != hdr_sz) + goto out; + } + + do { + r = read(fd, buf, BUFSIZ); + if (r > 0) { + size += r; + if (write(output_fd, buf, r) != r) + goto out; + } + } while (r > 0); + + /* ugh, handle big-endian hdr_size == 4 */ + sizep = (char*)&size; + if (bigendian()) + sizep += sizeof(u64) - hdr_sz; + + if (hdr_sz && pwrite(output_fd, sizep, hdr_sz, hdr_pos) < 0) { + pr_debug("writing file size failed\n"); + goto out; + } + + err = 0; +out: + close(fd); + return err; +} + +static int record_header_files(void) +{ + char *path = get_events_file("header_page"); + struct stat st; + int err = -EIO; + + if (!path) { + pr_debug("can't get tracing/events/header_page"); + return -ENOMEM; + } + + if (stat(path, &st) < 0) { + pr_debug("can't read '%s'", path); + goto out; + } + + if (write(output_fd, "header_page", 12) != 12) { + pr_debug("can't write header_page\n"); + goto out; + } + + if (record_file(path, 8) < 0) { + pr_debug("can't record header_page file\n"); + goto out; + } + + put_events_file(path); + + path = get_events_file("header_event"); + if (!path) { + pr_debug("can't get tracing/events/header_event"); + err = -ENOMEM; + goto out; + } + + if (stat(path, &st) < 0) { + pr_debug("can't read '%s'", path); + goto out; + } + + if (write(output_fd, "header_event", 13) != 13) { + pr_debug("can't write header_event\n"); + goto out; + } + + if (record_file(path, 8) < 0) { + pr_debug("can't record header_event file\n"); + goto out; + } + + err = 0; +out: + put_events_file(path); + return err; +} + +static bool name_in_tp_list(char *sys, struct tracepoint_path *tps) +{ + while (tps) { + if (!strcmp(sys, tps->name)) + return true; + tps = tps->next; + } + + return false; +} + +#define for_each_event_tps(dir, dent, tps) \ + while ((dent = readdir(dir))) \ + if (dent->d_type == DT_DIR && \ + (strcmp(dent->d_name, ".")) && \ + (strcmp(dent->d_name, ".."))) \ + +static int copy_event_system(const char *sys, struct tracepoint_path *tps) +{ + struct dirent *dent; + struct stat st; + char *format; + DIR *dir; + int count = 0; + int ret; + int err; + + dir = opendir(sys); + if (!dir) { + pr_debug("can't read directory '%s'", sys); + return -errno; + } + + for_each_event_tps(dir, dent, tps) { + if (!name_in_tp_list(dent->d_name, tps)) + continue; + + if (asprintf(&format, "%s/%s/format", sys, dent->d_name) < 0) { + err = -ENOMEM; + goto out; + } + ret = stat(format, &st); + free(format); + if (ret < 0) + continue; + count++; + } + + if (write(output_fd, &count, 4) != 4) { + err = -EIO; + pr_debug("can't write count\n"); + goto out; + } + + rewinddir(dir); + for_each_event_tps(dir, dent, tps) { + if (!name_in_tp_list(dent->d_name, tps)) + continue; + + if (asprintf(&format, "%s/%s/format", sys, dent->d_name) < 0) { + err = -ENOMEM; + goto out; + } + ret = stat(format, &st); + + if (ret >= 0) { + err = record_file(format, 8); + if (err) { + free(format); + goto out; + } + } + free(format); + } + err = 0; +out: + closedir(dir); + return err; +} + +static int record_ftrace_files(struct tracepoint_path *tps) +{ + char *path; + int ret; + + path = get_events_file("ftrace"); + if (!path) { + pr_debug("can't get tracing/events/ftrace"); + return -ENOMEM; + } + + ret = copy_event_system(path, tps); + + put_tracing_file(path); + + return ret; +} + +static bool system_in_tp_list(char *sys, struct tracepoint_path *tps) +{ + while (tps) { + if (!strcmp(sys, tps->system)) + return true; + tps = tps->next; + } + + return false; +} + +static int record_event_files(struct tracepoint_path *tps) +{ + struct dirent *dent; + struct stat st; + char *path; + char *sys; + DIR *dir; + int count = 0; + int ret; + int err; + + path = get_tracing_file("events"); + if (!path) { + pr_debug("can't get tracing/events"); + return -ENOMEM; + } + + dir = opendir(path); + if (!dir) { + err = -errno; + pr_debug("can't read directory '%s'", path); + goto out; + } + + for_each_event_tps(dir, dent, tps) { + if (strcmp(dent->d_name, "ftrace") == 0 || + !system_in_tp_list(dent->d_name, tps)) + continue; + + count++; + } + + if (write(output_fd, &count, 4) != 4) { + err = -EIO; + pr_debug("can't write count\n"); + goto out; + } + + rewinddir(dir); + for_each_event_tps(dir, dent, tps) { + if (strcmp(dent->d_name, "ftrace") == 0 || + !system_in_tp_list(dent->d_name, tps)) + continue; + + if (asprintf(&sys, "%s/%s", path, dent->d_name) < 0) { + err = -ENOMEM; + goto out; + } + ret = stat(sys, &st); + if (ret >= 0) { + ssize_t size = strlen(dent->d_name) + 1; + + if (write(output_fd, dent->d_name, size) != size || + copy_event_system(sys, tps) < 0) { + err = -EIO; + free(sys); + goto out; + } + } + free(sys); + } + err = 0; +out: + closedir(dir); + put_tracing_file(path); + + return err; +} + +static int record_proc_kallsyms(void) +{ + unsigned long long size = 0; + /* + * Just to keep older perf.data file parsers happy, record a zero + * sized kallsyms file, i.e. do the same thing that was done when + * /proc/kallsyms (or something specified via --kallsyms, in a + * different path) couldn't be read. + */ + return write(output_fd, &size, 4) != 4 ? -EIO : 0; +} + +static int record_ftrace_printk(void) +{ + unsigned int size; + char *path; + struct stat st; + int ret, err = 0; + + path = get_tracing_file("printk_formats"); + if (!path) { + pr_debug("can't get tracing/printk_formats"); + return -ENOMEM; + } + + ret = stat(path, &st); + if (ret < 0) { + /* not found */ + size = 0; + if (write(output_fd, &size, 4) != 4) + err = -EIO; + goto out; + } + err = record_file(path, 4); + +out: + put_tracing_file(path); + return err; +} + +static int record_saved_cmdline(void) +{ + unsigned long long size; + char *path; + struct stat st; + int ret, err = 0; + + path = get_tracing_file("saved_cmdlines"); + if (!path) { + pr_debug("can't get tracing/saved_cmdline"); + return -ENOMEM; + } + + ret = stat(path, &st); + if (ret < 0) { + /* not found */ + size = 0; + if (write(output_fd, &size, 8) != 8) + err = -EIO; + goto out; + } + err = record_file(path, 8); + +out: + put_tracing_file(path); + return err; +} + +static void +put_tracepoints_path(struct tracepoint_path *tps) +{ + while (tps) { + struct tracepoint_path *t = tps; + + tps = tps->next; + zfree(&t->name); + zfree(&t->system); + free(t); + } +} + +static struct tracepoint_path *tracepoint_id_to_path(u64 config) +{ + struct tracepoint_path *path = NULL; + DIR *sys_dir, *evt_dir; + struct dirent *sys_dirent, *evt_dirent; + char id_buf[24]; + int fd; + u64 id; + char evt_path[MAXPATHLEN]; + char *dir_path; + + sys_dir = tracing_events__opendir(); + if (!sys_dir) + return NULL; + + for_each_subsystem(sys_dir, sys_dirent) { + dir_path = get_events_file(sys_dirent->d_name); + if (!dir_path) + continue; + evt_dir = opendir(dir_path); + if (!evt_dir) + goto next; + + for_each_event(dir_path, evt_dir, evt_dirent) { + + scnprintf(evt_path, MAXPATHLEN, "%s/%s/id", dir_path, + evt_dirent->d_name); + fd = open(evt_path, O_RDONLY); + if (fd < 0) + continue; + if (read(fd, id_buf, sizeof(id_buf)) < 0) { + close(fd); + continue; + } + close(fd); + id = atoll(id_buf); + if (id == config) { + put_events_file(dir_path); + closedir(evt_dir); + closedir(sys_dir); + path = zalloc(sizeof(*path)); + if (!path) + return NULL; + if (asprintf(&path->system, "%.*s", + MAX_EVENT_LENGTH, sys_dirent->d_name) < 0) { + free(path); + return NULL; + } + if (asprintf(&path->name, "%.*s", + MAX_EVENT_LENGTH, evt_dirent->d_name) < 0) { + zfree(&path->system); + free(path); + return NULL; + } + return path; + } + } + closedir(evt_dir); +next: + put_events_file(dir_path); + } + + closedir(sys_dir); + return NULL; +} + +static struct tracepoint_path *tracepoint_name_to_path(const char *name) +{ + struct tracepoint_path *path = zalloc(sizeof(*path)); + char *str = strchr(name, ':'); + + if (path == NULL || str == NULL) { + free(path); + return NULL; + } + + path->system = strndup(name, str - name); + path->name = strdup(str+1); + + if (path->system == NULL || path->name == NULL) { + zfree(&path->system); + zfree(&path->name); + zfree(&path); + } + + return path; +} + +static struct tracepoint_path * +get_tracepoints_path(struct list_head *pattrs) +{ + struct tracepoint_path path, *ppath = &path; + struct evsel *pos; + int nr_tracepoints = 0; + + list_for_each_entry(pos, pattrs, core.node) { + if (pos->core.attr.type != PERF_TYPE_TRACEPOINT) + continue; + ++nr_tracepoints; + + if (pos->name) { + ppath->next = tracepoint_name_to_path(pos->name); + if (ppath->next) + goto next; + + if (strchr(pos->name, ':') == NULL) + goto try_id; + + goto error; + } + +try_id: + ppath->next = tracepoint_id_to_path(pos->core.attr.config); + if (!ppath->next) { +error: + pr_debug("No memory to alloc tracepoints list\n"); + put_tracepoints_path(path.next); + return NULL; + } +next: + ppath = ppath->next; + } + + return nr_tracepoints > 0 ? path.next : NULL; +} + +bool have_tracepoints(struct list_head *pattrs) +{ + struct evsel *pos; + + list_for_each_entry(pos, pattrs, core.node) + if (pos->core.attr.type == PERF_TYPE_TRACEPOINT) + return true; + + return false; +} + +static int tracing_data_header(void) +{ + char buf[20]; + ssize_t size; + + /* just guessing this is someone's birthday.. ;) */ + buf[0] = 23; + buf[1] = 8; + buf[2] = 68; + memcpy(buf + 3, "tracing", 7); + + if (write(output_fd, buf, 10) != 10) + return -1; + + size = strlen(VERSION) + 1; + if (write(output_fd, VERSION, size) != size) + return -1; + + /* save endian */ + if (bigendian()) + buf[0] = 1; + else + buf[0] = 0; + + if (write(output_fd, buf, 1) != 1) + return -1; + + /* save size of long */ + buf[0] = sizeof(long); + if (write(output_fd, buf, 1) != 1) + return -1; + + /* save page_size */ + if (write(output_fd, &page_size, 4) != 4) + return -1; + + return 0; +} + +struct tracing_data *tracing_data_get(struct list_head *pattrs, + int fd, bool temp) +{ + struct tracepoint_path *tps; + struct tracing_data *tdata; + int err; + + output_fd = fd; + + tps = get_tracepoints_path(pattrs); + if (!tps) + return NULL; + + tdata = malloc(sizeof(*tdata)); + if (!tdata) + return NULL; + + tdata->temp = temp; + tdata->size = 0; + + if (temp) { + int temp_fd; + + snprintf(tdata->temp_file, sizeof(tdata->temp_file), + "/tmp/perf-XXXXXX"); + if (!mkstemp(tdata->temp_file)) { + pr_debug("Can't make temp file"); + free(tdata); + return NULL; + } + + temp_fd = open(tdata->temp_file, O_RDWR); + if (temp_fd < 0) { + pr_debug("Can't read '%s'", tdata->temp_file); + free(tdata); + return NULL; + } + + /* + * Set the temp file the default output, so all the + * tracing data are stored into it. + */ + output_fd = temp_fd; + } + + err = tracing_data_header(); + if (err) + goto out; + err = record_header_files(); + if (err) + goto out; + err = record_ftrace_files(tps); + if (err) + goto out; + err = record_event_files(tps); + if (err) + goto out; + err = record_proc_kallsyms(); + if (err) + goto out; + err = record_ftrace_printk(); + if (err) + goto out; + err = record_saved_cmdline(); + +out: + /* + * All tracing data are stored by now, we can restore + * the default output file in case we used temp file. + */ + if (temp) { + tdata->size = lseek(output_fd, 0, SEEK_CUR); + close(output_fd); + output_fd = fd; + } + + if (err) + zfree(&tdata); + + put_tracepoints_path(tps); + return tdata; +} + +int tracing_data_put(struct tracing_data *tdata) +{ + int err = 0; + + if (tdata->temp) { + err = record_file(tdata->temp_file, 0); + unlink(tdata->temp_file); + } + + free(tdata); + return err; +} + +int read_tracing_data(int fd, struct list_head *pattrs) +{ + int err; + struct tracing_data *tdata; + + /* + * We work over the real file, so we can write data + * directly, no temp file is needed. + */ + tdata = tracing_data_get(pattrs, fd, false); + if (!tdata) + return -ENOMEM; + + err = tracing_data_put(tdata); + return err; +} |