blob: 970e6bca38b742c905499c60f9a1a48257b3bb59 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License (not later!)
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define _GNU_SOURCE
#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 <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "trace-cmd.h"
#define LOCAL_PLUGIN_DIR ".trace-cmd/plugins"
#define DEBUGFS_PATH "/sys/kernel/debug"
int tracecmd_disable_sys_plugins;
int tracecmd_disable_plugins;
#define _STR(x) #x
#define STR(x) _STR(x)
#ifndef MAX_PATH
# define MAX_PATH 1024
#endif
struct plugin_list {
struct plugin_list *next;
char *name;
void *handle;
};
void parse_cmdlines(struct pevent *pevent,
char *file, int size __unused)
{
char *comm;
char *line;
char *next = NULL;
int pid;
line = strtok_r(file, "\n", &next);
while (line) {
sscanf(line, "%d %as", &pid,
(float *)(void *)&comm); /* workaround gcc warning */
pevent_register_comm(pevent, comm, pid);
free(comm);
line = strtok_r(NULL, "\n", &next);
}
}
void parse_proc_kallsyms(struct pevent *pevent,
char *file, unsigned int size __unused)
{
unsigned long long addr;
char *func;
char *line;
char *next = NULL;
char *addr_str;
char *mod;
char ch;
int ret;
line = strtok_r(file, "\n", &next);
while (line) {
mod = NULL;
ret = sscanf(line, "%as %c %as\t[%as",
(float *)(void *)&addr_str, /* workaround gcc warning */
&ch,
(float *)(void *)&func,
(float *)(void *)&mod);
addr = strtoull(addr_str, NULL, 16);
free(addr_str);
/* truncate the extra ']' */
if (mod)
mod[strlen(mod) - 1] = 0;
pevent_register_function(pevent, func, addr, mod);
free(func);
free(mod);
line = strtok_r(NULL, "\n", &next);
}
}
void parse_ftrace_printk(struct pevent *pevent,
char *file, unsigned int size __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) {
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);
pevent_register_print_string(pevent, printk, addr);
}
}
static void load_plugin(struct pevent *pevent, const char *path,
const char *file, void *data)
{
struct plugin_list **plugin_list = data;
pevent_plugin_load_func func;
struct plugin_list *list;
char *plugin;
void *handle;
plugin = malloc_or_die(strlen(path) + strlen(file) + 2);
strcpy(plugin, path);
strcat(plugin, "/");
strcat(plugin, file);
handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
if (!handle) {
warning("cound not load plugin '%s'\n%s\n",
plugin, dlerror());
goto out_free;
}
func = dlsym(handle, PEVENT_PLUGIN_LOADER_NAME);
if (!func) {
warning("cound not find func '%s' in plugin '%s'\n%s\n",
PEVENT_PLUGIN_LOADER_NAME, plugin, dlerror());
goto out_free;
}
list = malloc_or_die(sizeof(*list));
list->next = *plugin_list;
list->handle = handle;
list->name = plugin;
*plugin_list = list;
pr_stat("registering plugin: %s", plugin);
func(pevent);
return;
out_free:
free(plugin);
}
static int mount_debugfs(void)
{
struct stat st;
int ret;
/* make sure debugfs exists */
ret = stat(DEBUGFS_PATH, &st);
if (ret < 0)
die("debugfs is not configured on this kernel");
ret = mount("nodev", DEBUGFS_PATH,
"debugfs", 0, NULL);
return ret;
}
char *tracecmd_find_tracing_dir(void)
{
char debugfs[MAX_PATH+1];
char *tracing_dir;
char type[100];
FILE *fp;
if ((fp = fopen("/proc/mounts","r")) == NULL) {
warning("Can't open /proc/mounts for read");
return NULL;
}
while (fscanf(fp, "%*s %"
STR(MAX_PATH)
"s %99s %*s %*d %*d\n",
debugfs, type) == 2) {
if (strcmp(type, "debugfs") == 0)
break;
}
fclose(fp);
if (strcmp(type, "debugfs") != 0) {
/* If debugfs is not mounted, try to mount it */
if (mount_debugfs() < 0) {
warning("debugfs not mounted, please mount");
return NULL;
}
strcpy(debugfs, DEBUGFS_PATH);
}
tracing_dir = malloc_or_die(strlen(debugfs) + 9);
if (!tracing_dir)
return NULL;
sprintf(tracing_dir, "%s/tracing", debugfs);
return tracing_dir;
}
static char *append_file(const char *dir, const char *name)
{
char *file;
file = malloc_or_die(strlen(dir) + strlen(name) + 2);
if (!file)
return NULL;
sprintf(file, "%s/%s", dir, name);
return file;
}
/**
* tracecmd_add_list - add an new string to a string list.
* @list: list to add the string to (may be NULL)
* @name: the string to add
* @len: current length of list of strings.
*
* The typical usage is:
*
* systems = tracecmd_add_list(systems, name, len++);
*
* Returns the new allocated list with an allocated name added.
* The list will end with NULL.
*/
char **tracecmd_add_list(char **list, const char *name, int len)
{
if (!list)
list = malloc_or_die(sizeof(*list) * 2);
else {
list = realloc(list, sizeof(*list) * (len + 2));
if (!list)
die("Can not allocate list");
}
list[len] = strdup(name);
if (!list[len])
die("Can not allocate list");
list[len + 1] = NULL;
return list;
}
/**
* tracecmd_free_list - free a list created with tracecmd_add_list.
* @list: The list to free.
*
* Frees the list as well as the names within the list.
*/
void tracecmd_free_list(char **list)
{
int i;
if (!list)
return;
for (i = 0; list[i]; i++)
free(list[i]);
free(list);
}
/**
* 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_or_die(sizeof(*list) * 2);
else {
list = realloc(list, sizeof(*list) * (len + 2));
if (!list)
die("Can ont allocate list");
}
list[len++] = id;
list[len] = -1;
return list;
}
/**
* tracecmd_event_systems - return list of systems for tracing
* @tracing_dir: directory holding the "events" directory
*
* Returns an allocated list of system names. Both the names and
* the list must be freed with free().
* The list returned ends with a "NULL" pointer.
*/
char **tracecmd_event_systems(const char *tracing_dir)
{
struct dirent *dent;
char **systems = NULL;
char *events_dir;
struct stat st;
DIR *dir;
int len = 0;
int ret;
if (!tracing_dir)
return NULL;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
/*
* Search all the directories in the events directory,
* and collect the ones that have the "enable" file.
*/
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
dir = opendir(events_dir);
if (!dir)
goto out_free;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *enable;
char *sys;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
sys = append_file(events_dir, name);
ret = stat(sys, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(sys);
continue;
}
enable = append_file(sys, "enable");
ret = stat(enable, &st);
if (ret >= 0)
systems = tracecmd_add_list(systems, name, len++);
free(enable);
free(sys);
}
closedir(dir);
out_free:
free(events_dir);
return systems;
}
/**
* tracecmd_system_events - return list of events for system
* @tracing_dir: directory holding the "events" directory
* @system: the system to return the events for
*
* Returns an allocated list of event names. Both the names and
* the list must be freed with free().
* The list returned ends with a "NULL" pointer.
*/
char **tracecmd_system_events(const char *tracing_dir, const char *system)
{
struct dirent *dent;
char **events = NULL;
char *events_dir;
char *system_dir;
struct stat st;
DIR *dir;
int len = 0;
int ret;
if (!tracing_dir || !system)
return NULL;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
/*
* Search all the directories in the systems directory,
* and collect the ones that have the "enable" file.
*/
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
system_dir = append_file(events_dir, system);
if (!system_dir)
goto out_free;
ret = stat(system_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free_sys;
dir = opendir(system_dir);
if (!dir)
goto out_free_sys;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *enable;
char *event;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
event = append_file(system_dir, name);
ret = stat(event, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(event);
continue;
}
enable = append_file(event, "enable");
ret = stat(enable, &st);
if (ret >= 0)
events = tracecmd_add_list(events, name, len++);
free(enable);
free(event);
}
closedir(dir);
out_free_sys:
free(system_dir);
out_free:
free(events_dir);
return events;
}
static int read_file(const char *file, char **buffer)
{
char *buf;
int len = 0;
int fd;
int r;
fd = open(file, O_RDONLY);
if (fd < 0)
return -1;
buf = malloc_or_die(BUFSIZ + 1);
while ((r = read(fd, buf + len, BUFSIZ)) > 0) {
len += r;
buf = realloc(buf, len + BUFSIZ + 1);
if (!buf) {
len = -1;
goto out;
}
}
*buffer = buf;
buf[len] = 0;
out:
close(fd);
return len;
}
static void load_events(struct pevent *pevent, const char *system,
const char *sys_dir)
{
struct dirent *dent;
struct stat st;
DIR *dir;
int len = 0;
int ret;
ret = stat(sys_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
return;
dir = opendir(sys_dir);
if (!dir)
return;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *event;
char *format;
char *buf;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
event = append_file(sys_dir, name);
ret = stat(event, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto free_event;
format = append_file(event, "format");
ret = stat(format, &st);
if (ret < 0)
goto free_format;
len = read_file(format, &buf);
if (len < 0)
goto free_format;
pevent_parse_event(pevent, buf, len, system);
free(buf);
free_format:
free(format);
free_event:
free(event);
}
closedir(dir);
}
static int read_header(struct pevent *pevent, const char *events_dir)
{
struct stat st;
char *header;
char *buf;
int len;
int ret = -1;
header = append_file(events_dir, "header_page");
ret = stat(header, &st);
if (ret < 0)
goto out;
len = read_file(header, &buf);
if (len < 0)
goto out;
pevent_parse_header_page(pevent, buf, len, sizeof(long));
free(buf);
ret = 0;
out:
free(header);
return ret;
}
/**
* tracecmd_local_events - create a pevent from the events on system
* @tracing_dir: The directory that contains the events.
*
* Returns a pevent structure that contains the pevents local to
* the system.
*/
struct pevent *tracecmd_local_events(const char *tracing_dir)
{
struct pevent *pevent = NULL;
struct dirent *dent;
char *events_dir;
struct stat st;
DIR *dir;
int ret;
if (!tracing_dir)
return NULL;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
dir = opendir(events_dir);
if (!dir)
goto out_free;
pevent = pevent_alloc();
if (!pevent)
goto out_free;
ret = read_header(pevent, events_dir);
if (ret < 0) {
pevent_free(pevent);
pevent = NULL;
goto out_free;
}
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *sys;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
sys = append_file(events_dir, name);
ret = stat(sys, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(sys);
continue;
}
load_events(pevent, name, sys);
free(sys);
}
closedir(dir);
out_free:
free(events_dir);
return pevent;
}
/**
* tracecmd_local_plugins - returns an array of available tracer plugins
* @tracing_dir: The directory that contains the tracing directory
*
* Returns an allocate list of plugins. The array ends with NULL.
* Both the plugin names and array must be freed with free().
*/
char **tracecmd_local_plugins(const char *tracing_dir)
{
char *available_tracers;
struct stat st;
char **plugins = NULL;
char *buf;
char *str, *saveptr;
char *plugin;
int slen;
int len;
int ret;
if (!tracing_dir)
return NULL;
available_tracers = append_file(tracing_dir, "available_tracers");
if (!available_tracers)
return NULL;
ret = stat(available_tracers, &st);
if (ret < 0)
goto out_free;
len = read_file(available_tracers, &buf);
if (len < 0)
goto out_free;
len = 0;
for (str = buf; ; str = NULL) {
plugin = strtok_r(str, " ", &saveptr);
if (!plugin)
break;
if (!(slen = strlen(plugin)))
continue;
/* chop off any newlines */
if (plugin[slen - 1] == '\n')
plugin[slen - 1] = '\0';
/* Skip the non tracers */
if (strcmp(plugin, "nop") == 0 ||
strcmp(plugin, "none") == 0)
continue;
plugins = tracecmd_add_list(plugins, plugin, len++);
}
free(buf);
out_free:
free(available_tracers);
return plugins;
}
static void
trace_util_load_plugins_dir(struct pevent *pevent, const char *suffix,
const char *path,
void (*load_plugin)(struct pevent *pevent,
const char *path,
const char *name,
void *data),
void *data)
{
struct dirent *dent;
struct stat st;
DIR *dir;
int ret;
ret = stat(path, &st);
if (ret < 0)
return;
if (!S_ISDIR(st.st_mode))
return;
dir = opendir(path);
if (!dir)
return;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
/* Only load plugins that end in suffix */
if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0)
continue;
load_plugin(pevent, path, name, data);
}
closedir(dir);
return;
}
void trace_util_load_plugins(struct pevent *pevent, const char *suffix,
void (*load_plugin)(struct pevent *pevent,
const char *path,
const char *name,
void *data),
void *data)
{
char *home;
char *path;
char *envdir;
if (tracecmd_disable_plugins)
return;
/* If a system plugin directory was defined, check that first */
#ifdef PLUGIN_DIR
if (!tracecmd_disable_sys_plugins)
trace_util_load_plugins_dir(pevent, suffix, PLUGIN_DIR,
load_plugin, data);
#endif
/* Next let the environment-set plugin directory override the system defaults */
envdir = getenv("TRACE_CMD_PLUGIN_DIR");
if (envdir)
trace_util_load_plugins_dir(pevent, suffix, envdir, load_plugin, data);
/* Now let the home directory override the environment or system defaults */
home = getenv("HOME");
if (!home)
return;
path = malloc_or_die(strlen(home) + strlen(LOCAL_PLUGIN_DIR) + 2);
strcpy(path, home);
strcat(path, "/");
strcat(path, LOCAL_PLUGIN_DIR);
trace_util_load_plugins_dir(pevent, suffix, path, load_plugin, data);
free(path);
}
struct plugin_list *tracecmd_load_plugins(struct pevent *pevent)
{
struct plugin_list *list = NULL;
trace_util_load_plugins(pevent, ".so", load_plugin, &list);
return list;
}
void tracecmd_unload_plugins(struct plugin_list *plugin_list)
{
pevent_plugin_unload_func func;
struct plugin_list *list;
while (plugin_list) {
list = plugin_list;
plugin_list = list->next;
func = dlsym(list->handle, PEVENT_PLUGIN_UNLOADER_NAME);
if (func)
func();
dlclose(list->handle);
free(list->name);
free(list);
}
}
char *tracecmd_get_tracing_file(const char *name)
{
static const char *tracing;
char *file;
if (!tracing) {
tracing = tracecmd_find_tracing_dir();
if (!tracing)
die("Can't find tracing dir");
}
file = malloc_or_die(strlen(tracing) + strlen(name) + 2);
if (!file)
return NULL;
sprintf(file, "%s/%s", tracing, name);
return file;
}
void tracecmd_put_tracing_file(char *name)
{
free(name);
}