| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2014 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| #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); |
| } |