| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| |
| #include "tracefs.h" |
| #include "trace-local.h" |
| |
| |
| static void dump_file_content(const char *path) |
| { |
| char buf[BUFSIZ]; |
| ssize_t n; |
| FILE *fp; |
| |
| fp = fopen(path, "r"); |
| if (!fp) |
| die("reading %s", path); |
| |
| do { |
| n = fread(buf, 1, BUFSIZ, fp); |
| if (n > 0) |
| fwrite(buf, 1, n, stdout); |
| } while (n > 0); |
| fclose(fp); |
| } |
| |
| |
| |
| void show_instance_file(struct buffer_instance *instance, const char *name) |
| { |
| char *path; |
| |
| path = tracefs_instance_get_file(instance->tracefs, name); |
| dump_file_content(path); |
| tracefs_put_tracing_file(path); |
| } |
| |
| enum { |
| SHOW_EVENT_FORMAT = 1 << 0, |
| SHOW_EVENT_FILTER = 1 << 1, |
| SHOW_EVENT_TRIGGER = 1 << 2, |
| SHOW_EVENT_FULL = 1 << 3, |
| }; |
| |
| |
| void show_file(const char *name) |
| { |
| char *path; |
| |
| path = tracefs_get_tracing_file(name); |
| dump_file_content(path); |
| tracefs_put_tracing_file(path); |
| } |
| |
| typedef int (*process_file_func)(char *buf, int len, int flags); |
| |
| static void process_file_re(process_file_func func, |
| const char *name, const char *re, int flags) |
| { |
| regex_t reg; |
| char *path; |
| char *buf = NULL; |
| char *str; |
| FILE *fp; |
| ssize_t n; |
| size_t l = strlen(re); |
| |
| /* Just in case :-p */ |
| if (!re || l == 0) { |
| show_file(name); |
| return; |
| } |
| |
| /* Handle the newline at end of names for the user */ |
| str = malloc(l + 3); |
| if (!str) |
| die("Failed to allocate reg ex %s", re); |
| strcpy(str, re); |
| if (re[l-1] == '$') |
| strcpy(&str[l-1], "\n*$"); |
| |
| if (regcomp(®, str, REG_ICASE|REG_NOSUB)) |
| die("invalid function regex '%s'", re); |
| |
| free(str); |
| |
| path = tracefs_get_tracing_file(name); |
| fp = fopen(path, "r"); |
| if (!fp) |
| die("reading %s", path); |
| tracefs_put_tracing_file(path); |
| |
| do { |
| n = getline(&buf, &l, fp); |
| if (n > 0 && regexec(®, buf, 0, NULL, 0) == 0) |
| func(buf, n, flags); |
| } while (n > 0); |
| free(buf); |
| fclose(fp); |
| |
| regfree(®); |
| } |
| |
| static void show_event(process_file_func func, const char *system, |
| const char *event, int flags) |
| { |
| char *buf; |
| int ret; |
| |
| ret = asprintf(&buf, "%s:%s", system, event); |
| if (ret < 0) |
| die("Can not allocate event"); |
| func(buf, strlen(buf), flags); |
| free(buf); |
| } |
| |
| static void show_system(process_file_func func, const char *system, int flags) |
| { |
| char **events; |
| int e; |
| |
| events = tracefs_system_events(NULL, system); |
| if (!events) /* die? */ |
| return; |
| |
| for (e = 0; events[e]; e++) |
| show_event(func, system, events[e], flags); |
| } |
| |
| static void show_event_systems(process_file_func func, char **systems, int flags) |
| { |
| int s; |
| |
| for (s = 0; systems[s]; s++) |
| show_system(func, systems[s], flags); |
| } |
| |
| static void match_system_events(process_file_func func, const char *system, |
| regex_t *reg, int flags) |
| { |
| char **events; |
| int e; |
| |
| events = tracefs_system_events(NULL, system); |
| if (!events) /* die? */ |
| return; |
| for (e = 0; events[e]; e++) { |
| if (regexec(reg, events[e], 0, NULL, 0) == 0) |
| show_event(func, system, events[e], flags); |
| } |
| tracefs_list_free(events); |
| } |
| |
| static void process_events(process_file_func func, const char *re, int flags) |
| { |
| const char *ftrace = "ftrace"; |
| regex_t system_reg; |
| regex_t event_reg; |
| char *str; |
| size_t l = strlen(re); |
| bool just_systems = true; |
| char **systems; |
| char *system; |
| char *event; |
| int s; |
| |
| systems = tracefs_event_systems(NULL); |
| if (!systems) |
| return process_file_re(func, "available_events", re, flags); |
| |
| if (!re || l == 0) { |
| show_event_systems(func, systems, flags); |
| return; |
| } |
| |
| str = strdup(re); |
| if (!str) |
| die("Can not allocate momory for regex"); |
| |
| system = strtok(str, ":"); |
| event = strtok(NULL, ""); |
| |
| if (regcomp(&system_reg, system, REG_ICASE|REG_NOSUB)) |
| die("invalid regex '%s'", system); |
| |
| if (event) { |
| if (regcomp(&event_reg, event, REG_ICASE|REG_NOSUB)) |
| die("invalid regex '%s'", event); |
| } else { |
| /* |
| * If the regex ends with ":", then event would be null, |
| * but we do not want to match events. |
| */ |
| if (re[l-1] != ':') |
| just_systems = false; |
| } |
| free(str); |
| |
| /* |
| * See if this matches the special ftrace system, as ftrace is not included |
| * in the systems list, but can get events from tracefs_system_events(). |
| */ |
| if (regexec(&system_reg, ftrace, 0, NULL, 0) == 0) { |
| if (!event) |
| show_system(func, ftrace, flags); |
| else |
| match_system_events(func, ftrace, &event_reg, flags); |
| } else if (!just_systems) { |
| match_system_events(func, ftrace, &system_reg, flags); |
| } |
| |
| for (s = 0; systems[s]; s++) { |
| |
| if (regexec(&system_reg, systems[s], 0, NULL, 0) == 0) { |
| if (!event) { |
| show_system(func, systems[s], flags); |
| continue; |
| } |
| match_system_events(func, systems[s], &event_reg, flags); |
| continue; |
| } |
| if (just_systems) |
| continue; |
| |
| match_system_events(func, systems[s], &system_reg, flags); |
| } |
| tracefs_list_free(systems); |
| |
| regfree(&system_reg); |
| if (event) |
| regfree(&event_reg); |
| } |
| |
| static int show_file_write(char *buf, int len, int flags) |
| { |
| return fwrite(buf, 1, len, stdout); |
| } |
| |
| static void show_file_re(const char *name, const char *re) |
| { |
| process_file_re(show_file_write, name, re, 0); |
| } |
| |
| static char *get_event_file(const char *type, char *buf, int len) |
| { |
| char *system; |
| char *event; |
| char *path; |
| char *file; |
| int ret; |
| |
| if (buf[len-1] == '\n') |
| buf[len-1] = '\0'; |
| |
| system = strtok(buf, ":"); |
| if (!system) |
| die("no system found in %s", buf); |
| |
| event = strtok(NULL, ":"); |
| if (!event) |
| die("no event found in %s\n", buf); |
| |
| path = tracefs_get_tracing_file("events"); |
| ret = asprintf(&file, "%s/%s/%s/%s", path, system, event, type); |
| if (ret < 0) |
| die("Failed to allocate event file %s %s", system, event); |
| |
| tracefs_put_tracing_file(path); |
| |
| return file; |
| } |
| |
| static int event_filter_write(char *buf, int len, int flags) |
| { |
| char *file; |
| |
| if (buf[len-1] == '\n') |
| buf[len-1] = '\0'; |
| |
| printf("%s\n", buf); |
| |
| file = get_event_file("filter", buf, len); |
| dump_file_content(file); |
| free(file); |
| printf("\n"); |
| |
| return 0; |
| } |
| |
| static int event_trigger_write(char *buf, int len, int flags) |
| { |
| char *file; |
| |
| if (buf[len-1] == '\n') |
| buf[len-1] = '\0'; |
| |
| printf("%s\n", buf); |
| |
| file = get_event_file("trigger", buf, len); |
| dump_file_content(file); |
| free(file); |
| printf("\n"); |
| |
| return 0; |
| } |
| |
| static int event_format_write(char *fbuf, int len, int flags) |
| { |
| char *file = get_event_file("format", fbuf, len); |
| char *buf = NULL; |
| size_t l; |
| FILE *fp; |
| bool full; |
| int n; |
| |
| full = flags & SHOW_EVENT_FULL; |
| |
| /* The get_event_file() crops system in fbuf */ |
| printf("system: %s\n", fbuf); |
| |
| /* Don't print the print fmt, it's ugly */ |
| |
| fp = fopen(file, "r"); |
| if (!fp) |
| die("reading %s", file); |
| |
| do { |
| n = getline(&buf, &l, fp); |
| if (n > 0) { |
| if (!full && strncmp(buf, "print fmt", 9) == 0) |
| break; |
| fwrite(buf, 1, n, stdout); |
| } |
| } while (n > 0); |
| fclose(fp); |
| free(buf); |
| free(file); |
| |
| return 0; |
| } |
| |
| static int event_name(char *buf, int len, int flags) |
| { |
| printf("%s\n", buf); |
| |
| return 0; |
| } |
| |
| static void show_event_filter_re(const char *re) |
| { |
| process_events(event_filter_write, re, 0); |
| } |
| |
| |
| static void show_event_trigger_re(const char *re) |
| { |
| process_events(event_trigger_write, re, 0); |
| } |
| |
| |
| static void show_event_format_re(const char *re, int flags) |
| { |
| process_events(event_format_write, re, flags); |
| } |
| |
| static void show_event_names_re(const char *re) |
| { |
| process_events(event_name, re, 0); |
| } |
| |
| static void show_events(const char *eventre, int flags) |
| { |
| if (flags && !eventre) |
| die("When specifying event files, an event must be named"); |
| |
| if (eventre) { |
| if (flags & SHOW_EVENT_FORMAT) |
| show_event_format_re(eventre, flags); |
| |
| else if (flags & SHOW_EVENT_FILTER) |
| show_event_filter_re(eventre); |
| |
| else if (flags & SHOW_EVENT_TRIGGER) |
| show_event_trigger_re(eventre); |
| else |
| show_event_names_re(eventre); |
| } else |
| show_file("available_events"); |
| } |
| |
| |
| static void show_tracers(void) |
| { |
| show_file("available_tracers"); |
| } |
| |
| void show_options(const char *prefix, struct buffer_instance *buffer) |
| { |
| struct tracefs_instance *instance = buffer ? buffer->tracefs : NULL; |
| struct dirent *dent; |
| struct stat st; |
| char *path; |
| DIR *dir; |
| |
| if (!prefix) |
| prefix = ""; |
| |
| path = tracefs_instance_get_file(instance, "options"); |
| if (!path) |
| goto show_file; |
| if (stat(path, &st) < 0) |
| goto show_file; |
| |
| if ((st.st_mode & S_IFMT) != S_IFDIR) |
| goto show_file; |
| |
| dir = opendir(path); |
| if (!dir) |
| die("Can not read instance directory"); |
| |
| while ((dent = readdir(dir))) { |
| const char *name = dent->d_name; |
| long long val; |
| char *file; |
| int ret; |
| |
| if (strcmp(name, ".") == 0 || |
| strcmp(name, "..") == 0) |
| continue; |
| |
| ret = asprintf(&file, "options/%s", name); |
| if (ret < 0) |
| die("Failed to allocate file name"); |
| ret = tracefs_instance_file_read_number(instance, file, &val); |
| if (!ret) { |
| if (val) |
| printf("%s%s\n", prefix, name); |
| else |
| printf("%sno%s\n", prefix, name); |
| } |
| free(file); |
| } |
| closedir(dir); |
| tracefs_put_tracing_file(path); |
| return; |
| |
| show_file: |
| tracefs_put_tracing_file(path); |
| show_file("trace_options"); |
| } |
| |
| static void show_clocks(void) |
| { |
| char *clocks; |
| int size; |
| |
| clocks = tracefs_instance_file_read(NULL, "trace_clock", &size); |
| if (!clocks) |
| die("getting clocks"); |
| if (clocks[size - 1] == '\n') |
| clocks[size - 1] = 0; |
| |
| if (trace_tsc2nsec_is_supported()) |
| printf("%s %s\n", clocks, TSCNSEC_CLOCK); |
| else |
| printf("%s\n", clocks); |
| |
| free(clocks); |
| } |
| |
| |
| static void show_functions(const char *funcre) |
| { |
| if (funcre) |
| show_file_re("available_filter_functions", funcre); |
| else |
| show_file("available_filter_functions"); |
| } |
| |
| |
| static void show_buffers(void) |
| { |
| struct dirent *dent; |
| DIR *dir; |
| char *path; |
| int printed = 0; |
| |
| path = tracefs_get_tracing_file("instances"); |
| dir = opendir(path); |
| tracefs_put_tracing_file(path); |
| if (!dir) |
| die("Can not read instance directory"); |
| |
| while ((dent = readdir(dir))) { |
| const char *name = dent->d_name; |
| |
| if (strcmp(name, ".") == 0 || |
| strcmp(name, "..") == 0) |
| continue; |
| |
| printf("%s\n", name); |
| printed = 1; |
| } |
| closedir(dir); |
| |
| if (!printed) |
| printf("No buffer instances defined\n"); |
| } |
| |
| |
| static void show_systems(void) |
| { |
| struct dirent *dent; |
| char *path; |
| DIR *dir; |
| |
| path = tracefs_get_tracing_file("events"); |
| dir = opendir(path); |
| |
| if (!dir) |
| die("Can not read events directory"); |
| |
| while ((dent = readdir(dir))) { |
| const char *name = dent->d_name; |
| struct stat st; |
| char *spath; |
| int ret; |
| |
| if (strcmp(name, ".") == 0 || |
| strcmp(name, "..") == 0) |
| continue; |
| |
| if (asprintf(&spath, "%s/%s", path, name) < 0) |
| continue; |
| ret = stat(spath, &st); |
| if (!ret && S_ISDIR(st.st_mode)) |
| printf("%s\n", name); |
| |
| free(spath); |
| } |
| |
| printf("\n"); |
| closedir(dir); |
| tracefs_put_tracing_file(path); |
| } |
| |
| static void show_plugin_options(void) |
| { |
| struct tep_handle *pevent; |
| struct tep_plugin_list *list; |
| struct trace_seq s; |
| |
| tracecmd_ftrace_load_options(); |
| |
| pevent = tep_alloc(); |
| if (!pevent) |
| die("Can not allocate pevent\n"); |
| |
| trace_seq_init(&s); |
| |
| list = trace_load_plugins(pevent, 0); |
| tep_plugin_print_options(&s); |
| trace_seq_do_printf(&s); |
| tep_unload_plugins(list, pevent); |
| tep_free(pevent); |
| } |
| |
| |
| void trace_option(int argc, char **argv) |
| { |
| show_plugin_options(); |
| } |
| |
| |
| static void show_plugins(void) |
| { |
| struct tep_handle *pevent; |
| struct tep_plugin_list *list; |
| struct trace_seq s; |
| |
| pevent = tep_alloc(); |
| if (!pevent) |
| die("Can not allocate pevent\n"); |
| |
| trace_seq_init(&s); |
| |
| list = trace_load_plugins(pevent, 0); |
| tep_print_plugins(&s, " ", "\n", list); |
| |
| trace_seq_do_printf(&s); |
| tep_unload_plugins(list, pevent); |
| tep_free(pevent); |
| } |
| |
| static void show_compression(void) |
| { |
| char **versions, **names; |
| int c, i; |
| |
| c = tracecmd_compress_protos_get(&names, &versions); |
| if (c <= 0) { |
| printf("No compression algorithms are supported\n"); |
| return; |
| } |
| printf("Supported compression algorithms:\n"); |
| for (i = 0; i < c; i++) |
| printf("\t%s, %s\n", names[i], versions[i]); |
| |
| free(names); |
| free(versions); |
| } |
| |
| void trace_list(int argc, char **argv) |
| { |
| int events = 0; |
| int tracer = 0; |
| int options = 0; |
| int funcs = 0; |
| int buffers = 0; |
| int clocks = 0; |
| int plug = 0; |
| int plug_op = 0; |
| int flags = 0; |
| int systems = 0; |
| int show_all = 1; |
| int compression = 0; |
| int i; |
| const char *arg; |
| const char *funcre = NULL; |
| const char *eventre = NULL; |
| |
| for (i = 2; i < argc; i++) { |
| arg = NULL; |
| if (argv[i][0] == '-') { |
| if (i < argc - 1) { |
| if (argv[i+1][0] != '-') |
| arg = argv[i+1]; |
| } |
| switch (argv[i][1]) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'e': |
| events = 1; |
| eventre = arg; |
| show_all = 0; |
| break; |
| case 'B': |
| buffers = 1; |
| show_all = 0; |
| break; |
| case 'C': |
| clocks = 1; |
| show_all = 0; |
| break; |
| case 'F': |
| flags |= SHOW_EVENT_FORMAT; |
| break; |
| case 'R': |
| flags |= SHOW_EVENT_TRIGGER; |
| break; |
| case 'l': |
| flags |= SHOW_EVENT_FILTER; |
| break; |
| case 'p': |
| case 't': |
| tracer = 1; |
| show_all = 0; |
| break; |
| case 'P': |
| plug = 1; |
| show_all = 0; |
| break; |
| case 'O': |
| plug_op = 1; |
| show_all = 0; |
| break; |
| case 'o': |
| options = 1; |
| show_all = 0; |
| break; |
| case 'f': |
| funcs = 1; |
| funcre = arg; |
| show_all = 0; |
| break; |
| case 's': |
| systems = 1; |
| show_all = 0; |
| break; |
| case 'c': |
| compression = 1; |
| show_all = 0; |
| break; |
| case '-': |
| if (strcmp(argv[i], "--debug") == 0) { |
| tracecmd_set_debug(true); |
| break; |
| } |
| if (strcmp(argv[i], "--full") == 0) { |
| flags |= SHOW_EVENT_FULL; |
| break; |
| } |
| fprintf(stderr, "list: invalid option -- '%s'\n", |
| argv[i]); |
| default: |
| fprintf(stderr, "list: invalid option -- '%c'\n", |
| argv[i][1]); |
| usage(argv); |
| } |
| } |
| } |
| |
| if (events) |
| show_events(eventre, flags); |
| |
| if (tracer) |
| show_tracers(); |
| |
| if (options) |
| show_options(NULL, NULL); |
| |
| if (plug) |
| show_plugins(); |
| |
| if (plug_op) |
| show_plugin_options(); |
| |
| if (funcs) |
| show_functions(funcre); |
| |
| if (buffers) |
| show_buffers(); |
| |
| if (clocks) |
| show_clocks(); |
| if (systems) |
| show_systems(); |
| if (compression) |
| show_compression(); |
| if (show_all) { |
| printf("event systems:\n"); |
| show_systems(); |
| printf("events:\n"); |
| show_events(NULL, 0); |
| printf("\ntracers:\n"); |
| show_tracers(); |
| printf("\noptions:\n"); |
| show_options(NULL, NULL); |
| show_compression(); |
| } |
| |
| return; |
| |
| } |