diff options
Diffstat (limited to 'tracecmd/trace-stat.c')
-rw-r--r-- | tracecmd/trace-stat.c | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/tracecmd/trace-stat.c b/tracecmd/trace-stat.c new file mode 100644 index 00000000..a5fb777b --- /dev/null +++ b/tracecmd/trace-stat.c @@ -0,0 +1,926 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#include "tracefs.h" +#include "trace-local.h" + +#ifndef BUFSIZ +#define BUFSIZ 1024 +#endif + +static inline int is_top_instance(struct buffer_instance *instance) +{ + return instance == &top_instance; +} + +static int get_instance_file_fd(struct buffer_instance *instance, + const char *file) +{ + char *path; + int fd; + + path = tracefs_instance_get_file(instance->tracefs, file); + fd = open(path, O_RDONLY); + tracefs_put_tracing_file(path); + + return fd; +} + +char *strstrip(char *str) +{ + char *s; + + if (!str) + return NULL; + + s = str + strlen(str) - 1; + while (s >= str && isspace(*s)) + s--; + s++; + *s = '\0'; + + for (s = str; *s && isspace(*s); s++) + ; + + return s; +} + +/* FIXME: append_file() is duplicated and could be consolidated */ +char *append_file(const char *dir, const char *name) +{ + char *file; + int ret; + + ret = asprintf(&file, "%s/%s", dir, name); + if (ret < 0) + die("Failed to allocate %s/%s", dir, name); + + return file; +} + +static char *get_fd_content(int fd, const char *file) +{ + char *str = NULL; + int cnt = 0; + int ret; + + for (;;) { + str = realloc(str, BUFSIZ * ++cnt); + if (!str) + die("malloc"); + ret = read(fd, str + BUFSIZ * (cnt - 1), BUFSIZ); + if (ret < 0) + die("reading %s\n", file); + if (ret < BUFSIZ) + break; + } + str[BUFSIZ * (cnt-1) + ret] = 0; + + return str; +} + +char *get_file_content(const char *file) +{ + char *str; + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) + return NULL; + + str = get_fd_content(fd, file); + close(fd); + + return str; +} + +static char *get_instance_file_content(struct buffer_instance *instance, + const char *file) +{ + char *str = NULL; + int fd; + + fd = get_instance_file_fd(instance, file); + if (fd < 0) + return NULL; + + str = get_fd_content(fd, file); + + close(fd); + return str; +} + +static void report_file(struct buffer_instance *instance, + char *name, char *def_value, char *description) +{ + char *str; + char *cont; + + if (!tracefs_file_exists(instance->tracefs, name)) + return; + str = get_instance_file_content(instance, name); + if (!str) + return; + cont = strstrip(str); + if (cont[0] && strcmp(cont, def_value) != 0) + printf("\n%s%s\n", description, cont); + + free(str); +} + +static int report_instance(const char *name, void *data) +{ + bool *first = (bool *)data; + + if (*first) { + *first = false; + printf("\nInstances:\n"); + } + printf(" %s\n", name); + return 0; +} + +static void report_instances(void) +{ + bool first = true; + + tracefs_instances_walk(report_instance, &first); +} + +struct event_iter *trace_event_iter_alloc(const char *path) +{ + struct event_iter *iter; + + iter = malloc(sizeof(*iter)); + if (!iter) + die("Failed to allocate event_iter for path %s", path); + memset(iter, 0, sizeof(*iter)); + + iter->system_dir = opendir(path); + if (!iter->system_dir) + die("opendir"); + + return iter; +} + +enum event_iter_type +trace_event_iter_next(struct event_iter *iter, const char *path, const char *system) +{ + struct dirent *dent; + + if (system && !iter->event_dir) { + char *event; + struct stat st; + + event = append_file(path, system); + + stat(event, &st); + if (!S_ISDIR(st.st_mode)) { + free(event); + goto do_system; + } + + iter->event_dir = opendir(event); + if (!iter->event_dir) + die("opendir %s", event); + free(event); + } + + if (iter->event_dir) { + while ((dent = readdir(iter->event_dir))) { + const char *name = dent->d_name; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + iter->event_dent = dent; + return EVENT_ITER_EVENT; + } + closedir(iter->event_dir); + iter->event_dir = NULL; + } + + do_system: + while ((dent = readdir(iter->system_dir))) { + const char *name = dent->d_name; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + iter->system_dent = dent; + + return EVENT_ITER_SYSTEM; + } + + return EVENT_ITER_NONE; +} + +void trace_event_iter_free(struct event_iter *iter) +{ + if (!iter) + return; + + if (iter->event_dir) + closedir(iter->event_dir); + + closedir(iter->system_dir); + free(iter); +} + +static void reset_event_iter(struct event_iter *iter) +{ + if (iter->event_dir) { + closedir(iter->event_dir); + iter->event_dir = NULL; + } + + rewinddir(iter->system_dir); +} + +static int process_individual_events(const char *path, struct event_iter *iter) +{ + struct stat st; + const char *system = iter->system_dent->d_name; + char *file; + char *enable = NULL; + char *str; + int ret = 0; + + file = append_file(path, system); + + stat(file, &st); + if (!S_ISDIR(st.st_mode)) + goto out; + + enable = append_file(file, "enable"); + str = get_file_content(enable); + if (!str) + goto out; + + if (*str != '1' && *str != '0') + ret = 1; + free(str); + + out: + free(enable); + free(file); + + return ret; +} + +static void +process_event_enable(char *path, const char *system, const char *name, + enum event_process *processed) +{ + struct stat st; + char *enable = NULL; + char *file; + char *str; + + if (system) + path = append_file(path, system); + + file = append_file(path, name); + + if (system) + free(path); + + stat(file, &st); + if (!S_ISDIR(st.st_mode)) + goto out; + + enable = append_file(file, "enable"); + str = get_file_content(enable); + if (!str) + goto out; + + if (*str == '1') { + if (!system) { + if (!*processed) + printf(" Individual systems:\n"); + printf( " %s\n", name); + *processed = PROCESSED_SYSTEM; + } else { + if (!*processed) { + printf(" Individual events:\n"); + *processed = PROCESSED_SYSTEM; + } + if (*processed == PROCESSED_SYSTEM) { + printf(" %s\n", system); + *processed = PROCESSED_EVENT; + } + printf( " %s\n", name); + } + } + free(str); + + out: + free(enable); + free(file); +} + +static void report_events(struct buffer_instance *instance) +{ + struct event_iter *iter; + char *str; + char *cont; + char *path; + char *system; + enum event_iter_type type; + enum event_process processed = PROCESSED_NONE; + enum event_process processed_part = PROCESSED_NONE; + + str = get_instance_file_content(instance, "events/enable"); + if (!str) + return; + + cont = strstrip(str); + + printf("\nEvents:\n"); + + switch(*cont) { + case '1': + printf(" All enabled\n"); + free(str); + return; + case '0': + printf(" All disabled\n"); + free(str); + return; + } + + free(str); + + path = tracefs_instance_get_file(instance->tracefs, "events"); + if (!path) + die("malloc"); + + iter = trace_event_iter_alloc(path); + + while (trace_event_iter_next(iter, path, NULL)) { + process_event_enable(path, NULL, iter->system_dent->d_name, &processed); + } + + reset_event_iter(iter); + + system = NULL; + while ((type = trace_event_iter_next(iter, path, system))) { + + if (type == EVENT_ITER_SYSTEM) { + + /* Only process systems that are not fully enabled */ + if (!process_individual_events(path, iter)) + continue; + + system = iter->system_dent->d_name; + if (processed_part) + processed_part = PROCESSED_SYSTEM; + continue; + } + + process_event_enable(path, iter->system_dent->d_name, + iter->event_dent->d_name, &processed_part); + } + + trace_event_iter_free(iter); + + if (!processed && !processed_part) + printf(" (none enabled)\n"); + + 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; + char *str; + char *cont; + + path = append_file(path, system); + file = append_file(path, event); + free(path); + + stat(file, &st); + if (!S_ISDIR(st.st_mode)) + goto out; + + filter = append_file(file, "filter"); + str = get_file_content(filter); + if (!str) + goto out; + + cont = strstrip(str); + + if (strcmp(cont, "none") == 0) { + free(str); + goto out; + } + + if (!*processed) + printf("\nFilters:\n"); + printf( " %s:%s \"%s\"\n", system, event, cont); + *processed = PROCESSED_SYSTEM; + free(str); + + out: + free(filter); + free(file); +} + +static void report_event_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 +process_event_trigger(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 *trigger = NULL; + char *file; + char *str; + char *cont; + + path = append_file(path, system); + file = append_file(path, event); + free(path); + + stat(file, &st); + if (!S_ISDIR(st.st_mode)) + goto out; + + trigger = append_file(file, "trigger"); + str = get_file_content(trigger); + if (!str) + goto out; + + cont = strstrip(str); + + if (cont[0] == '#') { + free(str); + goto out; + } + + if (!*processed) + printf("\nTriggers:\n"); + printf( " %s:%s \"%s\"\n", system, event, cont); + *processed = PROCESSED_SYSTEM; + free(str); + + out: + free(trigger); + free(file); +} + +static void report_event_triggers(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_trigger(path, iter, &processed); + } + + trace_event_iter_free(iter); + + tracefs_put_tracing_file(path); +} + +enum func_states { + FUNC_STATE_START, + FUNC_STATE_SKIP, + FUNC_STATE_PRINT, +}; + +static void list_functions(const char *path, char *string) +{ + enum func_states state; + struct stat st; + char *str; + int ret = 0; + int len; + int i; + int first = 0; + + /* Ignore if it does not exist. */ + ret = stat(path, &st); + if (ret < 0) + return; + + str = get_file_content(path); + if (!str) + return; + + len = strlen(str); + + state = FUNC_STATE_START; + + /* Skip all lines that start with '#' */ + for (i = 0; i < len; i++) { + + if (state == FUNC_STATE_PRINT) + putchar(str[i]); + + if (str[i] == '\n') { + state = FUNC_STATE_START; + continue; + } + + if (state == FUNC_STATE_SKIP) + continue; + + if (state == FUNC_STATE_START && str[i] == '#') { + state = FUNC_STATE_SKIP; + continue; + } + + if (!first) { + printf("\n%s:\n", string); + first = 1; + } + + if (state != FUNC_STATE_PRINT) { + state = FUNC_STATE_PRINT; + printf(" "); + putchar(str[i]); + } + } + free(str); +} + +static void report_graph_funcs(struct buffer_instance *instance) +{ + char *path; + + path = tracefs_instance_get_file(instance->tracefs, "set_graph_function"); + if (!path) + die("malloc"); + + list_functions(path, "Function Graph Filter"); + + tracefs_put_tracing_file(path); + + path = tracefs_instance_get_file(instance->tracefs, "set_graph_notrace"); + if (!path) + die("malloc"); + + list_functions(path, "Function Graph No Trace"); + + tracefs_put_tracing_file(path); +} + +static void report_ftrace_filters(struct buffer_instance *instance) +{ + char *path; + + path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_filter"); + if (!path) + die("malloc"); + + list_functions(path, "Function Filter"); + + tracefs_put_tracing_file(path); + + path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_notrace"); + if (!path) + die("malloc"); + + list_functions(path, "Function No Trace"); + + tracefs_put_tracing_file(path); +} + +static void report_buffers(struct buffer_instance *instance) +{ +#define FILE_SIZE 100 + char *str; + char *cont; + char file[FILE_SIZE]; + int cpu; + + str = get_instance_file_content(instance, "buffer_size_kb"); + if (!str) + return; + + cont = strstrip(str); + + /* If it's not expanded yet, just skip */ + if (strstr(cont, "expanded") != NULL) + goto out; + + if (strcmp(cont, "X") != 0) { + printf("\nBuffer size in kilobytes (per cpu):\n"); + printf(" %s\n", str); + goto total; + } + + /* Read the sizes of each CPU buffer */ + for (cpu = 0; ; cpu++) { + + snprintf(file, FILE_SIZE, "per_cpu/cpu%d/buffer_size_kb", cpu); + str = get_instance_file_content(instance, file); + if (!str) + break; + + cont = strstrip(str); + if (!cpu) + putchar('\n'); + + printf("CPU %d buffer size (kb): %s\n", cpu, cont); + free(str); + } + + total: + free(str); + + str = get_instance_file_content(instance, "buffer_total_size_kb"); + if (!str) + return; + + cont = strstrip(str); + printf("\nBuffer total size in kilobytes:\n"); + printf(" %s\n", str); + + out: + free(str); +} + +static void report_clock(struct buffer_instance *instance) +{ + struct tracefs_instance *tracefs = instance ? instance->tracefs : NULL; + char *clock; + + clock = tracefs_get_clock(tracefs); + + /* Default clock is "local", only show others */ + if (clock && strcmp(clock, "local") != 0) + printf("\nClock: %s\n", clock); + + free(clock); +} + +static void report_cpumask(struct buffer_instance *instance) +{ + char *str; + char *cont; + int cpus; + int n; + int i; + + str = get_instance_file_content(instance, "tracing_cpumask"); + if (!str) + return; + + cont = strstrip(str); + + /* check to make sure all CPUs on this machine are set */ + cpus = tracecmd_count_cpus(); + + for (i = strlen(cont) - 1; i >= 0 && cpus > 0; i--) { + if (cont[i] == ',') + continue; + + if (cont[i] == 'f') { + cpus -= 4; + continue; + } + + if (cpus >= 4) + break; + + if (cont[i] >= '0' && cont[i] <= '9') + n = cont[i] - '0'; + else + n = 10 + (cont[i] - 'a'); + + while (cpus > 0) { + if (!(n & 1)) + break; + n >>= 1; + cpus--; + } + break; + } + + /* If cpus is greater than zero, one isn't set */ + if (cpus > 0) + printf("\nCPU mask: %s\n", cont); + + free(str); +} + +static void report_probes(struct buffer_instance *instance, + const char *file, const char *string) +{ + char *str; + char *cont; + int newline; + int i; + + str = get_instance_file_content(instance, file); + if (!str) + return; + + cont = strstrip(str); + if (strlen(cont) == 0) + goto out; + + printf("\n%s:\n", string); + + newline = 1; + for (i = 0; cont[i]; i++) { + if (newline) + printf(" "); + putchar(cont[i]); + if (cont[i] == '\n') + newline = 1; + else + newline = 0; + } + putchar('\n'); + out: + free(str); +} + +static void report_kprobes(struct buffer_instance *instance) +{ + report_probes(instance, "kprobe_events", "Kprobe events"); +} + +static void report_uprobes(struct buffer_instance *instance) +{ + report_probes(instance, "uprobe_events", "Uprobe events"); +} + +static void report_traceon(struct buffer_instance *instance) +{ + char *str; + char *cont; + + str = get_instance_file_content(instance, "tracing_on"); + if (!str) + return; + + cont = strstrip(str); + + /* double newline as this is the last thing printed */ + if (strcmp(cont, "0") == 0) + printf("\nTracing is disabled\n\n"); + else + printf("\nTracing is enabled\n\n"); + + free(str); +} + +static void stat_instance(struct buffer_instance *instance, bool opt) +{ + if (instance != &top_instance) { + if (instance != first_instance) + printf("---------------\n"); + printf("Instance: %s\n", + tracefs_instance_get_name(instance->tracefs)); + } + + report_file(instance, "current_tracer", "nop", "Tracer: "); + report_events(instance); + report_event_filters(instance); + report_event_triggers(instance); + report_ftrace_filters(instance); + report_graph_funcs(instance); + report_buffers(instance); + report_clock(instance); + report_cpumask(instance); + report_file(instance, "tracing_max_latency", "0", "Max Latency: "); + report_kprobes(instance); + report_uprobes(instance); + report_file(instance, "set_event_pid", "", "Filtered event PIDs:\n"); + report_file(instance, "set_ftrace_pid", "no pid", + "Filtered function tracer PIDs:\n"); + if (opt) { + printf("\nOptions:\n"); + show_options(" ", instance); + } + report_traceon(instance); + report_file(instance, "error_log", "", "Error log:\n"); + if (instance == &top_instance) + report_instances(); +} + +void trace_stat (int argc, char **argv) +{ + struct buffer_instance *instance = &top_instance; + bool opt = false; + int topt = 0; + int status; + int c; + + init_top_instance(); + + for (;;) { + c = getopt(argc-1, argv+1, "htoB:"); + 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, tracecmd_count_cpus()); + /* top instance requires direct access */ + if (!topt && is_top_instance(first_instance)) + first_instance = instance; + break; + case 't': + /* Force to use top instance */ + topt = 1; + instance = &top_instance; + break; + case 'o': + opt = 1; + break; + default: + usage(argv); + } + } + + update_first_instance(instance, topt); + + for_all_instances(instance) { + stat_instance(instance, opt); + } + + if (tracecmd_stack_tracer_status(&status) >= 0) { + if (status > 0) + printf("Stack tracing is enabled\n\n"); + } else { + printf("Error reading stack tracer status\n\n"); + } + + exit(0); +} |