blob: b755ebe9fc0f0794475341e876594c6f90d1701b [file] [log] [blame]
/*
* Copyright (C) 2008,2009, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include "trace-local.h"
#define _STR(x) #x
#define STR(x) _STR(x)
#define MAX_PATH 256
#define TRACE_CTRL "tracing_on"
#define TRACE "trace"
#define AVAILABLE "available_tracers"
#define CURRENT "current_tracer"
#define ITER_CTRL "trace_options"
#define MAX_LATENCY "tracing_max_latency"
unsigned int page_size;
static const char *output_file = "trace.dat";
static int latency;
static int sleep_time = 1000;
static int cpu_count;
static int *pids;
struct event_list {
struct event_list *next;
const char *event;
int neg;
};
static struct event_list *event_selection;
struct events {
struct events *sibling;
struct events *children;
struct events *next;
char *name;
};
static struct tracecmd_recorder *recorder;
static char *get_temp_file(int cpu)
{
char *file = NULL;
int size;
size = snprintf(file, 0, "%s.cpu%d", output_file, cpu);
file = malloc_or_die(size + 1);
sprintf(file, "%s.cpu%d", output_file, cpu);
return file;
}
static void put_temp_file(char *file)
{
free(file);
}
static void delete_temp_file(int cpu)
{
char file[MAX_PATH];
snprintf(file, MAX_PATH, "%s.cpu%d", output_file, cpu);
unlink(file);
}
static void kill_threads(void)
{
int i;
if (!cpu_count)
return;
for (i = 0; i < cpu_count; i++) {
if (pids[i] > 0) {
kill(pids[i], SIGKILL);
delete_temp_file(i);
pids[i] = 0;
}
}
}
static void delete_thread_data(void)
{
int i;
if (!cpu_count)
return;
for (i = 0; i < cpu_count; i++) {
if (pids[i]) {
delete_temp_file(i);
if (pids[i] < 0)
pids[i] = 0;
}
}
}
static void stop_threads(void)
{
int i;
if (!cpu_count)
return;
for (i = 0; i < cpu_count; i++) {
if (pids[i] > 0) {
kill(pids[i], SIGINT);
waitpid(pids[i], NULL, 0);
pids[i] = -1;
}
}
}
static void flush_threads(void)
{
int i;
if (!cpu_count)
return;
for (i = 0; i < cpu_count; i++) {
if (pids[i] > 0)
kill(pids[i], SIGUSR1);
}
}
void die(char *fmt, ...)
{
va_list ap;
int ret = errno;
if (errno)
perror("trace-cmd");
else
ret = -1;
kill_threads();
va_start(ap, fmt);
fprintf(stderr, " ");
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(ret);
}
void warning(char *fmt, ...)
{
va_list ap;
if (errno)
perror("trace-cmd");
errno = 0;
va_start(ap, fmt);
fprintf(stderr, " ");
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
void *malloc_or_die(unsigned int size)
{
void *data;
data = malloc(size);
if (!data)
die("malloc");
return data;
}
static int set_ftrace(int set)
{
struct stat buf;
char *path = "/proc/sys/kernel/ftrace_enabled";
int fd;
char *val = set ? "1" : "0";
/* if ftace_enable does not exist, simply ignore it */
fd = stat(path, &buf);
if (fd < 0)
return -ENODEV;
fd = open(path, O_WRONLY);
if (fd < 0)
die ("Can't %s ftrace", set ? "enable" : "disable");
write(fd, val, 1);
close(fd);
return 0;
}
void run_cmd(int argc, char **argv)
{
int status;
int pid;
if ((pid = fork()) < 0)
die("failed to fork");
if (!pid) {
/* child */
if (execvp(argv[0], argv))
exit(-1);
}
waitpid(pid, &status, 0);
}
static char *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;
}
static void put_tracing_file(char *file)
{
free(file);
}
static void show_events(void)
{
char buf[BUFSIZ];
char *path;
FILE *fp;
size_t n;
path = get_tracing_file("available_events");
fp = fopen(path, "r");
if (!fp)
die("reading %s", path);
put_tracing_file(path);
do {
n = fread(buf, 1, BUFSIZ, fp);
if (n > 0)
fwrite(buf, 1, n, stdout);
} while (n > 0);
fclose(fp);
}
static void show_plugins(void)
{
char buf[BUFSIZ];
char *path;
FILE *fp;
size_t n;
path = get_tracing_file("available_tracers");
fp = fopen(path, "r");
if (!fp)
die("reading %s", path);
put_tracing_file(path);
do {
n = fread(buf, 1, BUFSIZ, fp);
if (n > 0)
fwrite(buf, 1, n, stdout);
} while (n > 0);
fclose(fp);
}
static void set_plugin(const char *name)
{
FILE *fp;
char *path;
path = get_tracing_file("current_tracer");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite(name, 1, strlen(name), fp);
fclose(fp);
}
static void show_options(void)
{
char buf[BUFSIZ];
char *path;
FILE *fp;
size_t n;
path = get_tracing_file("trace_options");
fp = fopen(path, "r");
if (!fp)
die("reading %s", path);
put_tracing_file(path);
do {
n = fread(buf, 1, BUFSIZ, fp);
if (n > 0)
fwrite(buf, 1, n, stdout);
} while (n > 0);
fclose(fp);
}
static void set_option(const char *option)
{
FILE *fp;
char *path;
path = get_tracing_file("trace_options");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite(option, 1, strlen(option), fp);
fclose(fp);
}
static void enable_event(const char *name)
{
struct stat st;
FILE *fp;
char *path;
int ret;
fprintf(stderr, "enable %s\n", name);
if (strcmp(name, "all") == 0) {
path = get_tracing_file("events/enable");
ret = stat(path, &st);
if (ret < 0) {
put_tracing_file(path);
/* need to use old way */
path = get_tracing_file("set_event");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("*:*\n", 4, 1, fp);
fclose(fp);
return;
}
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
ret = fwrite("1", 1, 1, fp);
fclose(fp);
if (ret < 0)
die("writing to '%s'", path);
return;
}
path = get_tracing_file("set_event");
fp = fopen(path, "a");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
ret = fwrite(name, 1, strlen(name), fp);
if (ret < 0)
die("bad event '%s'", name);
ret = fwrite("\n", 1, 1, fp);
if (ret < 0)
die("bad event '%s'", name);
fclose(fp);
}
static void disable_event(const char *name)
{
struct stat st;
FILE *fp;
char *path;
int ret;
if (strcmp(name, "all") == 0) {
path = get_tracing_file("events/enable");
ret = stat(path, &st);
if (ret < 0) {
put_tracing_file(path);
/* need to use old way */
path = get_tracing_file("set_event");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("\n", 1, 1, fp);
fclose(fp);
return;
}
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("0", 1, 1, fp);
fclose(fp);
return;
}
path = get_tracing_file("set_event");
fp = fopen(path, "a");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
ret = fwrite("!", 1, 1, fp);
if (ret < 0)
die("can't write negative");
ret = fwrite(name, 1, strlen(name), fp);
if (ret < 0)
die("bad event '%s'", name);
ret = fwrite("\n", 1, 1, fp);
if (ret < 0)
die("bad event '%s'", name);
fclose(fp);
}
static void enable_tracing(void)
{
FILE *fp;
char *path;
/* reset the trace */
path = get_tracing_file("tracing_on");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("1", 1, 1, fp);
fclose(fp);
}
static void disable_tracing(void)
{
FILE *fp;
char *path;
/* reset the trace */
path = get_tracing_file("tracing_on");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("0", 1, 1, fp);
fclose(fp);
}
static void disable_all(void)
{
FILE *fp;
char *path;
disable_tracing();
set_plugin("nop");
disable_event("all");
/* reset the trace */
path = get_tracing_file("trace");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("0", 1, 1, fp);
fclose(fp);
}
static void reset_max_latency(void)
{
FILE *fp;
char *path;
/* reset the trace */
path = get_tracing_file("tracing_max_latency");
fp = fopen(path, "w");
if (!fp)
die("writing to '%s'", path);
put_tracing_file(path);
fwrite("0", 1, 1, fp);
fclose(fp);
}
static void enable_events(void)
{
struct event_list *event;
for (event = event_selection; event; event = event->next) {
if (!event->neg)
enable_event(event->event);
}
/* Now disable any events */
for (event = event_selection; event; event = event->next) {
if (event->neg)
disable_event(event->event);
}
}
static int count_cpus(void)
{
FILE *fp;
char buf[1024];
int cpus = 0;
char *pbuf;
size_t *pn;
size_t n;
int r;
n = 1024;
pn = &n;
pbuf = buf;
fp = fopen("/proc/cpuinfo", "r");
if (!fp)
die("Can not read cpuinfo");
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;
}
static int finished;
static void finish(int sig)
{
/* all done */
if (recorder)
tracecmd_stop_recording(recorder);
finished = 1;
}
static void flush(int sig)
{
if (recorder)
tracecmd_stop_recording(recorder);
}
static int create_recorder(int cpu)
{
char *file;
int pid;
pid = fork();
if (pid < 0)
die("fork");
if (pid)
return pid;
signal(SIGINT, finish);
signal(SIGUSR1, flush);
/* do not kill tasks on error */
cpu_count = 0;
file = get_temp_file(cpu);
recorder = tracecmd_create_recorder(file, cpu);
put_temp_file(file);
if (!recorder)
die ("can't create recorder");
while (!finished)
tracecmd_start_recording(recorder, sleep_time);
tracecmd_free_recorder(recorder);
exit(0);
}
static void start_threads(void)
{
int i;
cpu_count = count_cpus();
/* make a thread for every CPU we have */
pids = malloc_or_die(sizeof(*pids) * cpu_count);
memset(pids, 0, sizeof(*pids) * cpu_count);
for (i = 0; i < cpu_count; i++) {
pids[i] = create_recorder(i);
}
}
static void record_data(void)
{
struct tracecmd_output *handle;
char **temp_files;
int i;
if (latency)
handle = tracecmd_create_file_latency(output_file, cpu_count);
else {
if (!cpu_count)
return;
temp_files = malloc_or_die(sizeof(*temp_files) * cpu_count);
for (i = 0; i < cpu_count; i++)
temp_files[i] = get_temp_file(i);
handle = tracecmd_create_file(output_file, cpu_count, temp_files);
for (i = 0; i < cpu_count; i++)
put_temp_file(temp_files[i]);
free(temp_files);
}
if (!handle)
die("could not write to file");
tracecmd_output_close(handle);
}
static int trace_empty(void)
{
char *path;
FILE *fp;
char *line = NULL;
size_t size;
ssize_t n;
int ret;
/*
* Test if the trace file is empty.
*
* Yes, this is a heck of a hack. What is done here
* is to read the trace file and ignore the
* lines starting with '#', and if we get a line
* that is without a '#' the trace is not empty.
* Otherwise it is.
*/
path = get_tracing_file("trace");
fp = fopen(path, "r");
if (!fp)
die("reading '%s'", path);
do {
n = getline(&line, &size, fp);
if (!line)
ret = 1;
else if (line[0] != '#')
ret = 0;
if (n < 0)
ret = 1;
} while (line && n > 0);
put_tracing_file(path);
fclose(fp);
return ret;
}
void usage(char **argv)
{
char *arg = argv[0];
char *p = arg+strlen(arg);
while (p >= arg && *p != '/')
p--;
p++;
printf("\n"
"%s version %s\n\n"
"usage:\n"
" %s record [-v][-e event][-p plugin][-d][-o file][-s usecs][-O option ] [command ...]\n"
" -e run command with event enabled\n"
" -p run command with plugin enabled\n"
" -v will negate all -e after it (disable those events)\n"
" -d disable function tracer when running\n"
" -o data output file [default trace.dat]\n"
" -O option to enable (or disable)\n"
" -s sleep interval between recording (in usecs) [default: 1000]\n"
"\n"
" %s start [-e event][-p plugin] [-d] [-O option ]\n"
" Uses same options as record, but does not run a command.\n"
" It only enables the tracing and exits\n"
"\n"
" %s extract [-p plugin][-O option][-o file]\n"
" Uses same options as record, but only reads an existing trace.\n"
"\n"
" %s stop\n"
" Stops the tracer from recording more data.\n"
" Used in conjunction with start\n"
"\n"
" %s reset\n"
" Disables the tracer (may reset trace file)\n"
" Used in conjunction with start\n"
"\n"
" %s report [-i file] [--cpu cpu] [-e][-f][-l][-P][-E]\n"
" -i input file [default trace.dat]\n"
" -e show file endianess\n"
" -f show function list\n"
" -P show printk list\n"
" -E show event files stored\n"
" -l show latency format (default with latency tracers)\n"
"\n"
" %s list [-e][-p]\n"
" -e list available events\n"
" -p list available plugins\n"
" -o list available options\n"
"\n", p, TRACECMD_VERSION, p, p, p, p, p, p, p);
exit(-1);
}
int main (int argc, char **argv)
{
const char *plugin = NULL;
const char *output = NULL;
const char *option;
struct event_list *event;
struct trace_seq s;
int disable = 0;
int plug = 0;
int events = 0;
int options = 0;
int record = 0;
int extract = 0;
int run_command = 0;
int neg_event = 0;
int fset;
int cpu;
int c;
errno = 0;
if (argc < 2)
usage(argv);
if (strcmp(argv[1], "report") == 0) {
trace_report(argc, argv);
exit(0);
} else if ((record = (strcmp(argv[1], "record") == 0)) ||
(strcmp(argv[1], "start") == 0) ||
((extract = strcmp(argv[1], "extract") == 0))) {
while ((c = getopt(argc-1, argv+1, "+he:p:do:O:s:v")) >= 0) {
switch (c) {
case 'h':
usage(argv);
break;
case 'e':
if (extract)
usage(argv);
events = 1;
event = malloc_or_die(sizeof(*event));
event->event = optarg;
event->next = event_selection;
event->neg = neg_event;
event_selection = event;
break;
case 'v':
if (extract)
usage(argv);
neg_event = 1;
break;
case 'p':
if (plugin)
die("only one plugin allowed");
plugin = optarg;
fprintf(stderr, " plugin %s\n", plugin);
break;
case 'd':
if (extract)
usage(argv);
disable = 1;
break;
case 'o':
if (!record && !extract)
die("start does not take output\n"
"Did you mean 'record'?");
if (output)
die("only one output file allowed");
output = optarg;
break;
case 'O':
option = optarg;
set_option(option);
break;
case 's':
if (extract)
usage(argv);
sleep_time = atoi(optarg);
break;
}
}
} else if (strcmp(argv[1], "stop") == 0) {
disable_tracing();
exit(0);
} else if (strcmp(argv[1], "reset") == 0) {
disable_all();
exit(0);
} else if (strcmp(argv[1], "list") == 0) {
while ((c = getopt(argc-1, argv+1, "+hepo")) >= 0) {
switch (c) {
case 'h':
usage(argv);
break;
case 'e':
events = 1;
break;
case 'p':
plug = 1;
break;
case 'o':
options = 1;
break;
default:
usage(argv);
}
}
if (events)
show_events();
if (plug)
show_plugins();
if (options)
show_options();
if (!events && !plug && !options) {
printf("events:\n");
show_events();
printf("\nplugins:\n");
show_plugins();
printf("\noptions:\n");
show_options();
}
exit(0);
} else {
fprintf(stderr, "unknown command: %s\n", argv[1]);
usage(argv);
}
if ((argc - optind) >= 2) {
if (!record)
die("Command start does not take any commands\n"
"Did you mean 'record'?");
if (extract)
die("Command extract does not take any commands\n"
"Did you mean 'record'?");
run_command = 1;
}
if (!events && !plugin && !extract)
die("no event or plugin was specified... aborting");
if (output)
output_file = output;
if (!extract) {
fset = set_ftrace(!disable);
disable_all();
if (events)
enable_events();
}
if (plugin) {
/*
* Latency tracers just save the trace and kill
* the threads.
*/
if (strcmp(plugin, "irqsoff") == 0 ||
strcmp(plugin, "preemptoff") == 0 ||
strcmp(plugin, "preemptirqsoff") == 0 ||
strcmp(plugin, "wakeup") == 0 ||
strcmp(plugin, "wakeup_rt") == 0) {
latency = 1;
}
if (fset < 0 && (strcmp(plugin, "function") == 0 ||
strcmp(plugin, "function_graph") == 0))
die("function tracing not configured on this kernel");
if (!extract)
set_plugin(plugin);
}
if (record || extract) {
if (!latency)
start_threads();
signal(SIGINT, finish);
}
if (extract) {
while (!finished && !trace_empty()) {
flush_threads();
sleep(1);
}
} else {
enable_tracing();
if (latency)
reset_max_latency();
if (!record)
exit(0);
if (run_command)
run_cmd((argc - optind) - 1, &argv[optind + 1]);
else {
/* sleep till we are woken with Ctrl^C */
printf("Hit Ctrl^C to stop recording\n");
while (!finished)
sleep(10);
}
disable_tracing();
}
stop_threads();
record_data();
delete_thread_data();
printf("Buffer statistics:\n\n");
for (cpu = 0; cpu < cpu_count; cpu++) {
trace_seq_init(&s);
trace_seq_printf(&s, "CPU: %d\n", cpu);
tracecmd_stat_cpu(&s, cpu);
trace_seq_do_printf(&s);
printf("\n");
}
exit(0);
return 0;
}