blob: 44f245d5a4c010db6405254efc588897e8f1ab7e [file]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include "tracefs.h"
#include "trace-cmd-private.h"
#include "trace-cmd-local.h"
#include "event-utils.h"
/* F_GETPIPE_SZ was introduced in 2.6.35, older systems don't have it */
#ifndef F_GETPIPE_SZ
# define F_GETPIPE_SZ 1032 /* The Linux number for the option */
#endif
#ifndef SPLICE_F_MOVE
# define SPLICE_F_MOVE 1
# define SPLICE_F_NONBLOCK 2
# define SPLICE_F_MORE 4
# define SPLICE_F_GIFT 8
#endif
#define POLL_TIMEOUT_MS 1000
struct tracecmd_recorder {
struct tracefs_cpu *tcpu;
int fd;
int fd1;
int fd2;
int page_size;
int subbuf_size;
int cpu;
int stop;
int max;
int pages;
int count;
unsigned flags;
};
static int append_file(int size, int dst, int src)
{
char buf[size];
int r;
lseek(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) {
lseek(recorder->fd1, 0, SEEK_END);
goto close;
}
lseek(recorder->fd1, 0, SEEK_SET);
ftruncate(recorder->fd1, 0);
}
append_file(recorder->page_size, recorder->fd1, recorder->fd2);
}
close:
tracefs_cpu_close(recorder->tcpu);
if (recorder->fd1 >= 0)
close(recorder->fd1);
if (recorder->fd2 >= 0)
close(recorder->fd2);
free(recorder);
}
static int set_nonblock(struct tracecmd_recorder *recorder)
{
return tracefs_cpu_stop(recorder->tcpu);
}
static struct tracecmd_recorder *
create_buffer_recorder_fd2(int fd, int fd2, int cpu, unsigned flags,
struct tracefs_instance *instance, int maxkb, int tfd)
{
struct tracecmd_recorder *recorder;
bool nonblock = false;
recorder = malloc(sizeof(*recorder));
if (!recorder)
return NULL;
recorder->flags = flags;
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;
if (recorder->flags & TRACECMD_RECORD_POLL)
nonblock = true;
if (tfd >= 0)
recorder->tcpu = tracefs_cpu_alloc_fd(tfd, recorder->page_size, nonblock);
else
recorder->tcpu = tracefs_cpu_open(instance, cpu, nonblock);
if (!recorder->tcpu)
goto out_free;
recorder->subbuf_size = tracefs_cpu_read_size(recorder->tcpu);
return recorder;
out_free:
tracecmd_free_recorder(recorder);
return NULL;
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_fd2(int fd, int fd2, int cpu, unsigned flags,
struct tracefs_instance *instance, int maxkb)
{
return create_buffer_recorder_fd2(fd, fd2, cpu, flags, instance, maxkb, -1);
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_fd(int fd, int cpu, unsigned flags, struct tracefs_instance *instance)
{
return tracecmd_create_buffer_recorder_fd2(fd, -1, cpu, flags, instance, 0);
}
static struct tracecmd_recorder *
__tracecmd_create_buffer_recorder(const char *file, int cpu, unsigned flags,
struct tracefs_instance *instance, int tfd, int maxkb)
{
struct tracecmd_recorder *recorder;
int fd, fd2 = -1;
char *file2;
fd = open(file, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (fd < 0)
return NULL;
if (maxkb) {
int len = strlen(file);
file2 = malloc(len + 3);
if (!file2)
return NULL;
sprintf(file2, "%s.1", file);
fd2 = open(file2, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (fd2 < 0) {
close(fd);
unlink(file);
free(file2);
return NULL;
}
}
recorder = create_buffer_recorder_fd2(fd, fd2, cpu, flags, instance, maxkb, tfd);
if (!recorder) {
close(fd);
unlink(file);
if (fd2 != -1)
close(fd2);
}
if (fd2 != -1) {
/* Unlink file2, we need to add everything to file at the end */
unlink(file2);
free(file2);
}
return recorder;
}
struct tracecmd_recorder *
tracecmd_create_buffer_recorder_maxkb(const char *file, int cpu, unsigned flags,
struct tracefs_instance *instance, 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, instance);
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 (fd2 < 0)
goto err;
recorder = tracecmd_create_buffer_recorder_fd2(fd, fd2, cpu, flags, instance, 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_buffer_recorder(const char *file, int cpu, unsigned flags,
struct tracefs_instance *instance)
{
return __tracecmd_create_buffer_recorder(file, cpu, flags, instance, -1, 0);
}
/**
* tracecmd_create_recorder_virt - Create a recorder reading tracing data
* from the trace_fd file descriptor instead of from the local tracefs
* @file: output filename where tracing data will be written
* @cpu: which CPU is being traced
* @flags: flags configuring the recorder (see TRACECMD_RECORDER_* enums)
* @trace_fd: file descriptor from where tracing data will be read
*/
struct tracecmd_recorder *
tracecmd_create_recorder_virt(const char *file, int cpu, unsigned flags,
int trace_fd, int maxkb)
{
return __tracecmd_create_buffer_recorder(file, cpu, flags, NULL, trace_fd, maxkb);
}
struct tracecmd_recorder *tracecmd_create_recorder_fd(int fd, int cpu, unsigned flags)
{
return tracecmd_create_buffer_recorder_fd(fd, cpu, flags, NULL);
}
struct tracecmd_recorder *tracecmd_create_recorder(const char *file, int cpu, unsigned flags)
{
return tracecmd_create_buffer_recorder(file, cpu, flags, NULL);
}
struct tracecmd_recorder *
tracecmd_create_recorder_maxkb(const char *file, int cpu, unsigned flags, int maxkb)
{
return tracecmd_create_buffer_recorder_maxkb(file, cpu, flags, NULL, 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->pages += recorder->count / recorder->page_size;
recorder->count = 0;
}
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 */
lseek(fd, 0, SEEK_SET);
ftruncate(fd, 0);
recorder->fd = fd;
}
/*
* Returns -1 on error.
* or bytes of data read.
*/
static long read_data(struct tracecmd_recorder *recorder)
{
bool nonblock = recorder->stop;
char buf[recorder->subbuf_size];
long left;
long r, w;
r = tracefs_cpu_read(recorder->tcpu, buf, nonblock);
if (r < 0)
return r;
left = r;
do {
w = write(recorder->fd, buf + (r - left), left);
if (w > 0) {
left -= w;
update_fd(recorder, w);
}
} while (w >= 0 && left);
if (w < 0)
r = w;
return r;
}
/*
* Returns -1 on error.
* or bytes of data read.
*/
static long direct_splice_data(struct tracecmd_recorder *recorder)
{
bool nonblock = recorder->stop;
return tracefs_cpu_pipe(recorder->tcpu, recorder->fd, nonblock);
}
static long move_data(struct tracecmd_recorder *recorder)
{
bool nonblock = recorder->stop;
long ret;
if (recorder->flags & TRACECMD_RECORD_NOSPLICE)
return read_data(recorder);
if (recorder->flags & TRACECMD_RECORD_NOBRASS)
return direct_splice_data(recorder);
ret = tracefs_cpu_write(recorder->tcpu, recorder->fd, nonblock);
if (ret > 0)
update_fd(recorder, ret);
return ret;
}
long tracecmd_flush_recording(struct tracecmd_recorder *recorder, bool finish)
{
char buf[recorder->subbuf_size];
long total = 0;
long wrote = 0;
long ret;
if (!recorder)
return 0;
if (!finish)
return tracefs_cpu_flush_write(recorder->tcpu, recorder->fd);
set_nonblock(recorder);
do {
ret = tracefs_cpu_flush_write(recorder->tcpu, recorder->fd);
if (ret > 0)
wrote += ret;
} while (ret > 0);
/* Make sure we finish off with a page size boundary */
wrote &= recorder->subbuf_size - 1;
if (wrote) {
memset(buf, 0, recorder->subbuf_size);
write(recorder->fd, buf, recorder->subbuf_size - wrote);
total += recorder->subbuf_size;
}
return total;
}
int tracecmd_start_recording(struct tracecmd_recorder *recorder, unsigned long sleep)
{
struct timespec req = {
.tv_sec = sleep / 1000000,
.tv_nsec = (sleep % 1000000) * 1000,
};
long read = 1;
long ret;
recorder->stop = 0;
do {
/* Only sleep if we did not read anything last time */
if (!read && sleep)
nanosleep(&req, NULL);
read = 0;
do {
ret = move_data(recorder);
if (ret < 0) {
if (errno == EINTR)
continue;
if ((recorder->flags & TRACECMD_RECORD_POLL) &&
errno == EAGAIN)
continue;
return ret;
}
read += ret;
} while (ret > 0 && !recorder->stop);
} while (!recorder->stop);
/* Flush out the rest */
ret = tracecmd_flush_recording(recorder, true);
if (ret < 0)
return ret;
return 0;
}
int tracecmd_stop_recording(struct tracecmd_recorder *recorder)
{
if (!recorder)
return -1;
recorder->stop = 1;
return set_nonblock(recorder);
}