| /* |
| * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License (not later!) |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, see <http://www.gnu.org/licenses> |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| */ |
| #include <dirent.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <sys/socket.h> |
| #ifndef NO_PTRACE |
| #include <sys/ptrace.h> |
| #else |
| #ifdef WARN_NO_PTRACE |
| #warning ptrace not supported. -c feature will not work |
| #endif |
| #endif |
| #include <netdb.h> |
| #include <pthread.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <sched.h> |
| #include <glob.h> |
| #include <errno.h> |
| |
| #include "trace-local.h" |
| |
| #define _STR(x) #x |
| #define STR(x) _STR(x) |
| #define MAX_PATH 256 |
| |
| #define TRACE_CTRL "tracing_on" |
| #define TRACE "trace" |
| #define AVAILABLE "available_tracers" |
| #define CURRENT "current_tracer" |
| #define ITER_CTRL "trace_options" |
| #define MAX_LATENCY "tracing_max_latency" |
| #define STAMP "stamp" |
| #define FUNC_STACK_TRACE "func_stack_trace" |
| |
| #define UDP_MAX_PACKET (65536 - 20) |
| |
| static int tracing_on_init_val; |
| |
| static int rt_prio; |
| |
| static int use_tcp; |
| |
| static unsigned int page_size; |
| |
| static int buffer_size; |
| |
| static const char *output_file = "trace.dat"; |
| |
| static int latency; |
| static int sleep_time = 1000; |
| static int cpu_count; |
| static int *pids; |
| |
| static char *host; |
| static int *client_ports; |
| static int sfd; |
| static struct tracecmd_output *network_handle; |
| |
| static int do_ptrace; |
| |
| static int filter_task; |
| static int filter_pid = -1; |
| |
| static int finished; |
| |
| /* Try a few times to get an accurate date */ |
| static int date2ts_tries = 5; |
| |
| struct func_list { |
| struct func_list *next; |
| const char *func; |
| }; |
| |
| static struct func_list *filter_funcs; |
| static struct func_list *notrace_funcs; |
| static struct func_list *graph_funcs; |
| |
| static int func_stack; |
| |
| struct filter_pids { |
| struct filter_pids *next; |
| int pid; |
| }; |
| |
| static struct filter_pids *filter_pids; |
| static int nr_filter_pids; |
| static int len_filter_pids; |
| |
| struct opt_list { |
| struct opt_list *next; |
| const char *option; |
| }; |
| |
| static struct opt_list *options; |
| |
| static char *common_pid_filter; |
| |
| struct event_list { |
| struct event_list *next; |
| const char *event; |
| char *filter; |
| char *pid_filter; |
| char *filter_file; |
| char *enable_file; |
| int neg; |
| }; |
| |
| static struct event_list *sched_switch_event; |
| static struct event_list *sched_wakeup_event; |
| static struct event_list *sched_wakeup_new_event; |
| |
| static struct event_list *event_selection; |
| static struct event_list **event_selection_next = &event_selection; |
| |
| struct tracecmd_event_list *listed_events; |
| |
| struct events { |
| struct events *sibling; |
| struct events *children; |
| struct events *next; |
| char *name; |
| }; |
| |
| static struct tracecmd_recorder *recorder; |
| |
| static int ignore_event_not_found = 0; |
| |
| static void add_event(struct event_list *event) |
| { |
| *event_selection_next = event; |
| event_selection_next = &event->next; |
| event->next = NULL; |
| } |
| |
| static void reset_event_list(void) |
| { |
| event_selection = NULL; |
| event_selection_next = &event_selection; |
| } |
| |
| static char *get_temp_file(int cpu) |
| { |
| char *file = NULL; |
| int size; |
| |
| size = snprintf(file, 0, "%s.cpu%d", output_file, cpu); |
| file = malloc_or_die(size + 1); |
| sprintf(file, "%s.cpu%d", output_file, cpu); |
| |
| return file; |
| } |
| |
| static void put_temp_file(char *file) |
| { |
| free(file); |
| } |
| |
| static void delete_temp_file(int cpu) |
| { |
| char file[MAX_PATH]; |
| |
| snprintf(file, MAX_PATH, "%s.cpu%d", output_file, cpu); |
| unlink(file); |
| } |
| |
| static void kill_threads(void) |
| { |
| int i; |
| |
| if (!cpu_count || !pids) |
| return; |
| |
| for (i = 0; i < cpu_count; i++) { |
| if (pids[i] > 0) { |
| kill(pids[i], SIGKILL); |
| delete_temp_file(i); |
| pids[i] = 0; |
| } |
| } |
| } |
| |
| void die(const char *fmt, ...) |
| { |
| va_list ap; |
| int ret = errno; |
| |
| if (errno) |
| perror("trace-cmd"); |
| else |
| ret = -1; |
| |
| kill_threads(); |
| va_start(ap, fmt); |
| fprintf(stderr, " "); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| |
| fprintf(stderr, "\n"); |
| exit(ret); |
| } |
| |
| static void delete_thread_data(void) |
| { |
| int i; |
| |
| if (!cpu_count) |
| return; |
| |
| for (i = 0; i < cpu_count; i++) { |
| if (pids) { |
| if (pids[i]) { |
| delete_temp_file(i); |
| if (pids[i] < 0) |
| pids[i] = 0; |
| } |
| } else |
| /* Extract does not allocate pids */ |
| delete_temp_file(i); |
| } |
| } |
| |
| static void stop_threads(void) |
| { |
| int i; |
| |
| if (!cpu_count) |
| return; |
| |
| for (i = 0; i < cpu_count; i++) { |
| if (pids[i] > 0) { |
| kill(pids[i], SIGINT); |
| waitpid(pids[i], NULL, 0); |
| pids[i] = -1; |
| } |
| } |
| } |
| |
| static int create_recorder(int cpu, int extract); |
| |
| static void flush_threads(void) |
| { |
| long ret; |
| int i; |
| |
| if (!cpu_count) |
| return; |
| |
| for (i = 0; i < cpu_count; i++) { |
| ret = create_recorder(i, 1); |
| if (ret < 0) |
| die("error reading ring buffer"); |
| } |
| } |
| |
| static int set_ftrace(int set) |
| { |
| struct stat buf; |
| char *path = "/proc/sys/kernel/ftrace_enabled"; |
| int fd; |
| char *val = set ? "1" : "0"; |
| |
| /* if ftace_enable does not exist, simply ignore it */ |
| fd = stat(path, &buf); |
| if (fd < 0) |
| return -ENODEV; |
| |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die ("Can't %s ftrace", set ? "enable" : "disable"); |
| |
| write(fd, val, 1); |
| close(fd); |
| |
| return 0; |
| } |
| |
| static void clear_trace(void) |
| { |
| FILE *fp; |
| char *path; |
| |
| /* reset the trace */ |
| path = tracecmd_get_tracing_file("trace"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| tracecmd_put_tracing_file(path); |
| fwrite("0", 1, 1, fp); |
| fclose(fp); |
| } |
| |
| static void reset_max_latency(void) |
| { |
| FILE *fp; |
| char *path; |
| |
| /* reset the trace */ |
| path = tracecmd_get_tracing_file("tracing_max_latency"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| tracecmd_put_tracing_file(path); |
| fwrite("0", 1, 1, fp); |
| fclose(fp); |
| } |
| |
| static void add_filter_pid(int pid) |
| { |
| struct filter_pids *p; |
| char buf[100]; |
| |
| p = malloc_or_die(sizeof(*p)); |
| p->next = filter_pids; |
| p->pid = pid; |
| filter_pids = p; |
| nr_filter_pids++; |
| |
| len_filter_pids += sprintf(buf, "%d", pid); |
| } |
| |
| static void update_ftrace_pid(const char *pid, int reset) |
| { |
| static char *path; |
| int ret; |
| static int fd = -1; |
| |
| if (!pid) { |
| if (fd >= 0) |
| close(fd); |
| if (path) |
| tracecmd_put_tracing_file(path); |
| fd = -1; |
| path = NULL; |
| return; |
| } |
| |
| /* Force reopen on reset */ |
| if (reset && fd >= 0) { |
| close(fd); |
| fd = -1; |
| } |
| |
| if (fd < 0) { |
| if (!path) |
| path = tracecmd_get_tracing_file("set_ftrace_pid"); |
| if (!path) |
| return; |
| fd = open(path, O_WRONLY | O_CLOEXEC | (reset ? O_TRUNC : 0)); |
| if (fd < 0) |
| return; |
| } |
| |
| ret = write(fd, pid, strlen(pid)); |
| |
| /* |
| * Older kernels required "-1" to disable pid |
| */ |
| if (ret < 0 && !strlen(pid)) |
| ret = write(fd, "-1", 2); |
| |
| if (ret < 0) |
| die("error writing to %s", path); |
| |
| /* add whitespace in case another pid is written */ |
| write(fd, " ", 1); |
| } |
| |
| static void update_ftrace_pids(int reset) |
| { |
| char buf[100]; |
| struct filter_pids *pid; |
| |
| for (pid = filter_pids; pid; pid = pid->next) { |
| snprintf(buf, 100, "%d ", pid->pid); |
| update_ftrace_pid(buf, reset); |
| /* Only reset the first entry */ |
| reset = 0; |
| } |
| } |
| |
| static void update_event_filters(void); |
| static void update_pid_event_filters(void); |
| static void enable_tracing(void); |
| |
| /** |
| * make_pid_filter - create a filter string to all pids against @field |
| * @curr_filter: Append to a previous filter (may realloc). Can be NULL |
| * @field: The fild to compare the pids against |
| * |
| * Creates a new string or appends to an existing one if @curr_filter |
| * is not NULL. The new string will contain a filter with all pids |
| * in pid_filter list with the format (@field == pid) || .. |
| * If @curr_filter is not NULL, it will add this string as: |
| * (@curr_filter) && ((@field == pid) || ...) |
| */ |
| static char *make_pid_filter(char *curr_filter, const char *field) |
| { |
| struct filter_pids *p; |
| char *filter; |
| char *orit; |
| char *str; |
| int curr_len = 0; |
| int len; |
| |
| len = len_filter_pids + (strlen(field) + strlen("(==)||")) * nr_filter_pids; |
| |
| if (curr_filter) { |
| curr_len = strlen(curr_filter); |
| filter = realloc(curr_filter, curr_len + len + strlen("(&&())")); |
| if (!filter) |
| die("realloc"); |
| memmove(filter+1, curr_filter, curr_len); |
| filter[0] = '('; |
| strcat(filter, ")&&("); |
| curr_len = strlen(filter); |
| } else |
| filter = malloc_or_die(len); |
| |
| /* Last '||' that is not used will cover the \0 */ |
| str = filter + curr_len; |
| |
| for (p = filter_pids; p; p = p->next) { |
| if (p == filter_pids) |
| orit = ""; |
| else |
| orit = "||"; |
| len = sprintf(str, "%s(%s==%d)", orit, field, p->pid); |
| str += len; |
| } |
| |
| if (curr_len) |
| sprintf(str, ")"); |
| |
| return filter; |
| } |
| |
| static void update_task_filter(void) |
| { |
| int pid = getpid(); |
| |
| if (filter_task) |
| add_filter_pid(pid); |
| |
| if (!filter_pids) |
| return; |
| |
| common_pid_filter = make_pid_filter(NULL, "common_pid"); |
| |
| update_ftrace_pids(1); |
| update_pid_event_filters(); |
| } |
| |
| #ifndef NO_PTRACE |
| |
| /** |
| * append_pid_filter - add a new pid to an existing filter |
| * @curr_filter: the filter to append to. If NULL, then allocate one |
| * @field: The fild to compare the pid to |
| * @pid: The pid to add to. |
| */ |
| static char *append_pid_filter(char *curr_filter, const char *field, int pid) |
| { |
| char *filter; |
| int len; |
| |
| len = snprintf(NULL, 0, "(%s==%d)||", field, pid); |
| |
| if (!curr_filter) { |
| /* No need for +1 as we don't use the "||" */ |
| filter = malloc_or_die(len); |
| sprintf(filter, "(%s==%d)", field, pid); |
| } else { |
| int indx = strlen(curr_filter); |
| |
| len += indx; |
| filter = realloc(curr_filter, len + indx + 1); |
| if (!filter) |
| die("realloc"); |
| sprintf(filter + indx, "||(%s==%d)", field, pid); |
| } |
| |
| return filter; |
| } |
| |
| static void append_sched_event(struct event_list *event, const char *field, int pid) |
| { |
| if (!event || !event->pid_filter) |
| return; |
| |
| event->pid_filter = append_pid_filter(event->pid_filter, field, pid); |
| } |
| |
| static void add_new_filter_pid(int pid) |
| { |
| char buf[100]; |
| |
| add_filter_pid(pid); |
| sprintf(buf, "%d", pid); |
| update_ftrace_pid(buf, 0); |
| |
| common_pid_filter = append_pid_filter(common_pid_filter, "common_pid", pid); |
| |
| /* |
| * Also make sure that the sched_switch to this pid |
| * and wakeups of this pid are also traced. |
| * Only need to do this if the events are active. |
| */ |
| append_sched_event(sched_switch_event, "next_pid", pid); |
| append_sched_event(sched_wakeup_event, "pid", pid); |
| append_sched_event(sched_wakeup_new_event, "pid", pid); |
| |
| update_event_filters(); |
| } |
| |
| static void ptrace_attach(int pid) |
| { |
| int ret; |
| |
| ret = ptrace(PTRACE_ATTACH, pid, NULL, 0); |
| if (ret < 0) { |
| warning("Unable to trace process %d children", pid); |
| do_ptrace = 0; |
| return; |
| } |
| add_filter_pid(pid); |
| } |
| |
| static void enable_ptrace(void) |
| { |
| if (!do_ptrace || !filter_task) |
| return; |
| |
| ptrace(PTRACE_TRACEME, 0, NULL, 0); |
| } |
| |
| static void ptrace_wait(int main_pid) |
| { |
| unsigned long send_sig; |
| unsigned long child; |
| siginfo_t sig; |
| int cstatus; |
| int status; |
| int event; |
| int pid; |
| int ret; |
| |
| do { |
| ret = waitpid(-1, &status, WSTOPPED | __WALL); |
| if (ret < 0) |
| continue; |
| |
| pid = ret; |
| |
| if (WIFSTOPPED(status)) { |
| event = (status >> 16) & 0xff; |
| ptrace(PTRACE_GETSIGINFO, pid, NULL, &sig); |
| send_sig = sig.si_signo; |
| /* Don't send ptrace sigs to child */ |
| if (send_sig == SIGTRAP || send_sig == SIGSTOP) |
| send_sig = 0; |
| switch (event) { |
| case PTRACE_EVENT_FORK: |
| case PTRACE_EVENT_VFORK: |
| case PTRACE_EVENT_CLONE: |
| /* forked a child */ |
| ptrace(PTRACE_GETEVENTMSG, pid, NULL, &child); |
| ptrace(PTRACE_SETOPTIONS, child, NULL, |
| PTRACE_O_TRACEFORK | |
| PTRACE_O_TRACEVFORK | |
| PTRACE_O_TRACECLONE | |
| PTRACE_O_TRACEEXIT); |
| add_new_filter_pid(child); |
| ptrace(PTRACE_CONT, child, NULL, 0); |
| break; |
| |
| case PTRACE_EVENT_EXIT: |
| ptrace(PTRACE_GETEVENTMSG, pid, NULL, &cstatus); |
| ptrace(PTRACE_DETACH, pid, NULL, NULL); |
| break; |
| } |
| ptrace(PTRACE_SETOPTIONS, pid, NULL, |
| PTRACE_O_TRACEFORK | |
| PTRACE_O_TRACEVFORK | |
| PTRACE_O_TRACECLONE | |
| PTRACE_O_TRACEEXIT); |
| ptrace(PTRACE_CONT, pid, NULL, send_sig); |
| } |
| } while (!finished && ret > 0 && |
| (!WIFEXITED(status) || pid != main_pid)); |
| } |
| #else |
| static inline void ptrace_wait(int main_pid) { } |
| static inline void enable_ptrace(void) { } |
| static inline void ptrace_attach(int pid) { } |
| |
| #endif /* NO_PTRACE */ |
| |
| void trace_or_sleep(void) |
| { |
| if (do_ptrace && filter_pid >= 0) |
| ptrace_wait(filter_pid); |
| else |
| sleep(10); |
| } |
| |
| void run_cmd(int argc, char **argv) |
| { |
| int status; |
| int pid; |
| |
| if ((pid = fork()) < 0) |
| die("failed to fork"); |
| if (!pid) { |
| /* child */ |
| update_task_filter(); |
| enable_tracing(); |
| enable_ptrace(); |
| if (execvp(argv[0], argv)) { |
| fprintf(stderr, "\n********************\n"); |
| fprintf(stderr, " Unable to exec %s\n", argv[0]); |
| fprintf(stderr, "********************\n"); |
| die("Failed to exec %s", argv[0]); |
| } |
| } |
| if (do_ptrace) { |
| add_filter_pid(pid); |
| ptrace_wait(pid); |
| } else |
| waitpid(pid, &status, 0); |
| } |
| |
| static void set_plugin(const char *name) |
| { |
| FILE *fp; |
| char *path; |
| char zero = '0'; |
| |
| path = tracecmd_get_tracing_file("current_tracer"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| tracecmd_put_tracing_file(path); |
| |
| fwrite(name, 1, strlen(name), fp); |
| fclose(fp); |
| |
| if (strncmp(name, "function", 8) != 0) |
| return; |
| |
| /* Make sure func_stack_trace option is disabled */ |
| path = tracecmd_get_tracing_file("options/func_stack_trace"); |
| fp = fopen(path, "w"); |
| tracecmd_put_tracing_file(path); |
| if (!fp) |
| return; |
| fwrite(&zero, 1, 1, fp); |
| fclose(fp); |
| } |
| |
| static void save_option(const char *option) |
| { |
| struct opt_list *opt; |
| |
| opt = malloc_or_die(sizeof(*opt)); |
| opt->next = options; |
| options = opt; |
| opt->option = option; |
| } |
| |
| static void set_option(const char *option) |
| { |
| FILE *fp; |
| char *path; |
| |
| path = tracecmd_get_tracing_file("trace_options"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| tracecmd_put_tracing_file(path); |
| |
| fwrite(option, 1, strlen(option), fp); |
| fclose(fp); |
| } |
| |
| static void set_options(void) |
| { |
| struct opt_list *opt; |
| |
| while (options) { |
| opt = options; |
| options = opt->next; |
| set_option(opt->option); |
| free(opt); |
| } |
| } |
| |
| static int use_old_event_method(void) |
| { |
| static int old_event_method; |
| static int processed; |
| struct stat st; |
| char *path; |
| int ret; |
| |
| if (processed) |
| return old_event_method; |
| |
| /* Check if the kernel has the events/enable file */ |
| path = tracecmd_get_tracing_file("events/enable"); |
| ret = stat(path, &st); |
| tracecmd_put_tracing_file(path); |
| if (ret < 0) |
| old_event_method = 1; |
| |
| processed = 1; |
| |
| return old_event_method; |
| } |
| |
| static void old_update_events(const char *name, char update) |
| { |
| char *path; |
| FILE *fp; |
| int ret; |
| |
| if (strcmp(name, "all") == 0) |
| name = "*:*"; |
| |
| /* need to use old way */ |
| path = tracecmd_get_tracing_file("set_event"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("opening '%s'", path); |
| tracecmd_put_tracing_file(path); |
| |
| /* Disable the event with "!" */ |
| if (update == '0') |
| fwrite("!", 1, 1, fp); |
| |
| ret = fwrite(name, 1, strlen(name), fp); |
| if (ret < 0) |
| die("bad event '%s'", name); |
| |
| ret = fwrite("\n", 1, 1, fp); |
| if (ret < 0) |
| die("bad event '%s'", name); |
| |
| fclose(fp); |
| |
| return; |
| } |
| |
| static void reset_events(void) |
| { |
| glob_t globbuf; |
| char *path; |
| char c; |
| int fd; |
| int i; |
| int ret; |
| |
| if (use_old_event_method()) { |
| old_update_events("all", '0'); |
| return; |
| } |
| |
| c = '0'; |
| path = tracecmd_get_tracing_file("events/enable"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die("opening to '%s'", path); |
| ret = write(fd, &c, 1); |
| close(fd); |
| tracecmd_put_tracing_file(path); |
| |
| path = tracecmd_get_tracing_file("events/*/filter"); |
| globbuf.gl_offs = 0; |
| ret = glob(path, 0, NULL, &globbuf); |
| tracecmd_put_tracing_file(path); |
| if (ret < 0) |
| return; |
| |
| for (i = 0; i < globbuf.gl_pathc; i++) { |
| path = globbuf.gl_pathv[i]; |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die("opening to '%s'", path); |
| ret = write(fd, &c, 1); |
| close(fd); |
| } |
| globfree(&globbuf); |
| } |
| |
| static void write_filter(const char *file, const char *filter) |
| { |
| char buf[BUFSIZ]; |
| int fd; |
| int ret; |
| |
| fd = open(file, O_WRONLY); |
| if (fd < 0) |
| die("opening to '%s'", file); |
| ret = write(fd, filter, strlen(filter)); |
| close(fd); |
| if (ret < 0) { |
| /* filter failed */ |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| die("writing to '%s'", file); |
| /* the filter has the error */ |
| while ((ret = read(fd, buf, BUFSIZ)) > 0) |
| fprintf(stderr, "%.*s", ret, buf); |
| die("Failed filter of %s\n", file); |
| close(fd); |
| } |
| } |
| |
| static void |
| update_event(struct event_list *event, const char *filter, |
| int filter_only, char update) |
| { |
| const char *name = event->event; |
| FILE *fp; |
| char *path; |
| int ret; |
| |
| if (use_old_event_method()) { |
| if (filter_only) |
| return; |
| old_update_events(name, update); |
| return; |
| } |
| |
| if (filter && event->filter_file) |
| write_filter(event->filter_file, filter); |
| |
| if (filter_only || !event->enable_file) |
| return; |
| |
| path = event->enable_file; |
| |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| ret = fwrite(&update, 1, 1, fp); |
| fclose(fp); |
| if (ret < 0) |
| die("writing to '%s'", path); |
| } |
| |
| /* |
| * The debugfs file tracing_enabled needs to be deprecated. |
| * But just in case anyone fiddled with it. If it exists, |
| * make sure it is one. |
| * No error checking needed here. |
| */ |
| static void check_tracing_enabled(void) |
| { |
| static int fd = -1; |
| char *path; |
| |
| if (fd < 0) { |
| path = tracecmd_get_tracing_file("tracing_enabled"); |
| fd = open(path, O_WRONLY | O_CLOEXEC); |
| tracecmd_put_tracing_file(path); |
| |
| if (fd < 0) |
| return; |
| } |
| write(fd, "1", 1); |
| } |
| |
| static int tracing_on_fd = -1; |
| |
| static int open_tracing_on(void) |
| { |
| int fd = tracing_on_fd; |
| char *path; |
| |
| if (fd >= 0) |
| return fd; |
| |
| path = tracecmd_get_tracing_file("tracing_on"); |
| fd = open(path, O_RDWR | O_CLOEXEC); |
| if (fd < 0) |
| die("opening '%s'", path); |
| tracecmd_put_tracing_file(path); |
| tracing_on_fd = fd; |
| |
| return fd; |
| } |
| |
| static void write_tracing_on(int on) |
| { |
| int ret; |
| int fd; |
| |
| fd = open_tracing_on(); |
| if (fd < 0) |
| return; |
| |
| if (on) |
| ret = write(fd, "1", 1); |
| else |
| ret = write(fd, "0", 1); |
| |
| if (ret < 0) |
| die("writing 'tracing_on'"); |
| } |
| |
| static int read_tracing_on(void) |
| { |
| int fd; |
| char buf[10]; |
| int ret; |
| |
| fd = open_tracing_on(); |
| if (fd < 0) |
| return 0; |
| |
| ret = read(fd, buf, 10); |
| if (ret <= 0) |
| die("Reading 'tracing_on'"); |
| buf[9] = 0; |
| ret = atoi(buf); |
| |
| return ret; |
| } |
| |
| static void enable_tracing(void) |
| { |
| check_tracing_enabled(); |
| |
| write_tracing_on(1); |
| |
| if (latency) |
| reset_max_latency(); |
| } |
| |
| static void disable_tracing(void) |
| { |
| write_tracing_on(0); |
| } |
| |
| static void disable_all(int disable_tracer) |
| { |
| disable_tracing(); |
| |
| if (disable_tracer) |
| set_plugin("nop"); |
| |
| reset_events(); |
| |
| /* Force close and reset of ftrace pid file */ |
| update_ftrace_pid("", 1); |
| update_ftrace_pid(NULL, 0); |
| |
| clear_trace(); |
| } |
| |
| static void |
| update_sched_event(struct event_list *event, const char *field) |
| { |
| if (!event) |
| return; |
| |
| event->pid_filter = make_pid_filter(event->pid_filter, field); |
| } |
| |
| static void update_event_filters(void) |
| { |
| struct event_list *event; |
| char *event_filter; |
| int free_it; |
| int len; |
| int common_len = 0; |
| |
| if (common_pid_filter) |
| common_len = strlen(common_pid_filter); |
| |
| for (event = event_selection; event; event = event->next) { |
| if (!event->neg) { |
| |
| free_it = 0; |
| if (event->filter) { |
| if (!common_pid_filter) |
| /* |
| * event->pid_filter is only created if |
| * common_pid_filter is. No need to check that. |
| * Just use the current event->filter. |
| */ |
| event_filter = event->filter; |
| else if (event->pid_filter) { |
| free_it = 1; |
| len = common_len + strlen(event->pid_filter) + |
| strlen(event->filter) + strlen("()&&(||)") + 1; |
| event_filter = malloc_or_die(len); |
| sprintf(event_filter, "(%s)&&(%s||%s)", |
| event->filter, common_pid_filter, |
| event->pid_filter); |
| } else { |
| free_it = 1; |
| len = common_len + strlen(event->filter) + |
| strlen("()&&()") + 1; |
| event_filter = malloc_or_die(len); |
| sprintf(event_filter, "(%s)&&(%s)", |
| event->filter, common_pid_filter); |
| } |
| } else { |
| /* event->pid_filter only exists when common_pid_filter does */ |
| if (!common_pid_filter) |
| continue; |
| |
| if (event->pid_filter) { |
| free_it = 1; |
| len = common_len + strlen(event->pid_filter) + |
| strlen("||") + 1; |
| event_filter = malloc_or_die(len); |
| sprintf(event_filter, "%s||%s", |
| common_pid_filter, event->pid_filter); |
| } else |
| event_filter = common_pid_filter; |
| } |
| |
| update_event(event, event_filter, 1, '1'); |
| if (free_it) |
| free(event_filter); |
| } |
| } |
| } |
| |
| static void update_pid_event_filters(void) |
| { |
| /* |
| * Also make sure that the sched_switch to this pid |
| * and wakeups of this pid are also traced. |
| * Only need to do this if the events are active. |
| */ |
| update_sched_event(sched_switch_event, "next_pid"); |
| update_sched_event(sched_wakeup_event, "pid"); |
| update_sched_event(sched_wakeup_new_event, "pid"); |
| |
| update_event_filters(); |
| } |
| |
| static void enable_events(void) |
| { |
| struct event_list *event; |
| |
| for (event = event_selection; event; event = event->next) { |
| if (!event->neg) |
| update_event(event, event->filter, 0, '1'); |
| } |
| |
| /* Now disable any events */ |
| for (event = event_selection; event; event = event->next) { |
| if (event->neg) |
| update_event(event, NULL, 0, '0'); |
| } |
| } |
| |
| static struct event_list *create_event(char *path, struct event_list *old_event) |
| { |
| struct event_list *event; |
| struct stat st; |
| char *p; |
| int ret; |
| |
| event = malloc_or_die(sizeof(*event)); |
| *event = *old_event; |
| add_event(event); |
| |
| if (event->filter || filter_task || filter_pid) { |
| event->filter_file = strdup(path); |
| if (!event->filter_file) |
| die("malloc filter file"); |
| } |
| for (p = path + strlen(path) - 1; p > path; p--) |
| if (*p == '/') |
| break; |
| *p = '\0'; |
| p = malloc_or_die(strlen(path) + strlen("/enable") + 1); |
| sprintf(p, "%s/enable", path); |
| ret = stat(p, &st); |
| if (ret >= 0) |
| event->enable_file = p; |
| else |
| free(p); |
| |
| return event; |
| } |
| |
| static void make_sched_event(struct event_list **event, struct event_list *sched, |
| const char *sched_path) |
| { |
| char *path; |
| char *p; |
| |
| /* Do nothing if the event already exists */ |
| if (*event) |
| return; |
| |
| path = malloc_or_die(strlen(sched->filter_file) + strlen(sched_path) + 2); |
| |
| sprintf(path, "%s", sched->filter_file); |
| |
| /* Remove the /filter from filter file */ |
| p = path + strlen(path) - strlen("filter"); |
| sprintf(p, "%s/filter", sched_path); |
| |
| *event = create_event(path, sched); |
| free(path); |
| } |
| |
| static void test_event(struct event_list *event, const char *path, |
| const char *name, struct event_list **save, int len) |
| { |
| path += len - strlen(name); |
| |
| if (strcmp(path, name) != 0) |
| return; |
| |
| *save = event; |
| } |
| |
| static int expand_event_files(const char *file, struct event_list *old_event) |
| { |
| struct event_list **save_event_tail = event_selection_next; |
| struct event_list *sched_event = NULL; |
| struct event_list *event; |
| glob_t globbuf; |
| char *path; |
| char *p; |
| int ret; |
| int i; |
| |
| p = malloc_or_die(strlen(file) + strlen("events//filter") + 1); |
| sprintf(p, "events/%s/filter", file); |
| |
| path = tracecmd_get_tracing_file(p); |
| printf("%s\n", path); |
| |
| globbuf.gl_offs = 0; |
| ret = glob(path, 0, NULL, &globbuf); |
| tracecmd_put_tracing_file(path); |
| free(p); |
| |
| if (ret < 0) |
| die("No filters found"); |
| |
| for (i = 0; i < globbuf.gl_pathc; i++) { |
| int len; |
| |
| path = globbuf.gl_pathv[i]; |
| |
| event = create_event(path, old_event); |
| |
| len = strlen(path); |
| |
| test_event(event, path, "sched", &sched_event, len); |
| test_event(event, path, "sched/sched_switch", &sched_switch_event, len); |
| test_event(event, path, "sched/sched_wakeup_new", &sched_wakeup_new_event, len); |
| test_event(event, path, "sched/sched_wakeup", &sched_wakeup_event, len); |
| } |
| |
| if (sched_event && sched_event->filter_file) { |
| /* make sure all sched events exist */ |
| make_sched_event(&sched_switch_event, sched_event, "sched_switch"); |
| make_sched_event(&sched_wakeup_event, sched_event, "sched_wakeup"); |
| make_sched_event(&sched_wakeup_new_event, sched_event, "sched_wakeup_new"); |
| |
| } |
| |
| |
| globfree(&globbuf); |
| |
| /* If the event list tail changed, that means events were added */ |
| return save_event_tail == event_selection_next; |
| } |
| |
| static void expand_event(struct event_list *event) |
| { |
| const char *name = event->event; |
| char *str; |
| char *ptr; |
| int len; |
| int ret; |
| int ret2; |
| |
| /* |
| * We allow the user to use "all" to enable all events. |
| * Expand event_selection to all systems. |
| */ |
| if (strcmp(name, "all") == 0) { |
| expand_event_files("*", event); |
| return; |
| } |
| |
| ptr = strchr(name, ':'); |
| |
| if (ptr) { |
| len = ptr - name; |
| str = malloc_or_die(strlen(name) + 1); /* may add '*' */ |
| strcpy(str, name); |
| str[len] = '/'; |
| ptr++; |
| if (!strlen(ptr)) { |
| str[len + 1] = '*'; |
| str[len + 2] = '\0'; |
| } |
| |
| ret = expand_event_files(str, event); |
| if (!ignore_event_not_found && ret) |
| die("No events enabled with %s", name); |
| free(str); |
| return; |
| } |
| |
| /* No ':' so enable all matching systems and events */ |
| ret = expand_event_files(name, event); |
| |
| len = strlen(name) + strlen("*/") + 1; |
| str = malloc_or_die(len); |
| snprintf(str, len, "*/%s", name); |
| ret2 = expand_event_files(str, event); |
| free(str); |
| |
| if (!ignore_event_not_found && ret && ret2) |
| die("No events enabled with %s", name); |
| |
| return; |
| } |
| |
| static void expand_event_list(void) |
| { |
| struct event_list *compressed_list = event_selection; |
| struct event_list *event; |
| |
| if (use_old_event_method()) |
| return; |
| |
| reset_event_list(); |
| |
| while (compressed_list) { |
| event = compressed_list; |
| compressed_list = event->next; |
| expand_event(event); |
| free(event); |
| } |
| } |
| |
| static int count_cpus(void) |
| { |
| FILE *fp; |
| char buf[1024]; |
| int cpus = 0; |
| char *pbuf; |
| size_t *pn; |
| size_t n; |
| int r; |
| |
| cpus = sysconf(_SC_NPROCESSORS_CONF); |
| if (cpus > 0) |
| return cpus; |
| |
| warning("sysconf could not determine number of CPUS"); |
| |
| /* Do the hack to figure out # of CPUS */ |
| n = 1024; |
| pn = &n; |
| pbuf = buf; |
| |
| fp = fopen("/proc/cpuinfo", "r"); |
| if (!fp) |
| die("Can not read cpuinfo"); |
| |
| while ((r = getline(&pbuf, pn, fp)) >= 0) { |
| char *p; |
| |
| if (strncmp(buf, "processor", 9) != 0) |
| continue; |
| for (p = buf+9; isspace(*p); p++) |
| ; |
| if (*p == ':') |
| cpus++; |
| } |
| fclose(fp); |
| |
| return cpus; |
| } |
| |
| static void finish(int sig) |
| { |
| /* all done */ |
| if (recorder) |
| tracecmd_stop_recording(recorder); |
| finished = 1; |
| } |
| |
| static void flush(int sig) |
| { |
| if (recorder) |
| tracecmd_stop_recording(recorder); |
| } |
| |
| static void connect_port(int cpu) |
| { |
| struct addrinfo hints; |
| struct addrinfo *results, *rp; |
| int s; |
| char buf[BUFSIZ]; |
| |
| snprintf(buf, BUFSIZ, "%d", client_ports[cpu]); |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = use_tcp ? SOCK_STREAM : SOCK_DGRAM; |
| |
| s = getaddrinfo(host, buf, &hints, &results); |
| if (s != 0) |
| die("connecting to %s server %s:%s", |
| use_tcp ? "TCP" : "UDP", host, buf); |
| |
| for (rp = results; rp != NULL; rp = rp->ai_next) { |
| sfd = socket(rp->ai_family, rp->ai_socktype, |
| rp->ai_protocol); |
| if (sfd == -1) |
| continue; |
| if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) |
| break; |
| close(sfd); |
| } |
| |
| if (rp == NULL) |
| die("Can not connect to %s server %s:%s", |
| use_tcp ? "TCP" : "UDP", host, buf); |
| |
| freeaddrinfo(results); |
| |
| client_ports[cpu] = sfd; |
| } |
| |
| static void set_prio(int prio) |
| { |
| struct sched_param sp; |
| |
| memset(&sp, 0, sizeof(sp)); |
| sp.sched_priority = prio; |
| if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0) |
| warning("failed to set priority"); |
| } |
| |
| /* |
| * If extract is set, then this is going to set up the recorder, |
| * connections and exit as the tracing is serialized by a single thread. |
| */ |
| static int create_recorder(int cpu, int extract) |
| { |
| long ret; |
| char *file; |
| int pid; |
| |
| if (!extract) { |
| signal(SIGUSR1, flush); |
| |
| pid = fork(); |
| if (pid < 0) |
| die("fork"); |
| |
| if (pid) |
| return pid; |
| |
| if (rt_prio) |
| set_prio(rt_prio); |
| |
| /* do not kill tasks on error */ |
| cpu_count = 0; |
| } |
| |
| if (client_ports) { |
| connect_port(cpu); |
| recorder = tracecmd_create_recorder_fd(client_ports[cpu], cpu); |
| } else { |
| file = get_temp_file(cpu); |
| recorder = tracecmd_create_recorder(file, cpu); |
| put_temp_file(file); |
| } |
| |
| if (!recorder) |
| die ("can't create recorder"); |
| |
| if (extract) { |
| ret = tracecmd_flush_recording(recorder); |
| tracecmd_free_recorder(recorder); |
| return ret; |
| } |
| |
| while (!finished) { |
| if (tracecmd_start_recording(recorder, sleep_time) < 0) |
| break; |
| } |
| tracecmd_free_recorder(recorder); |
| |
| exit(0); |
| } |
| |
| static void setup_network(void) |
| { |
| struct addrinfo hints; |
| struct addrinfo *result, *rp; |
| int sfd, s; |
| ssize_t n; |
| char buf[BUFSIZ]; |
| char *server; |
| char *port; |
| char *p; |
| int cpu; |
| int i; |
| |
| if (!strchr(host, ':')) { |
| server = strdup("localhost"); |
| if (!server) |
| die("alloctating server"); |
| port = host; |
| host = server; |
| } else { |
| host = strdup(host); |
| if (!host) |
| die("alloctating server"); |
| server = strtok_r(host, ":", &p); |
| port = strtok_r(NULL, ":", &p); |
| } |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| |
| s = getaddrinfo(server, port, &hints, &result); |
| if (s != 0) |
| die("getaddrinfo: %s", gai_strerror(s)); |
| |
| for (rp = result; rp != NULL; rp = rp->ai_next) { |
| sfd = socket(rp->ai_family, rp->ai_socktype, |
| rp->ai_protocol); |
| if (sfd == -1) |
| continue; |
| |
| if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) |
| break; |
| close(sfd); |
| } |
| |
| if (!rp) |
| die("Can not connect to %s:%s", server, port); |
| |
| freeaddrinfo(result); |
| |
| n = read(sfd, buf, 8); |
| |
| /* Make sure the server is the tracecmd server */ |
| if (memcmp(buf, "tracecmd", 8) != 0) |
| die("server not tracecmd server"); |
| |
| /* write the number of CPUs we have (in ASCII) */ |
| |
| sprintf(buf, "%d", cpu_count); |
| |
| /* include \0 */ |
| write(sfd, buf, strlen(buf)+1); |
| |
| /* write the pagesize (in ASCII) */ |
| sprintf(buf, "%d", page_size); |
| |
| /* include \0 */ |
| write(sfd, buf, strlen(buf)+1); |
| |
| /* |
| * If we are using IPV4 and our page size is greater than |
| * or equal to 64K, we need to punt and use TCP. :-( |
| */ |
| |
| /* TODO, test for ipv4 */ |
| if (page_size >= UDP_MAX_PACKET) { |
| warning("page size too big for UDP using TCP in live read"); |
| use_tcp = 1; |
| } |
| |
| if (use_tcp) { |
| /* Send one option */ |
| write(sfd, "1", 2); |
| /* Size 4 */ |
| write(sfd, "4", 2); |
| /* use TCP */ |
| write(sfd, "TCP", 4); |
| } else |
| /* No options */ |
| write(sfd, "0", 2); |
| |
| client_ports = malloc_or_die(sizeof(int) * cpu_count); |
| |
| /* |
| * Now we will receive back a comma deliminated list |
| * of client ports to connect to. |
| */ |
| for (cpu = 0; cpu < cpu_count; cpu++) { |
| for (i = 0; i < BUFSIZ; i++) { |
| n = read(sfd, buf+i, 1); |
| if (n != 1) |
| die("Error, reading server ports"); |
| if (!buf[i] || buf[i] == ',') |
| break; |
| } |
| if (i == BUFSIZ) |
| die("read bad port number"); |
| buf[i] = 0; |
| client_ports[cpu] = atoi(buf); |
| } |
| |
| /* Now create the handle through this socket */ |
| network_handle = tracecmd_create_init_fd_glob(sfd, listed_events); |
| |
| /* OK, we are all set, let'r rip! */ |
| } |
| |
| static void finish_network(void) |
| { |
| close(sfd); |
| free(host); |
| } |
| |
| static void start_threads(void) |
| { |
| int i; |
| |
| if (host) |
| setup_network(); |
| |
| /* make a thread for every CPU we have */ |
| pids = malloc_or_die(sizeof(*pids) * cpu_count); |
| |
| memset(pids, 0, sizeof(*pids) * cpu_count); |
| |
| /* Make sure all output is flushed before forking */ |
| fflush(stdout); |
| |
| for (i = 0; i < cpu_count; i++) { |
| pids[i] = create_recorder(i, 0); |
| } |
| } |
| |
| static void record_data(char *date2ts, struct trace_seq *s) |
| { |
| struct tracecmd_output *handle; |
| char **temp_files; |
| int i; |
| |
| if (host) { |
| finish_network(); |
| return; |
| } |
| |
| if (latency) |
| handle = tracecmd_create_file_latency(output_file, cpu_count); |
| else { |
| if (!cpu_count) |
| return; |
| |
| temp_files = malloc_or_die(sizeof(*temp_files) * cpu_count); |
| |
| for (i = 0; i < cpu_count; i++) |
| temp_files[i] = get_temp_file(i); |
| |
| handle = tracecmd_create_init_file_glob(output_file, listed_events); |
| if (!handle) |
| die("Error creating output file"); |
| |
| if (date2ts) |
| tracecmd_add_option(handle, TRACECMD_OPTION_DATE, |
| strlen(date2ts)+1, date2ts); |
| |
| for (i = 0; i < cpu_count; i++) |
| tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT, |
| s[i].len+1, s[i].buffer); |
| |
| tracecmd_append_cpu_data(handle, cpu_count, temp_files); |
| |
| for (i = 0; i < cpu_count; i++) |
| put_temp_file(temp_files[i]); |
| free(temp_files); |
| } |
| if (!handle) |
| die("could not write to file"); |
| tracecmd_output_close(handle); |
| } |
| |
| static void write_func_file(const char *file, struct func_list **list) |
| { |
| struct func_list *item; |
| char *path; |
| int fd; |
| int ret; |
| |
| path = tracecmd_get_tracing_file(file); |
| |
| fd = open(path, O_WRONLY | O_TRUNC); |
| if (fd < 0) |
| goto free; |
| |
| while (*list) { |
| item = *list; |
| *list = item->next; |
| ret = write(fd, item->func, strlen(item->func)); |
| if (ret < 0) |
| goto failed; |
| ret = write(fd, " ", 1); |
| if (ret < 0) |
| goto failed; |
| free(item); |
| } |
| close(fd); |
| |
| free: |
| tracecmd_put_tracing_file(path); |
| return; |
| failed: |
| die("Failed to write %s to %s.\n" |
| "Perhaps this function is not available for tracing.\n" |
| "run 'trace-cmd list -f %s' to see if it is.", |
| item->func, file, item->func); |
| } |
| |
| static int functions_filtered(void) |
| { |
| char buf[1] = { '#' }; |
| char *path; |
| int fd; |
| |
| path = tracecmd_get_tracing_file("set_ftrace_filter"); |
| fd = open(path, O_RDONLY); |
| tracecmd_put_tracing_file(path); |
| if (fd < 0) |
| return 0; |
| |
| /* |
| * If functions are not filtered, than the first character |
| * will be '#'. Make sure it is not an '#' and also not space. |
| */ |
| read(fd, buf, 1); |
| close(fd); |
| |
| if (buf[0] == '#' || isspace(buf[0])) |
| return 0; |
| return 1; |
| } |
| |
| static void set_funcs(void) |
| { |
| write_func_file("set_ftrace_filter", &filter_funcs); |
| write_func_file("set_ftrace_notrace", ¬race_funcs); |
| write_func_file("set_graph_function", &graph_funcs); |
| |
| /* make sure we are filtering functions */ |
| if (func_stack) { |
| if (!functions_filtered()) { |
| disable_all(1); |
| die("Function stack trace set, but functions not filtered"); |
| } |
| save_option(FUNC_STACK_TRACE); |
| } |
| } |
| |
| static void add_func(struct func_list **list, const char *func) |
| { |
| struct func_list *item; |
| |
| item = malloc_or_die(sizeof(*item)); |
| item->func = func; |
| item->next = *list; |
| *list = item; |
| } |
| |
| static unsigned long long |
| find_ts_in_page(struct pevent *pevent, void *page, int size) |
| { |
| struct event_format *event; |
| struct format_field *field; |
| struct pevent_record *last_record = NULL; |
| struct pevent_record *record; |
| unsigned long long ts = 0; |
| int id; |
| |
| if (size <= 0) |
| return 0; |
| |
| while (!ts) { |
| record = tracecmd_read_page_record(pevent, page, size, |
| last_record); |
| if (!record) |
| break; |
| free_record(last_record); |
| id = pevent_data_type(pevent, record); |
| event = pevent_data_event_from_type(pevent, id); |
| if (event) { |
| /* Make sure this is our event */ |
| field = pevent_find_field(event, "buf"); |
| /* the trace_marker adds a '\n' */ |
| if (field && strcmp(STAMP"\n", record->data + field->offset) == 0) |
| ts = record->ts; |
| } |
| last_record = record; |
| } |
| free_record(last_record); |
| |
| return ts; |
| } |
| |
| static unsigned long long find_time_stamp(struct pevent *pevent) |
| { |
| struct dirent *dent; |
| unsigned long long ts = 0; |
| void *page; |
| char *path; |
| char *file; |
| DIR *dir; |
| int len; |
| int fd; |
| int r; |
| |
| path = tracecmd_get_tracing_file("per_cpu"); |
| if (!path) |
| return 0; |
| |
| dir = opendir(path); |
| if (!dir) |
| goto out; |
| |
| len = strlen(path); |
| file = malloc_or_die(len + strlen("trace_pipe_raw") + 32); |
| page = malloc_or_die(page_size); |
| |
| while ((dent = readdir(dir))) { |
| const char *name = dent->d_name; |
| |
| if (strncmp(name, "cpu", 3) != 0) |
| continue; |
| |
| sprintf(file, "%s/%s/trace_pipe_raw", path, name); |
| fd = open(file, O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| continue; |
| do { |
| r = read(fd, page, page_size); |
| ts = find_ts_in_page(pevent, page, r); |
| if (ts) |
| break; |
| } while (r > 0); |
| if (ts) |
| break; |
| } |
| free(file); |
| free(page); |
| closedir(dir); |
| |
| out: |
| tracecmd_put_tracing_file(path); |
| return ts; |
| } |
| |
| static char *read_file(char *file, int *psize) |
| { |
| char buffer[BUFSIZ]; |
| char *path; |
| char *buf; |
| int size = 0; |
| int fd; |
| int r; |
| |
| path = tracecmd_get_tracing_file(file); |
| fd = open(path, O_RDONLY); |
| tracecmd_put_tracing_file(path); |
| if (fd < 0) { |
| warning("%s not found, --date ignored", file); |
| return NULL; |
| } |
| do { |
| r = read(fd, buffer, BUFSIZ); |
| if (r <= 0) |
| continue; |
| if (size) { |
| buf = realloc(buf, size+r+1); |
| if (!buf) |
| die("malloc"); |
| } else |
| buf = malloc_or_die(r+1); |
| memcpy(buf+size, buffer, r); |
| size += r; |
| } while (r); |
| |
| buf[size] = '\0'; |
| if (psize) |
| *psize = size; |
| return buf; |
| } |
| |
| /* |
| * Try to write the date into the ftrace buffer and then |
| * read it back, mapping the timestamp to the date. |
| */ |
| static char *get_date_to_ts(void) |
| { |
| unsigned long long min = -1ULL; |
| unsigned long long diff; |
| unsigned long long stamp; |
| unsigned long long min_stamp; |
| unsigned long long min_ts; |
| unsigned long long ts; |
| struct pevent *pevent; |
| struct timeval start; |
| struct timeval end; |
| char *date2ts = NULL; |
| char *path; |
| char *buf; |
| int size; |
| int tfd; |
| int ret; |
| int i; |
| |
| /* Set up a pevent to read the raw format */ |
| pevent = pevent_alloc(); |
| if (!pevent) { |
| warning("failed to alloc pevent, --date ignored"); |
| return NULL; |
| } |
| |
| buf = read_file("events/header_page", &size); |
| if (!buf) |
| goto out_pevent; |
| ret = pevent_parse_header_page(pevent, buf, size, sizeof(unsigned long)); |
| free(buf); |
| if (ret < 0) { |
| warning("Can't parse header page, --date ignored"); |
| goto out_pevent; |
| } |
| |
| /* Find the format for ftrace:print. */ |
| buf = read_file("events/ftrace/print/format", &size); |
| if (!buf) |
| goto out_pevent; |
| ret = pevent_parse_event(pevent, buf, size, "ftrace"); |
| free(buf); |
| if (ret < 0) { |
| warning("Can't parse print event, --date ignored"); |
| goto out_pevent; |
| } |
| |
| path = tracecmd_get_tracing_file("trace_marker"); |
| tfd = open(path, O_WRONLY); |
| tracecmd_put_tracing_file(path); |
| if (tfd < 0) { |
| warning("Can not open 'trace_marker', --date ignored"); |
| goto out_pevent; |
| } |
| |
| for (i = 0; i < date2ts_tries; i++) { |
| disable_tracing(); |
| clear_trace(); |
| enable_tracing(); |
| |
| gettimeofday(&start, NULL); |
| write(tfd, STAMP, 5); |
| gettimeofday(&end, NULL); |
| |
| disable_tracing(); |
| ts = find_time_stamp(pevent); |
| if (!ts) |
| continue; |
| |
| diff = (unsigned long long)end.tv_sec * 1000000; |
| diff += (unsigned long long)end.tv_usec; |
| stamp = diff; |
| diff -= (unsigned long long)start.tv_sec * 1000000; |
| diff -= (unsigned long long)start.tv_usec; |
| |
| if (diff < min) { |
| min_ts = ts; |
| min_stamp = stamp - diff / 2; |
| min = diff; |
| } |
| } |
| |
| close(tfd); |
| |
| if (min == -1ULL) { |
| warning("Failed to make date offset, --date ignored"); |
| goto out_pevent; |
| } |
| |
| /* 16 hex chars + 0x + \0 */ |
| date2ts = malloc(19); |
| if (!date2ts) |
| goto out_pevent; |
| |
| /* |
| * The difference between the timestamp and the gtod is |
| * stored as an ASCII string in hex. |
| */ |
| snprintf(date2ts, 19, "0x%llx", min_stamp - min_ts / 1000); |
| |
| out_pevent: |
| pevent_free(pevent); |
| |
| return date2ts; |
| } |
| |
| void set_buffer_size(void) |
| { |
| char buf[BUFSIZ]; |
| char *path; |
| int ret; |
| int fd; |
| |
| if (!buffer_size) |
| return; |
| |
| if (buffer_size < 0) |
| die("buffer size must be positive"); |
| |
| snprintf(buf, BUFSIZ, "%d", buffer_size); |
| |
| path = tracecmd_get_tracing_file("buffer_size_kb"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die("can't open %s", path); |
| |
| ret = write(fd, buf, strlen(buf)); |
| if (ret < 0) |
| warning("Can't write to %s", path); |
| tracecmd_put_tracing_file(path); |
| close(fd); |
| } |
| |
| static void check_plugin(const char *plugin) |
| { |
| char *buf; |
| char *str; |
| char *tok; |
| |
| /* |
| * nop is special. We may want to just trace |
| * trace_printks, that are in the kernel. |
| */ |
| if (strcmp(plugin, "nop") == 0) |
| return; |
| |
| buf = read_file("available_tracers", NULL); |
| if (!buf) |
| die("No plugins available"); |
| |
| str = buf; |
| while ((tok = strtok(str, " "))) { |
| str = NULL; |
| if (strcmp(tok, plugin) == 0) |
| goto out; |
| } |
| die ("Plugin '%s' does not exist", plugin); |
| out: |
| fprintf(stderr, " plugin '%s'\n", plugin); |
| free(buf); |
| } |
| |
| static void record_all_events(void) |
| { |
| struct tracecmd_event_list *list; |
| |
| while (listed_events) { |
| list = listed_events; |
| listed_events = list->next; |
| free(list); |
| } |
| list = malloc_or_die(sizeof(*list)); |
| list->next = NULL; |
| list->glob = "*/*"; |
| listed_events = list; |
| } |
| |
| enum { |
| OPT_funcstack = 254, |
| OPT_date = 255, |
| }; |
| |
| void trace_record (int argc, char **argv) |
| { |
| const char *plugin = NULL; |
| const char *output = NULL; |
| const char *option; |
| struct event_list *event; |
| struct event_list *last_event; |
| struct tracecmd_event_list *list; |
| struct trace_seq *s; |
| char *pids; |
| char *pid; |
| char *sav; |
| char *date2ts = NULL; |
| int record_all = 0; |
| int disable = 0; |
| int events = 0; |
| int record = 0; |
| int extract = 0; |
| int run_command = 0; |
| int neg_event = 0; |
| int keep = 0; |
| int date = 0; |
| int fset; |
| int cpu; |
| |
| int c; |
| |
| cpu_count = count_cpus(); |
| |
| if ((record = (strcmp(argv[1], "record") == 0))) |
| ; /* do nothing */ |
| else if (strcmp(argv[1], "start") == 0) |
| ; /* do nothing */ |
| else if ((extract = strcmp(argv[1], "extract") == 0)) |
| ; /* do nothing */ |
| else if (strcmp(argv[1], "stop") == 0) { |
| disable_tracing(); |
| exit(0); |
| } else if (strcmp(argv[1], "reset") == 0) { |
| while ((c = getopt(argc-1, argv+1, "b:")) >= 0) { |
| switch (c) { |
| case 'b': |
| buffer_size = atoi(optarg); |
| /* Min buffer size is 1 */ |
| if (strcmp(optarg, "0") == 0) |
| buffer_size = 1; |
| break; |
| } |
| } |
| disable_all(1); |
| set_buffer_size(); |
| exit(0); |
| } else |
| usage(argv); |
| |
| for (;;) { |
| int option_index = 0; |
| static struct option long_options[] = { |
| {"date", no_argument, NULL, OPT_date}, |
| {"func-stack", no_argument, NULL, OPT_funcstack}, |
| {"help", no_argument, NULL, '?'}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| c = getopt_long (argc-1, argv+1, "+hae:f:Fp:cdo:O:s:r:vg:l:n:P:N:tb:kiT", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'a': |
| if (!extract) { |
| record_all = 1; |
| record_all_events(); |
| } |
| break; |
| case 'e': |
| if (extract) |
| usage(argv); |
| events = 1; |
| event = malloc_or_die(sizeof(*event)); |
| memset(event, 0, sizeof(*event)); |
| event->event = optarg; |
| add_event(event); |
| event->neg = neg_event; |
| event->filter = NULL; |
| last_event = event; |
| |
| if (!record_all) { |
| list = malloc_or_die(sizeof(*list)); |
| list->next = listed_events; |
| list->glob = optarg; |
| listed_events = list; |
| } |
| |
| break; |
| case 'f': |
| if (!last_event) |
| die("filter must come after event"); |
| if (last_event->filter) { |
| last_event->filter = |
| realloc(last_event->filter, |
| strlen(last_event->filter) + |
| strlen("&&()") + |
| strlen(optarg) + 1); |
| strcat(last_event->filter, "&&("); |
| strcat(last_event->filter, optarg); |
| strcat(last_event->filter, ")"); |
| } else { |
| last_event->filter = |
| malloc_or_die(strlen(optarg) + |
| strlen("()") + 1); |
| sprintf(last_event->filter, "(%s)", optarg); |
| } |
| break; |
| |
| case 'F': |
| filter_task = 1; |
| break; |
| case 'P': |
| pids = strdup(optarg); |
| if (!pids) |
| die("strdup"); |
| pid = strtok_r(pids, ",", &sav); |
| while (pid) { |
| add_filter_pid(atoi(pid)); |
| pid = strtok_r(NULL, ",", &sav); |
| } |
| free(pids); |
| break; |
| case 'c': |
| #ifdef NO_PTRACE |
| die("-c invalid: ptrace not supported"); |
| #endif |
| do_ptrace = 1; |
| break; |
| case 'v': |
| if (extract) |
| usage(argv); |
| neg_event = 1; |
| break; |
| case 'l': |
| add_func(&filter_funcs, optarg); |
| break; |
| case 'n': |
| add_func(¬race_funcs, optarg); |
| break; |
| case 'g': |
| add_func(&graph_funcs, optarg); |
| break; |
| case 'p': |
| if (plugin) |
| die("only one plugin allowed"); |
| for (plugin = optarg; isspace(*plugin); plugin++) |
| ; |
| for (optarg += strlen(optarg) - 1; |
| optarg > plugin && isspace(*optarg); optarg--) |
| ; |
| optarg++; |
| optarg[0] = '\0'; |
| break; |
| case 'd': |
| if (extract) |
| usage(argv); |
| disable = 1; |
| break; |
| case 'o': |
| if (host) |
| die("-o incompatible with -N"); |
| if (!record && !extract) |
| die("start does not take output\n" |
| "Did you mean 'record'?"); |
| if (output) |
| die("only one output file allowed"); |
| output = optarg; |
| break; |
| case 'O': |
| option = optarg; |
| save_option(option); |
| break; |
| case 'T': |
| save_option("stacktrace"); |
| break; |
| case 's': |
| if (extract) |
| usage(argv); |
| sleep_time = atoi(optarg); |
| break; |
| case 'r': |
| rt_prio = atoi(optarg); |
| break; |
| case 'N': |
| if (!record) |
| die("-N only available with record"); |
| if (output) |
| die("-N incompatible with -o"); |
| host = optarg; |
| break; |
| case 't': |
| use_tcp = 1; |
| break; |
| case 'b': |
| buffer_size = atoi(optarg); |
| break; |
| case 'k': |
| keep = 1; |
| break; |
| case 'i': |
| ignore_event_not_found = 1; |
| break; |
| case OPT_date: |
| date = 1; |
| break; |
| case OPT_funcstack: |
| func_stack = 1; |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| if (plugin && strncmp(plugin, "function", 8) == 0 && |
| func_stack && !filter_funcs) |
| die("Must supply function filtering with --func-stack\n"); |
| |
| if (do_ptrace && !filter_task && (filter_pid < 0)) |
| die(" -c can only be used with -F or -P"); |
| |
| if ((argc - optind) >= 2) { |
| if (!record) |
| die("Command start does not take any commands\n" |
| "Did you mean 'record'?"); |
| if (extract) |
| die("Command extract does not take any commands\n" |
| "Did you mean 'record'?"); |
| run_command = 1; |
| } |
| |
| if (!events && !plugin && !extract) |
| die("no event or plugin was specified... aborting"); |
| |
| if (output) |
| output_file = output; |
| |
| tracing_on_init_val = read_tracing_on(); |
| |
| /* Extracting data records all events in the system. */ |
| if (extract && !record_all) |
| record_all_events(); |
| |
| if (event_selection) |
| expand_event_list(); |
| |
| page_size = getpagesize(); |
| |
| if (!extract) { |
| fset = set_ftrace(!disable); |
| disable_all(1); |
| |
| /* Record records the date first */ |
| if (record && date) |
| date2ts = get_date_to_ts(); |
| |
| set_funcs(); |
| |
| if (events) |
| enable_events(); |
| set_buffer_size(); |
| } |
| |
| if (plugin) { |
| |
| check_plugin(plugin); |
| /* |
| * Latency tracers just save the trace and kill |
| * the threads. |
| */ |
| if (strcmp(plugin, "irqsoff") == 0 || |
| strcmp(plugin, "preemptoff") == 0 || |
| strcmp(plugin, "preemptirqsoff") == 0 || |
| strcmp(plugin, "wakeup") == 0 || |
| strcmp(plugin, "wakeup_rt") == 0) { |
| latency = 1; |
| if (host) |
| die("Network tracing not available with latency tracer plugins"); |
| } |
| if (fset < 0 && (strcmp(plugin, "function") == 0 || |
| strcmp(plugin, "function_graph") == 0)) |
| die("function tracing not configured on this kernel"); |
| if (!extract) |
| set_plugin(plugin); |
| } |
| |
| set_options(); |
| |
| s = malloc_or_die(sizeof(*s) * cpu_count); |
| |
| if (record) { |
| signal(SIGINT, finish); |
| if (!latency) |
| start_threads(); |
| } |
| |
| if (extract) { |
| flush_threads(); |
| |
| } else { |
| if (!record) { |
| update_task_filter(); |
| enable_tracing(); |
| exit(0); |
| } |
| |
| if (run_command) |
| run_cmd((argc - optind) - 1, &argv[optind + 1]); |
| else { |
| update_task_filter(); |
| enable_tracing(); |
| /* We don't ptrace ourself */ |
| if (do_ptrace && filter_pid >= 0) |
| ptrace_attach(filter_pid); |
| /* sleep till we are woken with Ctrl^C */ |
| printf("Hit Ctrl^C to stop recording\n"); |
| while (!finished) |
| trace_or_sleep(); |
| } |
| |
| disable_tracing(); |
| if (!latency) |
| stop_threads(); |
| } |
| |
| for (cpu = 0; cpu < cpu_count; cpu++) { |
| trace_seq_init(&s[cpu]); |
| trace_seq_printf(&s[cpu], "CPU: %d\n", cpu); |
| tracecmd_stat_cpu(&s[cpu], cpu); |
| } |
| |
| if (!keep) |
| disable_all(0); |
| |
| printf("Kernel buffer statistics:\n" |
| " Note: \"entries\" are the entries left in the kernel ring buffer and are not\n" |
| " recorded in the trace data. They should all be zero.\n\n"); |
| for (cpu = 0; cpu < cpu_count; cpu++) { |
| trace_seq_do_printf(&s[cpu]); |
| printf("\n"); |
| } |
| |
| |
| /* extract records the date after extraction */ |
| if (extract && date) { |
| /* |
| * We need to start tracing, don't let other traces |
| * screw with our trace_marker. |
| */ |
| disable_all(1); |
| date2ts = get_date_to_ts(); |
| } |
| |
| record_data(date2ts, s); |
| delete_thread_data(); |
| |
| for (cpu = 0; cpu < cpu_count; cpu++) |
| trace_seq_destroy(&s[cpu]); |
| |
| if (keep) |
| exit(0); |
| |
| set_plugin("nop"); |
| |
| /* If tracing_on was enabled before we started, set it on now */ |
| if (tracing_on_init_val) |
| write_tracing_on(tracing_on_init_val); |
| |
| if (host) |
| tracecmd_output_close(network_handle); |
| |
| exit(0); |
| } |