| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| */ |
| #include <dirent.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <time.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <sys/socket.h> |
| #include <sys/syscall.h> |
| #include <sys/utsname.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 <fcntl.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <sched.h> |
| #include <glob.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <libgen.h> |
| #include <poll.h> |
| #include <pwd.h> |
| #include <grp.h> |
| |
| #include "tracefs.h" |
| #include "version.h" |
| #include "trace-local.h" |
| #include "trace-msg.h" |
| |
| #define _STR(x) #x |
| #define STR(x) _STR(x) |
| |
| #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 TSC_CLOCK "x86-tsc" |
| |
| #define dprint(fmt, ...) tracecmd_debug(fmt, ##__VA_ARGS__) |
| |
| enum trace_type { |
| TRACE_TYPE_RECORD = 1, |
| TRACE_TYPE_START = (1 << 1), |
| TRACE_TYPE_STREAM = (1 << 2), |
| TRACE_TYPE_EXTRACT = (1 << 3), |
| TRACE_TYPE_SET = (1 << 4), |
| }; |
| |
| static tracecmd_handle_init_func handle_init = NULL; |
| |
| static int rt_prio; |
| |
| static int keep; |
| |
| static int latency; |
| static int sleep_time = 1000; |
| static int recorder_threads; |
| static struct pid_record_data *pids; |
| static int buffers; |
| |
| /* Clear all function filters */ |
| static int clear_function_filters; |
| |
| static bool no_fifos; |
| |
| static char *host; |
| |
| static const char *gai_err; |
| |
| static bool quiet; |
| |
| static bool fork_process; |
| |
| /* Max size to let a per cpu file get */ |
| static int max_kb; |
| |
| static int do_ptrace; |
| |
| static int filter_task; |
| static bool no_filter = false; |
| |
| static int local_cpu_count; |
| |
| static int finished; |
| |
| /* setting of /proc/sys/kernel/ftrace_enabled */ |
| static int fset; |
| |
| static unsigned recorder_flags; |
| |
| /* Try a few times to get an accurate date */ |
| static int date2ts_tries = 50; |
| |
| static struct func_list *graph_funcs; |
| |
| static int func_stack; |
| |
| static int save_stdout = -1; |
| |
| static struct hook_list *hooks; |
| |
| struct event_list { |
| struct event_list *next; |
| const char *event; |
| char *trigger; |
| char *filter; |
| char *pid_filter; |
| char *filter_file; |
| char *trigger_file; |
| char *enable_file; |
| int neg; |
| }; |
| |
| struct tracecmd_event_list *listed_events; |
| |
| struct events { |
| struct events *sibling; |
| struct events *children; |
| struct events *next; |
| char *name; |
| }; |
| |
| /* Files to be reset when done recording */ |
| struct reset_file { |
| struct reset_file *next; |
| char *path; |
| char *reset; |
| int prio; |
| }; |
| |
| static struct reset_file *reset_files; |
| |
| /* Triggers need to be cleared in a special way */ |
| static struct reset_file *reset_triggers; |
| |
| struct buffer_instance top_instance; |
| struct buffer_instance *buffer_instances; |
| struct buffer_instance *first_instance; |
| |
| static struct tracecmd_recorder *recorder; |
| |
| static int ignore_event_not_found = 0; |
| |
| static inline int is_top_instance(struct buffer_instance *instance) |
| { |
| return instance == &top_instance; |
| } |
| |
| static inline int no_top_instance(void) |
| { |
| return first_instance != &top_instance; |
| } |
| |
| static void init_instance(struct buffer_instance *instance) |
| { |
| instance->event_next = &instance->events; |
| } |
| |
| enum { |
| RESET_DEFAULT_PRIO = 0, |
| RESET_HIGH_PRIO = 100000, |
| }; |
| |
| enum trace_cmd { |
| CMD_extract, |
| CMD_start, |
| CMD_stream, |
| CMD_profile, |
| CMD_record, |
| CMD_record_agent, |
| CMD_set, |
| }; |
| |
| struct common_record_context { |
| enum trace_cmd curr_cmd; |
| struct buffer_instance *instance; |
| const char *output; |
| char *date2ts; |
| char *user; |
| const char *clock; |
| const char *compression; |
| struct tsc_nsec tsc2nsec; |
| int data_flags; |
| int tsync_loop_interval; |
| |
| int record_all; |
| int total_disable; |
| int disable; |
| int events; |
| int global; |
| int filtered; |
| int date; |
| int manual; |
| int topt; |
| int run_command; |
| int saved_cmdlines_size; |
| int file_version; |
| }; |
| |
| static void add_reset_file(const char *file, const char *val, int prio) |
| { |
| struct reset_file *reset; |
| struct reset_file **last = &reset_files; |
| |
| /* Only reset if we are not keeping the state */ |
| if (keep) |
| return; |
| |
| reset = malloc(sizeof(*reset)); |
| if (!reset) |
| die("Failed to allocate reset"); |
| reset->path = strdup(file); |
| reset->reset = strdup(val); |
| reset->prio = prio; |
| if (!reset->path || !reset->reset) |
| die("Failed to allocate reset path or val"); |
| |
| while (*last && (*last)->prio > prio) |
| last = &(*last)->next; |
| |
| reset->next = *last; |
| *last = reset; |
| } |
| |
| static void add_reset_trigger(const char *file) |
| { |
| struct reset_file *reset; |
| |
| /* Only reset if we are not keeping the state */ |
| if (keep) |
| return; |
| |
| reset = malloc(sizeof(*reset)); |
| if (!reset) |
| die("Failed to allocate reset"); |
| reset->path = strdup(file); |
| |
| reset->next = reset_triggers; |
| reset_triggers = reset; |
| } |
| |
| /* To save the contents of the file */ |
| static void reset_save_file(const char *file, int prio) |
| { |
| char *content; |
| |
| content = get_file_content(file); |
| if (content) { |
| add_reset_file(file, content, prio); |
| free(content); |
| } |
| } |
| |
| /* |
| * @file: the file to check |
| * @nop: If the content of the file is this, use the reset value |
| * @reset: What to write if the file == @nop |
| */ |
| static void reset_save_file_cond(const char *file, int prio, |
| const char *nop, const char *reset) |
| { |
| char *content; |
| char *cond; |
| |
| if (keep) |
| return; |
| |
| content = get_file_content(file); |
| |
| cond = strstrip(content); |
| |
| if (strcmp(cond, nop) == 0) |
| add_reset_file(file, reset, prio); |
| else |
| add_reset_file(file, content, prio); |
| |
| free(content); |
| } |
| |
| /** |
| * add_instance - add a buffer instance to the internal list |
| * @instance: The buffer instance to add |
| */ |
| void add_instance(struct buffer_instance *instance, int cpu_count) |
| { |
| init_instance(instance); |
| instance->next = buffer_instances; |
| if (first_instance == buffer_instances) |
| first_instance = instance; |
| buffer_instances = instance; |
| instance->cpu_count = cpu_count; |
| buffers++; |
| } |
| |
| static void instance_reset_file_save(struct buffer_instance *instance, char *file, int prio) |
| { |
| char *path; |
| |
| path = tracefs_instance_get_file(instance->tracefs, file); |
| if (path) |
| reset_save_file(path, prio); |
| tracefs_put_tracing_file(path); |
| } |
| |
| static void test_set_event_pid(struct buffer_instance *instance) |
| { |
| static int have_set_event_pid; |
| static int have_event_fork; |
| static int have_func_fork; |
| |
| if (!have_set_event_pid && |
| tracefs_file_exists(top_instance.tracefs, "set_event_pid")) |
| have_set_event_pid = 1; |
| if (!have_event_fork && |
| tracefs_file_exists(top_instance.tracefs, "options/event-fork")) |
| have_event_fork = 1; |
| if (!have_func_fork && |
| tracefs_file_exists(top_instance.tracefs, "options/function-fork")) |
| have_func_fork = 1; |
| |
| if (!instance->have_set_event_pid && have_set_event_pid) { |
| instance->have_set_event_pid = 1; |
| instance_reset_file_save(instance, "set_event_pid", |
| RESET_DEFAULT_PRIO); |
| } |
| if (!instance->have_event_fork && have_event_fork) { |
| instance->have_event_fork = 1; |
| instance_reset_file_save(instance, "options/event-fork", |
| RESET_DEFAULT_PRIO); |
| } |
| if (!instance->have_func_fork && have_func_fork) { |
| instance->have_func_fork = 1; |
| instance_reset_file_save(instance, "options/function-fork", |
| RESET_DEFAULT_PRIO); |
| } |
| } |
| |
| /** |
| * allocate_instance - allocate a new buffer instance, |
| * it must exist in the ftrace system |
| * @name: The name of the instance (instance will point to this) |
| * |
| * Returns a newly allocated instance. In case of an error or if the |
| * instance does not exist in the ftrace system, NULL is returned. |
| */ |
| struct buffer_instance *allocate_instance(const char *name) |
| { |
| struct buffer_instance *instance; |
| |
| instance = calloc(1, sizeof(*instance)); |
| if (!instance) |
| return NULL; |
| if (name) |
| instance->name = strdup(name); |
| if (tracefs_instance_exists(name)) { |
| instance->tracefs = tracefs_instance_create(name); |
| if (!instance->tracefs) |
| goto error; |
| } |
| |
| return instance; |
| |
| error: |
| if (instance) { |
| free(instance->name); |
| tracefs_instance_free(instance->tracefs); |
| free(instance); |
| } |
| return NULL; |
| } |
| |
| static int __add_all_instances(const char *tracing_dir) |
| { |
| struct dirent *dent; |
| char *instances_dir; |
| struct stat st; |
| DIR *dir; |
| int ret; |
| |
| if (!tracing_dir) |
| return -1; |
| |
| instances_dir = append_file(tracing_dir, "instances"); |
| if (!instances_dir) |
| return -1; |
| |
| ret = stat(instances_dir, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) { |
| ret = -1; |
| goto out_free; |
| } |
| |
| dir = opendir(instances_dir); |
| if (!dir) { |
| ret = -1; |
| goto out_free; |
| } |
| |
| while ((dent = readdir(dir))) { |
| const char *name = strdup(dent->d_name); |
| char *instance_path; |
| struct buffer_instance *instance; |
| |
| if (strcmp(name, ".") == 0 || |
| strcmp(name, "..") == 0) |
| continue; |
| |
| instance_path = append_file(instances_dir, name); |
| ret = stat(instance_path, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) { |
| free(instance_path); |
| continue; |
| } |
| free(instance_path); |
| |
| instance = allocate_instance(name); |
| if (!instance) |
| die("Failed to create instance"); |
| add_instance(instance, local_cpu_count); |
| } |
| |
| closedir(dir); |
| ret = 0; |
| |
| out_free: |
| free(instances_dir); |
| return ret; |
| } |
| |
| /** |
| * add_all_instances - Add all pre-existing instances to the internal list |
| * @tracing_dir: The top-level tracing directory |
| * |
| * Returns whether the operation succeeded |
| */ |
| void add_all_instances(void) |
| { |
| const char *tracing_dir = tracefs_tracing_dir(); |
| if (!tracing_dir) |
| die("can't get the tracing directory"); |
| |
| __add_all_instances(tracing_dir); |
| } |
| |
| /** |
| * tracecmd_stat_cpu - show the buffer stats of a particular CPU |
| * @s: the trace_seq to record the data in. |
| * @cpu: the CPU to stat |
| * |
| */ |
| void tracecmd_stat_cpu_instance(struct buffer_instance *instance, |
| struct trace_seq *s, int cpu) |
| { |
| char buf[BUFSIZ]; |
| char *path; |
| char *file; |
| int fd; |
| int r; |
| |
| file = malloc(40); |
| if (!file) |
| return; |
| snprintf(file, 40, "per_cpu/cpu%d/stats", cpu); |
| |
| path = tracefs_instance_get_file(instance->tracefs, file); |
| free(file); |
| fd = open(path, O_RDONLY); |
| tracefs_put_tracing_file(path); |
| if (fd < 0) |
| return; |
| |
| while ((r = read(fd, buf, BUFSIZ)) > 0) |
| trace_seq_printf(s, "%.*s", r, buf); |
| |
| close(fd); |
| } |
| |
| /** |
| * tracecmd_stat_cpu - show the buffer stats of a particular CPU |
| * @s: the trace_seq to record the data in. |
| * @cpu: the CPU to stat |
| * |
| */ |
| void tracecmd_stat_cpu(struct trace_seq *s, int cpu) |
| { |
| tracecmd_stat_cpu_instance(&top_instance, s, cpu); |
| } |
| |
| static void add_event(struct buffer_instance *instance, struct event_list *event) |
| { |
| *instance->event_next = event; |
| instance->event_next = &event->next; |
| event->next = NULL; |
| } |
| |
| static void reset_event_list(struct buffer_instance *instance) |
| { |
| instance->events = NULL; |
| init_instance(instance); |
| } |
| |
| static char *get_temp_file(struct buffer_instance *instance, int cpu) |
| { |
| const char *output_file = instance->output_file; |
| const char *name; |
| char *file = NULL; |
| int size; |
| |
| name = tracefs_instance_get_name(instance->tracefs); |
| if (name) { |
| size = snprintf(file, 0, "%s.%s.cpu%d", output_file, name, cpu); |
| file = malloc(size + 1); |
| if (!file) |
| die("Failed to allocate temp file for %s", name); |
| sprintf(file, "%s.%s.cpu%d", output_file, name, cpu); |
| } else { |
| size = snprintf(file, 0, "%s.cpu%d", output_file, cpu); |
| file = malloc(size + 1); |
| if (!file) |
| die("Failed to allocate temp file for %s", name); |
| sprintf(file, "%s.cpu%d", output_file, cpu); |
| } |
| |
| return file; |
| } |
| |
| char *trace_get_guest_file(const char *file, const char *guest) |
| { |
| const char *p; |
| char *out = NULL; |
| int ret, base_len; |
| |
| p = strrchr(file, '.'); |
| if (p && p != file) |
| base_len = p - file; |
| else |
| base_len = strlen(file); |
| |
| ret = asprintf(&out, "%.*s-%s%s", base_len, file, |
| guest, file + base_len); |
| if (ret < 0) |
| return NULL; |
| return out; |
| } |
| |
| static void put_temp_file(char *file) |
| { |
| free(file); |
| } |
| |
| static void delete_temp_file(struct buffer_instance *instance, int cpu) |
| { |
| const char *output_file = instance->output_file; |
| const char *name; |
| char file[PATH_MAX]; |
| |
| name = tracefs_instance_get_name(instance->tracefs); |
| if (name) |
| snprintf(file, PATH_MAX, "%s.%s.cpu%d", output_file, name, cpu); |
| else |
| snprintf(file, PATH_MAX, "%s.cpu%d", output_file, cpu); |
| unlink(file); |
| } |
| |
| static int kill_thread_instance(int start, struct buffer_instance *instance) |
| { |
| int n = start; |
| int i; |
| |
| for (i = 0; i < instance->cpu_count; i++) { |
| if (pids[n].pid > 0) { |
| kill(pids[n].pid, SIGKILL); |
| delete_temp_file(instance, i); |
| pids[n].pid = 0; |
| if (pids[n].brass[0] >= 0) |
| close(pids[n].brass[0]); |
| } |
| n++; |
| } |
| |
| return n; |
| } |
| |
| static void kill_threads(void) |
| { |
| struct buffer_instance *instance; |
| int i = 0; |
| |
| if (!recorder_threads || !pids) |
| return; |
| |
| for_all_instances(instance) |
| i = kill_thread_instance(i, instance); |
| } |
| |
| 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 int delete_thread_instance(int start, struct buffer_instance *instance) |
| { |
| int n = start; |
| int i; |
| |
| for (i = 0; i < instance->cpu_count; i++) { |
| if (pids) { |
| if (pids[n].pid) { |
| delete_temp_file(instance, i); |
| if (pids[n].pid < 0) |
| pids[n].pid = 0; |
| } |
| n++; |
| } else |
| /* Extract does not allocate pids */ |
| delete_temp_file(instance, i); |
| } |
| return n; |
| } |
| |
| static void delete_thread_data(void) |
| { |
| struct buffer_instance *instance; |
| int i = 0; |
| |
| for_all_instances(instance) |
| i = delete_thread_instance(i, instance); |
| /* |
| * Top instance temp files are still created even if it |
| * isn't used. |
| */ |
| if (no_top_instance()) { |
| for (i = 0; i < local_cpu_count; i++) |
| delete_temp_file(&top_instance, i); |
| } |
| } |
| |
| static void |
| add_tsc2nsec(struct tracecmd_output *handle, struct tsc_nsec *tsc2nsec) |
| { |
| /* multiplier, shift, offset */ |
| struct iovec vector[3]; |
| |
| vector[0].iov_len = 4; |
| vector[0].iov_base = &tsc2nsec->mult; |
| vector[1].iov_len = 4; |
| vector[1].iov_base = &tsc2nsec->shift; |
| vector[2].iov_len = 8; |
| vector[2].iov_base = &tsc2nsec->offset; |
| |
| tracecmd_add_option_v(handle, TRACECMD_OPTION_TSC2NSEC, vector, 3); |
| } |
| |
| static void host_tsync_complete(struct common_record_context *ctx, |
| struct buffer_instance *instance) |
| { |
| struct tracecmd_output *handle = NULL; |
| int fd = -1; |
| int ret; |
| |
| ret = tracecmd_tsync_with_guest_stop(instance->tsync); |
| if (!ret) { |
| fd = open(instance->output_file, O_RDWR); |
| if (fd < 0) |
| die("error opening %s", instance->output_file); |
| handle = tracecmd_get_output_handle_fd(fd); |
| if (!handle) |
| die("cannot create output handle"); |
| |
| if (ctx->tsc2nsec.mult) |
| add_tsc2nsec(handle, &ctx->tsc2nsec); |
| |
| tracecmd_write_guest_time_shift(handle, instance->tsync); |
| tracecmd_append_options(handle); |
| tracecmd_output_close(handle); |
| } |
| |
| tracecmd_tsync_free(instance->tsync); |
| instance->tsync = NULL; |
| } |
| |
| static void tell_guests_to_stop(struct common_record_context *ctx) |
| { |
| struct buffer_instance *instance; |
| |
| /* Send close message to guests */ |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| tracecmd_msg_send_close_msg(instance->msg_handle); |
| } |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| host_tsync_complete(ctx, instance); |
| } |
| |
| /* Wait for guests to acknowledge */ |
| for_all_instances(instance) { |
| if (is_guest(instance)) { |
| tracecmd_msg_wait_close_resp(instance->msg_handle); |
| tracecmd_msg_handle_close(instance->msg_handle); |
| } |
| } |
| } |
| |
| static void stop_threads(enum trace_type type) |
| { |
| int ret; |
| int i; |
| |
| if (!recorder_threads) |
| return; |
| |
| /* Tell all threads to finish up */ |
| for (i = 0; i < recorder_threads; i++) { |
| if (pids[i].pid > 0) { |
| kill(pids[i].pid, SIGUSR1); |
| } |
| } |
| |
| /* Flush out the pipes */ |
| if (type & TRACE_TYPE_STREAM) { |
| do { |
| ret = trace_stream_read(pids, recorder_threads, NULL); |
| } while (ret > 0); |
| } |
| } |
| |
| static void wait_threads() |
| { |
| int i; |
| |
| for (i = 0; i < recorder_threads; i++) { |
| if (pids[i].pid > 0) { |
| waitpid(pids[i].pid, NULL, 0); |
| pids[i].pid = -1; |
| } |
| } |
| } |
| |
| static int create_recorder(struct buffer_instance *instance, int cpu, |
| enum trace_type type, int *brass); |
| |
| static void flush_threads(void) |
| { |
| struct buffer_instance *instance; |
| long ret; |
| int i; |
| |
| for_all_instances(instance) { |
| for (i = 0; i < instance->cpu_count; i++) { |
| /* Extract doesn't support sub buffers yet */ |
| ret = create_recorder(instance, i, TRACE_TYPE_EXTRACT, NULL); |
| if (ret < 0) |
| die("error reading ring buffer"); |
| } |
| } |
| } |
| |
| static int set_ftrace_enable(const char *path, int set) |
| { |
| struct stat st; |
| int fd; |
| char *val = set ? "1" : "0"; |
| int ret; |
| |
| /* if ftace_enable does not exist, simply ignore it */ |
| fd = stat(path, &st); |
| if (fd < 0) |
| return -ENODEV; |
| |
| reset_save_file(path, RESET_DEFAULT_PRIO); |
| |
| ret = -1; |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| goto out; |
| |
| /* Now set or clear the function option */ |
| ret = write(fd, val, 1); |
| close(fd); |
| |
| out: |
| return ret < 0 ? ret : 0; |
| } |
| |
| static int set_ftrace_proc(int set) |
| { |
| const char *path = "/proc/sys/kernel/ftrace_enabled"; |
| int ret; |
| |
| ret = set_ftrace_enable(path, set); |
| if (ret == -1) |
| die ("Can't %s ftrace", set ? "enable" : "disable"); |
| return ret; |
| } |
| |
| static int set_ftrace(struct buffer_instance *instance, int set, int use_proc) |
| { |
| char *path; |
| int ret; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "options/function-trace"); |
| if (!path) |
| return -1; |
| ret = set_ftrace_enable(path, set); |
| tracefs_put_tracing_file(path); |
| |
| /* Always enable ftrace_enable proc file when set is true */ |
| if (ret < 0 || set || use_proc) |
| ret = set_ftrace_proc(set); |
| |
| return ret; |
| } |
| |
| static int write_file(const char *file, const char *str) |
| { |
| int ret; |
| int fd; |
| |
| fd = open(file, O_WRONLY | O_TRUNC); |
| if (fd < 0) |
| die("opening to '%s'", file); |
| ret = write(fd, str, strlen(str)); |
| close(fd); |
| return ret; |
| } |
| |
| static void __clear_trace(struct buffer_instance *instance) |
| { |
| FILE *fp; |
| char *path; |
| |
| if (is_guest(instance)) |
| return; |
| |
| /* reset the trace */ |
| path = tracefs_instance_get_file(instance->tracefs, "trace"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("writing to '%s'", path); |
| tracefs_put_tracing_file(path); |
| fwrite("0", 1, 1, fp); |
| fclose(fp); |
| } |
| |
| static void clear_trace_instances(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| __clear_trace(instance); |
| } |
| |
| static void reset_max_latency(struct buffer_instance *instance) |
| { |
| tracefs_instance_file_write(instance->tracefs, |
| "tracing_max_latency", "0"); |
| } |
| |
| static int add_filter_pid(struct buffer_instance *instance, int pid, int exclude) |
| { |
| struct filter_pids *p; |
| char buf[100]; |
| |
| for (p = instance->filter_pids; p; p = p->next) { |
| if (p->pid == pid) { |
| p->exclude = exclude; |
| return 0; |
| } |
| } |
| |
| p = malloc(sizeof(*p)); |
| if (!p) |
| die("Failed to allocate pid filter"); |
| p->next = instance->filter_pids; |
| p->exclude = exclude; |
| p->pid = pid; |
| instance->filter_pids = p; |
| instance->nr_filter_pids++; |
| |
| instance->len_filter_pids += sprintf(buf, "%d", pid); |
| |
| return 1; |
| } |
| |
| static void add_filter_pid_all(int pid, int exclude) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| add_filter_pid(instance, pid, exclude); |
| } |
| |
| static void reset_save_ftrace_pid(struct buffer_instance *instance) |
| { |
| static char *path; |
| |
| if (!tracefs_file_exists(instance->tracefs, "set_ftrace_pid")) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_pid"); |
| if (!path) |
| return; |
| |
| reset_save_file_cond(path, RESET_DEFAULT_PRIO, "no pid", ""); |
| |
| tracefs_put_tracing_file(path); |
| } |
| |
| static void update_ftrace_pid(struct buffer_instance *instance, |
| const char *pid, int reset) |
| { |
| int fd = -1; |
| char *path; |
| int ret; |
| |
| if (!tracefs_file_exists(instance->tracefs, "set_ftrace_pid")) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_pid"); |
| if (!path) |
| return; |
| |
| fd = open(path, O_WRONLY | O_CLOEXEC | (reset ? O_TRUNC : 0)); |
| tracefs_put_tracing_file(path); |
| 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); |
| close(fd); |
| } |
| |
| static void update_ftrace_pids(int reset) |
| { |
| struct buffer_instance *instance; |
| struct filter_pids *pid; |
| static int first = 1; |
| char buf[100]; |
| int rst; |
| |
| for_all_instances(instance) { |
| if (first) |
| reset_save_ftrace_pid(instance); |
| rst = reset; |
| for (pid = instance->filter_pids; pid; pid = pid->next) { |
| if (pid->exclude) |
| continue; |
| snprintf(buf, 100, "%d ", pid->pid); |
| update_ftrace_pid(instance, buf, rst); |
| /* Only reset the first entry */ |
| rst = 0; |
| } |
| } |
| |
| if (first) |
| first = 0; |
| } |
| |
| static void update_event_filters(struct buffer_instance *instance); |
| static void update_pid_event_filters(struct buffer_instance *instance); |
| |
| static void append_filter_pid_range(char **filter, int *curr_len, |
| const char *field, |
| int start_pid, int end_pid, bool exclude) |
| { |
| const char *op = "", *op1, *op2, *op3; |
| int len; |
| |
| if (*filter && **filter) |
| op = exclude ? "&&" : "||"; |
| |
| /* Handle thus case explicitly so that we get `pid==3` instead of |
| * `pid>=3&&pid<=3` for singleton ranges |
| */ |
| if (start_pid == end_pid) { |
| #define FMT "%s(%s%s%d)" |
| len = snprintf(NULL, 0, FMT, op, |
| field, exclude ? "!=" : "==", start_pid); |
| *filter = realloc(*filter, *curr_len + len + 1); |
| if (!*filter) |
| die("realloc"); |
| |
| len = snprintf(*filter + *curr_len, len + 1, FMT, op, |
| field, exclude ? "!=" : "==", start_pid); |
| *curr_len += len; |
| |
| return; |
| #undef FMT |
| } |
| |
| if (exclude) { |
| op1 = "<"; |
| op2 = "||"; |
| op3 = ">"; |
| } else { |
| op1 = ">="; |
| op2 = "&&"; |
| op3 = "<="; |
| } |
| |
| #define FMT "%s(%s%s%d%s%s%s%d)" |
| len = snprintf(NULL, 0, FMT, op, |
| field, op1, start_pid, op2, |
| field, op3, end_pid); |
| *filter = realloc(*filter, *curr_len + len + 1); |
| if (!*filter) |
| die("realloc"); |
| |
| len = snprintf(*filter + *curr_len, len + 1, FMT, op, |
| field, op1, start_pid, op2, |
| field, op3, end_pid); |
| *curr_len += len; |
| } |
| |
| /** |
| * 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 field 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(struct buffer_instance *instance, |
| char *curr_filter, const char *field) |
| { |
| int start_pid = -1, last_pid = -1; |
| int last_exclude = -1; |
| struct filter_pids *p; |
| char *filter = NULL; |
| int curr_len = 0; |
| |
| /* Use the new method if possible */ |
| if (instance->have_set_event_pid) |
| return NULL; |
| |
| if (!instance->filter_pids) |
| return curr_filter; |
| |
| for (p = instance->filter_pids; p; p = p->next) { |
| /* |
| * PIDs are inserted in `filter_pids` from the front and that's |
| * why we expect them in descending order here. |
| */ |
| if (p->pid == last_pid - 1 && p->exclude == last_exclude) { |
| last_pid = p->pid; |
| continue; |
| } |
| |
| if (start_pid != -1) |
| append_filter_pid_range(&filter, &curr_len, field, |
| last_pid, start_pid, |
| last_exclude); |
| |
| start_pid = last_pid = p->pid; |
| last_exclude = p->exclude; |
| |
| } |
| append_filter_pid_range(&filter, &curr_len, field, |
| last_pid, start_pid, last_exclude); |
| |
| if (curr_filter) { |
| char *save = filter; |
| asprintf(&filter, "(%s)&&(%s)", curr_filter, filter); |
| free(save); |
| } |
| |
| return filter; |
| } |
| |
| #define _STRINGIFY(x) #x |
| #define STRINGIFY(x) _STRINGIFY(x) |
| |
| static int get_pid_addr_maps(struct buffer_instance *instance, int pid) |
| { |
| struct pid_addr_maps *maps = instance->pid_maps; |
| struct tracecmd_proc_addr_map *map; |
| unsigned long long begin, end; |
| struct pid_addr_maps *m; |
| char mapname[PATH_MAX+1]; |
| char fname[PATH_MAX+1]; |
| char buf[PATH_MAX+100]; |
| FILE *f; |
| int ret; |
| int res; |
| int i; |
| |
| sprintf(fname, "/proc/%d/exe", pid); |
| ret = readlink(fname, mapname, PATH_MAX); |
| if (ret >= PATH_MAX || ret < 0) |
| return -ENOENT; |
| mapname[ret] = 0; |
| |
| sprintf(fname, "/proc/%d/maps", pid); |
| f = fopen(fname, "r"); |
| if (!f) |
| return -ENOENT; |
| |
| while (maps) { |
| if (pid == maps->pid) |
| break; |
| maps = maps->next; |
| } |
| |
| ret = -ENOMEM; |
| if (!maps) { |
| maps = calloc(1, sizeof(*maps)); |
| if (!maps) |
| goto out_fail; |
| maps->pid = pid; |
| maps->next = instance->pid_maps; |
| instance->pid_maps = maps; |
| } else { |
| for (i = 0; i < maps->nr_lib_maps; i++) |
| free(maps->lib_maps[i].lib_name); |
| free(maps->lib_maps); |
| maps->lib_maps = NULL; |
| maps->nr_lib_maps = 0; |
| free(maps->proc_name); |
| } |
| |
| maps->proc_name = strdup(mapname); |
| if (!maps->proc_name) |
| goto out; |
| |
| while (fgets(buf, sizeof(buf), f)) { |
| mapname[0] = '\0'; |
| res = sscanf(buf, "%llx-%llx %*s %*x %*s %*d %"STRINGIFY(PATH_MAX)"s", |
| &begin, &end, mapname); |
| if (res == 3 && mapname[0] != '\0') { |
| map = realloc(maps->lib_maps, |
| (maps->nr_lib_maps + 1) * sizeof(*map)); |
| if (!map) |
| goto out_fail; |
| map[maps->nr_lib_maps].end = end; |
| map[maps->nr_lib_maps].start = begin; |
| map[maps->nr_lib_maps].lib_name = strdup(mapname); |
| if (!map[maps->nr_lib_maps].lib_name) |
| goto out_fail; |
| maps->lib_maps = map; |
| maps->nr_lib_maps++; |
| } |
| } |
| out: |
| fclose(f); |
| return 0; |
| |
| out_fail: |
| fclose(f); |
| if (maps) { |
| for (i = 0; i < maps->nr_lib_maps; i++) |
| free(maps->lib_maps[i].lib_name); |
| if (instance->pid_maps != maps) { |
| m = instance->pid_maps; |
| while (m) { |
| if (m->next == maps) { |
| m->next = maps->next; |
| break; |
| } |
| m = m->next; |
| } |
| } else |
| instance->pid_maps = maps->next; |
| free(maps->lib_maps); |
| maps->lib_maps = NULL; |
| maps->nr_lib_maps = 0; |
| free(maps->proc_name); |
| maps->proc_name = NULL; |
| free(maps); |
| } |
| return ret; |
| } |
| |
| static void get_filter_pid_maps(void) |
| { |
| struct buffer_instance *instance; |
| struct filter_pids *p; |
| |
| for_all_instances(instance) { |
| if (!instance->get_procmap) |
| continue; |
| for (p = instance->filter_pids; p; p = p->next) { |
| if (p->exclude) |
| continue; |
| get_pid_addr_maps(instance, p->pid); |
| } |
| } |
| } |
| |
| static void update_task_filter(void) |
| { |
| struct buffer_instance *instance; |
| int pid = getpid(); |
| |
| if (no_filter) |
| return; |
| |
| get_filter_pid_maps(); |
| |
| if (filter_task) |
| add_filter_pid_all(pid, 0); |
| |
| for_all_instances(instance) { |
| if (!instance->filter_pids) |
| continue; |
| if (instance->common_pid_filter) |
| free(instance->common_pid_filter); |
| instance->common_pid_filter = make_pid_filter(instance, NULL, |
| "common_pid"); |
| } |
| update_ftrace_pids(1); |
| for_all_instances(instance) |
| update_pid_event_filters(instance); |
| } |
| |
| static pid_t trace_waitpid(enum trace_type type, pid_t pid, int *status, int options) |
| { |
| struct timeval tv = { 1, 0 }; |
| int ret; |
| |
| if (type & TRACE_TYPE_STREAM) |
| options |= WNOHANG; |
| |
| do { |
| ret = waitpid(pid, status, options); |
| if (ret != 0) |
| return ret; |
| |
| if (type & TRACE_TYPE_STREAM) |
| trace_stream_read(pids, recorder_threads, &tv); |
| } while (1); |
| } |
| |
| #ifndef __NR_pidfd_open |
| #define __NR_pidfd_open 434 |
| #endif |
| |
| static int pidfd_open(pid_t pid, unsigned int flags) { |
| return syscall(__NR_pidfd_open, pid, flags); |
| } |
| |
| static int trace_waitpidfd(id_t pidfd) { |
| struct pollfd pollfd; |
| |
| pollfd.fd = pidfd; |
| pollfd.events = POLLIN; |
| |
| while (!finished) { |
| int ret = poll(&pollfd, 1, -1); |
| /* If waitid was interrupted, keep waiting */ |
| if (ret < 0 && errno == EINTR) |
| continue; |
| else if (ret < 0) |
| return 1; |
| else |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int trace_wait_for_processes(struct buffer_instance *instance) { |
| int ret = 0; |
| int nr_fds = 0; |
| int i; |
| int *pidfds; |
| struct filter_pids *pid; |
| |
| pidfds = malloc(sizeof(int) * instance->nr_process_pids); |
| if (!pidfds) |
| return 1; |
| |
| for (pid = instance->process_pids; |
| pid && instance->nr_process_pids; |
| pid = pid->next) { |
| if (pid->exclude) { |
| instance->nr_process_pids--; |
| continue; |
| } |
| pidfds[nr_fds] = pidfd_open(pid->pid, 0); |
| |
| /* If the pid doesn't exist, the process has probably exited */ |
| if (pidfds[nr_fds] < 0 && errno == ESRCH) { |
| instance->nr_process_pids--; |
| continue; |
| } else if (pidfds[nr_fds] < 0) { |
| ret = 1; |
| goto out; |
| } |
| |
| nr_fds++; |
| instance->nr_process_pids--; |
| } |
| |
| for (i = 0; i < nr_fds; i++) { |
| if (trace_waitpidfd(pidfds[i])) { |
| ret = 1; |
| goto out; |
| } |
| } |
| |
| out: |
| for (i = 0; i < nr_fds; i++) |
| close(pidfds[i]); |
| free(pidfds); |
| return ret; |
| } |
| |
| static void add_event_pid(struct buffer_instance *instance, const char *buf) |
| { |
| tracefs_instance_file_write(instance->tracefs, "set_event_pid", buf); |
| } |
| |
| #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(len); |
| if (!filter) |
| die("Failed to allocate pid filter"); |
| 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 update_sched_events(struct buffer_instance *instance, int 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(instance->sched_switch_event, "next_pid", pid); |
| append_sched_event(instance->sched_wakeup_event, "pid", pid); |
| append_sched_event(instance->sched_wakeup_new_event, "pid", pid); |
| } |
| |
| static int open_instance_fd(struct buffer_instance *instance, |
| const char *file, int flags); |
| |
| static void add_new_filter_child_pid(int pid, int child) |
| { |
| struct buffer_instance *instance; |
| struct filter_pids *fpid; |
| char buf[100]; |
| |
| for_all_instances(instance) { |
| if (!instance->ptrace_child || !instance->filter_pids) |
| continue; |
| for (fpid = instance->filter_pids; fpid; fpid = fpid->next) { |
| if (fpid->pid == pid) |
| break; |
| } |
| if (!fpid) |
| continue; |
| |
| add_filter_pid(instance, child, 0); |
| sprintf(buf, "%d", child); |
| update_ftrace_pid(instance, buf, 0); |
| |
| instance->common_pid_filter = append_pid_filter(instance->common_pid_filter, |
| "common_pid", pid); |
| if (instance->have_set_event_pid) { |
| add_event_pid(instance, buf); |
| } else { |
| update_sched_events(instance, pid); |
| update_event_filters(instance); |
| } |
| } |
| |
| } |
| |
| static void ptrace_attach(struct buffer_instance *instance, 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; |
| } |
| if (instance) |
| add_filter_pid(instance, pid, 0); |
| else |
| add_filter_pid_all(pid, 0); |
| } |
| |
| static void enable_ptrace(void) |
| { |
| if (!do_ptrace || !filter_task) |
| return; |
| |
| ptrace(PTRACE_TRACEME, 0, NULL, 0); |
| } |
| |
| static struct buffer_instance *get_intance_fpid(int pid) |
| { |
| struct buffer_instance *instance; |
| struct filter_pids *fpid; |
| |
| for_all_instances(instance) { |
| for (fpid = instance->filter_pids; fpid; fpid = fpid->next) { |
| if (fpid->exclude) |
| continue; |
| if (fpid->pid == pid) |
| break; |
| } |
| if (fpid) |
| return instance; |
| } |
| |
| return NULL; |
| } |
| |
| static void ptrace_wait(enum trace_type type) |
| { |
| struct buffer_instance *instance; |
| struct filter_pids *fpid; |
| unsigned long send_sig; |
| unsigned long child; |
| int nr_pids = 0; |
| siginfo_t sig; |
| int main_pids; |
| int cstatus; |
| int status; |
| int i = 0; |
| int *pids; |
| int event; |
| int pid; |
| int ret; |
| |
| |
| for_all_instances(instance) |
| nr_pids += instance->nr_filter_pids; |
| |
| pids = calloc(nr_pids, sizeof(int)); |
| if (!pids) { |
| warning("Unable to allocate array for %d PIDs", nr_pids); |
| return; |
| } |
| for_all_instances(instance) { |
| if (!instance->ptrace_child && !instance->get_procmap) |
| continue; |
| |
| for (fpid = instance->filter_pids; fpid && i < nr_pids; fpid = fpid->next) { |
| if (fpid->exclude) |
| continue; |
| pids[i++] = fpid->pid; |
| } |
| } |
| main_pids = i; |
| |
| do { |
| ret = trace_waitpid(type, -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_child_pid(pid, child); |
| ptrace(PTRACE_CONT, child, NULL, 0); |
| break; |
| |
| case PTRACE_EVENT_EXIT: |
| instance = get_intance_fpid(pid); |
| if (instance && instance->get_procmap) |
| get_pid_addr_maps(instance, pid); |
| 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); |
| } |
| if (WIFEXITED(status) || |
| (WIFSTOPPED(status) && event == PTRACE_EVENT_EXIT)) { |
| for (i = 0; i < nr_pids; i++) { |
| if (pid == pids[i]) { |
| pids[i] = 0; |
| main_pids--; |
| if (!main_pids) |
| finished = 1; |
| } |
| } |
| } |
| } while (!finished && ret > 0); |
| |
| free(pids); |
| } |
| #else |
| static inline void ptrace_wait(enum trace_type type) { } |
| static inline void enable_ptrace(void) { } |
| static inline void ptrace_attach(struct buffer_instance *instance, int pid) { } |
| |
| #endif /* NO_PTRACE */ |
| |
| static void trace_or_sleep(enum trace_type type, bool pwait) |
| { |
| struct timeval tv = { 1 , 0 }; |
| |
| if (pwait) |
| ptrace_wait(type); |
| else if (type & TRACE_TYPE_STREAM) |
| trace_stream_read(pids, recorder_threads, &tv); |
| else |
| sleep(10); |
| } |
| |
| static int change_user(const char *user) |
| { |
| struct passwd *pwd; |
| |
| if (!user) |
| return 0; |
| |
| pwd = getpwnam(user); |
| if (!pwd) |
| return -1; |
| if (initgroups(user, pwd->pw_gid) < 0) |
| return -1; |
| if (setgid(pwd->pw_gid) < 0) |
| return -1; |
| if (setuid(pwd->pw_uid) < 0) |
| return -1; |
| |
| if (setenv("HOME", pwd->pw_dir, 1) < 0) |
| return -1; |
| if (setenv("USER", pwd->pw_name, 1) < 0) |
| return -1; |
| if (setenv("LOGNAME", pwd->pw_name, 1) < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void run_cmd(enum trace_type type, const char *user, int argc, char **argv) |
| { |
| int status; |
| int pid; |
| |
| if ((pid = fork()) < 0) |
| die("failed to fork"); |
| if (!pid) { |
| /* child */ |
| update_task_filter(); |
| tracecmd_enable_tracing(); |
| if (!fork_process) |
| enable_ptrace(); |
| /* |
| * If we are using stderr for stdout, switch |
| * it back to the saved stdout for the code we run. |
| */ |
| if (save_stdout >= 0) { |
| close(1); |
| dup2(save_stdout, 1); |
| close(save_stdout); |
| } |
| |
| if (change_user(user) < 0) |
| die("Failed to change user to %s", user); |
| |
| 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 (fork_process) |
| exit(0); |
| if (do_ptrace) { |
| ptrace_attach(NULL, pid); |
| ptrace_wait(type); |
| } else |
| trace_waitpid(type, pid, &status, 0); |
| if (type & (TRACE_TYPE_START | TRACE_TYPE_SET)) |
| exit(0); |
| } |
| |
| static void |
| set_plugin_instance(struct buffer_instance *instance, const char *name) |
| { |
| char *path; |
| char zero = '0'; |
| int ret; |
| int fd; |
| |
| if (is_guest(instance)) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "current_tracer"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) { |
| /* |
| * Legacy kernels do not have current_tracer file, and they |
| * always use nop. So, it doesn't need to try to change the |
| * plugin for those if name is "nop". |
| */ |
| if (!strncmp(name, "nop", 3)) { |
| tracefs_put_tracing_file(path); |
| return; |
| } |
| die("Opening '%s'", path); |
| } |
| ret = write(fd, name, strlen(name)); |
| close(fd); |
| |
| if (ret < 0) |
| die("writing to '%s'", path); |
| |
| tracefs_put_tracing_file(path); |
| |
| if (strncmp(name, "function", 8) != 0) |
| return; |
| |
| /* Make sure func_stack_trace option is disabled */ |
| /* First try instance file, then top level */ |
| path = tracefs_instance_get_file(instance->tracefs, "options/func_stack_trace"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) { |
| tracefs_put_tracing_file(path); |
| path = tracefs_get_tracing_file("options/func_stack_trace"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) { |
| tracefs_put_tracing_file(path); |
| return; |
| } |
| } |
| /* |
| * Always reset func_stack_trace to zero. Don't bother saving |
| * the original content. |
| */ |
| add_reset_file(path, "0", RESET_HIGH_PRIO); |
| tracefs_put_tracing_file(path); |
| write(fd, &zero, 1); |
| close(fd); |
| } |
| |
| static void set_plugin(const char *name) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| set_plugin_instance(instance, name); |
| } |
| |
| static void save_option(struct buffer_instance *instance, const char *option) |
| { |
| struct opt_list *opt; |
| |
| opt = malloc(sizeof(*opt)); |
| if (!opt) |
| die("Failed to allocate option"); |
| opt->next = instance->options; |
| instance->options = opt; |
| opt->option = option; |
| } |
| |
| static int set_option(struct buffer_instance *instance, const char *option) |
| { |
| FILE *fp; |
| char *path; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "trace_options"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| warning("writing to '%s'", path); |
| tracefs_put_tracing_file(path); |
| |
| if (!fp) |
| return -1; |
| |
| fwrite(option, 1, strlen(option), fp); |
| fclose(fp); |
| |
| return 0; |
| } |
| |
| static void disable_func_stack_trace_instance(struct buffer_instance *instance) |
| { |
| struct stat st; |
| char *content; |
| char *path; |
| char *cond; |
| int size; |
| int ret; |
| |
| if (is_guest(instance)) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "current_tracer"); |
| ret = stat(path, &st); |
| tracefs_put_tracing_file(path); |
| if (ret < 0) |
| return; |
| |
| content = tracefs_instance_file_read(instance->tracefs, |
| "current_tracer", &size); |
| cond = strstrip(content); |
| if (memcmp(cond, "function", size - (cond - content)) !=0) |
| goto out; |
| |
| set_option(instance, "nofunc_stack_trace"); |
| out: |
| free(content); |
| } |
| |
| static void disable_func_stack_trace(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| disable_func_stack_trace_instance(instance); |
| } |
| |
| static void add_reset_options(struct buffer_instance *instance) |
| { |
| struct opt_list *opt; |
| const char *option; |
| char *content; |
| char *path; |
| char *ptr; |
| int len; |
| |
| if (keep) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "trace_options"); |
| content = get_file_content(path); |
| |
| for (opt = instance->options; opt; opt = opt->next) { |
| option = opt->option; |
| len = strlen(option); |
| ptr = content; |
| again: |
| ptr = strstr(ptr, option); |
| if (ptr) { |
| /* First make sure its the option we want */ |
| if (ptr[len] != '\n') { |
| ptr += len; |
| goto again; |
| } |
| if (ptr - content >= 2 && strncmp(ptr - 2, "no", 2) == 0) { |
| /* Make sure this isn't ohno-option */ |
| if (ptr > content + 2 && *(ptr - 3) != '\n') { |
| ptr += len; |
| goto again; |
| } |
| /* we enabled it */ |
| ptr[len] = 0; |
| add_reset_file(path, ptr-2, RESET_DEFAULT_PRIO); |
| ptr[len] = '\n'; |
| continue; |
| } |
| /* make sure this is our option */ |
| if (ptr > content && *(ptr - 1) != '\n') { |
| ptr += len; |
| goto again; |
| } |
| /* this option hasn't changed, ignore it */ |
| continue; |
| } |
| |
| /* ptr is NULL, not found, maybe option is a no */ |
| if (strncmp(option, "no", 2) != 0) |
| /* option is really not found? */ |
| continue; |
| |
| option += 2; |
| len = strlen(option); |
| ptr = content; |
| loop: |
| ptr = strstr(content, option); |
| if (!ptr) |
| /* Really not found? */ |
| continue; |
| |
| /* make sure this is our option */ |
| if (ptr[len] != '\n') { |
| ptr += len; |
| goto loop; |
| } |
| |
| if (ptr > content && *(ptr - 1) != '\n') { |
| ptr += len; |
| goto loop; |
| } |
| |
| add_reset_file(path, option, RESET_DEFAULT_PRIO); |
| } |
| tracefs_put_tracing_file(path); |
| free(content); |
| } |
| |
| static void set_options(void) |
| { |
| struct buffer_instance *instance; |
| struct opt_list *opt; |
| int ret; |
| |
| for_all_instances(instance) { |
| add_reset_options(instance); |
| while (instance->options) { |
| opt = instance->options; |
| instance->options = opt->next; |
| ret = set_option(instance, opt->option); |
| if (ret < 0) |
| die("Failed to set ftrace option %s", |
| opt->option); |
| free(opt); |
| } |
| } |
| } |
| |
| static void set_saved_cmdlines_size(struct common_record_context *ctx) |
| { |
| int fd, len, ret = -1; |
| char *path, *str; |
| |
| if (!ctx->saved_cmdlines_size) |
| return; |
| |
| path = tracefs_get_tracing_file("saved_cmdlines_size"); |
| if (!path) |
| goto err; |
| |
| reset_save_file(path, RESET_DEFAULT_PRIO); |
| |
| fd = open(path, O_WRONLY); |
| tracefs_put_tracing_file(path); |
| if (fd < 0) |
| goto err; |
| |
| len = asprintf(&str, "%d", ctx->saved_cmdlines_size); |
| if (len < 0) |
| die("%s couldn't allocate memory", __func__); |
| |
| if (write(fd, str, len) > 0) |
| ret = 0; |
| |
| close(fd); |
| free(str); |
| err: |
| if (ret) |
| warning("Couldn't set saved_cmdlines_size"); |
| } |
| |
| static int trace_check_file_exists(struct buffer_instance *instance, char *file) |
| { |
| struct stat st; |
| char *path; |
| int ret; |
| |
| path = tracefs_instance_get_file(instance->tracefs, file); |
| ret = stat(path, &st); |
| tracefs_put_tracing_file(path); |
| |
| return ret < 0 ? 0 : 1; |
| } |
| |
| static int use_old_event_method(void) |
| { |
| static int old_event_method; |
| static int processed; |
| |
| if (processed) |
| return old_event_method; |
| |
| /* Check if the kernel has the events/enable file */ |
| if (!trace_check_file_exists(&top_instance, "events/enable")) |
| 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 = tracefs_get_tracing_file("set_event"); |
| fp = fopen(path, "w"); |
| if (!fp) |
| die("opening '%s'", path); |
| tracefs_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_instance(struct buffer_instance *instance) |
| { |
| glob_t globbuf; |
| char *path; |
| char c; |
| int fd; |
| int i; |
| int ret; |
| |
| if (is_guest(instance)) |
| return; |
| |
| if (use_old_event_method()) { |
| /* old way only had top instance */ |
| if (!is_top_instance(instance)) |
| return; |
| old_update_events("all", '0'); |
| return; |
| } |
| |
| c = '0'; |
| path = tracefs_instance_get_file(instance->tracefs, "events/enable"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die("opening to '%s'", path); |
| ret = write(fd, &c, 1); |
| close(fd); |
| tracefs_put_tracing_file(path); |
| |
| path = tracefs_instance_get_file(instance->tracefs, "events/*/filter"); |
| globbuf.gl_offs = 0; |
| ret = glob(path, 0, NULL, &globbuf); |
| tracefs_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 reset_events(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| reset_events_instance(instance); |
| } |
| |
| enum { |
| STATE_NEWLINE, |
| STATE_SKIP, |
| STATE_COPY, |
| }; |
| |
| static char *read_file(const char *file) |
| { |
| char stbuf[BUFSIZ]; |
| char *buf = NULL; |
| int size = 0; |
| char *nbuf; |
| int fd; |
| int r; |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| return NULL; |
| |
| do { |
| r = read(fd, stbuf, BUFSIZ); |
| if (r <= 0) |
| continue; |
| nbuf = realloc(buf, size+r+1); |
| if (!nbuf) { |
| free(buf); |
| buf = NULL; |
| break; |
| } |
| buf = nbuf; |
| memcpy(buf+size, stbuf, r); |
| size += r; |
| } while (r > 0); |
| |
| close(fd); |
| if (r == 0 && size > 0) |
| buf[size] = '\0'; |
| |
| return buf; |
| } |
| |
| static void read_error_log(const char *log) |
| { |
| char *buf, *line; |
| char *start = NULL; |
| char *p; |
| |
| buf = read_file(log); |
| if (!buf) |
| return; |
| |
| line = buf; |
| |
| /* Only the last lines have meaning */ |
| while ((p = strstr(line, "\n")) && p[1]) { |
| if (line[0] != ' ') |
| start = line; |
| line = p + 1; |
| } |
| |
| if (start) |
| printf("%s", start); |
| |
| free(buf); |
| } |
| |
| static void show_error(const char *file, const char *type) |
| { |
| struct stat st; |
| char *path = strdup(file); |
| char *p; |
| int ret; |
| |
| if (!path) |
| die("Could not allocate memory"); |
| |
| p = strstr(path, "tracing"); |
| if (p) { |
| if (strncmp(p + sizeof("tracing"), "instances", sizeof("instances") - 1) == 0) { |
| p = strstr(p + sizeof("tracing") + sizeof("instances"), "/"); |
| if (!p) |
| goto read_file; |
| } else { |
| p += sizeof("tracing") - 1; |
| } |
| ret = asprintf(&p, "%.*s/error_log", (int)(p - path), path); |
| if (ret < 0) |
| die("Could not allocate memory"); |
| ret = stat(p, &st); |
| if (ret < 0) { |
| free(p); |
| goto read_file; |
| } |
| read_error_log(p); |
| goto out; |
| } |
| |
| read_file: |
| p = read_file(path); |
| if (p) |
| printf("%s", p); |
| |
| out: |
| printf("Failed %s of %s\n", type, file); |
| free(path); |
| return; |
| } |
| |
| static void write_filter(const char *file, const char *filter) |
| { |
| if (write_file(file, filter) < 0) |
| show_error(file, "filter"); |
| } |
| |
| static void clear_filter(const char *file) |
| { |
| write_filter(file, "0"); |
| } |
| |
| static void write_trigger(const char *file, const char *trigger) |
| { |
| if (write_file(file, trigger) < 0) |
| show_error(file, "trigger"); |
| } |
| |
| static int clear_trigger(const char *file) |
| { |
| char trigger[BUFSIZ]; |
| char *save = NULL; |
| char *line; |
| char *buf; |
| int len; |
| int ret; |
| |
| buf = read_file(file); |
| if (!buf) { |
| perror(file); |
| return 0; |
| } |
| |
| trigger[0] = '!'; |
| |
| for (line = strtok_r(buf, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { |
| if (line[0] == '#') |
| continue; |
| len = strlen(line); |
| if (len > BUFSIZ - 2) |
| len = BUFSIZ - 2; |
| strncpy(trigger + 1, line, len); |
| trigger[len + 1] = '\0'; |
| /* We don't want any filters or extra on the line */ |
| strtok(trigger, " "); |
| write_file(file, trigger); |
| } |
| |
| free(buf); |
| |
| /* |
| * Some triggers have an order in removing them. |
| * They will not be removed if done in the wrong order. |
| */ |
| buf = read_file(file); |
| if (!buf) |
| return 0; |
| |
| ret = 0; |
| for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) { |
| if (line[0] == '#') |
| continue; |
| ret = 1; |
| break; |
| } |
| free(buf); |
| return ret; |
| } |
| |
| static void clear_func_filter(const char *file) |
| { |
| char filter[BUFSIZ]; |
| struct stat st; |
| char *line; |
| char *buf; |
| char *p; |
| int len; |
| int ret; |
| int fd; |
| |
| /* Function filters may not exist */ |
| ret = stat(file, &st); |
| if (ret < 0) |
| return; |
| |
| /* First zero out normal filters */ |
| fd = open(file, O_WRONLY | O_TRUNC); |
| if (fd < 0) |
| die("opening to '%s'", file); |
| close(fd); |
| |
| buf = read_file(file); |
| if (!buf) { |
| perror(file); |
| return; |
| } |
| |
| /* Now remove filters */ |
| filter[0] = '!'; |
| |
| /* |
| * To delete a filter, we need to write a '!filter' |
| * to the file for each filter. |
| */ |
| for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) { |
| if (line[0] == '#') |
| continue; |
| len = strlen(line); |
| if (len > BUFSIZ - 2) |
| len = BUFSIZ - 2; |
| |
| strncpy(filter + 1, line, len); |
| filter[len + 1] = '\0'; |
| /* |
| * To remove "unlimited" filters, we must remove |
| * the ":unlimited" from what we write. |
| */ |
| if ((p = strstr(filter, ":unlimited"))) { |
| *p = '\0'; |
| len = p - filter; |
| } |
| /* |
| * The write to this file expects white space |
| * at the end :-p |
| */ |
| filter[len] = '\n'; |
| filter[len+1] = '\0'; |
| write_file(file, filter); |
| } |
| } |
| |
| static void update_reset_triggers(void) |
| { |
| struct reset_file *reset; |
| |
| while (reset_triggers) { |
| reset = reset_triggers; |
| reset_triggers = reset->next; |
| |
| clear_trigger(reset->path); |
| free(reset->path); |
| free(reset); |
| } |
| } |
| |
| static void update_reset_files(void) |
| { |
| struct reset_file *reset; |
| |
| while (reset_files) { |
| reset = reset_files; |
| reset_files = reset->next; |
| |
| if (!keep) |
| write_file(reset->path, reset->reset); |
| free(reset->path); |
| free(reset->reset); |
| free(reset); |
| } |
| } |
| |
| 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) { |
| add_reset_file(event->filter_file, "0", RESET_DEFAULT_PRIO); |
| write_filter(event->filter_file, filter); |
| } |
| |
| if (event->trigger_file) { |
| add_reset_trigger(event->trigger_file); |
| clear_trigger(event->trigger_file); |
| write_trigger(event->trigger_file, event->trigger); |
| /* Make sure we don't write this again */ |
| free(event->trigger_file); |
| free(event->trigger); |
| event->trigger_file = NULL; |
| event->trigger = NULL; |
| } |
| |
| 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 = tracefs_get_tracing_file("tracing_enabled"); |
| fd = open(path, O_WRONLY | O_CLOEXEC); |
| tracefs_put_tracing_file(path); |
| |
| if (fd < 0) |
| return; |
| } |
| write(fd, "1", 1); |
| } |
| |
| static int open_instance_fd(struct buffer_instance *instance, |
| const char *file, int flags) |
| { |
| int fd; |
| char *path; |
| |
| path = tracefs_instance_get_file(instance->tracefs, file); |
| fd = open(path, flags); |
| if (fd < 0) { |
| /* instances may not be created yet */ |
| if (is_top_instance(instance)) |
| die("opening '%s'", path); |
| } |
| tracefs_put_tracing_file(path); |
| |
| return fd; |
| } |
| |
| static int open_tracing_on(struct buffer_instance *instance) |
| { |
| int fd = instance->tracing_on_fd; |
| |
| /* OK, we keep zero for stdin */ |
| if (fd > 0) |
| return fd; |
| |
| fd = open_instance_fd(instance, "tracing_on", O_RDWR | O_CLOEXEC); |
| if (fd < 0) { |
| return fd; |
| } |
| instance->tracing_on_fd = fd; |
| |
| return fd; |
| } |
| |
| static void write_tracing_on(struct buffer_instance *instance, int on) |
| { |
| int ret; |
| int fd; |
| |
| if (is_guest(instance)) |
| return; |
| |
| fd = open_tracing_on(instance); |
| 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(struct buffer_instance *instance) |
| { |
| int fd; |
| char buf[10]; |
| int ret; |
| |
| if (is_guest(instance)) |
| return -1; |
| |
| fd = open_tracing_on(instance); |
| if (fd < 0) |
| return fd; |
| |
| ret = read(fd, buf, 10); |
| if (ret <= 0) |
| die("Reading 'tracing_on'"); |
| buf[9] = 0; |
| ret = atoi(buf); |
| |
| return ret; |
| } |
| |
| static void reset_max_latency_instance(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| reset_max_latency(instance); |
| } |
| |
| void tracecmd_enable_tracing(void) |
| { |
| struct buffer_instance *instance; |
| |
| check_tracing_enabled(); |
| |
| for_all_instances(instance) |
| write_tracing_on(instance, 1); |
| |
| if (latency) |
| reset_max_latency_instance(); |
| } |
| |
| void tracecmd_disable_tracing(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| write_tracing_on(instance, 0); |
| } |
| |
| void tracecmd_disable_all_tracing(int disable_tracer) |
| { |
| struct buffer_instance *instance; |
| |
| tracecmd_disable_tracing(); |
| |
| if (disable_tracer) { |
| disable_func_stack_trace(); |
| set_plugin("nop"); |
| } |
| |
| reset_events(); |
| |
| /* Force close and reset of ftrace pid file */ |
| for_all_instances(instance) |
| update_ftrace_pid(instance, "", 1); |
| |
| clear_trace_instances(); |
| } |
| |
| static void |
| update_sched_event(struct buffer_instance *instance, |
| struct event_list *event, const char *field) |
| { |
| if (!event) |
| return; |
| |
| event->pid_filter = make_pid_filter(instance, event->pid_filter, field); |
| } |
| |
| static void update_event_filters(struct buffer_instance *instance) |
| { |
| struct event_list *event; |
| char *event_filter; |
| int free_it; |
| int len; |
| int common_len = 0; |
| |
| if (instance->common_pid_filter) |
| common_len = strlen(instance->common_pid_filter); |
| |
| for (event = instance->events; event; event = event->next) { |
| if (!event->neg) { |
| |
| free_it = 0; |
| if (event->filter) { |
| if (!instance->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(len); |
| if (!event_filter) |
| die("Failed to allocate event_filter"); |
| sprintf(event_filter, "(%s)&&(%s||%s)", |
| event->filter, instance->common_pid_filter, |
| event->pid_filter); |
| } else { |
| free_it = 1; |
| len = common_len + strlen(event->filter) + |
| strlen("()&&()") + 1; |
| event_filter = malloc(len); |
| if (!event_filter) |
| die("Failed to allocate event_filter"); |
| sprintf(event_filter, "(%s)&&(%s)", |
| event->filter, instance->common_pid_filter); |
| } |
| } else { |
| /* event->pid_filter only exists when common_pid_filter does */ |
| if (!instance->common_pid_filter) |
| continue; |
| |
| if (event->pid_filter) { |
| free_it = 1; |
| len = common_len + strlen(event->pid_filter) + |
| strlen("||") + 1; |
| event_filter = malloc(len); |
| if (!event_filter) |
| die("Failed to allocate event_filter"); |
| sprintf(event_filter, "%s||%s", |
| instance->common_pid_filter, event->pid_filter); |
| } else |
| event_filter = instance->common_pid_filter; |
| } |
| |
| update_event(event, event_filter, 1, '1'); |
| if (free_it) |
| free(event_filter); |
| } |
| } |
| } |
| |
| static void update_pid_filters(struct buffer_instance *instance) |
| { |
| struct filter_pids *p; |
| char *filter; |
| char *str; |
| int len; |
| int ret; |
| int fd; |
| |
| if (is_guest(instance)) |
| return; |
| |
| fd = open_instance_fd(instance, "set_event_pid", |
| O_WRONLY | O_CLOEXEC | O_TRUNC); |
| if (fd < 0) |
| die("Failed to access set_event_pid"); |
| |
| len = instance->len_filter_pids + instance->nr_filter_pids; |
| filter = malloc(len); |
| if (!filter) |
| die("Failed to allocate pid filter"); |
| |
| str = filter; |
| |
| for (p = instance->filter_pids; p; p = p->next) { |
| if (p->exclude) |
| continue; |
| len = sprintf(str, "%d ", p->pid); |
| str += len; |
| } |
| |
| if (filter == str) |
| goto out; |
| |
| len = str - filter; |
| str = filter; |
| do { |
| ret = write(fd, str, len); |
| if (ret < 0) |
| die("Failed to write to set_event_pid"); |
| str += ret; |
| len -= ret; |
| } while (ret >= 0 && len); |
| |
| out: |
| close(fd); |
| } |
| |
| static void update_pid_event_filters(struct buffer_instance *instance) |
| { |
| if (instance->have_set_event_pid) |
| return update_pid_filters(instance); |
| /* |
| * 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(instance, instance->sched_switch_event, "next_pid"); |
| update_sched_event(instance, instance->sched_wakeup_event, "pid"); |
| update_sched_event(instance, instance->sched_wakeup_new_event, "pid"); |
| |
| update_event_filters(instance); |
| } |
| |
| #define MASK_STR_MAX 4096 /* Don't expect more than 32768 CPUS */ |
| |
| static char *alloc_mask_from_hex(struct buffer_instance *instance, const char *str) |
| { |
| char *cpumask; |
| |
| if (strcmp(str, "-1") == 0) { |
| /* set all CPUs */ |
| int bytes = (instance->cpu_count + 7) / 8; |
| int last = instance->cpu_count % 8; |
| int i; |
| |
| cpumask = malloc(MASK_STR_MAX); |
| if (!cpumask) |
| die("can't allocate cpumask"); |
| |
| if (bytes > (MASK_STR_MAX-1)) { |
| warning("cpumask can't handle more than 32768 CPUS!"); |
| bytes = MASK_STR_MAX-1; |
| } |
| |
| sprintf(cpumask, "%x", (1 << last) - 1); |
| |
| for (i = 1; i < bytes; i++) |
| cpumask[i] = 'f'; |
| |
| cpumask[i+1] = 0; |
| } else { |
| cpumask = strdup(str); |
| if (!cpumask) |
| die("can't allocate cpumask"); |
| } |
| |
| return cpumask; |
| } |
| |
| static void set_mask(struct buffer_instance *instance) |
| { |
| struct stat st; |
| char *path; |
| int fd; |
| int ret; |
| |
| if (is_guest(instance)) |
| return; |
| |
| if (!instance->cpumask) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "tracing_cpumask"); |
| if (!path) |
| die("could not allocate path"); |
| reset_save_file(path, RESET_DEFAULT_PRIO); |
| |
| ret = stat(path, &st); |
| if (ret < 0) { |
| warning("%s not found", path); |
| goto out; |
| } |
| |
| fd = open(path, O_WRONLY | O_TRUNC); |
| if (fd < 0) |
| die("could not open %s\n", path); |
| |
| write(fd, instance->cpumask, strlen(instance->cpumask)); |
| |
| close(fd); |
| out: |
| tracefs_put_tracing_file(path); |
| free(instance->cpumask); |
| instance->cpumask = NULL; |
| } |
| |
| static void enable_events(struct buffer_instance *instance) |
| { |
| struct event_list *event; |
| |
| if (is_guest(instance)) |
| return; |
| |
| for (event = instance->events; event; event = event->next) { |
| if (!event->neg) |
| update_event(event, event->filter, 0, '1'); |
| } |
| |
| /* Now disable any events */ |
| for (event = instance->events; event; event = event->next) { |
| if (event->neg) |
| update_event(event, NULL, 0, '0'); |
| } |
| } |
| |
| void tracecmd_enable_events(void) |
| { |
| enable_events(first_instance); |
| } |
| |
| static void set_clock(struct common_record_context *ctx, struct buffer_instance *instance) |
| { |
| const char *clock; |
| char *path; |
| char *content; |
| char *str; |
| |
| if (is_guest(instance)) |
| return; |
| |
| if (instance->clock) |
| clock = instance->clock; |
| else |
| clock = ctx->clock; |
| |
| if (!clock) |
| return; |
| |
| /* The current clock is in brackets, reset it when we are done */ |
| content = tracefs_instance_file_read(instance->tracefs, |
| "trace_clock", NULL); |
| |
| /* check if first clock is set */ |
| if (*content == '[') |
| str = strtok(content+1, "]"); |
| else { |
| str = strtok(content, "["); |
| if (!str) |
| die("Can not find clock in trace_clock"); |
| str = strtok(NULL, "]"); |
| } |
| path = tracefs_instance_get_file(instance->tracefs, "trace_clock"); |
| add_reset_file(path, str, RESET_DEFAULT_PRIO); |
| |
| free(content); |
| tracefs_put_tracing_file(path); |
| |
| tracefs_instance_file_write(instance->tracefs, |
| "trace_clock", clock); |
| } |
| |
| static void set_max_graph_depth(struct buffer_instance *instance, char *max_graph_depth) |
| { |
| char *path; |
| int ret; |
| |
| if (is_guest(instance)) |
| return; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "max_graph_depth"); |
| reset_save_file(path, RESET_DEFAULT_PRIO); |
| tracefs_put_tracing_file(path); |
| ret = tracefs_instance_file_write(instance->tracefs, "max_graph_depth", |
| max_graph_depth); |
| if (ret < 0) |
| die("could not write to max_graph_depth"); |
| } |
| |
| static bool check_file_in_dir(char *dir, char *file) |
| { |
| struct stat st; |
| char *path; |
| int ret; |
| |
| ret = asprintf(&path, "%s/%s", dir, file); |
| if (ret < 0) |
| die("Failed to allocate id file path for %s/%s", dir, file); |
| ret = stat(path, &st); |
| free(path); |
| if (ret < 0 || S_ISDIR(st.st_mode)) |
| return false; |
| return true; |
| } |
| |
| /** |
| * create_event - create and event descriptor |
| * @instance: instance to use |
| * @path: path to event attribute |
| * @old_event: event descriptor to use as base |
| * |
| * NOTE: the function purpose is to create a data structure to describe |
| * an ftrace event. During the process it becomes handy to change the |
| * string `path`. So, do not rely on the content of `path` after you |
| * invoke this function. |
| */ |
| static struct event_list * |
| create_event(struct buffer_instance *instance, char *path, struct event_list *old_event) |
| { |
| struct event_list *event; |
| struct stat st; |
| char *path_dirname; |
| char *p; |
| int ret; |
| |
| event = malloc(sizeof(*event)); |
| if (!event) |
| die("Failed to allocate event"); |
| *event = *old_event; |
| add_event(instance, event); |
| |
| if (event->filter || filter_task || instance->filter_pids) { |
| event->filter_file = strdup(path); |
| if (!event->filter_file) |
| die("malloc filter file"); |
| } |
| |
| path_dirname = dirname(path); |
| |
| ret = asprintf(&p, "%s/enable", path_dirname); |
| if (ret < 0) |
| die("Failed to allocate enable path for %s", path); |
| ret = stat(p, &st); |
| if (ret >= 0) |
| event->enable_file = p; |
| else |
| free(p); |
| |
| if (old_event->trigger) { |
| if (check_file_in_dir(path_dirname, "trigger")) { |
| event->trigger = strdup(old_event->trigger); |
| ret = asprintf(&p, "%s/trigger", path_dirname); |
| if (ret < 0) |
| die("Failed to allocate trigger path for %s", path); |
| event->trigger_file = p; |
| } else { |
| /* Check if this is event or system. |
| * Systems do not have trigger files by design |
| */ |
| if (check_file_in_dir(path_dirname, "id")) |
| die("trigger specified but not supported by this kernel"); |
| } |
| } |
| |
| return event; |
| } |
| |
| static void make_sched_event(struct buffer_instance *instance, |
| struct event_list **event, struct event_list *sched, |
| const char *sched_path) |
| { |
| char *path_dirname; |
| char *tmp_file; |
| char *path; |
| int ret; |
| |
| /* Do nothing if the event already exists */ |
| if (*event) |
| return; |
| |
| /* we do not want to corrupt sched->filter_file when using dirname() */ |
| tmp_file = strdup(sched->filter_file); |
| if (!tmp_file) |
| die("Failed to allocate path for %s", sched_path); |
| path_dirname = dirname(tmp_file); |
| |
| ret = asprintf(&path, "%s/%s/filter", path_dirname, sched_path); |
| free(tmp_file); |
| if (ret < 0) |
| die("Failed to allocate path for %s", sched_path); |
| |
| *event = create_event(instance, 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 void print_event(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!show_status) |
| return; |
| |
| va_start(ap, fmt); |
| vprintf(fmt, ap); |
| va_end(ap); |
| |
| printf("\n"); |
| } |
| |
| |
| static int expand_event_files(struct buffer_instance *instance, |
| const char *file, struct event_list *old_event) |
| { |
| struct event_list **save_event_tail = instance->event_next; |
| struct event_list *sched_event = NULL; |
| struct event_list *event; |
| glob_t globbuf; |
| char *path; |
| char *p; |
| int ret; |
| int i; |
| |
| ret = asprintf(&p, "events/%s/filter", file); |
| if (ret < 0) |
| die("Failed to allocate event filter path for %s", file); |
| |
| path = tracefs_instance_get_file(instance->tracefs, p); |
| |
| globbuf.gl_offs = 0; |
| ret = glob(path, 0, NULL, &globbuf); |
| tracefs_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(instance, path, old_event); |
| print_event("%s\n", path); |
| |
| len = strlen(path); |
| |
| test_event(event, path, "sched", &sched_event, len); |
| test_event(event, path, "sched/sched_switch", &instance->sched_switch_event, len); |
| test_event(event, path, "sched/sched_wakeup_new", &instance->sched_wakeup_new_event, len); |
| test_event(event, path, "sched/sched_wakeup", &instance->sched_wakeup_event, len); |
| } |
| |
| if (sched_event && sched_event->filter_file) { |
| /* make sure all sched events exist */ |
| make_sched_event(instance, &instance->sched_switch_event, |
| sched_event, "sched_switch"); |
| make_sched_event(instance, &instance->sched_wakeup_event, |
| sched_event, "sched_wakeup"); |
| make_sched_event(instance, &instance->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 == instance->event_next; |
| } |
| |
| static int expand_events_all(struct buffer_instance *instance, |
| char *system_name, char *event_name, |
| struct event_list *event) |
| { |
| char *name; |
| int ret; |
| |
| ret = asprintf(&name, "%s/%s", system_name, event_name); |
| if (ret < 0) |
| die("Failed to allocate system/event for %s/%s", |
| system_name, event_name); |
| ret = expand_event_files(instance, name, event); |
| free(name); |
| |
| return ret; |
| } |
| |
| static void expand_event(struct buffer_instance *instance, struct event_list *event) |
| { |
| const char *name = event->event; |
| char *str; |
| char *ptr; |
| int ret; |
| |
| /* |
| * 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(instance, "*", event); |
| return; |
| } |
| |
| str = strdup(name); |
| if (!str) |
| die("Failed to allocate %s string", name); |
| |
| ptr = strchr(str, ':'); |
| if (ptr) { |
| *ptr = '\0'; |
| ptr++; |
| |
| if (strlen(ptr)) |
| ret = expand_events_all(instance, str, ptr, event); |
| else |
| ret = expand_events_all(instance, str, "*", event); |
| |
| if (!ignore_event_not_found && ret) |
| die("No events enabled with %s", name); |
| |
| goto out; |
| } |
| |
| /* No ':' so enable all matching systems and events */ |
| ret = expand_event_files(instance, str, event); |
| ret &= expand_events_all(instance, "*", str, event); |
| if (event->trigger) |
| ret &= expand_events_all(instance, str, "*", event); |
| |
| if (!ignore_event_not_found && ret) |
| die("No events enabled with %s", name); |
| |
| out: |
| free(str); |
| } |
| |
| static void expand_event_instance(struct buffer_instance *instance) |
| { |
| struct event_list *compressed_list = instance->events; |
| struct event_list *event; |
| |
| if (is_guest(instance)) |
| return; |
| |
| reset_event_list(instance); |
| |
| while (compressed_list) { |
| event = compressed_list; |
| compressed_list = event->next; |
| expand_event(instance, event); |
| free(event->trigger); |
| free(event); |
| } |
| } |
| |
| static void expand_event_list(void) |
| { |
| struct buffer_instance *instance; |
| |
| if (use_old_event_method()) |
| return; |
| |
| for_all_instances(instance) |
| expand_event_instance(instance); |
| } |
| |
| static void finish(int sig) |
| { |
| /* all done */ |
| if (recorder) |
| tracecmd_stop_recording(recorder); |
| finished = 1; |
| } |
| |
| static struct addrinfo *do_getaddrinfo(const char *host, unsigned int port, |
| enum port_type type) |
| { |
| struct addrinfo *results; |
| struct addrinfo hints; |
| char buf[BUFSIZ]; |
| int s; |
| |
| snprintf(buf, BUFSIZ, "%u", port); |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = type == USE_TCP ? SOCK_STREAM : SOCK_DGRAM; |
| |
| s = getaddrinfo(host, buf, &hints, &results); |
| if (s != 0) { |
| gai_err = gai_strerror(s); |
| return NULL; |
| } |
| |
| dprint("Attached port %s: %d to results: %p\n", |
| type == USE_TCP ? "TCP" : "UDP", port, results); |
| |
| return results; |
| } |
| |
| static int connect_addr(struct addrinfo *results) |
| { |
| struct addrinfo *rp; |
| int sfd = -1; |
| |
| 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) |
| return -1; |
| |
| dprint("connect results: %p with fd: %d\n", results, sfd); |
| |
| return sfd; |
| } |
| |
| static int connect_port(const char *host, unsigned int port, enum port_type type) |
| { |
| struct addrinfo *results; |
| int sfd; |
| |
| if (type == USE_VSOCK) |
| return trace_vsock_open(atoi(host), port); |
| |
| results = do_getaddrinfo(host, port, type); |
| |
| if (!results) |
| die("connecting to %s server %s:%u", |
| type == USE_TCP ? "TCP" : "UDP", host, port); |
| |
| sfd = connect_addr(results); |
| |
| freeaddrinfo(results); |
| |
| if (sfd < 0) |
| die("Can not connect to %s server %s:%u", |
| type == USE_TCP ? "TCP" : "UDP", host, port); |
| |
| return sfd; |
| } |
| |
| static int do_accept(int sd) |
| { |
| int cd; |
| |
| for (;;) { |
| dprint("Wait on accept: %d\n", sd); |
| cd = accept(sd, NULL, NULL); |
| dprint("accepted: %d\n", cd); |
| if (cd < 0) { |
| if (errno == EINTR) |
| continue; |
| die("accept"); |
| } |
| |
| return cd; |
| } |
| |
| return -1; |
| } |
| |
| /* Find all the tasks associated with the guest pid */ |
| static void find_tasks(struct trace_guest *guest) |
| { |
| struct dirent *dent; |
| char *path; |
| DIR *dir; |
| int ret; |
| int tasks = 0; |
| |
| ret = asprintf(&path, "/proc/%d/task", guest->pid); |
| if (ret < 0) |
| return; |
| |
| dir = opendir(path); |
| free(path); |
| if (!dir) |
| return; |
| |
| while ((dent = readdir(dir))) { |
| int *pids; |
| if (!(dent->d_type == DT_DIR && is_digits(dent->d_name))) |
| continue; |
| pids = realloc(guest->task_pids, sizeof(int) * (tasks + 2)); |
| if (!pids) |
| break; |
| pids[tasks++] = strtol(dent->d_name, NULL, 0); |
| pids[tasks] = -1; |
| guest->task_pids = pids; |
| } |
| closedir(dir); |
| } |
| |
| static char *parse_guest_name(char *gname, int *cid, int *port, |
| struct addrinfo **res) |
| { |
| struct trace_guest *guest = NULL; |
| struct addrinfo *result; |
| char *ip = NULL; |
| char *p; |
| |
| *res = NULL; |
| |
| *port = -1; |
| for (p = gname + strlen(gname); p > gname; p--) { |
| if (*p == ':') |
| break; |
| } |
| if (p > gname) { |
| *p = '\0'; |
| *port = atoi(p + 1); |
| } |
| |
| *cid = -1; |
| p = strrchr(gname, '@'); |
| if (p) { |
| *p = '\0'; |
| *cid = atoi(p + 1); |
| } else if (is_digits(gname)) { |
| *cid = atoi(gname); |
| } else { |
| /* Check if this is an IP address */ |
| if (strstr(gname, ":") || strstr(gname, ".")) |
| ip = gname; |
| } |
| |
| if (!ip && *cid < 0) |
| read_qemu_guests(); |
| |
| if (!ip) |
| guest = trace_get_guest(*cid, gname); |
| if (guest) { |
| *cid = guest->cid; |
| /* Mapping not found, search for them */ |
| if (!guest->cpu_pid) |
| find_tasks(guest); |
| return guest->name; |
| } |
| |
| /* Test to see if this is an internet address */ |
| result = do_getaddrinfo(gname, *port, USE_TCP); |
| if (!result) |
| return NULL; |
| |
| *res = result; |
| |
| return gname; |
| } |
| |
| 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"); |
| } |
| |
| static struct tracecmd_recorder * |
| create_recorder_instance_pipe(struct buffer_instance *instance, |
| int cpu, int *brass) |
| { |
| struct tracecmd_recorder *recorder; |
| unsigned flags = recorder_flags | TRACECMD_RECORD_BLOCK_SPLICE; |
| char *path; |
| |
| path = tracefs_instance_get_dir(instance->tracefs); |
| |
| if (!path) |
| die("malloc"); |
| |
| /* This is already the child */ |
| close(brass[0]); |
| |
| recorder = tracecmd_create_buffer_recorder_fd(brass[1], cpu, flags, path); |
| |
| tracefs_put_tracing_file(path); |
| |
| return recorder; |
| } |
| |
| static struct tracecmd_recorder * |
| create_recorder_instance(struct buffer_instance *instance, const char *file, int cpu, |
| int *brass) |
| { |
| struct tracecmd_recorder *record; |
| struct addrinfo *result; |
| char *path; |
| |
| if (is_guest(instance)) { |
| int fd; |
| unsigned int flags; |
| |
| if (instance->use_fifos) |
| fd = instance->fds[cpu]; |
| else if (is_network(instance)) { |
| result = do_getaddrinfo(instance->name, |
| instance->client_ports[cpu], |
| instance->port_type); |
| if (!result) |
| die("Failed to connect to %s port %d\n", |
| instance->name, |
| instance->client_ports[cpu]); |
| fd = connect_addr(result); |
| freeaddrinfo(result); |
| } else |
| fd = trace_vsock_open(instance->cid, instance->client_ports[cpu]); |
| if (fd < 0) |
| die("Failed to connect to agent"); |
| |
| flags = recorder_flags; |
| if (instance->use_fifos) |
| flags |= TRACECMD_RECORD_NOBRASS; |
| else if (!trace_vsock_can_splice_read()) |
| flags |= TRACECMD_RECORD_NOSPLICE; |
| return tracecmd_create_recorder_virt(file, cpu, flags, fd); |
| } |
| |
| if (brass) |
| return create_recorder_instance_pipe(instance, cpu, brass); |
| |
| if (!tracefs_instance_get_name(instance->tracefs)) |
| return tracecmd_create_recorder_maxkb(file, cpu, recorder_flags, max_kb); |
| |
| path = tracefs_instance_get_dir(instance->tracefs); |
| |
| record = tracecmd_create_buffer_recorder_maxkb(file, cpu, recorder_flags, |
| path, max_kb); |
| tracefs_put_tracing_file(path); |
| |
| return record; |
| } |
| |
| /* |
| * 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(struct buffer_instance *instance, int cpu, |
| enum trace_type type, int *brass) |
| { |
| long ret; |
| char *file; |
| pid_t pid; |
| |
| if (type != TRACE_TYPE_EXTRACT) { |
| |
| pid = fork(); |
| if (pid < 0) |
| die("fork"); |
| |
| if (pid) |
| return pid; |
| |
| signal(SIGINT, SIG_IGN); |
| signal(SIGUSR1, finish); |
| |
| if (rt_prio) |
| set_prio(rt_prio); |
| |
| /* do not kill tasks on error */ |
| instance->cpu_count = 0; |
| } |
| |
| if ((instance->client_ports && !is_guest(instance)) || is_agent(instance)) { |
| unsigned int flags = recorder_flags; |
| char *path = NULL; |
| int fd; |
| |
| if (is_agent(instance)) { |
| if (instance->use_fifos) |
| fd = instance->fds[cpu]; |
| else { |
| again: |
| fd = do_accept(instance->fds[cpu]); |
| if (instance->host && |
| !trace_net_cmp_connection_fd(fd, instance->host)) { |
| dprint("Client does not match '%s' for cpu:%d\n", |
| instance->host, cpu); |
| goto again; |
| } |
| } |
| } else { |
| fd = connect_port(host, instance->client_ports[cpu], |
| instance->port_type); |
| } |
| if (fd < 0) |
| die("Failed connecting to client"); |
| if (tracefs_instance_get_name(instance->tracefs) && !is_agent(instance)) { |
| path = tracefs_instance_get_dir(instance->tracefs); |
| } else { |
| const char *dir = tracefs_tracing_dir(); |
| |
| if (dir) |
| path = strdup(dir); |
| } |
| if (!path) |
| die("can't get the tracing directory"); |
| |
| recorder = tracecmd_create_buffer_recorder_fd(fd, cpu, flags, path); |
| tracefs_put_tracing_file(path); |
| } else { |
| file = get_temp_file(instance, cpu); |
| recorder = create_recorder_instance(instance, file, cpu, brass); |
| put_temp_file(file); |
| } |
| |
| if (!recorder) |
| die ("can't create recorder"); |
| |
| if (type == TRACE_TYPE_EXTRACT) { |
| ret = tracecmd_flush_recording(recorder); |
| tracecmd_free_recorder(recorder); |
| recorder = NULL; |
| return ret; |
| } |
| |
| while (!finished) { |
| if (tracecmd_start_recording(recorder, sleep_time) < 0) |
| break; |
| } |
| tracecmd_free_recorder(recorder); |
| recorder = NULL; |
| |
| exit(0); |
| } |
| |
| static void check_first_msg_from_server(struct tracecmd_msg_handle *msg_handle) |
| { |
| char buf[BUFSIZ]; |
| |
| read(msg_handle->fd, buf, 8); |
| |
| /* Make sure the server is the tracecmd server */ |
| if (memcmp(buf, "tracecmd", 8) != 0) |
| die("server not tracecmd server"); |
| } |
| |
| static void communicate_with_listener_v1(struct tracecmd_msg_handle *msg_handle, |
| struct buffer_instance *instance) |
| { |
| unsigned int *client_ports; |
| char buf[BUFSIZ]; |
| ssize_t n; |
| int cpu, i; |
| |
| check_first_msg_from_server(msg_handle); |
| |
| /* write the number of CPUs we have (in ASCII) */ |
| sprintf(buf, "%d", local_cpu_count); |
| |
| /* include \0 */ |
| write(msg_handle->fd, buf, strlen(buf)+1); |
| |
| /* write the pagesize (in ASCII) */ |
| sprintf(buf, "%d", page_size); |
| |
| /* include \0 */ |
| write(msg_handle->fd, 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"); |
| instance->port_type = USE_TCP; |
| msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP; |
| } |
| |
| if (instance->port_type == USE_TCP) { |
| /* Send one option */ |
| write(msg_handle->fd, "1", 2); |
| /* Size 4 */ |
| write(msg_handle->fd, "4", 2); |
| /* use TCP */ |
| write(msg_handle->fd, "TCP", 4); |
| } else |
| /* No options */ |
| write(msg_handle->fd, "0", 2); |
| |
| client_ports = malloc(local_cpu_count * sizeof(*client_ports)); |
| if (!client_ports) |
| die("Failed to allocate client ports for %d cpus", local_cpu_count); |
| |
| /* |
| * Now we will receive back a comma deliminated list |
| * of client ports to connect to. |
| */ |
| for (cpu = 0; cpu < local_cpu_count; cpu++) { |
| for (i = 0; i < BUFSIZ; i++) { |
| n = read(msg_handle->fd, 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); |
| } |
| |
| instance->client_ports = client_ports; |
| } |
| |
| static void communicate_with_listener_v3(struct tracecmd_msg_handle *msg_handle, |
| unsigned int **client_ports) |
| { |
| if (tracecmd_msg_send_init_data(msg_handle, client_ports) < 0) |
| die("Cannot communicate with server"); |
| } |
| |
| static void check_protocol_version(struct tracecmd_msg_handle *msg_handle) |
| { |
| char buf[BUFSIZ]; |
| int fd = msg_handle->fd; |
| int n; |
| |
| check_first_msg_from_server(msg_handle); |
| |
| /* |
| * Write the protocol version, the magic number, and the dummy |
| * option(0) (in ASCII). The client understands whether the client |
| * uses the v3 protocol or not by checking a reply message from the |
| * server. If the message is "V3", the server uses v3 protocol. On the |
| * other hands, if the message is just number strings, the server |
| * returned port numbers. So, in that time, the client understands the |
| * server uses the v1 protocol. However, the old server tells the |
| * client port numbers after reading cpu_count, page_size, and option. |
| * So, we add the dummy number (the magic number and 0 option) to the |
| * first client message. |
| */ |
| write(fd, V3_CPU, sizeof(V3_CPU)); |
| |
| buf[0] = 0; |
| |
| /* read a reply message */ |
| n = read(fd, buf, BUFSIZ); |
| |
| if (n < 0 || !buf[0]) { |
| /* the server uses the v1 protocol, so we'll use it */ |
| msg_handle->version = V1_PROTOCOL; |
| tracecmd_plog("Use the v1 protocol\n"); |
| } else { |
| if (memcmp(buf, "V3", n) != 0) |
| die("Cannot handle the protocol %s", buf); |
| /* OK, let's use v3 protocol */ |
| write(fd, V3_MAGIC, sizeof(V3_MAGIC)); |
| |
| n = read(fd, buf, BUFSIZ - 1); |
| if (n != 2 || memcmp(buf, "OK", 2) != 0) { |
| if (n < 0) |
| n = 0; |
| buf[n] = 0; |
| die("Cannot handle the protocol %s", buf); |
| } |
| } |
| } |
| |
| static int connect_vsock(char *vhost) |
| { |
| char *cid; |
| char *port; |
| char *p; |
| int sd; |
| |
| host = strdup(vhost); |
| if (!host) |
| die("alloctating server"); |
| |
| cid = strtok_r(host, ":", &p); |
| port = strtok_r(NULL, "", &p); |
| |
| if (!port) |
| die("vsocket must have format of 'CID:PORT'"); |
| |
| sd = trace_vsock_open(atoi(cid), atoi(port)); |
| |
| return sd; |
| } |
| |
| static int connect_ip(char *thost) |
| { |
| struct addrinfo *result; |
| int sfd; |
| char *server; |
| char *port; |
| char *p; |
| |
| if (!strchr(host, ':')) { |
| server = strdup("localhost"); |
| if (!server) |
| die("alloctating server"); |
| port = thost; |
| host = server; |
| } else { |
| host = strdup(thost); |
| if (!host) |
| die("alloctating server"); |
| server = strtok_r(host, ":", &p); |
| port = strtok_r(NULL, ":", &p); |
| } |
| |
| result = do_getaddrinfo(server, atoi(port), USE_TCP); |
| if (!result) |
| die("getaddrinfo: %s", gai_err); |
| |
| sfd = connect_addr(result); |
| |
| freeaddrinfo(result); |
| |
| if (sfd < 0) |
| die("Can not connect to %s:%s", server, port); |
| |
| return sfd; |
| } |
| |
| static struct tracecmd_msg_handle *setup_network(struct buffer_instance *instance) |
| { |
| struct tracecmd_msg_handle *msg_handle = NULL; |
| enum port_type type = instance->port_type; |
| int sfd; |
| |
| again: |
| switch (type) { |
| case USE_VSOCK: |
| sfd = connect_vsock(host); |
| break; |
| default: |
| sfd = connect_ip(host); |
| } |
| |
| if (sfd < 0) |
| return NULL; |
| |
| if (msg_handle) { |
| msg_handle->fd = sfd; |
| } else { |
| msg_handle = tracecmd_msg_handle_alloc(sfd, 0); |
| if (!msg_handle) |
| die("Failed to allocate message handle"); |
| |
| msg_handle->cpu_count = local_cpu_count; |
| msg_handle->version = V3_PROTOCOL; |
| } |
| |
| switch (type) { |
| case USE_TCP: |
| msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP; |
| break; |
| case USE_VSOCK: |
| msg_handle->flags |= TRACECMD_MSG_FL_USE_VSOCK; |
| break; |
| default: |
| break; |
| } |
| |
| if (msg_handle->version == V3_PROTOCOL) { |
| check_protocol_version(msg_handle); |
| if (msg_handle->version == V1_PROTOCOL) { |
| /* reconnect to the server for using the v1 protocol */ |
| close(sfd); |
| free(host); |
| goto again; |
| } |
| communicate_with_listener_v3(msg_handle, &instance->client_ports); |
| } |
| |
| if (msg_handle->version == V1_PROTOCOL) |
| communicate_with_listener_v1(msg_handle, instance); |
| |
| return msg_handle; |
| } |
| |
| static void add_options(struct tracecmd_output *handle, struct common_record_context *ctx); |
| |
| static struct tracecmd_output *create_net_output(struct common_record_context *ctx, |
| struct tracecmd_msg_handle *msg_handle) |
| { |
| struct tracecmd_output *out; |
| |
| out = tracecmd_output_create(NULL); |
| if (!out) |
| return NULL; |
| if (ctx->file_version && tracecmd_output_set_version(out, ctx->file_version)) |
| goto error; |
| if (tracecmd_output_set_msg(out, msg_handle)) |
| goto error; |
| |
| if (ctx->compression) { |
| if (tracecmd_output_set_compression(out, ctx->compression)) |
| goto error; |
| } else if (ctx->file_version >= FILE_VERSION_COMPRESSION) { |
| tracecmd_output_set_compression(out, "any"); |
| } |
| |
| if (tracecmd_output_write_headers(out, listed_events)) |
| goto error; |
| |
| return out; |
| error: |
| tracecmd_output_close(out); |
| return NULL; |
| } |
| |
| static struct tracecmd_msg_handle * |
| setup_connection(struct buffer_instance *instance, struct common_record_context *ctx) |
| { |
| struct tracecmd_msg_handle *msg_handle = NULL; |
| struct tracecmd_output *network_handle = NULL; |
| int ret; |
| |
| msg_handle = setup_network(instance); |
| if (!msg_handle) |
| die("Failed to make connection"); |
| |
| /* Now create the handle through this socket */ |
| if (msg_handle->version == V3_PROTOCOL) { |
| network_handle = create_net_output(ctx, msg_handle); |
| if (!network_handle) |
| goto error; |
| tracecmd_set_quiet(network_handle, quiet); |
| add_options(network_handle, ctx); |
| ret = tracecmd_write_cmdlines(network_handle); |
| if (ret) |
| goto error; |
| ret = tracecmd_write_cpus(network_handle, instance->cpu_count); |
| if (ret) |
| goto error; |
| ret = tracecmd_write_buffer_info(network_handle); |
| if (ret) |
| goto error; |
| ret = tracecmd_write_options(network_handle); |
| if (ret) |
| goto error; |
| ret = tracecmd_msg_finish_sending_data(msg_handle); |
| if (ret) |
| goto error; |
| } else { |
| network_handle = tracecmd_output_create_fd(msg_handle->fd); |
| if (!network_handle) |
| goto error; |
| if (tracecmd_output_set_version(network_handle, ctx->file_version)) |
| goto error; |
| |
| if (ctx->compression) { |
| if (tracecmd_output_set_compression(network_handle, ctx->compression)) |
| goto error; |
| } else if (ctx->file_version >= FILE_VERSION_COMPRESSION) { |
| tracecmd_output_set_compression(network_handle, "any"); |
| } |
| |
| if (tracecmd_output_write_headers(network_handle, listed_events)) |
| goto error; |
| tracecmd_set_quiet(network_handle, quiet); |
| } |
| |
| instance->network_handle = network_handle; |
| |
| /* OK, we are all set, let'r rip! */ |
| return msg_handle; |
| |
| error: |
| if (msg_handle) |
| tracecmd_msg_handle_close(msg_handle); |
| if (network_handle) |
| tracecmd_output_close(network_handle); |
| return NULL; |
| } |
| |
| static void finish_network(struct tracecmd_msg_handle *msg_handle) |
| { |
| if (msg_handle->version == V3_PROTOCOL) |
| tracecmd_msg_send_close_msg(msg_handle); |
| tracecmd_msg_handle_close(msg_handle); |
| free(host); |
| } |
| |
| static int open_guest_fifos(const char *guest, int **fds) |
| { |
| char path[PATH_MAX]; |
| int i, fd, flags; |
| |
| for (i = 0; ; i++) { |
| snprintf(path, sizeof(path), GUEST_FIFO_FMT ".out", guest, i); |
| |
| /* O_NONBLOCK so we don't wait for writers */ |
| fd = open(path, O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| break; |
| |
| /* Success, now clear O_NONBLOCK */ |
| flags = fcntl(fd, F_GETFL); |
| fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); |
| |
| *fds = realloc(*fds, i + 1); |
| (*fds)[i] = fd; |
| } |
| |
| return i; |
| } |
| |
| struct trace_mapping { |
| struct tep_event *kvm_entry; |
| struct tep_format_field *vcpu_id; |
| struct tep_format_field *common_pid; |
| int *pids; |
| int *map; |
| int max_cpus; |
| }; |
| |
| static void start_mapping_vcpus(struct trace_guest *guest) |
| { |
| char *pids = NULL; |
| char *t; |
| int len = 0; |
| int s; |
| int i; |
| |
| if (!guest->task_pids) |
| return; |
| |
| guest->instance = tracefs_instance_create("map_guest_pids"); |
| if (!guest->instance) |
| return; |
| |
| for (i = 0; guest->task_pids[i] >= 0; i++) { |
| s = snprintf(NULL, 0, "%d ", guest->task_pids[i]); |
| t = realloc(pids, len + s + 1); |
| if (!t) { |
| free(pids); |
| pids = NULL; |
| break; |
| } |
| pids = t; |
| sprintf(pids + len, "%d ", guest->task_pids[i]); |
| len += s; |
| } |
| if (pids) { |
| tracefs_instance_file_write(guest->instance, "set_event_pid", pids); |
| free(pids); |
| } |
| tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "1"); |
| } |
| |
| static int map_vcpus(struct tep_event *event, struct tep_record *record, |
| int cpu, void *context) |
| { |
| struct trace_mapping *tmap = context; |
| unsigned long long val; |
| int type; |
| int pid; |
| int ret; |
| int i; |
| |
| /* Do we have junk in the buffer? */ |
| type = tep_data_type(event->tep, record); |
| if (type != tmap->kvm_entry->id) |
| return 0; |
| |
| ret = tep_read_number_field(tmap->common_pid, record->data, &val); |
| if (ret < 0) |
| return 0; |
| pid = (int)val; |
| |
| for (i = 0; tmap->pids[i] >= 0; i++) { |
| if (pid == tmap->pids[i]) |
| break; |
| } |
| /* Is this thread one we care about ? */ |
| if (tmap->pids[i] < 0) |
| return 0; |
| |
| ret = tep_read_number_field(tmap->vcpu_id, record->data, &val); |
| if (ret < 0) |
| return 0; |
| |
| cpu = (int)val; |
| |
| /* Sanity check, warn? */ |
| if (cpu >= tmap->max_cpus) |
| return 0; |
| |
| /* Already have this one? Should we check if it is the same? */ |
| if (tmap->map[cpu] >= 0) |
| return 0; |
| |
| tmap->map[cpu] = pid; |
| |
| /* Did we get them all */ |
| for (i = 0; i < tmap->max_cpus; i++) { |
| if (tmap->map[i] < 0) |
| break; |
| } |
| |
| return i == tmap->max_cpus; |
| } |
| |
| static void stop_mapping_vcpus(struct buffer_instance *instance, |
| struct trace_guest *guest) |
| { |
| struct trace_mapping tmap = { }; |
| struct tep_handle *tep; |
| const char *systems[] = { "kvm", NULL }; |
| int i; |
| |
| if (!guest->instance) |
| return; |
| |
| tmap.pids = guest->task_pids; |
| tmap.max_cpus = instance->cpu_count; |
| |
| tmap.map = malloc(sizeof(*tmap.map) * tmap.max_cpus); |
| if (!tmap.map) |
| return; |
| |
| for (i = 0; i < tmap.max_cpus; i++) |
| tmap.map[i] = -1; |
| |
| tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "0"); |
| |
| tep = tracefs_local_events_system(NULL, systems); |
| if (!tep) |
| goto out; |
| |
| tmap.kvm_entry = tep_find_event_by_name(tep, "kvm", "kvm_entry"); |
| if (!tmap.kvm_entry) |
| goto out_free; |
| |
| tmap.vcpu_id = tep_find_field(tmap.kvm_entry, "vcpu_id"); |
| if (!tmap.vcpu_id) |
| goto out_free; |
| |
| tmap.common_pid = tep_find_any_field(tmap.kvm_entry, "common_pid"); |
| if (!tmap.common_pid) |
| goto out_free; |
| |
| tracefs_iterate_raw_events(tep, guest->instance, NULL, 0, map_vcpus, &tmap); |
| |
| for (i = 0; i < tmap.max_cpus; i++) { |
| if (tmap.map[i] < 0) |
| break; |
| } |
| /* We found all the mapped CPUs */ |
| if (i == tmap.max_cpus) { |
| guest->cpu_pid = tmap.map; |
| guest->cpu_max = tmap.max_cpus; |
| tmap.map = NULL; |
| } |
| |
| out_free: |
| tep_free(tep); |
| out: |
| free(tmap.map); |
| tracefs_instance_destroy(guest->instance); |
| tracefs_instance_free(guest->instance); |
| } |
| |
| static int host_tsync(struct common_record_context *ctx, |
| struct buffer_instance *instance, |
| unsigned int tsync_port, char *proto) |
| { |
| struct trace_guest *guest; |
| int guest_pid = -1; |
| int fd; |
| |
| if (!proto) |
| return -1; |
| |
| if (is_network(instance)) { |
| fd = connect_port(instance->name, tsync_port, |
| instance->port_type); |
| } else { |
| guest = trace_get_guest(instance->cid, NULL); |
| if (guest == NULL) |
| return -1; |
| |
| guest_pid = guest->pid; |
| start_mapping_vcpus(guest); |
| fd = trace_vsock_open(instance->cid, tsync_port); |
| } |
| |
| instance->tsync = tracecmd_tsync_with_guest(top_instance.trace_id, |
| instance->tsync_loop_interval, |
| fd, guest_pid, |
| instance->cpu_count, |
| proto, ctx->clock); |
| if (!is_network(instance)) |
| stop_mapping_vcpus(instance, guest); |
| |
| if (!instance->tsync) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void connect_to_agent(struct common_record_context *ctx, |
| struct buffer_instance *instance) |
| { |
| struct tracecmd_tsync_protos *protos = NULL; |
| int sd, ret, nr_fifos, nr_cpus, page_size; |
| struct tracecmd_msg_handle *msg_handle; |
| enum tracecmd_time_sync_role role; |
| char *tsync_protos_reply = NULL; |
| unsigned int tsync_port = 0; |
| unsigned int *ports; |
| int i, *fds = NULL; |
| bool use_fifos = false; |
| |
| if (!no_fifos) { |
| nr_fifos = open_guest_fifos(instance->name, &fds); |
| use_fifos = nr_fifos > 0; |
| } |
| |
| if (ctx->instance->result) { |
| role = TRACECMD_TIME_SYNC_ROLE_CLIENT; |
| sd = connect_addr(ctx->instance->result); |
| if (sd < 0) |
| die("Failed to connect to host %s:%u", |
| instance->name, instance->port); |
| } else { |
| role = TRACECMD_TIME_SYNC_ROLE_HOST; |
| sd = trace_vsock_open(instance->cid, instance->port); |
| if (sd < 0) |
| die("Failed to connect to vsocket @%u:%u", |
| instance->cid, instance->port); |
| } |
| |
| msg_handle = tracecmd_msg_handle_alloc(sd, 0); |
| if (!msg_handle) |
| die("Failed to allocate message handle"); |
| |
| if (!instance->clock) |
| instance->clock = tracefs_get_clock(NULL); |
| |
| if (instance->tsync_loop_interval >= 0) |
| tracecmd_tsync_proto_getall(&protos, instance->clock, role); |
| |
| ret = tracecmd_msg_send_trace_req(msg_handle, instance->argc, |
| instance->argv, use_fifos, |
| top_instance.trace_id, protos); |
| if (ret < 0) |
| die("Failed to send trace request"); |
| |
| if (protos) { |
| free(protos->names); |
| free(protos); |
| } |
| ret = tracecmd_msg_recv_trace_resp(msg_handle, &nr_cpus, &page_size, |
| &ports, &use_fifos, |
| &instance->trace_id, |
| &tsync_protos_reply, &tsync_port); |
| if (ret < 0) |
| die("Failed to receive trace response %d", ret); |
| if (tsync_protos_reply && tsync_protos_reply[0]) { |
| if (tsync_proto_is_supported(tsync_protos_reply)) { |
| printf("Negotiated %s time sync protocol with guest %s\n", |
| tsync_protos_reply, |
| instance->name); |
| instance->cpu_count = nr_cpus; |
| host_tsync(ctx, instance, tsync_port, tsync_protos_reply); |
| } else |
| warning("Failed to negotiate timestamps synchronization with the guest"); |
| } |
| free(tsync_protos_reply); |
| |
| if (use_fifos) { |
| if (nr_cpus != nr_fifos) { |
| warning("number of FIFOs (%d) for guest %s differs " |
| "from number of virtual CPUs (%d)", |
| nr_fifos, instance->name, nr_cpus); |
| nr_cpus = nr_cpus < nr_fifos ? nr_cpus : nr_fifos; |
| } |
| free(ports); |
| instance->fds = fds; |
| } else { |
| for (i = 0; i < nr_fifos; i++) |
| close(fds[i]); |
| free(fds); |
| instance->client_ports = ports; |
| } |
| |
| instance->use_fifos = use_fifos; |
| instance->cpu_count = nr_cpus; |
| |
| /* the msg_handle now points to the guest fd */ |
| instance->msg_handle = msg_handle; |
| } |
| |
| static void setup_guest(struct buffer_instance *instance) |
| { |
| struct tracecmd_msg_handle *msg_handle = instance->msg_handle; |
| const char *output_file = instance->output_file; |
| char *file; |
| int fd; |
| |
| /* Create a place to store the guest meta data */ |
| file = trace_get_guest_file(output_file, instance->name); |
| if (!file) |
| die("Failed to allocate memory"); |
| |
| free(instance->output_file); |
| instance->output_file = file; |
| |
| fd = open(file, O_CREAT|O_WRONLY|O_TRUNC, 0644); |
| if (fd < 0) |
| die("Failed to open %s", file); |
| |
| /* Start reading tracing metadata */ |
| if (tracecmd_msg_read_data(msg_handle, fd)) |
| die("Failed receiving metadata"); |
| close(fd); |
| } |
| |
| static void setup_agent(struct buffer_instance *instance, |
| struct common_record_context *ctx) |
| { |
| struct tracecmd_output *network_handle; |
| |
| network_handle = create_net_output(ctx, instance->msg_handle); |
| add_options(network_handle, ctx); |
| tracecmd_write_cmdlines(network_handle); |
| tracecmd_write_cpus(network_handle, instance->cpu_count); |
| tracecmd_write_buffer_info(network_handle); |
| tracecmd_write_options(network_handle); |
| tracecmd_write_meta_strings(network_handle); |
| tracecmd_msg_finish_sending_data(instance->msg_handle); |
| instance->network_handle = network_handle; |
| } |
| |
| void start_threads(enum trace_type type, struct common_record_context *ctx) |
| { |
| struct buffer_instance *instance; |
| int total_cpu_count = 0; |
| int i = 0; |
| int ret; |
| |
| for_all_instances(instance) { |
| /* Start the connection now to find out how many CPUs we need */ |
| if (is_guest(instance)) |
| connect_to_agent(ctx, instance); |
| total_cpu_count += instance->cpu_count; |
| } |
| |
| /* make a thread for every CPU we have */ |
| pids = calloc(total_cpu_count * (buffers + 1), sizeof(*pids)); |
| if (!pids) |
| die("Failed to allocate pids for %d cpus", total_cpu_count); |
| |
| for_all_instances(instance) { |
| int *brass = NULL; |
| int x, pid; |
| |
| if (is_agent(instance)) { |
| setup_agent(instance, ctx); |
| } else if (is_guest(instance)) { |
| setup_guest(instance); |
| } else if (host) { |
| instance->msg_handle = setup_connection(instance, ctx); |
| if (!instance->msg_handle) |
| die("Failed to make connection"); |
| } |
| |
| for (x = 0; x < instance->cpu_count; x++) { |
| if (type & TRACE_TYPE_STREAM) { |
| brass = pids[i].brass; |
| ret = pipe(brass); |
| if (ret < 0) |
| die("pipe"); |
| pids[i].stream = trace_stream_init(instance, x, |
| brass[0], |
| instance->cpu_count, |
| hooks, handle_init, |
| ctx->global); |
| if (!pids[i].stream) |
| die("Creating stream for %d", i); |
| } else |
| pids[i].brass[0] = -1; |
| pids[i].cpu = x; |
| pids[i].instance = instance; |
| /* Make sure all output is flushed before forking */ |
| fflush(stdout); |
| pid = pids[i++].pid = create_recorder(instance, x, type, brass); |
| if (brass) |
| close(brass[1]); |
| if (pid > 0) |
| add_filter_pid(instance, pid, 1); |
| } |
| } |
| recorder_threads = i; |
| } |
| |
| static void touch_file(const char *file) |
| { |
| int fd; |
| |
| fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (fd < 0) |
| die("could not create file %s\n", file); |
| close(fd); |
| } |
| |
| static void append_buffer(struct tracecmd_output *handle, |
| struct buffer_instance *instance, |
| char **temp_files) |
| { |
| int cpu_count = instance->cpu_count; |
| int i; |
| |
| /* |
| * Since we can record remote and virtual machines in the same file |
| * as the host, the buffers may no longer have matching number of |
| * CPU data as the host. For backward compatibility for older |
| * trace-cmd versions, which will blindly read the number of CPUs |
| * for each buffer instance as there are for the host, if there are |
| * fewer CPUs on the remote machine than on the host, an "empty" |
| * CPU is needed for each CPU that the host has that the remote does |
| * not. If there are more CPUs on the remote, older executables will |
| * simply ignore them (which is OK, we only need to guarantee that |
| * old executables don't crash). |
| */ |
| if (instance->cpu_count < local_cpu_count) |
| cpu_count = local_cpu_count; |
| |
| for (i = 0; i < cpu_count; i++) { |
| temp_files[i] = get_temp_file(instance, i); |
| if (i >= instance->cpu_count) |
| touch_file(temp_files[i]); |
| } |
| |
| tracecmd_append_buffer_cpu_data(handle, tracefs_instance_get_name(instance->tracefs), |
| cpu_count, temp_files); |
| |
| for (i = 0; i < instance->cpu_count; i++) { |
| if (i >= instance->cpu_count) |
| delete_temp_file(instance, i); |
| put_temp_file(temp_files[i]); |
| } |
| } |
| |
| static void |
| add_guest_info(struct tracecmd_output *handle, struct buffer_instance *instance) |
| { |
| struct trace_guest *guest; |
| const char *name; |
| char *buf, *p; |
| int size; |
| int pid; |
| int i; |
| |
| if (is_network(instance)) { |
| name = instance->name; |
| } else { |
| guest = trace_get_guest(instance->cid, NULL); |
| if (!guest) |
| return; |
| name = guest->name; |
| } |
| |
| size = strlen(name) + 1; |
| size += sizeof(long long); /* trace_id */ |
| size += sizeof(int); /* cpu count */ |
| size += instance->cpu_count * 2 * sizeof(int); /* cpu,pid pair */ |
| |
| buf = calloc(1, size); |
| if (!buf) |
| return; |
| p = buf; |
| strcpy(p, name); |
| p += strlen(name) + 1; |
| |
| memcpy(p, &instance->trace_id, sizeof(long long)); |
| p += sizeof(long long); |
| |
| memcpy(p, &instance->cpu_count, sizeof(int)); |
| p += sizeof(int); |
| for (i = 0; i < instance->cpu_count; i++) { |
| pid = -1; |
| if (!is_network(instance)) { |
| if (i < guest->cpu_max) |
| pid = guest->cpu_pid[i]; |
| } |
| memcpy(p, &i, sizeof(int)); |
| p += sizeof(int); |
| memcpy(p, &pid, sizeof(int)); |
| p += sizeof(int); |
| } |
| |
| tracecmd_add_option(handle, TRACECMD_OPTION_GUEST, size, buf); |
| free(buf); |
| } |
| |
| static void |
| add_pid_maps(struct tracecmd_output *handle, struct buffer_instance *instance) |
| { |
| struct pid_addr_maps *maps = instance->pid_maps; |
| struct trace_seq s; |
| int i; |
| |
| trace_seq_init(&s); |
| while (maps) { |
| if (!maps->nr_lib_maps) { |
| maps = maps->next; |
| continue; |
| } |
| trace_seq_reset(&s); |
| trace_seq_printf(&s, "%x %x %s\n", |
| maps->pid, maps->nr_lib_maps, maps->proc_name); |
| for (i = 0; i < maps->nr_lib_maps; i++) |
| trace_seq_printf(&s, "%llx %llx %s\n", |
| maps->lib_maps[i].start, |
| maps->lib_maps[i].end, |
| maps->lib_maps[i].lib_name); |
| trace_seq_terminate(&s); |
| tracecmd_add_option(handle, TRACECMD_OPTION_PROCMAPS, |
| s.len + 1, s.buffer); |
| maps = maps->next; |
| } |
| trace_seq_destroy(&s); |
| } |
| |
| static void |
| add_trace_id(struct tracecmd_output *handle, struct buffer_instance *instance) |
| { |
| tracecmd_add_option(handle, TRACECMD_OPTION_TRACEID, |
| sizeof(long long), &instance->trace_id); |
| } |
| |
| static void |
| add_buffer_stat(struct tracecmd_output *handle, struct buffer_instance *instance) |
| { |
| struct trace_seq s; |
| int i; |
| |
| trace_seq_init(&s); |
| trace_seq_printf(&s, "\nBuffer: %s\n\n", |
| tracefs_instance_get_name(instance->tracefs)); |
| tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT, |
| s.len+1, s.buffer); |
| trace_seq_destroy(&s); |
| |
| for (i = 0; i < instance->cpu_count; i++) |
| tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT, |
| instance->s_save[i].len+1, |
| instance->s_save[i].buffer); |
| } |
| |
| static void add_option_hooks(struct tracecmd_output *handle) |
| { |
| struct hook_list *hook; |
| int len; |
| |
| for (hook = hooks; hook; hook = hook->next) { |
| len = strlen(hook->hook); |
| tracecmd_add_option(handle, TRACECMD_OPTION_HOOK, |
| len + 1, hook->hook); |
| } |
| } |
| |
| static void add_uname(struct tracecmd_output *handle) |
| { |
| struct utsname buf; |
| char *str; |
| int len; |
| int ret; |
| |
| ret = uname(&buf); |
| /* if this fails for some reason, just ignore it */ |
| if (ret < 0) |
| return; |
| |
| len = strlen(buf.sysname) + strlen(buf.nodename) + |
| strlen(buf.release) + strlen(buf.machine) + 4; |
| str = malloc(len); |
| if (!str) |
| return; |
| sprintf(str, "%s %s %s %s", buf.sysname, buf.nodename, buf.release, buf.machine); |
| tracecmd_add_option(handle, TRACECMD_OPTION_UNAME, len, str); |
| free(str); |
| } |
| |
| static void add_version(struct tracecmd_output *handle) |
| { |
| char *str; |
| int len; |
| |
| len = asprintf(&str, "%s %s", VERSION_STRING, VERSION_GIT); |
| if (len < 0) |
| return; |
| |
| tracecmd_add_option(handle, TRACECMD_OPTION_VERSION, len+1, str); |
| free(str); |
| } |
| |
| static void print_stat(struct buffer_instance *instance) |
| { |
| int cpu; |
| |
| if (quiet) |
| return; |
| |
| if (!is_top_instance(instance)) |
| printf("\nBuffer: %s\n\n", |
| tracefs_instance_get_name(instance->tracefs)); |
| |
| for (cpu = 0; cpu < instance->cpu_count; cpu++) |
| trace_seq_do_printf(&instance->s_print[cpu]); |
| } |
| |
| static char *get_trace_clock(bool selected) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| continue; |
| break; |
| } |
| |
| if (selected) |
| return tracefs_get_clock(instance ? instance->tracefs : NULL); |
| else |
| return tracefs_instance_file_read(instance ? instance->tracefs : NULL, |
| "trace_clock", NULL); |
| } |
| |
| enum { |
| DATA_FL_NONE = 0, |
| DATA_FL_DATE = 1, |
| DATA_FL_OFFSET = 2, |
| DATA_FL_GUEST = 4, |
| }; |
| |
| static void add_options(struct tracecmd_output *handle, struct common_record_context *ctx) |
| { |
| int type = 0; |
| char *clocks; |
| |
| if (ctx->date2ts) { |
| if (ctx->data_flags & DATA_FL_DATE) |
| type = TRACECMD_OPTION_DATE; |
| else if (ctx->data_flags & DATA_FL_OFFSET) |
| type = TRACECMD_OPTION_OFFSET; |
| } |
| |
| if (type) |
| tracecmd_add_option(handle, type, strlen(ctx->date2ts)+1, ctx->date2ts); |
| |
| clocks = get_trace_clock(false); |
| tracecmd_add_option(handle, TRACECMD_OPTION_TRACECLOCK, |
| clocks ? strlen(clocks)+1 : 0, clocks); |
| add_option_hooks(handle); |
| add_uname(handle); |
| add_version(handle); |
| if (!no_top_instance()) |
| add_trace_id(handle, &top_instance); |
| free(clocks); |
| } |
| |
| static void write_guest_file(struct buffer_instance *instance) |
| { |
| struct tracecmd_output *handle; |
| int cpu_count = instance->cpu_count; |
| char *file; |
| char **temp_files; |
| int i, fd; |
| |
| file = instance->output_file; |
| fd = open(file, O_RDWR); |
| if (fd < 0) |
| die("error opening %s", file); |
| |
| handle = tracecmd_get_output_handle_fd(fd); |
| if (!handle) |
| die("error writing to %s", file); |
| if (instance->flags & BUFFER_FL_TSC2NSEC) |
| tracecmd_set_out_clock(handle, TSCNSEC_CLOCK); |
| temp_files = malloc(sizeof(*temp_files) * cpu_count); |
| if (!temp_files) |
| die("failed to allocate temp_files for %d cpus", |
| cpu_count); |
| |
| for (i = 0; i < cpu_count; i++) { |
| temp_files[i] = get_temp_file(instance, i); |
| if (!temp_files[i]) |
| die("failed to allocate memory"); |
| } |
| |
| if (tracecmd_write_cpu_data(handle, cpu_count, temp_files, NULL) < 0) |
| die("failed to write CPU data"); |
| tracecmd_output_close(handle); |
| |
| for (i = 0; i < cpu_count; i++) |
| put_temp_file(temp_files[i]); |
| free(temp_files); |
| } |
| |
| static struct tracecmd_output *create_output(struct common_record_context *ctx) |
| { |
| struct tracecmd_output *out; |
| |
| if (!ctx->output) |
| return NULL; |
| |
| out = tracecmd_output_create(ctx->output); |
| if (!out) |
| goto error; |
| if (ctx->file_version && tracecmd_output_set_version(out, ctx->file_version)) |
| goto error; |
| |
| if (ctx->compression) { |
| if (tracecmd_output_set_compression(out, ctx->compression)) |
| goto error; |
| } else if (ctx->file_version >= FILE_VERSION_COMPRESSION) { |
| tracecmd_output_set_compression(out, "any"); |
| } |
| |
| if (tracecmd_output_write_headers(out, listed_events)) |
| goto error; |
| |
| return out; |
| error: |
| if (out) |
| tracecmd_output_close(out); |
| unlink(ctx->output); |
| return NULL; |
| } |
| |
| static void record_data(struct common_record_context *ctx) |
| { |
| struct tracecmd_output *handle; |
| struct buffer_instance *instance; |
| bool local = false; |
| int max_cpu_count = local_cpu_count; |
| char **temp_files; |
| int i; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| write_guest_file(instance); |
| else if (host && instance->msg_handle) |
| finish_network(instance->msg_handle); |
| else |
| local = true; |
| } |
| |
| if (!local) |
| return; |
| |
| if (latency) { |
| handle = tracecmd_create_file_latency(ctx->output, local_cpu_count, |
| ctx->file_version, ctx->compression); |
| tracecmd_set_quiet(handle, quiet); |
| } else { |
| if (!local_cpu_count) |
| return; |
| |
| /* Allocate enough temp files to handle each instance */ |
| for_all_instances(instance) { |
| if (instance->msg_handle) |
| continue; |
| if (instance->cpu_count > max_cpu_count) |
| max_cpu_count = instance->cpu_count; |
| } |
| |
| temp_files = malloc(sizeof(*temp_files) * max_cpu_count); |
| if (!temp_files) |
| die("Failed to allocate temp_files for %d cpus", |
| local_cpu_count); |
| |
| for (i = 0; i < max_cpu_count; i++) |
| temp_files[i] = get_temp_file(&top_instance, i); |
| |
| /* |
| * If top_instance was not used, we still need to create |
| * empty trace.dat files for it. |
| */ |
| if (no_top_instance() || top_instance.msg_handle) { |
| for (i = 0; i < local_cpu_count; i++) |
| touch_file(temp_files[i]); |
| } |
| |
| handle = create_output(ctx); |
| if (!handle) |
| die("Error creating output file"); |
| tracecmd_set_quiet(handle, quiet); |
| |
| add_options(handle, ctx); |
| |
| /* Only record the top instance under TRACECMD_OPTION_CPUSTAT*/ |
| if (!no_top_instance() && !top_instance.msg_handle) { |
| struct trace_seq *s = top_instance.s_save; |
| |
| for (i = 0; i < local_cpu_count; i++) |
| tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT, |
| s[i].len+1, s[i].buffer); |
| } |
| |
| if (buffers) { |
| i = 0; |
| for_each_instance(instance) { |
| int cpus = instance->cpu_count != local_cpu_count ? |
| instance->cpu_count : 0; |
| |
| if (instance->msg_handle) |
| continue; |
| tracecmd_add_buffer_info(handle, |
| tracefs_instance_get_name(instance->tracefs), |
| cpus); |
| add_buffer_stat(handle, instance); |
| } |
| } |
| |
| if (!no_top_instance() && !top_instance.msg_handle) |
| print_stat(&top_instance); |
| |
| for_all_instances(instance) { |
| add_pid_maps(handle, instance); |
| } |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| add_guest_info(handle, instance); |
| } |
| |
| if (ctx->tsc2nsec.mult) { |
| add_tsc2nsec(handle, &ctx->tsc2nsec); |
| tracecmd_set_out_clock(handle, TSCNSEC_CLOCK); |
| } |
| if (tracecmd_write_cmdlines(handle)) |
| die("Writing cmdlines"); |
| |
| tracecmd_append_cpu_data(handle, local_cpu_count, temp_files); |
| |
| for (i = 0; i < max_cpu_count; i++) |
| put_temp_file(temp_files[i]); |
| |
| if (buffers) { |
| i = 0; |
| for_each_instance(instance) { |
| if (instance->msg_handle) |
| continue; |
| print_stat(instance); |
| append_buffer(handle, instance, temp_files); |
| } |
| } |
| |
| free(temp_files); |
| } |
| if (!handle) |
| die("could not write to file"); |
| tracecmd_output_close(handle); |
| } |
| |
| enum filter_type { |
| FUNC_FILTER, |
| FUNC_NOTRACE, |
| }; |
| |
| static int filter_command(struct tracefs_instance *instance, const char *cmd) |
| { |
| return tracefs_instance_file_append(instance, "set_ftrace_filter", cmd); |
| } |
| |
| static int write_func_filter(enum filter_type type, struct buffer_instance *instance, |
| struct func_list **list) |
| { |
| struct func_list *item, *cmds = NULL; |
| const char *file; |
| int ret = -1; |
| int (*filter_function)(struct tracefs_instance *instance, const char *filter, |
| const char *module, unsigned int flags); |
| |
| if (!*list) |
| return 0; |
| |
| switch (type) { |
| case FUNC_FILTER: |
| filter_function = tracefs_function_filter; |
| file = "set_ftrace_filter"; |
| break; |
| case FUNC_NOTRACE: |
| filter_function = tracefs_function_notrace; |
| file = "set_ftrace_notrace"; |
| break; |
| } |
| |
| ret = filter_function(instance->tracefs, NULL, NULL, |
| TRACEFS_FL_RESET | TRACEFS_FL_CONTINUE); |
| if (ret < 0) |
| return ret; |
| |
| while (*list) { |
| item = *list; |
| *list = item->next; |
| /* Do commands separately at the end */ |
| if (type == FUNC_FILTER && strstr(item->func, ":")) { |
| item->next = cmds; |
| cmds = item; |
| continue; |
| } |
| ret = filter_function(instance->tracefs, item->func, item->mod, |
| TRACEFS_FL_CONTINUE); |
| if (ret < 0) |
| goto failed; |
| free(item); |
| } |
| ret = filter_function(instance->tracefs, NULL, NULL, 0); |
| |
| /* Now add any commands */ |
| while (cmds) { |
| item = cmds; |
| cmds = item->next; |
| ret = filter_command(instance->tracefs, item->func); |
| if (ret < 0) |
| goto failed; |
| free(item); |
| } |
| return ret; |
| 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); |
| return ret; |
| } |
| |
| static int write_func_file(struct buffer_instance *instance, |
| const char *file, struct func_list **list) |
| { |
| struct func_list *item; |
| const char *prefix = ":mod:"; |
| char *path; |
| int fd; |
| int ret = -1; |
| |
| if (!*list) |
| return 0; |
| |
| path = tracefs_instance_get_file(instance->tracefs, 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; |
| if (item->mod) { |
| ret = write(fd, prefix, strlen(prefix)); |
| if (ret < 0) |
| goto failed; |
| ret = write(fd, item->mod, strlen(item->mod)); |
| if (ret < 0) |
| goto failed; |
| } |
| ret = write(fd, " ", 1); |
| if (ret < 0) |
| goto failed; |
| free(item); |
| } |
| close(fd); |
| ret = 0; |
| free: |
| tracefs_put_tracing_file(path); |
| return ret; |
| 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); |
| return ret; |
| } |
| |
| static int functions_filtered(struct buffer_instance *instance) |
| { |
| char buf[1] = { '#' }; |
| char *path; |
| int fd; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_filter"); |
| fd = open(path, O_RDONLY); |
| tracefs_put_tracing_file(path); |
| if (fd < 0) { |
| if (is_top_instance(instance)) |
| warning("Can not set set_ftrace_filter"); |
| else |
| warning("Can not set set_ftrace_filter for %s", |
| tracefs_instance_get_name(instance->tracefs)); |
| 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(struct buffer_instance *instance) |
| { |
| int set_notrace = 0; |
| int ret; |
| |
| if (is_guest(instance)) |
| return; |
| |
| ret = write_func_filter(FUNC_FILTER, instance, &instance->filter_funcs); |
| if (ret < 0) |
| die("set_ftrace_filter does not exist. Can not filter functions"); |
| |
| /* graph tracing currently only works for top instance */ |
| if (is_top_instance(instance)) { |
| ret = write_func_file(instance, "set_graph_function", &graph_funcs); |
| if (ret < 0) |
| die("set_graph_function does not exist."); |
| if (instance->plugin && strcmp(instance->plugin, "function_graph") == 0) { |
| ret = write_func_file(instance, "set_graph_notrace", |
| &instance->notrace_funcs); |
| if (!ret) |
| set_notrace = 1; |
| } |
| if (!set_notrace) { |
| ret = write_func_filter(FUNC_NOTRACE, instance, |
| &instance->notrace_funcs); |
| if (ret < 0) |
| die("set_ftrace_notrace does not exist. Can not filter functions"); |
| } |
| } else |
| write_func_filter(FUNC_NOTRACE, instance, &instance->notrace_funcs); |
| |
| /* make sure we are filtering functions */ |
| if (func_stack && is_top_instance(instance)) { |
| if (!functions_filtered(instance)) |
| die("Function stack trace set, but functions not filtered"); |
| save_option(instance, FUNC_STACK_TRACE); |
| } |
| clear_function_filters = 1; |
| } |
| |
| static void add_func(struct func_list **list, const char *mod, const char *func) |
| { |
| struct func_list *item; |
| |
| item = malloc(sizeof(*item)); |
| if (!item) |
| die("Failed to allocate function descriptor"); |
| item->func = func; |
| item->mod = mod; |
| item->next = *list; |
| *list = item; |
| } |
| |
| static int find_ts(struct tep_event *event, struct tep_record *record, |
| int cpu, void *context) |
| { |
| unsigned long long *ts = (unsigned long long *)context; |
| struct tep_format_field *field; |
| |
| if (!ts) |
| return -1; |
| |
| field = tep_find_field(event, "buf"); |
| if (field && strcmp(STAMP"\n", record->data + field->offset) == 0) { |
| *ts = record->ts; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static unsigned long long find_time_stamp(struct tep_handle *tep, |
| struct tracefs_instance *instance) |
| { |
| unsigned long long ts = 0; |
| |
| if (!tracefs_iterate_raw_events(tep, instance, NULL, 0, find_ts, &ts)) |
| return ts; |
| |
| return 0; |
| } |
| |
| |
| static char *read_top_file(char *file, int *psize) |
| { |
| return tracefs_instance_file_read(top_instance.tracefs, file, psize); |
| } |
| |
| static struct tep_handle *get_ftrace_tep(void) |
| { |
| const char *systems[] = {"ftrace", NULL}; |
| struct tep_handle *tep; |
| char *buf; |
| int size; |
| int ret; |
| |
| tep = tracefs_local_events_system(NULL, systems); |
| if (!tep) |
| return NULL; |
| tep_set_file_bigendian(tep, tracecmd_host_bigendian()); |
| buf = read_top_file("events/header_page", &size); |
| if (!buf) |
| goto error; |
| ret = tep_parse_header_page(tep, buf, size, sizeof(unsigned long)); |
| free(buf); |
| if (ret < 0) |
| goto error; |
| |
| return tep; |
| |
| error: |
| tep_free(tep); |
| return NULL; |
| } |
| |
| /* |
| * 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) |
| { |
| struct tep_handle *tep; |
| 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 timespec start; |
| struct timespec end; |
| char *date2ts = NULL; |
| int tfd; |
| int i; |
| |
| /* Set up a tep to read the raw format */ |
| tep = get_ftrace_tep(); |
| if (!tep) { |
| warning("failed to alloc tep, --date ignored"); |
| return NULL; |
| } |
| tfd = tracefs_instance_file_open(NULL, "trace_marker", O_WRONLY); |
| if (tfd < 0) { |
| warning("Can not open 'trace_marker', --date ignored"); |
| goto out_pevent; |
| } |
| |
| for (i = 0; i < date2ts_tries; i++) { |
| tracecmd_disable_tracing(); |
| clear_trace_instances(); |
| tracecmd_enable_tracing(); |
| |
| clock_gettime(CLOCK_REALTIME, &start); |
| write(tfd, STAMP, 5); |
| clock_gettime(CLOCK_REALTIME, &end); |
| |
| tracecmd_disable_tracing(); |
| ts = find_time_stamp(tep, NULL); |
| if (!ts) |
| continue; |
| |
| diff = (unsigned long long)end.tv_sec * 1000000000LL; |
| diff += (unsigned long long)end.tv_nsec; |
| stamp = diff; |
| diff -= (unsigned long long)start.tv_sec * 1000000000LL; |
| diff -= (unsigned long long)start.tv_nsec; |
| |
| 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. |
| */ |
| diff = min_stamp - min_ts; |
| snprintf(date2ts, 19, "0x%llx", diff/1000); |
| out_pevent: |
| tep_free(tep); |
| |
| return date2ts; |
| } |
| |
| static void set_buffer_size_instance(struct buffer_instance *instance) |
| { |
| int buffer_size = instance->buffer_size; |
| char buf[BUFSIZ]; |
| char *path; |
| int ret; |
| int fd; |
| |
| if (is_guest(instance)) |
| return; |
| |
| if (!buffer_size) |
| return; |
| |
| if (buffer_size < 0) |
| die("buffer size must be positive"); |
| |
| snprintf(buf, BUFSIZ, "%d", buffer_size); |
| |
| path = tracefs_instance_get_file(instance->tracefs, "buffer_size_kb"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) { |
| warning("can't open %s", path); |
| goto out; |
| } |
| |
| ret = write(fd, buf, strlen(buf)); |
| if (ret < 0) |
| warning("Can't write to %s", path); |
| close(fd); |
| out: |
| tracefs_put_tracing_file(path); |
| } |
| |
| void set_buffer_size(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| set_buffer_size_instance(instance); |
| } |
| |
| static int |
| process_event_trigger(char *path, struct event_iter *iter) |
| { |
| const char *system = iter->system_dent->d_name; |
| const char *event = iter->event_dent->d_name; |
| struct stat st; |
| char *trigger = NULL; |
| char *file; |
| int ret; |
| |
| path = append_file(path, system); |
| file = append_file(path, event); |
| free(path); |
| |
| ret = stat(file, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| goto out; |
| |
| trigger = append_file(file, "trigger"); |
| |
| ret = stat(trigger, &st); |
| if (ret < 0) |
| goto out; |
| |
| ret = clear_trigger(trigger); |
| out: |
| free(trigger); |
| free(file); |
| return ret; |
| } |
| |
| static void clear_instance_triggers(struct buffer_instance *instance) |
| { |
| enum event_iter_type type; |
| struct event_iter *iter; |
| char *system; |
| char *path; |
| int retry = 0; |
| int ret; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "events"); |
| if (!path) |
| die("malloc"); |
| |
| iter = trace_event_iter_alloc(path); |
| |
| system = NULL; |
| while ((type = trace_event_iter_next(iter, path, system))) { |
| |
| if (type == EVENT_ITER_SYSTEM) { |
| system = iter->system_dent->d_name; |
| continue; |
| } |
| |
| ret = process_event_trigger(path, iter); |
| if (ret > 0) |
| retry++; |
| } |
| |
| trace_event_iter_free(iter); |
| |
| if (retry) { |
| int i; |
| |
| /* Order matters for some triggers */ |
| for (i = 0; i < retry; i++) { |
| int tries = 0; |
| |
| iter = trace_event_iter_alloc(path); |
| system = NULL; |
| while ((type = trace_event_iter_next(iter, path, system))) { |
| |
| if (type == EVENT_ITER_SYSTEM) { |
| system = iter->system_dent->d_name; |
| continue; |
| } |
| |
| ret = process_event_trigger(path, iter); |
| if (ret > 0) |
| tries++; |
| } |
| trace_event_iter_free(iter); |
| if (!tries) |
| break; |
| } |
| } |
| |
| tracefs_put_tracing_file(path); |
| } |
| |
| static void |
| process_event_filter(char *path, struct event_iter *iter, enum event_process *processed) |
| { |
| const char *system = iter->system_dent->d_name; |
| const char *event = iter->event_dent->d_name; |
| struct stat st; |
| char *filter = NULL; |
| char *file; |
| int ret; |
| |
| path = append_file(path, system); |
| file = append_file(path, event); |
| free(path); |
| |
| ret = stat(file, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| goto out; |
| |
| filter = append_file(file, "filter"); |
| |
| ret = stat(filter, &st); |
| if (ret < 0) |
| goto out; |
| |
| clear_filter(filter); |
| out: |
| free(filter); |
| free(file); |
| } |
| |
| static void clear_instance_filters(struct buffer_instance *instance) |
| { |
| struct event_iter *iter; |
| char *path; |
| char *system; |
| enum event_iter_type type; |
| enum event_process processed = PROCESSED_NONE; |
| |
| path = tracefs_instance_get_file(instance->tracefs, "events"); |
| if (!path) |
| die("malloc"); |
| |
| iter = trace_event_iter_alloc(path); |
| |
| processed = PROCESSED_NONE; |
| system = NULL; |
| while ((type = trace_event_iter_next(iter, path, system))) { |
| |
| if (type == EVENT_ITER_SYSTEM) { |
| system = iter->system_dent->d_name; |
| continue; |
| } |
| |
| process_event_filter(path, iter, &processed); |
| } |
| |
| trace_event_iter_free(iter); |
| |
| tracefs_put_tracing_file(path); |
| } |
| |
| static void clear_filters(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| clear_instance_filters(instance); |
| } |
| |
| static void reset_clock(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| tracefs_instance_file_write(instance->tracefs, |
| "trace_clock", "local"); |
| } |
| |
| static void reset_cpu_mask(void) |
| { |
| struct buffer_instance *instance; |
| int cpus = tracecmd_count_cpus(); |
| int fullwords = (cpus - 1) / 32; |
| int bits = (cpus - 1) % 32 + 1; |
| int len = (fullwords + 1) * 9; |
| char buf[len + 1]; |
| |
| buf[0] = '\0'; |
| |
| sprintf(buf, "%x", (unsigned int)((1ULL << bits) - 1)); |
| while (fullwords-- > 0) |
| strcat(buf, ",ffffffff"); |
| |
| for_all_instances(instance) |
| tracefs_instance_file_write(instance->tracefs, |
| "tracing_cpumask", buf); |
| } |
| |
| static void reset_event_pid(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| add_event_pid(instance, ""); |
| } |
| |
| static void clear_triggers(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| clear_instance_triggers(instance); |
| } |
| |
| static void clear_instance_error_log(struct buffer_instance *instance) |
| { |
| char *file; |
| |
| if (!tracefs_file_exists(instance->tracefs, "error_log")) |
| return; |
| |
| file = tracefs_instance_get_file(instance->tracefs, "error_log"); |
| if (!file) |
| return; |
| write_file(file, " "); |
| tracefs_put_tracing_file(file); |
| } |
| |
| static void clear_error_log(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| clear_instance_error_log(instance); |
| } |
| |
| static void clear_all_dynamic_events(void) |
| { |
| /* Clear event probes first, as they may be attached to other dynamic event */ |
| tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_EPROBE, true); |
| tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_ALL, true); |
| } |
| |
| static void clear_func_filters(void) |
| { |
| struct buffer_instance *instance; |
| char *path; |
| int i; |
| const char * const files[] = { "set_ftrace_filter", |
| "set_ftrace_notrace", |
| "set_graph_function", |
| "set_graph_notrace", |
| NULL }; |
| |
| for_all_instances(instance) { |
| for (i = 0; files[i]; i++) { |
| path = tracefs_instance_get_file(instance->tracefs, files[i]); |
| clear_func_filter(path); |
| tracefs_put_tracing_file(path); |
| } |
| } |
| } |
| |
| static void make_instances(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_each_instance(instance) { |
| if (is_guest(instance)) |
| continue; |
| if (instance->name && !instance->tracefs) { |
| instance->tracefs = tracefs_instance_create(instance->name); |
| /* Don't delete instances that already exist */ |
| if (instance->tracefs && !tracefs_instance_is_new(instance->tracefs)) |
| instance->flags |= BUFFER_FL_KEEP; |
| } |
| } |
| } |
| |
| void tracecmd_remove_instances(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_each_instance(instance) { |
| /* Only delete what we created */ |
| if (is_guest(instance) || (instance->flags & BUFFER_FL_KEEP)) |
| continue; |
| if (instance->tracing_on_fd > 0) { |
| close(instance->tracing_on_fd); |
| instance->tracing_on_fd = 0; |
| } |
| tracefs_instance_destroy(instance->tracefs); |
| } |
| } |
| |
| 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_top_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: |
| if (!quiet) |
| fprintf(stderr, " plugin '%s'\n", plugin); |
| free(buf); |
| } |
| |
| static void check_function_plugin(void) |
| { |
| const char *plugin; |
| |
| /* We only care about the top_instance */ |
| if (no_top_instance()) |
| return; |
| |
| plugin = top_instance.plugin; |
| if (!plugin) |
| return; |
| |
| if (plugin && strncmp(plugin, "function", 8) == 0 && |
| func_stack && !top_instance.filter_funcs) |
| die("Must supply function filtering with --func-stack\n"); |
| } |
| |
| static int __check_doing_something(struct buffer_instance *instance) |
| { |
| return is_guest(instance) || (instance->flags & BUFFER_FL_PROFILE) || |
| instance->plugin || instance->events || instance->get_procmap; |
| } |
| |
| static void check_doing_something(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) { |
| if (__check_doing_something(instance)) |
| return; |
| } |
| |
| die("no event or plugin was specified... aborting"); |
| } |
| |
| static void |
| update_plugin_instance(struct buffer_instance *instance, |
| enum trace_type type) |
| { |
| const char *plugin = instance->plugin; |
| |
| if (is_guest(instance)) |
| return; |
| |
| if (!plugin) |
| return; |
| |
| 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 (type & TRACE_TYPE_STREAM) |
| die("Streaming is not available with latency tracer plugins"); |
| } else if (type == TRACE_TYPE_RECORD) { |
| if (latency) |
| die("Can not record latency tracer and non latency trace together"); |
| } |
| |
| if (fset < 0 && (strcmp(plugin, "function") == 0 || |
| strcmp(plugin, "function_graph") == 0)) |
| die("function tracing not configured on this kernel"); |
| |
| if (type != TRACE_TYPE_EXTRACT) |
| set_plugin_instance(instance, plugin); |
| } |
| |
| static void update_plugins(enum trace_type type) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| update_plugin_instance(instance, type); |
| } |
| |
| static void allocate_seq(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) { |
| instance->s_save = malloc(sizeof(struct trace_seq) * instance->cpu_count); |
| instance->s_print = malloc(sizeof(struct trace_seq) * instance->cpu_count); |
| if (!instance->s_save || !instance->s_print) |
| die("Failed to allocate instance info"); |
| } |
| } |
| |
| /* Find the overrun output, and add it to the print seq */ |
| static void add_overrun(int cpu, struct trace_seq *src, struct trace_seq *dst) |
| { |
| const char overrun_str[] = "overrun: "; |
| const char commit_overrun_str[] = "commit overrun: "; |
| const char *p; |
| int overrun; |
| int commit_overrun; |
| |
| p = strstr(src->buffer, overrun_str); |
| if (!p) { |
| /* Warn? */ |
| trace_seq_printf(dst, "CPU %d: no overrun found?\n", cpu); |
| return; |
| } |
| |
| overrun = atoi(p + strlen(overrun_str)); |
| |
| p = strstr(p + 9, commit_overrun_str); |
| if (p) |
| commit_overrun = atoi(p + strlen(commit_overrun_str)); |
| else |
| commit_overrun = -1; |
| |
| if (!overrun && !commit_overrun) |
| return; |
| |
| trace_seq_printf(dst, "CPU %d:", cpu); |
| |
| if (overrun) |
| trace_seq_printf(dst, " %d events lost", overrun); |
| |
| if (commit_overrun) |
| trace_seq_printf(dst, " %d events lost due to commit overrun", |
| commit_overrun); |
| |
| trace_seq_putc(dst, '\n'); |
| } |
| |
| static void record_stats(void) |
| { |
| struct buffer_instance *instance; |
| struct trace_seq *s_save; |
| struct trace_seq *s_print; |
| int cpu; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| continue; |
| |
| s_save = instance->s_save; |
| s_print = instance->s_print; |
| for (cpu = 0; cpu < instance->cpu_count; cpu++) { |
| trace_seq_init(&s_save[cpu]); |
| trace_seq_init(&s_print[cpu]); |
| trace_seq_printf(&s_save[cpu], "CPU: %d\n", cpu); |
| tracecmd_stat_cpu_instance(instance, &s_save[cpu], cpu); |
| add_overrun(cpu, &s_save[cpu], &s_print[cpu]); |
| } |
| } |
| } |
| |
| static void print_stats(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) |
| print_stat(instance); |
| } |
| |
| static void destroy_stats(void) |
| { |
| struct buffer_instance *instance; |
| int cpu; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| continue; |
| |
| for (cpu = 0; cpu < instance->cpu_count; cpu++) { |
| trace_seq_destroy(&instance->s_save[cpu]); |
| trace_seq_destroy(&instance->s_print[cpu]); |
| } |
| } |
| } |
| |
| static void list_event(const char *event) |
| { |
| struct tracecmd_event_list *list; |
| |
| list = malloc(sizeof(*list)); |
| if (!list) |
| die("Failed to allocate list for event"); |
| list->next = listed_events; |
| list->glob = event; |
| listed_events = list; |
| } |
| |
| #define ALL_EVENTS "*/*" |
| |
| 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(sizeof(*list)); |
| if (!list) |
| die("Failed to allocate list for all events"); |
| list->next = NULL; |
| list->glob = ALL_EVENTS; |
| listed_events = list; |
| } |
| |
| static int recording_all_events(void) |
| { |
| return listed_events && strcmp(listed_events->glob, ALL_EVENTS) == 0; |
| } |
| |
| static void add_trigger(struct event_list *event, const char *trigger) |
| { |
| int ret; |
| |
| if (event->trigger) { |
| event->trigger = realloc(event->trigger, |
| strlen(event->trigger) + strlen("\n") + |
| strlen(trigger) + 1); |
| strcat(event->trigger, "\n"); |
| strcat(event->trigger, trigger); |
| } else { |
| ret = asprintf(&event->trigger, "%s", trigger); |
| if (ret < 0) |
| die("Failed to allocate event trigger"); |
| } |
| } |
| |
| static int test_stacktrace_trigger(struct buffer_instance *instance) |
| { |
| char *path; |
| int ret = 0; |
| int fd; |
| |
| path = tracefs_instance_get_file(instance->tracefs, |
| "events/sched/sched_switch/trigger"); |
| |
| clear_trigger(path); |
| |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| goto out; |
| |
| ret = write(fd, "stacktrace", 10); |
| if (ret != 10) |
| ret = 0; |
| else |
| ret = 1; |
| close(fd); |
| out: |
| tracefs_put_tracing_file(path); |
| |
| return ret; |
| } |
| |
| static int |
| profile_add_event(struct buffer_instance *instance, const char *event_str, int stack) |
| { |
| struct event_list *event; |
| char buf[BUFSIZ]; |
| char *p; |
| |
| strcpy(buf, "events/"); |
| strncpy(buf + 7, event_str, BUFSIZ - 7); |
| buf[BUFSIZ-1] = 0; |
| |
| if ((p = strstr(buf, ":"))) { |
| *p = '/'; |
| p++; |
| } |
| |
| if (!trace_check_file_exists(instance, buf)) |
| return -1; |
| |
| /* Only add event if it isn't already added */ |
| for (event = instance->events; event; event = event->next) { |
| if (p && strcmp(event->event, p) == 0) |
| break; |
| if (strcmp(event->event, event_str) == 0) |
| break; |
| } |
| |
| if (!event) { |
| event = malloc(sizeof(*event)); |
| if (!event) |
| die("Failed to allocate event"); |
| memset(event, 0, sizeof(*event)); |
| event->event = event_str; |
| add_event(instance, event); |
| } |
| |
| if (!recording_all_events()) |
| list_event(event_str); |
| |
| if (stack) { |
| if (!event->trigger || !strstr(event->trigger, "stacktrace")) |
| add_trigger(event, "stacktrace"); |
| } |
| |
| return 0; |
| } |
| |
| int tracecmd_add_event(const char *event_str, int stack) |
| { |
| return profile_add_event(first_instance, event_str, stack); |
| } |
| |
| static void enable_profile(struct buffer_instance *instance) |
| { |
| int stacktrace = 0; |
| int i; |
| char *trigger_events[] = { |
| "sched:sched_switch", |
| "sched:sched_wakeup", |
| NULL, |
| }; |
| char *events[] = { |
| "exceptions:page_fault_user", |
| "irq:irq_handler_entry", |
| "irq:irq_handler_exit", |
| "irq:softirq_entry", |
| "irq:softirq_exit", |
| "irq:softirq_raise", |
| "sched:sched_process_exec", |
| "raw_syscalls", |
| NULL, |
| }; |
| |
| if (!instance->plugin) { |
| if (trace_check_file_exists(instance, "max_graph_depth")) { |
| instance->plugin = "function_graph"; |
| set_max_graph_depth(instance, "1"); |
| } else |
| warning("Kernel does not support max_graph_depth\n" |
| " Skipping user/kernel profiling"); |
| } |
| |
| if (test_stacktrace_trigger(instance)) |
| stacktrace = 1; |
| else |
| /* |
| * The stacktrace trigger is not implemented with this |
| * kernel, then we need to default to the stack trace option. |
| * This is less efficient but still works. |
| */ |
| save_option(instance, "stacktrace"); |
| |
| |
| for (i = 0; trigger_events[i]; i++) |
| profile_add_event(instance, trigger_events[i], stacktrace); |
| |
| for (i = 0; events[i]; i++) |
| profile_add_event(instance, events[i], 0); |
| } |
| |
| static struct event_list * |
| create_hook_event(struct buffer_instance *instance, |
| const char *system, const char *event) |
| { |
| struct event_list *event_list; |
| char *event_name; |
| int len; |
| |
| if (!system) |
| system = "*"; |
| |
| len = strlen(event); |
| len += strlen(system) + 2; |
| |
| event_name = malloc(len); |
| if (!event_name) |
| die("Failed to allocate %s/%s", system, event); |
| sprintf(event_name, "%s:%s", system, event); |
| |
| event_list = malloc(sizeof(*event_list)); |
| if (!event_list) |
| die("Failed to allocate event list for %s", event_name); |
| memset(event_list, 0, sizeof(*event_list)); |
| event_list->event = event_name; |
| add_event(instance, event_list); |
| |
| list_event(event_name); |
| |
| return event_list; |
| } |
| |
| static void add_hook(struct buffer_instance *instance, const char *arg) |
| { |
| struct event_list *event; |
| struct hook_list *hook; |
| |
| hook = tracecmd_create_event_hook(arg); |
| if (!hook) |
| die("Failed to create event hook %s", arg); |
| |
| hook->instance = instance; |
| hook->next = hooks; |
| hooks = hook; |
| |
| /* Make sure the event is enabled */ |
| event = create_hook_event(instance, hook->start_system, hook->start_event); |
| create_hook_event(instance, hook->end_system, hook->end_event); |
| |
| if (hook->stack) { |
| if (!event->trigger || !strstr(event->trigger, "stacktrace")) |
| add_trigger(event, "stacktrace"); |
| } |
| } |
| |
| void update_first_instance(struct buffer_instance *instance, int topt) |
| { |
| if (topt || instance == &top_instance) |
| first_instance = &top_instance; |
| else |
| first_instance = buffer_instances; |
| } |
| |
| void init_top_instance(void) |
| { |
| if (!top_instance.tracefs) |
| top_instance.tracefs = tracefs_instance_create(NULL); |
| top_instance.cpu_count = tracecmd_count_cpus(); |
| top_instance.flags = BUFFER_FL_KEEP; |
| top_instance.trace_id = tracecmd_generate_traceid(); |
| init_instance(&top_instance); |
| } |
| |
| enum { |
| OPT_compression = 237, |
| OPT_file_ver = 238, |
| OPT_verbose = 239, |
| OPT_tsc2nsec = 240, |
| OPT_fork = 241, |
| OPT_tsyncinterval = 242, |
| OPT_user = 243, |
| OPT_procmap = 244, |
| OPT_quiet = 245, |
| OPT_debug = 246, |
| OPT_no_filter = 247, |
| OPT_max_graph_depth = 248, |
| OPT_tsoffset = 249, |
| OPT_bycomm = 250, |
| OPT_stderr = 251, |
| OPT_profile = 252, |
| OPT_nosplice = 253, |
| OPT_funcstack = 254, |
| OPT_date = 255, |
| OPT_module = 256, |
| OPT_nofifos = 257, |
| OPT_cmdlines_size = 258, |
| OPT_poll = 259, |
| OPT_name = 260, |
| }; |
| |
| void trace_stop(int argc, char **argv) |
| { |
| int topt = 0; |
| struct buffer_instance *instance = &top_instance; |
| |
| init_top_instance(); |
| |
| for (;;) { |
| int c; |
| |
| c = getopt(argc-1, argv+1, "hatB:"); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'B': |
| instance = allocate_instance(optarg); |
| if (!instance) |
| die("Failed to create instance"); |
| add_instance(instance, local_cpu_count); |
| break; |
| case 'a': |
| add_all_instances(); |
| break; |
| case 't': |
| /* Force to use top instance */ |
| topt = 1; |
| instance = &top_instance; |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| update_first_instance(instance, topt); |
| tracecmd_disable_tracing(); |
| exit(0); |
| } |
| |
| void trace_restart(int argc, char **argv) |
| { |
| int topt = 0; |
| struct buffer_instance *instance = &top_instance; |
| |
| init_top_instance(); |
| |
| for (;;) { |
| int c; |
| |
| c = getopt(argc-1, argv+1, "hatB:"); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'B': |
| instance = allocate_instance(optarg); |
| if (!instance) |
| die("Failed to create instance"); |
| add_instance(instance, local_cpu_count); |
| break; |
| case 'a': |
| add_all_instances(); |
| break; |
| case 't': |
| /* Force to use top instance */ |
| topt = 1; |
| instance = &top_instance; |
| break; |
| default: |
| usage(argv); |
| } |
| |
| } |
| update_first_instance(instance, topt); |
| tracecmd_enable_tracing(); |
| exit(0); |
| } |
| |
| void trace_reset(int argc, char **argv) |
| { |
| int c; |
| int topt = 0; |
| struct buffer_instance *instance = &top_instance; |
| |
| init_top_instance(); |
| |
| /* if last arg is -a, then -b and -d apply to all instances */ |
| int last_specified_all = 0; |
| struct buffer_instance *inst; /* iterator */ |
| |
| while ((c = getopt(argc-1, argv+1, "hab:B:td")) >= 0) { |
| |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'b': |
| { |
| int size = atoi(optarg); |
| /* Min buffer size is 1 */ |
| if (size <= 1) |
| size = 1; |
| if (last_specified_all) { |
| for_each_instance(inst) { |
| inst->buffer_size = size; |
| } |
| } else { |
| instance->buffer_size = size; |
| } |
| break; |
| } |
| case 'B': |
| last_specified_all = 0; |
| instance = allocate_instance(optarg); |
| if (!instance) |
| die("Failed to create instance"); |
| add_instance(instance, local_cpu_count); |
| /* -d will remove keep */ |
| instance->flags |= BUFFER_FL_KEEP; |
| break; |
| case 't': |
| /* Force to use top instance */ |
| last_specified_all = 0; |
| topt = 1; |
| instance = &top_instance; |
| break; |
| case 'a': |
| last_specified_all = 1; |
| add_all_instances(); |
| for_each_instance(inst) { |
| inst->flags |= BUFFER_FL_KEEP; |
| } |
| break; |
| case 'd': |
| if (last_specified_all) { |
| for_each_instance(inst) { |
| inst->flags &= ~BUFFER_FL_KEEP; |
| } |
| } else { |
| if (is_top_instance(instance)) |
| die("Can not delete top level buffer"); |
| instance->flags &= ~BUFFER_FL_KEEP; |
| } |
| break; |
| } |
| } |
| update_first_instance(instance, topt); |
| tracecmd_disable_all_tracing(1); |
| set_buffer_size(); |
| clear_filters(); |
| clear_triggers(); |
| clear_all_dynamic_events(); |
| clear_error_log(); |
| /* set clock to "local" */ |
| reset_clock(); |
| reset_event_pid(); |
| reset_max_latency_instance(); |
| reset_cpu_mask(); |
| tracecmd_remove_instances(); |
| clear_func_filters(); |
| /* restore tracing_on to 1 */ |
| tracecmd_enable_tracing(); |
| exit(0); |
| } |
| |
| static void init_common_record_context(struct common_record_context *ctx, |
| enum trace_cmd curr_cmd) |
| { |
| memset(ctx, 0, sizeof(*ctx)); |
| ctx->instance = &top_instance; |
| ctx->curr_cmd = curr_cmd; |
| local_cpu_count = tracecmd_count_cpus(); |
| ctx->file_version = tracecmd_default_file_version(); |
| init_top_instance(); |
| } |
| |
| #define IS_EXTRACT(ctx) ((ctx)->curr_cmd == CMD_extract) |
| #define IS_START(ctx) ((ctx)->curr_cmd == CMD_start) |
| #define IS_CMDSET(ctx) ((ctx)->curr_cmd == CMD_set) |
| #define IS_STREAM(ctx) ((ctx)->curr_cmd == CMD_stream) |
| #define IS_PROFILE(ctx) ((ctx)->curr_cmd == CMD_profile) |
| #define IS_RECORD(ctx) ((ctx)->curr_cmd == CMD_record) |
| #define IS_RECORD_AGENT(ctx) ((ctx)->curr_cmd == CMD_record_agent) |
| |
| static void add_argv(struct buffer_instance *instance, char *arg, bool prepend) |
| { |
| instance->argv = realloc(instance->argv, |
| (instance->argc + 1) * sizeof(char *)); |
| if (!instance->argv) |
| die("Can not allocate instance args"); |
| if (prepend) { |
| memmove(instance->argv + 1, instance->argv, |
| instance->argc * sizeof(*instance->argv)); |
| instance->argv[0] = arg; |
| } else { |
| instance->argv[instance->argc] = arg; |
| } |
| instance->argc++; |
| } |
| |
| static void add_arg(struct buffer_instance *instance, |
| int c, const char *opts, |
| struct option *long_options, char *optarg) |
| { |
| char *ptr, *arg; |
| int i, ret; |
| |
| /* Short or long arg */ |
| if (!(c & 0x80)) { |
| ptr = strchr(opts, c); |
| if (!ptr) |
| return; /* Not found? */ |
| ret = asprintf(&arg, "-%c", c); |
| if (ret < 0) |
| die("Can not allocate argument"); |
| add_argv(instance, arg, false); |
| if (ptr[1] == ':') { |
| arg = strdup(optarg); |
| if (!arg) |
| die("Can not allocate arguments"); |
| add_argv(instance, arg, false); |
| } |
| return; |
| } |
| for (i = 0; long_options[i].name; i++) { |
| if (c != long_options[i].val) |
| continue; |
| ret = asprintf(&arg, "--%s", long_options[i].name); |
| if (ret < 0) |
| die("Can not allocate argument"); |
| add_argv(instance, arg, false); |
| if (long_options[i].has_arg) { |
| arg = strdup(optarg); |
| if (!arg) |
| die("Can not allocate arguments"); |
| add_argv(instance, arg, false); |
| } |
| return; |
| } |
| /* Not found? */ |
| } |
| |
| static inline void cmd_check_die(struct common_record_context *ctx, |
| enum trace_cmd id, char *cmd, char *param) |
| { |
| if (ctx->curr_cmd == id) |
| die("%s has no effect with the command %s\n" |
| "Did you mean 'record'?", param, cmd); |
| } |
| |
| static inline void remove_instances(struct buffer_instance *instances) |
| { |
| struct buffer_instance *del; |
| |
| while (instances) { |
| del = instances; |
| instances = instances->next; |
| free(del->name); |
| tracefs_instance_destroy(del->tracefs); |
| tracefs_instance_free(del->tracefs); |
| free(del); |
| } |
| } |
| |
| static inline void |
| check_instance_die(struct buffer_instance *instance, char *param) |
| { |
| if (instance->delete) |
| die("Instance %s is marked for deletion, invalid option %s", |
| tracefs_instance_get_name(instance->tracefs), param); |
| } |
| |
| static bool clock_is_supported(struct tracefs_instance *instance, const char *clock) |
| { |
| char *all_clocks = NULL; |
| char *ret = NULL; |
| |
| all_clocks = tracefs_instance_file_read(instance, "trace_clock", NULL); |
| if (!all_clocks) |
| return false; |
| |
| ret = strstr(all_clocks, clock); |
| if (ret && (ret == all_clocks || ret[-1] == ' ' || ret[-1] == '[')) { |
| switch (ret[strlen(clock)]) { |
| case ' ': |
| case '\0': |
| case ']': |
| case '\n': |
| break; |
| default: |
| ret = NULL; |
| } |
| } else { |
| ret = NULL; |
| } |
| free(all_clocks); |
| |
| return ret != NULL; |
| } |
| |
| #ifdef PERF |
| static int get_tsc_nsec(int *shift, int *mult) |
| { |
| static int cpu_shift, cpu_mult; |
| static int supported; |
| int cpus = tracecmd_count_cpus(); |
| struct trace_perf perf; |
| int i; |
| |
| if (supported) |
| goto out; |
| |
| supported = -1; |
| if (trace_perf_init(&perf, 1, 0, getpid())) |
| return -1; |
| if (trace_perf_open(&perf)) |
| return -1; |
| cpu_shift = perf.mmap->time_shift; |
| cpu_mult = perf.mmap->time_mult; |
| for (i = 1; i < cpus; i++) { |
| trace_perf_close(&perf); |
| if (trace_perf_init(&perf, 1, i, getpid())) |
| break; |
| if (trace_perf_open(&perf)) |
| break; |
| if (perf.mmap->time_shift != cpu_shift || |
| perf.mmap->time_mult != cpu_mult) { |
| warning("Found different TSC multiplier and shift for CPU %d: %d;%d instead of %d;%d", |
| i, perf.mmap->time_mult, perf.mmap->time_shift, cpu_mult, cpu_shift); |
| break; |
| } |
| } |
| trace_perf_close(&perf); |
| if (i < cpus) |
| return -1; |
| |
| if (cpu_shift || cpu_mult) |
| supported = 1; |
| out: |
| if (supported < 0) |
| return -1; |
| |
| if (shift) |
| *shift = cpu_shift; |
| if (mult) |
| *mult = cpu_mult; |
| |
| return 0; |
| } |
| #else |
| static int get_tsc_nsec(int *shift, int *mult) |
| { |
| return -1; |
| } |
| #endif |
| |
| bool trace_tsc2nsec_is_supported(void) |
| { |
| return get_tsc_nsec(NULL, NULL) == 0; |
| } |
| |
| static void parse_record_options(int argc, |
| char **argv, |
| enum trace_cmd curr_cmd, |
| struct common_record_context *ctx) |
| { |
| const char *plugin = NULL; |
| const char *option; |
| struct event_list *event = NULL; |
| struct event_list *last_event = NULL; |
| struct addrinfo *result; |
| char *pids; |
| char *pid; |
| char *sav; |
| int name_counter = 0; |
| int negative = 0; |
| struct buffer_instance *instance, *del_list = NULL; |
| int do_children = 0; |
| int fpids_count = 0; |
| |
| init_common_record_context(ctx, curr_cmd); |
| |
| if (IS_CMDSET(ctx)) |
| keep = 1; |
| |
| for (;;) { |
| int option_index = 0; |
| int ret; |
| int c; |
| const char *opts; |
| static struct option long_options[] = { |
| {"date", no_argument, NULL, OPT_date}, |
| {"func-stack", no_argument, NULL, OPT_funcstack}, |
| {"nosplice", no_argument, NULL, OPT_nosplice}, |
| {"nofifos", no_argument, NULL, OPT_nofifos}, |
| {"profile", no_argument, NULL, OPT_profile}, |
| {"stderr", no_argument, NULL, OPT_stderr}, |
| {"by-comm", no_argument, NULL, OPT_bycomm}, |
| {"ts-offset", required_argument, NULL, OPT_tsoffset}, |
| {"max-graph-depth", required_argument, NULL, OPT_max_graph_depth}, |
| {"cmdlines-size", required_argument, NULL, OPT_cmdlines_size}, |
| {"no-filter", no_argument, NULL, OPT_no_filter}, |
| {"debug", no_argument, NULL, OPT_debug}, |
| {"quiet", no_argument, NULL, OPT_quiet}, |
| {"help", no_argument, NULL, '?'}, |
| {"proc-map", no_argument, NULL, OPT_procmap}, |
| {"user", required_argument, NULL, OPT_user}, |
| {"module", required_argument, NULL, OPT_module}, |
| {"tsync-interval", required_argument, NULL, OPT_tsyncinterval}, |
| {"fork", no_argument, NULL, OPT_fork}, |
| {"tsc2nsec", no_argument, NULL, OPT_tsc2nsec}, |
| {"poll", no_argument, NULL, OPT_poll}, |
| {"name", required_argument, NULL, OPT_name}, |
| {"verbose", optional_argument, NULL, OPT_verbose}, |
| {"compression", required_argument, NULL, OPT_compression}, |
| {"file-version", required_argument, NULL, OPT_file_ver}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| if (IS_EXTRACT(ctx)) |
| opts = "+haf:Fp:co:O:sr:g:l:n:P:N:tb:B:ksiT"; |
| else |
| opts = "+hae:f:FA:p:cC:dDGo:O:s:r:V:vg:l:n:P:N:tb:R:B:ksSiTm:M:H:q"; |
| c = getopt_long (argc-1, argv+1, opts, long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| /* |
| * If the current instance is to record a guest, then save |
| * all the arguments for this instance. |
| */ |
| if (c != 'B' && c != 'A' && c != OPT_name && is_guest(ctx->instance)) { |
| add_arg(ctx->instance, c, opts, long_options, optarg); |
| if (c == 'C') |
| ctx->instance->flags |= BUFFER_FL_HAS_CLOCK; |
| continue; |
| } |
| |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'a': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-a"); |
| if (IS_EXTRACT(ctx)) { |
| add_all_instances(); |
| } else { |
| ctx->record_all = 1; |
| record_all_events(); |
| } |
| break; |
| case 'e': |
| check_instance_die(ctx->instance, "-e"); |
| ctx->events = 1; |
| event = malloc(sizeof(*event)); |
| if (!event) |
| die("Failed to allocate event %s", optarg); |
| memset(event, 0, sizeof(*event)); |
| event->event = optarg; |
| add_event(ctx->instance, event); |
| event->neg = negative; |
| event->filter = NULL; |
| last_event = event; |
| |
| if (!ctx->record_all) |
| list_event(optarg); |
| 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 { |
| ret = asprintf(&last_event->filter, "(%s)", optarg); |
| if (ret < 0) |
| die("Failed to allocate filter %s", optarg); |
| } |
| break; |
| |
| case 'R': |
| if (!last_event) |
| die("trigger must come after event"); |
| add_trigger(event, optarg); |
| break; |
| |
| case OPT_name: |
| if (!ctx->instance) |
| die("No instance defined for name option\n"); |
| if (!is_guest(ctx->instance)) |
| die(" --name is only used for -A options\n"); |
| free(ctx->instance->name); |
| ctx->instance->name = strdup(optarg); |
| if (!ctx->instance->name) |
| die("Failed to allocate name"); |
| break; |
| |
| case 'A': { |
| char *name = NULL; |
| int cid = -1, port = -1; |
| |
| if (!IS_RECORD(ctx)) |
| die("-A is only allowed for record operations"); |
| |
| name = parse_guest_name(optarg, &cid, &port, &result); |
| if (cid == -1 && !result) |
| die("guest %s not found", optarg); |
| if (port == -1) |
| port = TRACE_AGENT_DEFAULT_PORT; |
| if (!name || !*name) { |
| ret = asprintf(&name, "unnamed-%d", name_counter++); |
| if (ret < 0) |
| name = NULL; |
| } else { |
| /* Needs to be allocate */ |
| name = strdup(name); |
| } |
| if (!name) |
| die("Failed to allocate guest name"); |
| |
| ctx->instance = allocate_instance(name); |
| if (!ctx->instance) |
| die("Failed to allocate instance"); |
| |
| if (result) { |
| ctx->instance->flags |= BUFFER_FL_NETWORK; |
| ctx->instance->port_type = USE_TCP; |
| } |
| |
| ctx->instance->flags |= BUFFER_FL_GUEST; |
| ctx->instance->result = result; |
| ctx->instance->cid = cid; |
| ctx->instance->port = port; |
| ctx->instance->name = name; |
| add_instance(ctx->instance, 0); |
| ctx->data_flags |= DATA_FL_GUEST; |
| break; |
| } |
| case 'F': |
| test_set_event_pid(ctx->instance); |
| filter_task = 1; |
| break; |
| case 'G': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-G"); |
| ctx->global = 1; |
| break; |
| case 'P': |
| check_instance_die(ctx->instance, "-P"); |
| test_set_event_pid(ctx->instance); |
| pids = strdup(optarg); |
| if (!pids) |
| die("strdup"); |
| pid = strtok_r(pids, ",", &sav); |
| while (pid) { |
| fpids_count += add_filter_pid(ctx->instance, |
| atoi(pid), 0); |
| pid = strtok_r(NULL, ",", &sav); |
| ctx->instance->nr_process_pids++; |
| } |
| ctx->instance->process_pids = ctx->instance->filter_pids; |
| free(pids); |
| break; |
| case 'c': |
| check_instance_die(ctx->instance, "-c"); |
| test_set_event_pid(ctx->instance); |
| do_children = 1; |
| if (!ctx->instance->have_event_fork) { |
| #ifdef NO_PTRACE |
| die("-c invalid: ptrace not supported"); |
| #endif |
| do_ptrace = 1; |
| ctx->instance->ptrace_child = 1; |
| |
| } else { |
| save_option(ctx->instance, "event-fork"); |
| } |
| if (ctx->instance->have_func_fork) |
| save_option(ctx->instance, "function-fork"); |
| break; |
| case 'C': |
| check_instance_die(ctx->instance, "-C"); |
| if (strcmp(optarg, TSCNSEC_CLOCK) == 0) { |
| ret = get_tsc_nsec(&ctx->tsc2nsec.shift, |
| &ctx->tsc2nsec.mult); |
| if (ret) |
| die("TSC to nanosecond is not supported"); |
| ctx->instance->flags |= BUFFER_FL_TSC2NSEC; |
| ctx->instance->clock = TSC_CLOCK; |
| } else { |
| ctx->instance->clock = optarg; |
| } |
| if (!clock_is_supported(NULL, ctx->instance->clock)) |
| die("Clock %s is not supported", ctx->instance->clock); |
| ctx->instance->clock = strdup(ctx->instance->clock); |
| if (!ctx->instance->clock) |
| die("Failed allocation"); |
| ctx->instance->flags |= BUFFER_FL_HAS_CLOCK; |
| if (!ctx->clock && !is_guest(ctx->instance)) |
| ctx->clock = ctx->instance->clock; |
| break; |
| case 'v': |
| negative = 1; |
| break; |
| case 'l': |
| add_func(&ctx->instance->filter_funcs, |
| ctx->instance->filter_mod, optarg); |
| ctx->filtered = 1; |
| break; |
| case 'n': |
| check_instance_die(ctx->instance, "-n"); |
| add_func(&ctx->instance->notrace_funcs, |
| ctx->instance->filter_mod, optarg); |
| ctx->filtered = 1; |
| break; |
| case 'g': |
| check_instance_die(ctx->instance, "-g"); |
| add_func(&graph_funcs, ctx->instance->filter_mod, optarg); |
| ctx->filtered = 1; |
| break; |
| case 'p': |
| check_instance_die(ctx->instance, "-p"); |
| if (ctx->instance->plugin) |
| die("only one plugin allowed"); |
| for (plugin = optarg; isspace(*plugin); plugin++) |
| ; |
| ctx->instance->plugin = plugin; |
| for (optarg += strlen(optarg) - 1; |
| optarg > plugin && isspace(*optarg); optarg--) |
| ; |
| optarg++; |
| optarg[0] = '\0'; |
| break; |
| case 'D': |
| ctx->total_disable = 1; |
| /* fall through */ |
| case 'd': |
| ctx->disable = 1; |
| break; |
| case 'o': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-o"); |
| if (IS_RECORD_AGENT(ctx)) |
| die("-o incompatible with agent recording"); |
| if (host) |
| die("-o incompatible with -N"); |
| if (IS_START(ctx)) |
| die("start does not take output\n" |
| "Did you mean 'record'?"); |
| if (IS_STREAM(ctx)) |
| die("stream does not take output\n" |
| "Did you mean 'record'?"); |
| if (ctx->output) |
| die("only one output file allowed"); |
| ctx->output = optarg; |
| |
| if (IS_PROFILE(ctx)) { |
| int fd; |
| |
| /* pipe the output to this file instead of stdout */ |
| save_stdout = dup(1); |
| close(1); |
| fd = open(optarg, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (fd < 0) |
| die("can't write to %s", optarg); |
| if (fd != 1) { |
| dup2(fd, 1); |
| close(fd); |
| } |
| } |
| break; |
| case 'O': |
| check_instance_die(ctx->instance, "-O"); |
| option = optarg; |
| save_option(ctx->instance, option); |
| break; |
| case 'T': |
| check_instance_die(ctx->instance, "-T"); |
| save_option(ctx->instance, "stacktrace"); |
| break; |
| case 'H': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-H"); |
| check_instance_die(ctx->instance, "-H"); |
| add_hook(ctx->instance, optarg); |
| ctx->events = 1; |
| break; |
| case 's': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-s"); |
| if (IS_EXTRACT(ctx)) { |
| if (optarg) |
| usage(argv); |
| recorder_flags |= TRACECMD_RECORD_SNAPSHOT; |
| break; |
| } |
| if (!optarg) |
| usage(argv); |
| sleep_time = atoi(optarg); |
| break; |
| case 'S': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-S"); |
| ctx->manual = 1; |
| /* User sets events for profiling */ |
| if (!event) |
| ctx->events = 0; |
| break; |
| case 'r': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-r"); |
| rt_prio = atoi(optarg); |
| break; |
| case 'N': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-N"); |
| if (!IS_RECORD(ctx)) |
| die("-N only available with record"); |
| if (IS_RECORD_AGENT(ctx)) |
| die("-N incompatible with agent recording"); |
| if (ctx->output) |
| die("-N incompatible with -o"); |
| host = optarg; |
| break; |
| case 'V': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-V"); |
| if (!IS_RECORD(ctx)) |
| die("-V only available with record"); |
| if (IS_RECORD_AGENT(ctx)) |
| die("-V incompatible with agent recording"); |
| if (ctx->output) |
| die("-V incompatible with -o"); |
| host = optarg; |
| ctx->instance->port_type = USE_VSOCK; |
| break; |
| case 'm': |
| if (max_kb) |
| die("-m can only be specified once"); |
| if (!IS_RECORD(ctx)) |
| die("only record take 'm' option"); |
| max_kb = atoi(optarg); |
| break; |
| case 'M': |
| check_instance_die(ctx->instance, "-M"); |
| ctx->instance->cpumask = alloc_mask_from_hex(ctx->instance, optarg); |
| break; |
| case 't': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-t"); |
| if (IS_EXTRACT(ctx)) |
| ctx->topt = 1; /* Extract top instance also */ |
| else |
| ctx->instance->port_type = USE_TCP; |
| break; |
| case 'b': |
| check_instance_die(ctx->instance, "-b"); |
| ctx->instance->buffer_size = atoi(optarg); |
| break; |
| case 'B': |
| ctx->instance = allocate_instance(optarg); |
| if (!ctx->instance) |
| die("Failed to create instance"); |
| ctx->instance->delete = negative; |
| negative = 0; |
| if (ctx->instance->delete) { |
| ctx->instance->next = del_list; |
| del_list = ctx->instance; |
| } else |
| add_instance(ctx->instance, local_cpu_count); |
| if (IS_PROFILE(ctx)) |
| ctx->instance->flags |= BUFFER_FL_PROFILE; |
| break; |
| case 'k': |
| cmd_check_die(ctx, CMD_set, *(argv+1), "-k"); |
| keep = 1; |
| break; |
| case 'i': |
| ignore_event_not_found = 1; |
| break; |
| case OPT_user: |
| ctx->user = strdup(optarg); |
| if (!ctx->user) |
| die("Failed to allocate user name"); |
| break; |
| case OPT_procmap: |
| cmd_check_die(ctx, CMD_start, *(argv+1), "--proc-map"); |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--proc-map"); |
| check_instance_die(ctx->instance, "--proc-map"); |
| ctx->instance->get_procmap = 1; |
| break; |
| case OPT_date: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--date"); |
| ctx->date = 1; |
| if (ctx->data_flags & DATA_FL_OFFSET) |
| die("Can not use both --date and --ts-offset"); |
| ctx->data_flags |= DATA_FL_DATE; |
| break; |
| case OPT_funcstack: |
| func_stack = 1; |
| break; |
| case OPT_nosplice: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--nosplice"); |
| recorder_flags |= TRACECMD_RECORD_NOSPLICE; |
| break; |
| case OPT_nofifos: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--nofifos"); |
| no_fifos = true; |
| break; |
| case OPT_profile: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--profile"); |
| check_instance_die(ctx->instance, "--profile"); |
| handle_init = trace_init_profile; |
| ctx->instance->flags |= BUFFER_FL_PROFILE; |
| ctx->events = 1; |
| break; |
| case OPT_stderr: |
| /* if -o was used (for profile), ignore this */ |
| if (save_stdout >= 0) |
| break; |
| save_stdout = dup(1); |
| close(1); |
| dup2(2, 1); |
| break; |
| case OPT_bycomm: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--by-comm"); |
| trace_profile_set_merge_like_comms(); |
| break; |
| case OPT_tsoffset: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--ts-offset"); |
| ctx->date2ts = strdup(optarg); |
| if (ctx->data_flags & DATA_FL_DATE) |
| die("Can not use both --date and --ts-offset"); |
| ctx->data_flags |= DATA_FL_OFFSET; |
| break; |
| case OPT_max_graph_depth: |
| check_instance_die(ctx->instance, "--max-graph-depth"); |
| free(ctx->instance->max_graph_depth); |
| ctx->instance->max_graph_depth = strdup(optarg); |
| if (!ctx->instance->max_graph_depth) |
| die("Could not allocate option"); |
| break; |
| case OPT_cmdlines_size: |
| ctx->saved_cmdlines_size = atoi(optarg); |
| break; |
| case OPT_no_filter: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--no-filter"); |
| no_filter = true; |
| break; |
| case OPT_debug: |
| tracecmd_set_debug(true); |
| break; |
| case OPT_module: |
| check_instance_die(ctx->instance, "--module"); |
| if (ctx->instance->filter_mod) |
| add_func(&ctx->instance->filter_funcs, |
| ctx->instance->filter_mod, "*"); |
| ctx->instance->filter_mod = optarg; |
| ctx->filtered = 0; |
| break; |
| case OPT_tsyncinterval: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--tsync-interval"); |
| ctx->tsync_loop_interval = atoi(optarg); |
| break; |
| case OPT_fork: |
| if (!IS_START(ctx)) |
| die("--fork option used for 'start' command only"); |
| fork_process = true; |
| break; |
| case OPT_tsc2nsec: |
| ret = get_tsc_nsec(&ctx->tsc2nsec.shift, |
| &ctx->tsc2nsec.mult); |
| if (ret) |
| die("TSC to nanosecond is not supported"); |
| ctx->instance->flags |= BUFFER_FL_TSC2NSEC; |
| break; |
| case OPT_poll: |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--poll"); |
| recorder_flags |= TRACECMD_RECORD_POLL; |
| break; |
| case OPT_compression: |
| cmd_check_die(ctx, CMD_start, *(argv+1), "--compression"); |
| cmd_check_die(ctx, CMD_set, *(argv+1), "--compression"); |
| cmd_check_die(ctx, CMD_extract, *(argv+1), "--compression"); |
| cmd_check_die(ctx, CMD_stream, *(argv+1), "--compression"); |
| cmd_check_die(ctx, CMD_profile, *(argv+1), "--compression"); |
| if (strcmp(optarg, "any") && strcmp(optarg, "none") && |
| !tracecmd_compress_is_supported(optarg, NULL)) |
| die("Compression algorithm %s is not supported", optarg); |
| ctx->compression = strdup(optarg); |
| break; |
| case OPT_file_ver: |
| if (ctx->curr_cmd != CMD_record && ctx->curr_cmd != CMD_record_agent) |
| die("--file_version has no effect with the command %s\n", |
| *(argv+1)); |
| ctx->file_version = atoi(optarg); |
| if (ctx->file_version < FILE_VERSION_MIN || |
| ctx->file_version > FILE_VERSION_MAX) |
| die("Unsupported file version %d, " |
| "supported versions are from %d to %d", |
| ctx->file_version, FILE_VERSION_MIN, FILE_VERSION_MAX); |
| break; |
| case OPT_quiet: |
| case 'q': |
| quiet = true; |
| break; |
| case OPT_verbose: |
| if (trace_set_verbose(optarg) < 0) |
| die("invalid verbose level %s", optarg); |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| remove_instances(del_list); |
| |
| /* If --date is specified, prepend it to all guest VM flags */ |
| if (ctx->date) { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| add_argv(instance, "--date", true); |
| } |
| } |
| |
| if (!ctx->filtered && ctx->instance->filter_mod) |
| add_func(&ctx->instance->filter_funcs, |
| ctx->instance->filter_mod, "*"); |
| |
| if (do_children && !filter_task && !fpids_count) |
| die(" -c can only be used with -F (or -P with event-fork support)"); |
| |
| if ((argc - optind) >= 2) { |
| if (IS_EXTRACT(ctx)) |
| die("Command extract does not take any commands\n" |
| "Did you mean 'record'?"); |
| ctx->run_command = 1; |
| } |
| if (ctx->user && !ctx->run_command) |
| warning("--user %s is ignored, no command is specified", |
| ctx->user); |
| |
| if (top_instance.get_procmap) { |
| /* use ptrace to get procmap on the command exit */ |
| if (ctx->run_command) { |
| do_ptrace = 1; |
| } else if (!top_instance.nr_filter_pids) { |
| warning("--proc-map is ignored for top instance, " |
| "no command or filtered PIDs are specified."); |
| top_instance.get_procmap = 0; |
| } |
| } |
| |
| for_all_instances(instance) { |
| if (instance->get_procmap && !instance->nr_filter_pids) { |
| warning("--proc-map is ignored for instance %s, " |
| "no filtered PIDs are specified.", |
| tracefs_instance_get_name(instance->tracefs)); |
| instance->get_procmap = 0; |
| } |
| } |
| } |
| |
| static enum trace_type get_trace_cmd_type(enum trace_cmd cmd) |
| { |
| const static struct { |
| enum trace_cmd cmd; |
| enum trace_type ttype; |
| } trace_type_per_command[] = { |
| {CMD_record, TRACE_TYPE_RECORD}, |
| {CMD_stream, TRACE_TYPE_STREAM}, |
| {CMD_extract, TRACE_TYPE_EXTRACT}, |
| {CMD_profile, TRACE_TYPE_STREAM}, |
| {CMD_start, TRACE_TYPE_START}, |
| {CMD_record_agent, TRACE_TYPE_RECORD}, |
| {CMD_set, TRACE_TYPE_SET} |
| }; |
| |
| for (int i = 0; i < ARRAY_SIZE(trace_type_per_command); i++) { |
| if (trace_type_per_command[i].cmd == cmd) |
| return trace_type_per_command[i].ttype; |
| } |
| |
| die("Trace type UNKNOWN for the given cmd_fun"); |
| } |
| |
| static void finalize_record_trace(struct common_record_context *ctx) |
| { |
| struct buffer_instance *instance; |
| |
| if (keep) |
| return; |
| |
| update_reset_files(); |
| update_reset_triggers(); |
| if (clear_function_filters) |
| clear_func_filters(); |
| |
| set_plugin("nop"); |
| |
| tracecmd_remove_instances(); |
| |
| /* If tracing_on was enabled before we started, set it on now */ |
| for_all_instances(instance) { |
| if (instance->flags & BUFFER_FL_KEEP) |
| write_tracing_on(instance, |
| instance->tracing_on_init_val); |
| if (is_agent(instance)) { |
| tracecmd_msg_send_close_resp_msg(instance->msg_handle); |
| tracecmd_output_close(instance->network_handle); |
| } |
| } |
| |
| if (host) |
| tracecmd_output_close(ctx->instance->network_handle); |
| } |
| |
| static bool has_local_instances(void) |
| { |
| struct buffer_instance *instance; |
| |
| for_all_instances(instance) { |
| if (is_guest(instance)) |
| continue; |
| if (host && instance->msg_handle) |
| continue; |
| return true; |
| } |
| return false; |
| } |
| |
| static void set_tsync_params(struct common_record_context *ctx) |
| { |
| struct buffer_instance *instance; |
| int shift, mult; |
| bool force_tsc = false; |
| char *clock = NULL; |
| |
| if (!ctx->clock) { |
| /* |
| * If no clock is configured && |
| * KVM time sync protocol is available && |
| * there is information of each guest PID process && |
| * tsc-x86 clock is supported && |
| * TSC to nsec multiplier and shift are available: |
| * force using the x86-tsc clock for this host-guest tracing session |
| * and store TSC to nsec multiplier and shift. |
| */ |
| if (tsync_proto_is_supported("kvm") && |
| trace_have_guests_pid() && |
| clock_is_supported(NULL, TSC_CLOCK) && |
| !get_tsc_nsec(&shift, &mult) && mult) { |
| clock = strdup(TSC_CLOCK); |
| if (!clock) |
| die("Cannot not allocate clock"); |
| ctx->tsc2nsec.mult = mult; |
| ctx->tsc2nsec.shift = shift; |
| force_tsc = true; |
| } else { /* Use the current clock of the first host instance */ |
| clock = get_trace_clock(true); |
| } |
| } else { |
| clock = strdup(ctx->clock); |
| if (!clock) |
| die("Cannot not allocate clock"); |
| } |
| |
| if (!clock && !ctx->tsync_loop_interval) |
| goto out; |
| for_all_instances(instance) { |
| if (clock && !(instance->flags & BUFFER_FL_HAS_CLOCK)) { |
| /* use the same clock in all tracing peers */ |
| if (is_guest(instance)) { |
| if (!instance->clock) { |
| instance->clock = strdup(clock); |
| if (!instance->clock) |
| die("Can not allocate instance clock"); |
| } |
| add_argv(instance, (char *)instance->clock, true); |
| add_argv(instance, "-C", true); |
| if (ctx->tsc2nsec.mult) |
| instance->flags |= BUFFER_FL_TSC2NSEC; |
| } else if (force_tsc && !instance->clock) { |
| instance->clock = strdup(clock); |
| if (!instance->clock) |
| die("Can not allocate instance clock"); |
| } |
| } |
| instance->tsync_loop_interval = ctx->tsync_loop_interval; |
| } |
| out: |
| free(clock); |
| } |
| |
| static void record_trace(int argc, char **argv, |
| struct common_record_context *ctx) |
| { |
| enum trace_type type = get_trace_cmd_type(ctx->curr_cmd); |
| struct buffer_instance *instance; |
| struct filter_pids *pid; |
| |
| /* |
| * If top_instance doesn't have any plugins or events, then |
| * remove it from being processed. |
| */ |
| if (!__check_doing_something(&top_instance) && !filter_task) |
| first_instance = buffer_instances; |
| else |
| ctx->topt = 1; |
| |
| update_first_instance(ctx->instance, ctx->topt); |
| if (!IS_CMDSET(ctx)) { |
| check_doing_something(); |
| check_function_plugin(); |
| } |
| |
| if (!ctx->output) |
| ctx->output = DEFAULT_INPUT_FILE; |
| |
| if (ctx->data_flags & DATA_FL_GUEST) |
| set_tsync_params(ctx); |
| |
| make_instances(); |
| |
| /* Save the state of tracing_on before starting */ |
| for_all_instances(instance) { |
| instance->output_file = strdup(ctx->output); |
| if (!instance->output_file) |
| die("Failed to allocate output file name for instance"); |
| if (!ctx->manual && instance->flags & BUFFER_FL_PROFILE) |
| enable_profile(instance); |
| |
| instance->tracing_on_init_val = read_tracing_on(instance); |
| /* Some instances may not be created yet */ |
| if (instance->tracing_on_init_val < 0) |
| instance->tracing_on_init_val = 1; |
| } |
| |
| if (ctx->events) |
| expand_event_list(); |
| |
| page_size = getpagesize(); |
| |
| if (!is_guest(ctx->instance)) |
| fset = set_ftrace(ctx->instance, !ctx->disable, ctx->total_disable); |
| if (!IS_CMDSET(ctx)) |
| tracecmd_disable_all_tracing(1); |
| |
| for_all_instances(instance) |
| set_clock(ctx, instance); |
| |
| |
| /* Record records the date first */ |
| if (ctx->date && |
| ((IS_RECORD(ctx) && has_local_instances()) || IS_RECORD_AGENT(ctx))) |
| ctx->date2ts = get_date_to_ts(); |
| |
| for_all_instances(instance) { |
| set_funcs(instance); |
| set_mask(instance); |
| } |
| |
| if (ctx->events) { |
| for_all_instances(instance) |
| enable_events(instance); |
| } |
| |
| set_saved_cmdlines_size(ctx); |
| set_buffer_size(); |
| update_plugins(type); |
| set_options(); |
| |
| for_all_instances(instance) { |
| if (instance->max_graph_depth) { |
| set_max_graph_depth(instance, instance->max_graph_depth); |
| free(instance->max_graph_depth); |
| instance->max_graph_depth = NULL; |
| } |
| } |
| |
| allocate_seq(); |
| |
| if (type & (TRACE_TYPE_RECORD | TRACE_TYPE_STREAM)) { |
| signal(SIGINT, finish); |
| if (!latency) |
| start_threads(type, ctx); |
| } |
| |
| if (ctx->run_command) { |
| run_cmd(type, ctx->user, (argc - optind) - 1, &argv[optind + 1]); |
| } else if (ctx->instance && is_agent(ctx->instance)) { |
| update_task_filter(); |
| tracecmd_enable_tracing(); |
| tracecmd_msg_wait_close(ctx->instance->msg_handle); |
| } else { |
| bool pwait = false; |
| bool wait_indefinitely = false; |
| |
| update_task_filter(); |
| |
| if (!IS_CMDSET(ctx)) |
| tracecmd_enable_tracing(); |
| |
| if (type & (TRACE_TYPE_START | TRACE_TYPE_SET)) |
| exit(0); |
| |
| /* We don't ptrace ourself */ |
| if (do_ptrace) { |
| for_all_instances(instance) { |
| for (pid = instance->filter_pids; pid; pid = pid->next) { |
| if (!pid->exclude && instance->ptrace_child) { |
| ptrace_attach(instance, pid->pid); |
| pwait = true; |
| } |
| } |
| } |
| } |
| /* sleep till we are woken with Ctrl^C */ |
| printf("Hit Ctrl^C to stop recording\n"); |
| for_all_instances(instance) { |
| /* If an instance is not tracing individual processes |
| * or there is an error while waiting for a process to |
| * exit, fallback to waiting indefinitely. |
| */ |
| if (!instance->nr_process_pids || |
| trace_wait_for_processes(instance)) |
| wait_indefinitely = true; |
| } |
| while (!finished && wait_indefinitely) |
| trace_or_sleep(type, pwait); |
| } |
| |
| tell_guests_to_stop(ctx); |
| tracecmd_disable_tracing(); |
| if (!latency) |
| stop_threads(type); |
| |
| record_stats(); |
| |
| if (!latency) |
| wait_threads(); |
| |
| if (IS_RECORD(ctx)) { |
| record_data(ctx); |
| delete_thread_data(); |
| } else |
| print_stats(); |
| |
| if (!keep) |
| tracecmd_disable_all_tracing(0); |
| |
| destroy_stats(); |
| finalize_record_trace(ctx); |
| } |
| |
| /* |
| * This function contains common code for the following commands: |
| * record, start, stream, profile. |
| */ |
| static void record_trace_command(int argc, char **argv, |
| struct common_record_context *ctx) |
| { |
| tracecmd_tsync_init(); |
| record_trace(argc, argv, ctx); |
| } |
| |
| void trace_start(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| |
| parse_record_options(argc, argv, CMD_start, &ctx); |
| record_trace_command(argc, argv, &ctx); |
| exit(0); |
| } |
| |
| void trace_set(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| |
| parse_record_options(argc, argv, CMD_set, &ctx); |
| record_trace_command(argc, argv, &ctx); |
| exit(0); |
| } |
| |
| void trace_extract(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| struct buffer_instance *instance; |
| enum trace_type type; |
| |
| parse_record_options(argc, argv, CMD_extract, &ctx); |
| |
| type = get_trace_cmd_type(ctx.curr_cmd); |
| |
| update_first_instance(ctx.instance, 1); |
| check_function_plugin(); |
| |
| if (!ctx.output) |
| ctx.output = DEFAULT_INPUT_FILE; |
| |
| /* Save the state of tracing_on before starting */ |
| for_all_instances(instance) { |
| instance->output_file = strdup(ctx.output); |
| if (!instance->output_file) |
| die("Failed to allocate output file name for instance"); |
| |
| if (!ctx.manual && instance->flags & BUFFER_FL_PROFILE) |
| enable_profile(ctx.instance); |
| |
| instance->tracing_on_init_val = read_tracing_on(instance); |
| /* Some instances may not be created yet */ |
| if (instance->tracing_on_init_val < 0) |
| instance->tracing_on_init_val = 1; |
| } |
| |
| /* Extracting data records all events in the system. */ |
| if (!ctx.record_all) |
| record_all_events(); |
| |
| if (ctx.events) |
| expand_event_list(); |
| |
| page_size = getpagesize(); |
| update_plugins(type); |
| set_options(); |
| |
| for_all_instances(instance) { |
| if (instance->max_graph_depth) { |
| set_max_graph_depth(instance, instance->max_graph_depth); |
| free(instance->max_graph_depth); |
| instance->max_graph_depth = NULL; |
| } |
| } |
| |
| allocate_seq(); |
| flush_threads(); |
| record_stats(); |
| |
| if (!keep) |
| tracecmd_disable_all_tracing(0); |
| |
| /* extract records the date after extraction */ |
| if (ctx.date) { |
| /* |
| * We need to start tracing, don't let other traces |
| * screw with our trace_marker. |
| */ |
| tracecmd_disable_all_tracing(1); |
| ctx.date2ts = get_date_to_ts(); |
| } |
| |
| record_data(&ctx); |
| delete_thread_data(); |
| destroy_stats(); |
| finalize_record_trace(&ctx); |
| exit(0); |
| } |
| |
| void trace_stream(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| |
| parse_record_options(argc, argv, CMD_stream, &ctx); |
| record_trace_command(argc, argv, &ctx); |
| exit(0); |
| } |
| |
| void trace_profile(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| |
| parse_record_options(argc, argv, CMD_profile, &ctx); |
| |
| handle_init = trace_init_profile; |
| ctx.events = 1; |
| |
| /* |
| * If no instances were set, then enable profiling on the top instance. |
| */ |
| if (!buffer_instances) |
| top_instance.flags |= BUFFER_FL_PROFILE; |
| |
| record_trace_command(argc, argv, &ctx); |
| do_trace_profile(); |
| exit(0); |
| } |
| |
| void trace_record(int argc, char **argv) |
| { |
| struct common_record_context ctx; |
| |
| parse_record_options(argc, argv, CMD_record, &ctx); |
| record_trace_command(argc, argv, &ctx); |
| exit(0); |
| } |
| |
| int trace_record_agent(struct tracecmd_msg_handle *msg_handle, |
| int cpus, int *fds, |
| int argc, char **argv, |
| bool use_fifos, |
| unsigned long long trace_id, const char *host) |
| { |
| struct common_record_context ctx; |
| char **argv_plus; |
| |
| /* Reset optind for getopt_long */ |
| optind = 1; |
| /* |
| * argc is the number of elements in argv, but we need to convert |
| * argc and argv into "trace-cmd", "record", argv. |
| * where argc needs to grow by two. |
| */ |
| argv_plus = calloc(argc + 2, sizeof(char *)); |
| if (!argv_plus) |
| die("Failed to allocate record arguments"); |
| |
| argv_plus[0] = "trace-cmd"; |
| argv_plus[1] = "record"; |
| memmove(argv_plus + 2, argv, argc * sizeof(char *)); |
| argc += 2; |
| |
| parse_record_options(argc, argv_plus, CMD_record_agent, &ctx); |
| if (ctx.run_command) |
| return -EINVAL; |
| |
| ctx.instance->fds = fds; |
| ctx.instance->use_fifos = use_fifos; |
| ctx.instance->flags |= BUFFER_FL_AGENT; |
| ctx.instance->msg_handle = msg_handle; |
| ctx.instance->host = host; |
| msg_handle->version = V3_PROTOCOL; |
| top_instance.trace_id = trace_id; |
| record_trace(argc, argv, &ctx); |
| |
| free(argv_plus); |
| return 0; |
| } |