blob: 1b9d364c34a7d6c277b25c974e0684072c546d55 [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"
#include "event-utils.h"
struct tracecmd_recorder {
int fd;
int fd1;
int fd2;
int trace_fd;
int brass[2];
int page_size;
int cpu;
int stop;
int max;
int pages;
int count;
unsigned fd_flags;
unsigned flags;
};
static int append_file(int size, int dst, int src)
{
char buf[size];
int r;
lseek64(src, 0, SEEK_SET);
/* If there's an error, then we are pretty much screwed :-p */
do {
r = read(src, buf, size);
if (r < 0)
return r;
r = write(dst, buf, r);
if (r < 0)
return r;
} while (r);
return 0;
}
void tracecmd_free_recorder(struct tracecmd_recorder *recorder)
{
if (!recorder)
return;
if (recorder->max) {
/* Need to put everything into fd1 */
if (recorder->fd == recorder->fd1) {
int ret;
/*
* Crap, the older data is in fd2, and we need
* to append fd1 onto it, and then copy over to fd1
*/
ret = append_file(recorder->page_size,
recorder->fd2, recorder->fd1);
/* Error on copying, then just keep fd1 */
if (ret) {
lseek64(recorder->fd1, 0, SEEK_END);
goto close;
}
lseek64(recorder->fd1, 0, SEEK_SET);
ftruncate(recorder->fd1, 0);
}
append_file(recorder->page_size, recorder->fd1, recorder->fd2);
}
close:
if (recorder->trace_fd >= 0)
close(recorder->trace_fd);
if (recorder->fd1 >= 0)
close(recorder->fd1);
if (recorder->fd2 >= 0)
close(recorder->fd2);
free(recorder);
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_fd2(int fd, int fd2, int cpu, unsigned flags,
const char *buffer, int maxkb)
{
struct tracecmd_recorder *recorder;
char *path = NULL;
int ret;
recorder = malloc(sizeof(*recorder));
if (!recorder)
return NULL;
recorder->cpu = cpu;
recorder->flags = flags;
recorder->fd_flags = 1; /* SPLICE_F_MOVE */
if (!(recorder->flags & TRACECMD_RECORD_BLOCK))
recorder->fd_flags |= 2; /* and NON_BLOCK */
/* Init to know what to free and release */
recorder->trace_fd = -1;
recorder->brass[0] = -1;
recorder->brass[1] = -1;
recorder->page_size = getpagesize();
if (maxkb) {
int kb_per_page = recorder->page_size >> 10;
if (!kb_per_page)
kb_per_page = 1;
recorder->max = maxkb / kb_per_page;
/* keep max half */
recorder->max >>= 1;
if (!recorder->max)
recorder->max = 1;
} else
recorder->max = 0;
recorder->count = 0;
recorder->pages = 0;
/* fd always points to what to write to */
recorder->fd = fd;
recorder->fd1 = fd;
recorder->fd2 = fd2;
path = malloc(strlen(buffer) + 40);
if (!path)
goto out_free;
if (flags & TRACECMD_RECORD_SNAPSHOT)
sprintf(path, "%s/per_cpu/cpu%d/snapshot_raw", buffer, cpu);
else
sprintf(path, "%s/per_cpu/cpu%d/trace_pipe_raw", buffer, cpu);
recorder->trace_fd = open(path, O_RDONLY);
if (recorder->trace_fd < 0)
goto out_free;
if ((recorder->flags & TRACECMD_RECORD_NOSPLICE) == 0) {
ret = pipe(recorder->brass);
if (ret < 0)
goto out_free;
}
free(path);
return recorder;
out_free:
free(path);
tracecmd_free_recorder(recorder);
return NULL;
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_fd(int fd, int cpu, unsigned flags, const char *buffer)
{
return tracecmd_create_buffer_recorder_fd2(fd, -1, cpu, flags, buffer, 0);
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder(const char *file, int cpu, unsigned flags, const char *buffer)
{
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_buffer_recorder_fd(fd, cpu, flags, buffer);
if (!recorder) {
close(fd);
unlink(file);
}
return recorder;
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_maxkb(const char *file, int cpu, unsigned flags,
const char *buffer, int maxkb)
{
struct tracecmd_recorder *recorder = NULL;
char *file2;
int len;
int fd;
int fd2;
if (!maxkb)
return tracecmd_create_buffer_recorder(file, cpu, flags, buffer);
len = strlen(file);
file2 = malloc(len + 3);
if (!file2)
return NULL;
sprintf(file2, "%s.1", file);
fd = open(file, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (fd < 0)
goto out;
fd2 = open(file2, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (fd < 0)
goto err;
recorder = tracecmd_create_buffer_recorder_fd2(fd, fd2, cpu, flags, buffer, maxkb);
if (!recorder)
goto err2;
out:
/* Unlink file2, we need to add everything to file at the end */
unlink(file2);
free(file2);
return recorder;
err2:
close(fd2);
err:
close(fd);
unlink(file);
goto out;
}
struct tracecmd_recorder *tracecmd_create_recorder_fd(int fd, int cpu, unsigned flags)
{
const char *tracing;
tracing = tracecmd_get_tracing_dir();
if (!tracing) {
errno = ENODEV;
return NULL;
}
return tracecmd_create_buffer_recorder_fd(fd, cpu, flags, tracing);
}
struct tracecmd_recorder *tracecmd_create_recorder(const char *file, int cpu, unsigned flags)
{
const char *tracing;
tracing = tracecmd_get_tracing_dir();
if (!tracing) {
errno = ENODEV;
return NULL;
}
return tracecmd_create_buffer_recorder(file, cpu, flags, tracing);
}
struct tracecmd_recorder *
tracecmd_create_recorder_maxkb(const char *file, int cpu, unsigned flags, int maxkb)
{
const char *tracing;
tracing = tracecmd_get_tracing_dir();
if (!tracing) {
errno = ENODEV;
return NULL;
}
return tracecmd_create_buffer_recorder_maxkb(file, cpu, flags, tracing, maxkb);
}
static inline void update_fd(struct tracecmd_recorder *recorder, int size)
{
int fd;
if (!recorder->max)
return;
recorder->count += size;
if (recorder->count >= recorder->page_size) {
recorder->count = 0;
recorder->pages++;
}
if (recorder->pages < recorder->max)
return;
recorder->pages = 0;
fd = recorder->fd;
/* Swap fd to next file. */
if (fd == recorder->fd1)
fd = recorder->fd2;
else
fd = recorder->fd1;
/* Zero out the new file we are writing to */
lseek64(fd, 0, SEEK_SET);
ftruncate(fd, 0);
recorder->fd = fd;
}
/*
* 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, recorder->fd_flags);
if (ret < 0) {
if (errno != EAGAIN && errno != EINTR) {
warning("recorder error in splice output");
return -1;
}
ret = 0;
} else
update_fd(recorder, ret);
return ret;
}
/*
* Returns -1 on error.
* or bytes of data read.
*/
static long read_data(struct tracecmd_recorder *recorder)
{
char buf[recorder->page_size];
long ret;
ret = read(recorder->trace_fd, buf, recorder->page_size);
if (ret < 0) {
if (errno != EAGAIN && errno != EINTR) {
warning("recorder error in read output");
return -1;
}
ret = 0;
}
if (ret > 0) {
write(recorder->fd, buf, ret);
update_fd(recorder, ret);
}
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);
/* Do not block on streams for write */
recorder->fd_flags |= 2; /* NON_BLOCK */
}
long tracecmd_flush_recording(struct tracecmd_recorder *recorder)
{
char buf[recorder->page_size];
long total = 0;
long wrote = 0;
long ret;
set_nonblock(recorder);
do {
if (recorder->flags & TRACECMD_RECORD_NOSPLICE)
ret = read_data(recorder);
else
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);
wrote += ret;
}
} while (ret > 0);
/* Make sure we finish off with a page size boundary */
wrote &= recorder->page_size - 1;
if (wrote) {
memset(buf, 0, recorder->page_size);
write(recorder->fd, buf, recorder->page_size - wrote);
total += recorder->page_size;
}
return total;
}
int tracecmd_start_recording(struct tracecmd_recorder *recorder, unsigned long sleep)
{
struct timespec req;
long read = 1;
long ret;
recorder->stop = 0;
do {
/* Only sleep if we did not read anything last time */
if (!read && sleep) {
req.tv_sec = sleep / 1000000;
req.tv_nsec = (sleep % 1000000) * 1000;
nanosleep(&req, NULL);
}
read = 0;
do {
if (recorder->flags & TRACECMD_RECORD_NOSPLICE)
ret = read_data(recorder);
else
ret = splice_data(recorder);
if (ret < 0)
return ret;
read += ret;
} while (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;
}