blob: 9564c81a5c99856b0212fa294b7a535f4316d0db [file] [log] [blame]
// 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;
}