You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
693 lines
14 KiB
693 lines
14 KiB
// SPDX-License-Identifier: LGPL-2.1
|
|
/*
|
|
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
*/
|
|
#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;
|
|
}
|