blob: adbf3c3eb3be0086137bbb40b3716c00a9fe4ec5 [file] [log] [blame]
/*
* Copyright (C) 2014 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 General Public License as published by
* the Free Software Foundation; version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <getopt.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "trace-local.h"
#ifndef BUFSIZ
#define BUFSIZ 1024
#endif
static inline int is_top_instance(struct buffer_instance *instance)
{
return instance == &top_instance;
}
static int get_instance_file_fd(struct buffer_instance *instance,
const char *file)
{
char *path;
int fd;
path = get_instance_file(instance, file);
fd = open(path, O_RDONLY);
tracecmd_put_tracing_file(path);
return fd;
}
char *strstrip(char *str)
{
char *s;
if (!str)
return NULL;
s = str + strlen(str) - 1;
while (s >= str && isspace(*s))
s--;
s++;
*s = '\0';
for (s = str; *s && isspace(*s); s++)
;
return s;
}
char *append_file(const char *dir, const char *name)
{
char *file;
file = malloc(strlen(dir) + strlen(name) + 2);
if (!file)
die("Failed to allocate %s/%s", dir, name);
sprintf(file, "%s/%s", dir, name);
return file;
}
static char *get_fd_content(int fd, const char *file)
{
char *str = NULL;
int cnt = 0;
int ret;
for (;;) {
str = realloc(str, BUFSIZ * ++cnt);
if (!str)
die("malloc");
ret = read(fd, str + BUFSIZ * (cnt - 1), BUFSIZ);
if (ret < 0)
die("reading %s\n", file);
if (ret < BUFSIZ)
break;
}
str[BUFSIZ * (cnt-1) + ret] = 0;
return str;
}
char *get_file_content(const char *file)
{
char *str;
int fd;
fd = open(file, O_RDONLY);
if (fd < 0)
return NULL;
str = get_fd_content(fd, file);
close(fd);
return str;
}
static char *get_instance_file_content(struct buffer_instance *instance,
const char *file)
{
char *str = NULL;
int fd;
fd = get_instance_file_fd(instance, file);
if (fd < 0)
return NULL;
str = get_fd_content(fd, file);
close(fd);
return str;
}
static void report_plugin(struct buffer_instance *instance)
{
char *str;
char *cont;
str = get_instance_file_content(instance, "current_tracer");
if (!str)
return;
cont = strstrip(str);
/* We only care if the plugin is something other than nop */
if (strcmp(cont, "nop") == 0)
goto out;
printf("\nTracer: %s\n", cont);
out:
free(str);
}
struct event_iter *trace_event_iter_alloc(const char *path)
{
struct event_iter *iter;
iter = malloc(sizeof(*iter));
if (!iter)
die("Failed to allocate event_iter for path %s", path);
memset(iter, 0, sizeof(*iter));
iter->system_dir = opendir(path);
if (!iter->system_dir)
die("opendir");
return iter;
}
enum event_iter_type
trace_event_iter_next(struct event_iter *iter, const char *path, const char *system)
{
struct dirent *dent;
if (system && !iter->event_dir) {
char *event;
struct stat st;
event = append_file(path, system);
stat(event, &st);
if (!S_ISDIR(st.st_mode)) {
free(event);
goto do_system;
}
iter->event_dir = opendir(event);
if (!iter->event_dir)
die("opendir %s", event);
free(event);
}
if (iter->event_dir) {
while ((dent = readdir(iter->event_dir))) {
const char *name = dent->d_name;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
iter->event_dent = dent;
return EVENT_ITER_EVENT;
}
closedir(iter->event_dir);
iter->event_dir = NULL;
}
do_system:
while ((dent = readdir(iter->system_dir))) {
const char *name = dent->d_name;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
iter->system_dent = dent;
return EVENT_ITER_SYSTEM;
}
return EVENT_ITER_NONE;
}
void trace_event_iter_free(struct event_iter *iter)
{
if (!iter)
return;
if (iter->event_dir)
closedir(iter->event_dir);
closedir(iter->system_dir);
free(iter);
}
static void reset_event_iter(struct event_iter *iter)
{
if (iter->event_dir) {
closedir(iter->event_dir);
iter->event_dir = NULL;
}
rewinddir(iter->system_dir);
}
static int process_individual_events(const char *path, struct event_iter *iter)
{
struct stat st;
const char *system = iter->system_dent->d_name;
char *file;
char *enable = NULL;
char *str;
int ret = 0;
file = append_file(path, system);
stat(file, &st);
if (!S_ISDIR(st.st_mode))
goto out;
enable = append_file(file, "enable");
str = get_file_content(enable);
if (!str)
goto out;
if (*str != '1' && *str != '0')
ret = 1;
free(str);
out:
free(enable);
free(file);
return ret;
}
static void
process_event_enable(char *path, const char *system, const char *name,
enum event_process *processed)
{
struct stat st;
char *enable = NULL;
char *file;
char *str;
if (system)
path = append_file(path, system);
file = append_file(path, name);
if (system)
free(path);
stat(file, &st);
if (!S_ISDIR(st.st_mode))
goto out;
enable = append_file(file, "enable");
str = get_file_content(enable);
if (!str)
goto out;
if (*str == '1') {
if (!system) {
if (!*processed)
printf(" Individual systems:\n");
printf( " %s\n", name);
*processed = PROCESSED_SYSTEM;
} else {
if (!*processed) {
printf(" Individual events:\n");
*processed = PROCESSED_SYSTEM;
}
if (*processed == PROCESSED_SYSTEM) {
printf(" %s\n", system);
*processed = PROCESSED_EVENT;
}
printf( " %s\n", name);
}
}
free(str);
out:
free(enable);
free(file);
}
static void report_events(struct buffer_instance *instance)
{
struct event_iter *iter;
char *str;
char *cont;
char *path;
char *system;
enum event_iter_type type;
enum event_process processed = PROCESSED_NONE;
str = get_instance_file_content(instance, "events/enable");
if (!str)
return;
cont = strstrip(str);
printf("\nEvents:\n");
switch(*cont) {
case '1':
printf(" All enabled\n");
free(str);
return;
case '0':
printf(" All disabled\n");
free(str);
return;
}
free(str);
path = get_instance_file(instance, "events");
if (!path)
die("malloc");
iter = trace_event_iter_alloc(path);
while (trace_event_iter_next(iter, path, NULL)) {
process_event_enable(path, NULL, iter->system_dent->d_name, &processed);
}
reset_event_iter(iter);
processed = PROCESSED_NONE;
system = NULL;
while ((type = trace_event_iter_next(iter, path, system))) {
if (type == EVENT_ITER_SYSTEM) {
/* Only process systems that are not fully enabled */
if (!process_individual_events(path, iter))
continue;
system = iter->system_dent->d_name;
if (processed)
processed = PROCESSED_SYSTEM;
continue;
}
process_event_enable(path, iter->system_dent->d_name,
iter->event_dent->d_name, &processed);
}
trace_event_iter_free(iter);
if (!processed)
printf(" (none enabled)\n");
tracecmd_put_tracing_file(path);
}
static void
process_event_filter(char *path, struct event_iter *iter, enum event_process *processed)
{
const char *system = iter->system_dent->d_name;
const char *event = iter->event_dent->d_name;
struct stat st;
char *filter = NULL;
char *file;
char *str;
char *cont;
path = append_file(path, system);
file = append_file(path, event);
free(path);
stat(file, &st);
if (!S_ISDIR(st.st_mode))
goto out;
filter = append_file(file, "filter");
str = get_file_content(filter);
if (!str)
goto out;
cont = strstrip(str);
if (strcmp(cont, "none") == 0) {
free(str);
goto out;
}
if (!*processed)
printf("\nFilters:\n");
printf( " %s:%s \"%s\"\n", system, event, cont);
*processed = PROCESSED_SYSTEM;
free(str);
out:
free(filter);
free(file);
}
static void report_event_filters(struct buffer_instance *instance)
{
struct event_iter *iter;
char *path;
char *system;
enum event_iter_type type;
enum event_process processed = PROCESSED_NONE;
path = get_instance_file(instance, "events");
if (!path)
die("malloc");
iter = trace_event_iter_alloc(path);
processed = PROCESSED_NONE;
system = NULL;
while ((type = trace_event_iter_next(iter, path, system))) {
if (type == EVENT_ITER_SYSTEM) {
system = iter->system_dent->d_name;
continue;
}
process_event_filter(path, iter, &processed);
}
trace_event_iter_free(iter);
tracecmd_put_tracing_file(path);
}
static void
process_event_trigger(char *path, struct event_iter *iter, enum event_process *processed)
{
const char *system = iter->system_dent->d_name;
const char *event = iter->event_dent->d_name;
struct stat st;
char *trigger = NULL;
char *file;
char *str;
char *cont;
path = append_file(path, system);
file = append_file(path, event);
free(path);
stat(file, &st);
if (!S_ISDIR(st.st_mode))
goto out;
trigger = append_file(file, "trigger");
str = get_file_content(trigger);
if (!str)
goto out;
cont = strstrip(str);
if (cont[0] == '#') {
free(str);
goto out;
}
if (!*processed)
printf("\nTriggers:\n");
printf( " %s:%s \"%s\"\n", system, event, cont);
*processed = PROCESSED_SYSTEM;
free(str);
out:
free(trigger);
free(file);
}
static void report_event_triggers(struct buffer_instance *instance)
{
struct event_iter *iter;
char *path;
char *system;
enum event_iter_type type;
enum event_process processed = PROCESSED_NONE;
path = get_instance_file(instance, "events");
if (!path)
die("malloc");
iter = trace_event_iter_alloc(path);
processed = PROCESSED_NONE;
system = NULL;
while ((type = trace_event_iter_next(iter, path, system))) {
if (type == EVENT_ITER_SYSTEM) {
system = iter->system_dent->d_name;
continue;
}
process_event_trigger(path, iter, &processed);
}
trace_event_iter_free(iter);
tracecmd_put_tracing_file(path);
}
enum func_states {
FUNC_STATE_START,
FUNC_STATE_SKIP,
FUNC_STATE_PRINT,
};
static void list_functions(const char *path, char *string)
{
enum func_states state;
struct stat st;
char *str;
int ret = 0;
int len;
int i;
int first = 0;
/* Ignore if it does not exist. */
ret = stat(path, &st);
if (ret < 0)
return;
str = get_file_content(path);
if (!str)
return;
len = strlen(str);
state = FUNC_STATE_START;
/* Skip all lines that start with '#' */
for (i = 0; i < len; i++) {
if (state == FUNC_STATE_PRINT)
putchar(str[i]);
if (str[i] == '\n') {
state = FUNC_STATE_START;
continue;
}
if (state == FUNC_STATE_SKIP)
continue;
if (state == FUNC_STATE_START && str[i] == '#') {
state = FUNC_STATE_SKIP;
continue;
}
if (!first) {
printf("\n%s:\n", string);
first = 1;
}
if (state != FUNC_STATE_PRINT) {
state = FUNC_STATE_PRINT;
printf(" ");
putchar(str[i]);
}
}
free(str);
}
static void report_graph_funcs(struct buffer_instance *instance)
{
char *path;
path = get_instance_file(instance, "set_graph_function");
if (!path)
die("malloc");
list_functions(path, "Function Graph Filter");
tracecmd_put_tracing_file(path);
path = get_instance_file(instance, "set_graph_notrace");
if (!path)
die("malloc");
list_functions(path, "Function Graph No Trace");
tracecmd_put_tracing_file(path);
}
static void report_ftrace_filters(struct buffer_instance *instance)
{
char *path;
path = get_instance_file(instance, "set_ftrace_filter");
if (!path)
die("malloc");
list_functions(path, "Function Filter");
tracecmd_put_tracing_file(path);
path = get_instance_file(instance, "set_ftrace_notrace");
if (!path)
die("malloc");
list_functions(path, "Function No Trace");
tracecmd_put_tracing_file(path);
}
static void report_buffers(struct buffer_instance *instance)
{
#define FILE_SIZE 100
char *str;
char *cont;
char file[FILE_SIZE];
int cpu;
str = get_instance_file_content(instance, "buffer_size_kb");
if (!str)
return;
cont = strstrip(str);
/* If it's not expanded yet, just skip */
if (strstr(cont, "expanded") != NULL)
goto out;
if (strcmp(cont, "X") != 0) {
printf("\nBuffer size in kilobytes (per cpu):\n");
printf(" %s\n", str);
goto total;
}
/* Read the sizes of each CPU buffer */
for (cpu = 0; ; cpu++) {
snprintf(file, FILE_SIZE, "per_cpu/cpu%d/buffer_size_kb", cpu);
str = get_instance_file_content(instance, file);
if (!str)
break;
cont = strstrip(str);
if (!cpu)
putchar('\n');
printf("CPU %d buffer size (kb): %s\n", cpu, cont);
free(str);
}
total:
free(str);
str = get_instance_file_content(instance, "buffer_total_size_kb");
if (!str)
return;
cont = strstrip(str);
printf("\nBuffer total size in kilobytes:\n");
printf(" %s\n", str);
out:
free(str);
}
static void report_clock(struct buffer_instance *instance)
{
char *str;
char *cont;
char *clock;
str = get_instance_file_content(instance, "trace_clock");
if (!str)
return;
clock = strstr(str, "[");
if (!clock)
goto out;
clock++;
cont = strstr(clock, "]");
if (!cont) /* should never happen */
goto out;
*cont = '\0';
/* Default clock is "local", only show others */
if (strcmp(clock, "local") == 0)
goto out;
printf("\nClock: %s\n", clock);
out:
free(str);
}
static void report_cpumask(struct buffer_instance *instance)
{
char *str;
char *cont;
int cpus;
int n;
int i;
str = get_instance_file_content(instance, "tracing_cpumask");
if (!str)
return;
cont = strstrip(str);
/* check to make sure all CPUs on this machine are set */
cpus = count_cpus();
for (i = strlen(cont) - 1; i >= 0 && cpus > 0; i--) {
if (cont[i] == ',')
continue;
if (cont[i] == 'f') {
cpus -= 4;
continue;
}
if (cpus >= 4)
break;
if (cont[i] >= '0' && cont[i] <= '9')
n = cont[i] - '0';
else
n = 10 + (cont[i] - 'a');
while (cpus > 0) {
if (!(n & 1))
break;
n >>= 1;
cpus--;
}
break;
}
/* If cpus is greater than zero, one isn't set */
if (cpus > 0)
printf("\nCPU mask: %s\n", cont);
free(str);
}
static void report_latency(struct buffer_instance *instance)
{
char *str;
char *cont;
str = get_instance_file_content(instance, "tracing_max_latency");
if (!str)
return;
cont = strstrip(str);
if (strcmp(cont, "0") != 0)
printf("\nMax Latency: %s\n", cont);
free(str);
}
static void report_probes(struct buffer_instance *instance,
const char *file, const char *string)
{
char *str;
char *cont;
int newline;
int i;
str = get_instance_file_content(instance, file);
if (!str)
return;
cont = strstrip(str);
if (strlen(cont) == 0)
goto out;
printf("\n%s:\n", string);
newline = 1;
for (i = 0; cont[i]; i++) {
if (newline)
printf(" ");
putchar(cont[i]);
if (cont[i] == '\n')
newline = 1;
else
newline = 0;
}
putchar('\n');
out:
free(str);
}
static void report_kprobes(struct buffer_instance *instance)
{
report_probes(instance, "kprobe_events", "Kprobe events");
}
static void report_uprobes(struct buffer_instance *instance)
{
report_probes(instance, "uprobe_events", "Uprobe events");
}
static void report_traceon(struct buffer_instance *instance)
{
char *str;
char *cont;
str = get_instance_file_content(instance, "tracing_on");
if (!str)
return;
cont = strstrip(str);
/* double newline as this is the last thing printed */
if (strcmp(cont, "0") == 0)
printf("\nTracing is disabled\n\n");
else
printf("\nTracing is enabled\n\n");
free(str);
}
static void stat_instance(struct buffer_instance *instance)
{
if (instance != &top_instance) {
if (instance != first_instance)
printf("---------------\n");
printf("Instance: %s\n", instance->name);
}
report_plugin(instance);
report_events(instance);
report_event_filters(instance);
report_event_triggers(instance);
report_ftrace_filters(instance);
report_graph_funcs(instance);
report_buffers(instance);
report_clock(instance);
report_cpumask(instance);
report_latency(instance);
report_kprobes(instance);
report_uprobes(instance);
report_traceon(instance);
}
void trace_stat (int argc, char **argv)
{
struct buffer_instance *instance = &top_instance;
int topt = 0;
int c;
for (;;) {
c = getopt(argc-1, argv+1, "tB:");
if (c == -1)
break;
switch (c) {
case 'h':
usage(argv);
break;
case 'B':
instance = create_instance(optarg);
if (!instance)
die("Failed to create instance");
add_instance(instance);
/* top instance requires direct access */
if (!topt && is_top_instance(first_instance))
first_instance = instance;
break;
case 't':
/* Force to use top instance */
topt = 1;
instance = &top_instance;
break;
default:
usage(argv);
}
}
update_first_instance(instance, topt);
for_all_instances(instance) {
stat_instance(instance);
}
exit(0);
}