| /* |
| * 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; |
| } |