| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <dirent.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <limits.h> |
| #include <libgen.h> |
| #include <sys/mount.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/sysinfo.h> |
| #include <time.h> |
| #include <event-parse.h> |
| #include <event-utils.h> |
| |
| #include "trace-cmd-private.h" |
| #include "trace-cmd-local.h" |
| |
| #define LOCAL_PLUGIN_DIR ".trace-cmd/plugins" |
| #define PROC_STACK_FILE "/proc/sys/kernel/stack_tracer_enabled" |
| |
| static bool debug; |
| static int log_level = TEP_LOG_INFO; |
| static FILE *logfp; |
| |
| const static struct { |
| const char *clock_str; |
| enum tracecmd_clocks clock_id; |
| } trace_clocks[] = { |
| {"local", TRACECMD_CLOCK_LOCAL}, |
| {"global", TRACECMD_CLOCK_GLOBAL}, |
| {"counter", TRACECMD_CLOCK_COUNTER}, |
| {"uptime", TRACECMD_CLOCK_UPTIME}, |
| {"perf", TRACECMD_CLOCK_PERF}, |
| {"mono", TRACECMD_CLOCK_MONO}, |
| {"mono_raw", TRACECMD_CLOCK_MONO_RAW}, |
| {"boot", TRACECMD_CLOCK_BOOT}, |
| {"x86-tsc", TRACECMD_CLOCK_X86_TSC}, |
| {NULL, -1} |
| }; |
| |
| /** |
| * tracecmd_clock_str2id - Convert ftrace clock name to clock ID |
| * @clock: Ftrace clock name |
| * Returns ID of the ftrace clock |
| */ |
| enum tracecmd_clocks tracecmd_clock_str2id(const char *clock) |
| { |
| int i; |
| |
| if (!clock) |
| return TRACECMD_CLOCK_UNKNOWN; |
| |
| for (i = 0; trace_clocks[i].clock_str; i++) { |
| if (!strncmp(clock, trace_clocks[i].clock_str, |
| strlen(trace_clocks[i].clock_str))) |
| return trace_clocks[i].clock_id; |
| } |
| return TRACECMD_CLOCK_UNKNOWN; |
| } |
| |
| /** |
| * tracecmd_clock_id2str - Convert clock ID to ftare clock name |
| * @clock: Clock ID |
| * Returns name of a ftrace clock |
| */ |
| const char *tracecmd_clock_id2str(enum tracecmd_clocks clock) |
| { |
| int i; |
| |
| for (i = 0; trace_clocks[i].clock_str; i++) { |
| if (trace_clocks[i].clock_id == clock) |
| return trace_clocks[i].clock_str; |
| } |
| return NULL; |
| } |
| |
| /** |
| * tracecmd_set_debug - Set debug mode of the tracecmd library |
| * @set_debug: The new "debug" mode. If true, the tracecmd library is |
| * in "debug" mode |
| */ |
| void tracecmd_set_debug(bool set_debug) |
| { |
| debug = set_debug; |
| |
| if (set_debug) |
| tracecmd_set_loglevel(TEP_LOG_DEBUG); |
| else |
| tracecmd_set_loglevel(TEP_LOG_CRITICAL); |
| } |
| |
| /** |
| * tracecmd_get_debug - Get debug mode of tracecmd library |
| * Returns true, if the tracecmd library is in debug mode. |
| * |
| */ |
| bool tracecmd_get_debug(void) |
| { |
| return debug; |
| } |
| |
| void tracecmd_parse_cmdlines(struct tep_handle *pevent, |
| char *file, int size __maybe_unused) |
| { |
| char *comm; |
| char *line; |
| char *next = NULL; |
| int pid; |
| |
| line = strtok_r(file, "\n", &next); |
| while (line) { |
| sscanf(line, "%d %m[^\n]s", &pid, &comm); |
| tep_register_comm(pevent, comm, pid); |
| free(comm); |
| line = strtok_r(NULL, "\n", &next); |
| } |
| } |
| |
| void tracecmd_parse_proc_kallsyms(struct tep_handle *pevent, |
| char *file, unsigned int size __maybe_unused) |
| { |
| unsigned long long addr; |
| int sav_errno; |
| char *func; |
| char *line; |
| char *next = NULL; |
| char *mod; |
| char ch; |
| |
| line = strtok_r(file, "\n", &next); |
| while (line) { |
| int func_start, func_end = 0; |
| int mod_start, mod_end = 0; |
| int n; |
| |
| mod = NULL; |
| sav_errno = errno; |
| errno = 0; |
| n = sscanf(line, "%16llx %c %n%*s%n%*1[\t][%n%*s%n", |
| &addr, &ch, &func_start, &func_end, &mod_start, &mod_end); |
| if (errno) |
| return; |
| errno = sav_errno; |
| |
| if (n != 2 || !func_end) |
| return; |
| |
| func = line + func_start; |
| /* |
| * Hacks for |
| * - arm arch that adds a lot of bogus '$a' functions |
| * - x86-64 that reports per-cpu variable offsets as absolute |
| */ |
| if (func[0] != '$' && ch != 'A' && ch != 'a') { |
| line[func_end] = 0; |
| if (mod_end) { |
| mod = line + mod_start; |
| /* truncate the extra ']' */ |
| line[mod_end - 1] = 0; |
| } |
| tep_register_function(pevent, func, addr, mod); |
| } |
| |
| line = strtok_r(NULL, "\n", &next); |
| } |
| } |
| |
| void tracecmd_parse_ftrace_printk(struct tep_handle *pevent, |
| char *file, unsigned int size __maybe_unused) |
| { |
| unsigned long long addr; |
| char *printk; |
| char *line; |
| char *next = NULL; |
| char *addr_str; |
| char *fmt; |
| |
| line = strtok_r(file, "\n", &next); |
| while (line) { |
| addr_str = strtok_r(line, ":", &fmt); |
| if (!addr_str) { |
| tracecmd_warning("printk format with empty entry"); |
| break; |
| } |
| addr = strtoull(addr_str, NULL, 16); |
| /* fmt still has a space, skip it */ |
| printk = strdup(fmt+1); |
| line = strtok_r(NULL, "\n", &next); |
| tep_register_print_string(pevent, printk, addr); |
| free(printk); |
| } |
| } |
| |
| /** |
| * tracecmd_add_id - add an int to the event id list |
| * @list: list to add the id to |
| * @id: id to add |
| * @len: current length of list of ids. |
| * |
| * The typical usage is: |
| * |
| * events = tracecmd_add_id(events, id, len++); |
| * |
| * Returns the new allocated list with the id included. |
| * the list will contain a '-1' at the end. |
| * |
| * The returned list should be freed with free(). |
| */ |
| int *tracecmd_add_id(int *list, int id, int len) |
| { |
| if (!list) |
| list = malloc(sizeof(*list) * 2); |
| else |
| list = realloc(list, sizeof(*list) * (len + 2)); |
| if (!list) |
| return NULL; |
| |
| list[len++] = id; |
| list[len] = -1; |
| |
| return list; |
| } |
| |
| struct add_plugin_data { |
| int ret; |
| int index; |
| char **files; |
| }; |
| |
| static void add_plugin_file(struct tep_handle *pevent, const char *path, |
| const char *name, void *data) |
| { |
| struct add_plugin_data *pdata = data; |
| char **ptr; |
| int size; |
| int i; |
| |
| if (pdata->ret) |
| return; |
| |
| size = pdata->index + 2; |
| ptr = realloc(pdata->files, sizeof(char *) * size); |
| if (!ptr) |
| goto out_free; |
| |
| ptr[pdata->index] = strdup(name); |
| if (!ptr[pdata->index]) |
| goto out_free; |
| |
| pdata->files = ptr; |
| pdata->index++; |
| pdata->files[pdata->index] = NULL; |
| return; |
| |
| out_free: |
| for (i = 0; i < pdata->index; i++) |
| free(pdata->files[i]); |
| free(pdata->files); |
| pdata->files = NULL; |
| pdata->ret = errno; |
| } |
| |
| /** |
| * trace_util_find_plugin_files - find list of possible plugin files |
| * @suffix: The suffix of the plugin files to find |
| * |
| * Searches the plugin directory for files that end in @suffix, and |
| * will return an allocated array of file names, or NULL if none is |
| * found. |
| * |
| * Must check against TRACECMD_ISERR(ret) as if an error happens |
| * the errno will be returned with the TRACECMD_ERR_MSK to denote |
| * such an error occurred. |
| * |
| * Use trace_util_free_plugin_files() to free the result. |
| */ |
| __hidden char **trace_util_find_plugin_files(const char *suffix) |
| { |
| struct add_plugin_data pdata; |
| |
| memset(&pdata, 0, sizeof(pdata)); |
| |
| tep_load_plugins_hook(NULL, suffix, add_plugin_file, &pdata); |
| |
| if (pdata.ret) |
| return TRACECMD_ERROR(pdata.ret); |
| |
| return pdata.files; |
| } |
| |
| /** |
| * trace_util_free_plugin_files - free the result of trace_util_find_plugin_files() |
| * @files: The result from trace_util_find_plugin_files() |
| * |
| * Frees the contents that were allocated by trace_util_find_plugin_files(). |
| */ |
| void __hidden trace_util_free_plugin_files(char **files) |
| { |
| int i; |
| |
| if (!files || TRACECMD_ISERR(files)) |
| return; |
| |
| for (i = 0; files[i]; i++) { |
| free(files[i]); |
| } |
| free(files); |
| } |
| |
| static char *get_source_plugins_dir(void) |
| { |
| char *p, path[PATH_MAX+1]; |
| int ret; |
| |
| ret = readlink("/proc/self/exe", path, PATH_MAX); |
| if (ret > PATH_MAX || ret < 0) |
| return NULL; |
| |
| path[ret] = 0; |
| dirname(path); |
| p = strrchr(path, '/'); |
| if (!p) |
| return NULL; |
| /* Check if we are in the the source tree */ |
| if (strcmp(p, "/tracecmd") != 0) |
| return NULL; |
| |
| strcpy(p, "/lib/traceevent/plugins"); |
| return strdup(path); |
| } |
| |
| __hidden struct tep_plugin_list * |
| trace_load_plugins(struct tep_handle *tep, int flags) |
| { |
| struct tep_plugin_list *list; |
| char *path; |
| |
| if (flags & TRACECMD_FL_LOAD_NO_PLUGINS) |
| tep_set_flag(tep, TEP_DISABLE_PLUGINS); |
| if (flags & TRACECMD_FL_LOAD_NO_SYSTEM_PLUGINS) |
| tep_set_flag(tep, TEP_DISABLE_SYS_PLUGINS); |
| |
| path = get_source_plugins_dir(); |
| if (path) |
| tep_add_plugin_path(tep, path, TEP_PLUGIN_LAST); |
| free(path); |
| |
| list = tep_load_plugins(tep); |
| |
| return list; |
| } |
| |
| /** |
| * tracecmd_set_loglevel - set log level of the library |
| * @level: desired level of the library messages |
| */ |
| void tracecmd_set_loglevel(enum tep_loglevel level) |
| { |
| log_level = level; |
| tracefs_set_loglevel(level); |
| tep_set_loglevel(level); |
| } |
| |
| void __weak tracecmd_warning(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (log_level < TEP_LOG_WARNING) |
| return; |
| |
| va_start(ap, fmt); |
| tep_vprint("libtracecmd", TEP_LOG_WARNING, true, fmt, ap); |
| va_end(ap); |
| } |
| |
| void __weak tracecmd_info(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (log_level < TEP_LOG_INFO) |
| return; |
| |
| va_start(ap, fmt); |
| tep_vprint("libtracecmd", TEP_LOG_INFO, false, fmt, ap); |
| va_end(ap); |
| } |
| |
| void __weak tracecmd_critical(const char *fmt, ...) |
| { |
| int ret; |
| va_list ap; |
| |
| if (log_level < TEP_LOG_CRITICAL) |
| return; |
| |
| va_start(ap, fmt); |
| ret = tep_vprint("libtracecmd", TEP_LOG_CRITICAL, true, fmt, ap); |
| va_end(ap); |
| |
| if (debug) { |
| if (!ret) |
| ret = -1; |
| exit(ret); |
| } |
| } |
| |
| void __weak tracecmd_debug(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!tracecmd_get_debug()) |
| return; |
| |
| va_start(ap, fmt); |
| vprintf(fmt, ap); |
| va_end(ap); |
| } |
| |
| #define LOG_BUF_SIZE 1024 |
| static void __plog(const char *prefix, const char *fmt, va_list ap, FILE *fp) |
| { |
| static int newline = 1; |
| char buf[LOG_BUF_SIZE]; |
| int r; |
| |
| r = vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); |
| |
| if (r > LOG_BUF_SIZE) |
| r = LOG_BUF_SIZE; |
| |
| if (logfp) { |
| if (newline) |
| fprintf(logfp, "[%d]%s%.*s", getpid(), prefix, r, buf); |
| else |
| fprintf(logfp, "[%d]%s%.*s", getpid(), prefix, r, buf); |
| newline = buf[r - 1] == '\n'; |
| fflush(logfp); |
| return; |
| } |
| |
| fprintf(fp, "%.*s", r, buf); |
| } |
| |
| void tracecmd_plog(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, fmt); |
| __plog("", fmt, ap, stdout); |
| va_end(ap); |
| /* Make sure it gets to the screen, in case we crash afterward */ |
| fflush(stdout); |
| } |
| |
| void tracecmd_plog_error(const char *fmt, ...) |
| { |
| va_list ap; |
| char *str = ""; |
| |
| va_start(ap, fmt); |
| __plog("Error: ", fmt, ap, stderr); |
| va_end(ap); |
| if (errno) |
| str = strerror(errno); |
| if (logfp) |
| fprintf(logfp, "\n%s\n", str); |
| else |
| fprintf(stderr, "\n%s\n", str); |
| } |
| |
| /** |
| * tracecmd_set_logfile - Set file for logging |
| * @logfile: Name of the log file |
| * |
| * Returns 0 on successful completion or -1 in case of error |
| */ |
| int tracecmd_set_logfile(char *logfile) |
| { |
| if (logfp) |
| fclose(logfp); |
| logfp = fopen(logfile, "w"); |
| if (!logfp) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * tracecmd_stack_tracer_status - Check stack trace status |
| * @status: Returned stack trace status: |
| * 0 - not configured, disabled |
| * non 0 - enabled |
| * |
| * Returns -1 in case of an error, 0 if file does not exist |
| * (stack tracer not configured in kernel) or 1 on successful completion. |
| */ |
| int tracecmd_stack_tracer_status(int *status) |
| { |
| struct stat stat_buf; |
| char buf[64]; |
| long num; |
| int fd; |
| int n; |
| |
| if (stat(PROC_STACK_FILE, &stat_buf) < 0) { |
| /* stack tracer not configured on running kernel */ |
| *status = 0; /* not configured means disabled */ |
| return 0; |
| } |
| |
| fd = open(PROC_STACK_FILE, O_RDONLY); |
| |
| if (fd < 0) |
| return -1; |
| |
| n = read(fd, buf, sizeof(buf)); |
| close(fd); |
| |
| if (n <= 0) |
| return -1; |
| |
| if (n >= sizeof(buf)) |
| return -1; |
| |
| buf[n] = 0; |
| |
| num = strtol(buf, NULL, 10); |
| |
| /* Check for various possible errors */ |
| if (num > INT_MAX || num < INT_MIN || (!num && errno)) |
| return -1; |
| |
| *status = num; |
| return 1; /* full success */ |
| } |
| |
| /** |
| * tracecmd_count_cpus - Get the number of CPUs in the system |
| * |
| * Returns the number of CPUs in the system, or 0 in case of an error |
| */ |
| int tracecmd_count_cpus(void) |
| { |
| static int once; |
| char buf[1024]; |
| int cpus = 0; |
| char *pbuf; |
| size_t *pn; |
| FILE *fp; |
| size_t n; |
| int r; |
| |
| cpus = sysconf(_SC_NPROCESSORS_CONF); |
| if (cpus > 0) |
| return cpus; |
| |
| if (!once) { |
| once++; |
| tracecmd_warning("sysconf could not determine number of CPUS"); |
| } |
| |
| /* Do the hack to figure out # of CPUS */ |
| n = 1024; |
| pn = &n; |
| pbuf = buf; |
| |
| fp = fopen("/proc/cpuinfo", "r"); |
| if (!fp) { |
| tracecmd_critical("Can not read cpuinfo"); |
| return 0; |
| } |
| |
| while ((r = getline(&pbuf, pn, fp)) >= 0) { |
| char *p; |
| |
| if (strncmp(buf, "processor", 9) != 0) |
| continue; |
| for (p = buf+9; isspace(*p); p++) |
| ; |
| if (*p == ':') |
| cpus++; |
| } |
| fclose(fp); |
| |
| return cpus; |
| } |
| |
| #define FNV_64_PRIME 0x100000001b3ULL |
| /* |
| * tracecmd_generate_traceid - Generate a unique ID, used to identify |
| * the current tracing session |
| * |
| * Returns unique ID |
| */ |
| unsigned long long tracecmd_generate_traceid(void) |
| { |
| unsigned long long hash = 0; |
| unsigned char *ustr; |
| struct sysinfo sinfo; |
| struct timespec ts; |
| char *str = NULL; |
| |
| clock_gettime(CLOCK_MONOTONIC_RAW, &ts); |
| sysinfo(&sinfo); |
| asprintf(&str, "%ld %ld %ld %ld %ld %ld %ld %ld %d", |
| ts.tv_sec, ts.tv_nsec, |
| sinfo.loads[0], sinfo.loads[1], sinfo.loads[2], |
| sinfo.freeram, sinfo.sharedram, sinfo.freeswap, |
| sinfo.procs); |
| if (!str) |
| return 0; |
| ustr = (unsigned char *)str; |
| hash = 0; |
| while (*ustr) { |
| hash ^= (unsigned long long)*ustr++; |
| hash *= FNV_64_PRIME; |
| } |
| |
| free(str); |
| return hash; |
| } |
| |
| /* |
| * tracecmd_default_file_version - Get default trace file version of the library |
| * |
| * Returns the default trace file version |
| */ |
| int tracecmd_default_file_version(void) |
| { |
| return FILE_VERSION_DEFAULT; |
| } |
| |
| bool tracecmd_is_version_supported(unsigned int version) |
| { |
| if (version <= FILE_VERSION_MAX) |
| return true; |
| return false; |
| } |
| |
| static void __attribute__ ((constructor)) tracecmd_lib_init(void) |
| { |
| tracecmd_compress_init(); |
| } |
| |
| static void __attribute__((destructor)) tracecmd_lib_free(void) |
| { |
| tracecmd_compress_free(); |
| } |
| |
| __hidden bool check_file_state(unsigned long file_version, int current_state, int new_state) |
| { |
| if (file_version >= FILE_VERSION_SECTIONS) { |
| if (current_state < TRACECMD_FILE_INIT) |
| return false; |
| |
| return true; |
| } |
| |
| switch (new_state) { |
| case TRACECMD_FILE_HEADERS: |
| case TRACECMD_FILE_FTRACE_EVENTS: |
| case TRACECMD_FILE_ALL_EVENTS: |
| case TRACECMD_FILE_KALLSYMS: |
| case TRACECMD_FILE_PRINTK: |
| case TRACECMD_FILE_CMD_LINES: |
| case TRACECMD_FILE_CPU_COUNT: |
| if (current_state == (new_state - 1)) |
| return true; |
| break; |
| case TRACECMD_FILE_OPTIONS: |
| if (file_version < FILE_VERSION_SECTIONS && current_state == TRACECMD_FILE_CPU_COUNT) |
| return true; |
| break; |
| case TRACECMD_FILE_CPU_LATENCY: |
| case TRACECMD_FILE_CPU_FLYRECORD: |
| if (current_state == TRACECMD_FILE_OPTIONS) |
| return true; |
| break; |
| } |
| |
| return false; |
| } |