blob: 233e11e3076ea3a0dfc7dda7bf069478e2d93f29 [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, see <http://www.gnu.org/licenses>
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define _LARGEFILE64_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-cmd.h"
struct tracecmd_recorder {
int fd;
int trace_fd;
int brass[2];
int page_size;
int cpu;
int stop;
};
void tracecmd_free_recorder(struct tracecmd_recorder *recorder)
{
if (!recorder)
return;
if (recorder->trace_fd >= 0)
close(recorder->trace_fd);
if (recorder->fd >= 0)
close(recorder->fd);
free(recorder);
}
struct tracecmd_recorder *tracecmd_create_recorder_fd(int fd, int cpu)
{
struct tracecmd_recorder *recorder;
char *tracing = NULL;
char *path = NULL;
int ret;
recorder = malloc_or_die(sizeof(*recorder));
if (!recorder)
return NULL;
recorder->cpu = cpu;
/* Init to know what to free and release */
recorder->trace_fd = -1;
recorder->brass[0] = -1;
recorder->brass[1] = -1;
recorder->page_size = getpagesize();
recorder->fd = fd;
tracing = tracecmd_find_tracing_dir();
if (!tracing) {
errno = ENODEV;
goto out_free;
}
path = malloc_or_die(strlen(tracing) + 40);
if (!path)
goto out_free;
sprintf(path, "%s/per_cpu/cpu%d/trace_pipe_raw", tracing, cpu);
recorder->trace_fd = open(path, O_RDONLY);
if (recorder->trace_fd < 0)
goto out_free;
free(tracing);
free(path);
ret = pipe(recorder->brass);
if (ret < 0)
goto out_free;
return recorder;
out_free:
free(tracing);
free(path);
tracecmd_free_recorder(recorder);
return NULL;
}
struct tracecmd_recorder *tracecmd_create_recorder(const char *file, int cpu)
{
struct tracecmd_recorder *recorder;
int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (fd < 0)
return NULL;
recorder = tracecmd_create_recorder_fd(fd, cpu);
if (!recorder) {
close(fd);
unlink(file);
}
return recorder;
}
/*
* Returns -1 on error.
* or bytes of data read.
*/
static long splice_data(struct tracecmd_recorder *recorder)
{
long ret;
ret = splice(recorder->trace_fd, NULL, recorder->brass[1], NULL,
recorder->page_size, 1 /* SPLICE_F_MOVE */);
if (ret < 0) {
if (errno != EAGAIN && errno != EINTR) {
warning("recorder error in splice input");
return -1;
}
if (errno == EINTR)
return 0;
} else if (ret == 0)
return 0;
ret = splice(recorder->brass[0], NULL, recorder->fd, NULL,
recorder->page_size, 3 /* and NON_BLOCK */);
if (ret < 0) {
if (errno != EAGAIN) {
warning("recorder error in splice output");
return -1;
}
ret = 0;
}
return ret;
}
static void set_nonblock(struct tracecmd_recorder *recorder)
{
long flags;
/* Do not block on reads for flushing */
flags = fcntl(recorder->trace_fd, F_GETFL);
fcntl(recorder->trace_fd, F_SETFL, flags | O_NONBLOCK);
}
long tracecmd_flush_recording(struct tracecmd_recorder *recorder)
{
char *buf[recorder->page_size];
long total = 0;
long ret;
set_nonblock(recorder);
do {
ret = splice_data(recorder);
if (ret < 0)
return ret;
total += ret;
} while (ret);
/* splice only reads full pages */
do {
ret = read(recorder->trace_fd, buf, recorder->page_size);
if (ret > 0)
write(recorder->fd, buf, ret);
} while (ret > 0);
return total;
}
int tracecmd_start_recording(struct tracecmd_recorder *recorder, unsigned long sleep)
{
struct timespec req;
long ret;
recorder->stop = 0;
do {
if (sleep) {
req.tv_sec = sleep / 1000000;
req.tv_nsec = (sleep % 1000000) * 1000;
nanosleep(&req, NULL);
}
ret = splice_data(recorder);
if (ret < 0)
return ret;
} while (!recorder->stop);
/* Flush out the rest */
ret = tracecmd_flush_recording(recorder);
if (ret < 0)
return ret;
return 0;
}
void tracecmd_stop_recording(struct tracecmd_recorder *recorder)
{
if (!recorder)
return;
set_nonblock(recorder);
recorder->stop = 1;
}
/**
* tracecmd_stat_cpu - show the buffer stats of a particular CPU
* @s: the trace_seq to record the data in.
* @cpu: the CPU to stat
*
*/
void tracecmd_stat_cpu(struct trace_seq *s, int cpu)
{
char buf[BUFSIZ];
char *tracing;
char *path;
int fd;
int r;
tracing = tracecmd_find_tracing_dir();
if (!tracing) {
errno = ENODEV;
goto out_free;
}
path = malloc_or_die(strlen(tracing) + 40);
if (!path)
goto out_free;
sprintf(path, "%s/per_cpu/cpu%d/stats", tracing, cpu);
fd = open(path, O_RDONLY);
if (fd < 0)
goto out_free;
while ((r = read(fd, buf, BUFSIZ)) > 0)
trace_seq_printf(s, "%.*s", r, buf);
close(fd);
out_free:
free(path);
free(tracing);
}