| /* | |
| * 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> | |
| * | |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| */ | |
| #define _LARGEFILE64_SOURCE | |
| #include <dirent.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <getopt.h> | |
| #include <stdarg.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <sys/wait.h> | |
| #include <sys/mman.h> | |
| #include <pthread.h> | |
| #include <regex.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include "trace-cmd-local.h" | |
| #include "kbuffer.h" | |
| #include "list.h" | |
| #define MISSING_EVENTS (1 << 31) | |
| #define MISSING_STORED (1 << 30) | |
| #define COMMIT_MASK ((1 << 27) - 1) | |
| /* for debugging read instead of mmap */ | |
| static int force_read = 0; | |
| struct page { | |
| struct list_head list; | |
| off64_t offset; | |
| struct tracecmd_input *handle; | |
| void *map; | |
| int ref_count; | |
| long long lost_events; | |
| #if DEBUG_RECORD | |
| struct pevent_record *records; | |
| #endif | |
| }; | |
| struct cpu_data { | |
| /* the first two never change */ | |
| unsigned long long file_offset; | |
| unsigned long long file_size; | |
| unsigned long long offset; | |
| unsigned long long size; | |
| unsigned long long timestamp; | |
| struct list_head pages; | |
| struct pevent_record *next; | |
| struct page *page; | |
| struct kbuffer *kbuf; | |
| int cpu; | |
| int pipe_fd; | |
| }; | |
| struct input_buffer_instance { | |
| char *name; | |
| size_t offset; | |
| }; | |
| struct tracecmd_input { | |
| struct pevent *pevent; | |
| struct plugin_list *plugin_list; | |
| struct tracecmd_input *parent; | |
| unsigned long flags; | |
| int fd; | |
| int long_size; | |
| int page_size; | |
| int cpus; | |
| int ref; | |
| int nr_buffers; /* buffer instances */ | |
| bool use_trace_clock; | |
| bool read_page; | |
| bool use_pipe; | |
| struct cpu_data *cpu_data; | |
| unsigned long long ts_offset; | |
| char * cpustats; | |
| char * uname; | |
| struct input_buffer_instance *buffers; | |
| struct tracecmd_ftrace finfo; | |
| struct hook_list *hooks; | |
| /* file information */ | |
| size_t header_files_start; | |
| size_t ftrace_files_start; | |
| size_t event_files_start; | |
| size_t total_file_size; | |
| }; | |
| __thread struct tracecmd_input *tracecmd_curr_thread_handle; | |
| void tracecmd_set_flag(struct tracecmd_input *handle, int flag) | |
| { | |
| handle->flags |= flag; | |
| } | |
| void tracecmd_clear_flag(struct tracecmd_input *handle, int flag) | |
| { | |
| handle->flags &= ~flag; | |
| } | |
| unsigned long tracecmd_get_flags(struct tracecmd_input *handle) | |
| { | |
| return handle->flags; | |
| } | |
| #if DEBUG_RECORD | |
| static void remove_record(struct page *page, struct pevent_record *record) | |
| { | |
| if (record->prev) | |
| record->prev->next = record->next; | |
| else | |
| page->records = record->next; | |
| if (record->next) | |
| record->next->prev = record->prev; | |
| } | |
| static void add_record(struct page *page, struct pevent_record *record) | |
| { | |
| if (page->records) | |
| page->records->prev = record; | |
| record->next = page->records; | |
| record->prev = NULL; | |
| page->records = record; | |
| } | |
| static const char *show_records(struct list_head *pages) | |
| { | |
| static char buf[BUFSIZ + 1]; | |
| struct pevent_record *record; | |
| struct page *page; | |
| int len; | |
| memset(buf, 0, sizeof(buf)); | |
| len = 0; | |
| list_for_each_entry(page, pages, list) { | |
| for (record = page->records; record; record = record->next) { | |
| int n; | |
| n = snprintf(buf+len, BUFSIZ - len, " 0x%lx", record->alloc_addr); | |
| len += n; | |
| if (len >= BUFSIZ) | |
| break; | |
| } | |
| } | |
| return buf; | |
| } | |
| #else | |
| static inline void remove_record(struct page *page, struct pevent_record *record) {} | |
| static inline void add_record(struct page *page, struct pevent_record *record) {} | |
| static const char *show_records(struct list_head *pages) | |
| { | |
| return ""; | |
| } | |
| #endif | |
| static int init_cpu(struct tracecmd_input *handle, int cpu); | |
| static int do_read(struct tracecmd_input *handle, void *data, int size) | |
| { | |
| int tot = 0; | |
| int r; | |
| do { | |
| r = read(handle->fd, data, size - tot); | |
| tot += r; | |
| if (!r) | |
| break; | |
| if (r < 0) | |
| return r; | |
| } while (tot != size); | |
| return tot; | |
| } | |
| static int | |
| do_read_check(struct tracecmd_input *handle, void *data, int size) | |
| { | |
| int ret; | |
| ret = do_read(handle, data, size); | |
| if (ret < 0) | |
| return ret; | |
| if (ret != size) | |
| return -1; | |
| return 0; | |
| } | |
| static char *read_string(struct tracecmd_input *handle) | |
| { | |
| char buf[BUFSIZ]; | |
| char *str = NULL; | |
| int size = 0; | |
| int i; | |
| int r; | |
| for (;;) { | |
| r = do_read(handle, buf, BUFSIZ); | |
| if (r < 0) | |
| goto fail; | |
| if (!r) | |
| goto fail; | |
| for (i = 0; i < r; i++) { | |
| if (!buf[i]) | |
| break; | |
| } | |
| if (i < r) | |
| break; | |
| if (str) { | |
| size += BUFSIZ; | |
| str = realloc(str, size); | |
| if (!str) | |
| return NULL; | |
| memcpy(str + (size - BUFSIZ), buf, BUFSIZ); | |
| } else { | |
| size = BUFSIZ; | |
| str = malloc(size); | |
| if (!str) | |
| return NULL; | |
| memcpy(str, buf, size); | |
| } | |
| } | |
| /* move the file descriptor to the end of the string */ | |
| r = lseek(handle->fd, -(r - (i+1)), SEEK_CUR); | |
| if (r < 0) | |
| goto fail; | |
| if (str) { | |
| size += i + 1; | |
| str = realloc(str, size); | |
| if (!str) | |
| return NULL; | |
| memcpy(str + (size - i), buf, i); | |
| str[size] = 0; | |
| } else { | |
| size = i + 1; | |
| str = malloc(size); | |
| if (!str) | |
| return NULL; | |
| memcpy(str, buf, i); | |
| str[i] = 0; | |
| } | |
| return str; | |
| fail: | |
| if (str) | |
| free(str); | |
| return NULL; | |
| } | |
| static unsigned int read4(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| unsigned int data; | |
| if (do_read_check(handle, &data, 4)) | |
| return -1; | |
| return __data2host4(pevent, data); | |
| } | |
| static unsigned long long read8(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| unsigned long long data; | |
| if (do_read_check(handle, &data, 8)) | |
| return -1; | |
| return __data2host8(pevent, data); | |
| } | |
| static int read_header_files(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| long long size; | |
| char *header; | |
| char buf[BUFSIZ]; | |
| if (do_read_check(handle, buf, 12)) | |
| return -1; | |
| if (memcmp(buf, "header_page", 12) != 0) | |
| return -1; | |
| size = read8(handle); | |
| if (size < 0) | |
| return -1; | |
| header = malloc(size); | |
| if (!header) | |
| return -1; | |
| if (do_read_check(handle, header, size)) | |
| goto failed_read; | |
| pevent_parse_header_page(pevent, header, size, handle->long_size); | |
| free(header); | |
| /* | |
| * The size field in the page is of type long, | |
| * use that instead, since it represents the kernel. | |
| */ | |
| handle->long_size = pevent->header_page_size_size; | |
| if (do_read_check(handle, buf, 13)) | |
| return -1; | |
| if (memcmp(buf, "header_event", 13) != 0) | |
| return -1; | |
| size = read8(handle); | |
| if (size < 0) | |
| return -1; | |
| header = malloc(size); | |
| if (!header) | |
| return -1; | |
| if (do_read_check(handle, header, size)) | |
| goto failed_read; | |
| free(header); | |
| handle->ftrace_files_start = | |
| lseek64(handle->fd, 0, SEEK_CUR); | |
| return 0; | |
| failed_read: | |
| free(header); | |
| return -1; | |
| } | |
| static int regex_event_buf(const char *file, int size, regex_t *epreg) | |
| { | |
| char *buf; | |
| char *line; | |
| int ret; | |
| buf = malloc(size + 1); | |
| if (!buf) | |
| die("malloc"); | |
| strncpy(buf, file, size); | |
| buf[size] = 0; | |
| /* get the name from the first line */ | |
| line = strtok(buf, "\n"); | |
| if (!line) { | |
| warning("No newline found in '%s'", buf); | |
| return 0; | |
| } | |
| /* skip name if it is there */ | |
| if (strncmp(line, "name: ", 6) == 0) | |
| line += 6; | |
| ret = regexec(epreg, line, 0, NULL, 0) == 0; | |
| free(buf); | |
| return ret; | |
| } | |
| static int read_ftrace_file(struct tracecmd_input *handle, | |
| unsigned long long size, | |
| int print, regex_t *epreg) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| char *buf; | |
| buf = malloc(size); | |
| if (!buf) | |
| return -1; | |
| if (do_read_check(handle, buf, size)) { | |
| free(buf); | |
| return -1; | |
| } | |
| if (epreg) { | |
| if (print || regex_event_buf(buf, size, epreg)) | |
| printf("%.*s\n", (int)size, buf); | |
| } else { | |
| if (pevent_parse_event(pevent, buf, size, "ftrace")) | |
| pevent->parsing_failures = 1; | |
| } | |
| free(buf); | |
| return 0; | |
| } | |
| static int read_event_file(struct tracecmd_input *handle, | |
| char *system, unsigned long long size, | |
| int print, int *sys_printed, | |
| regex_t *epreg) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| char *buf; | |
| buf = malloc(size); | |
| if (!buf) | |
| return -1; | |
| if (do_read_check(handle, buf, size)) { | |
| free(buf); | |
| return -1; | |
| } | |
| if (epreg) { | |
| if (print || regex_event_buf(buf, size, epreg)) { | |
| if (!*sys_printed) { | |
| printf("\nsystem: %s\n", system); | |
| *sys_printed = 1; | |
| } | |
| printf("%.*s\n", (int)size, buf); | |
| } | |
| } else { | |
| if (pevent_parse_event(pevent, buf, size, system)) | |
| pevent->parsing_failures = 1; | |
| } | |
| free(buf); | |
| return 0; | |
| } | |
| static int make_preg_files(const char *regex, regex_t *system, | |
| regex_t *event, int *unique) | |
| { | |
| char *buf; | |
| char *sstr; | |
| char *estr; | |
| int ret; | |
| /* unique is set if a colon is found */ | |
| *unique = 0; | |
| /* split "system:event" into "system" and "event" */ | |
| buf = strdup(regex); | |
| if (!buf) | |
| die("malloc"); | |
| sstr = strtok(buf, ":"); | |
| estr = strtok(NULL, ":"); | |
| /* If no colon is found, set event == system */ | |
| if (!estr) | |
| estr = sstr; | |
| else | |
| *unique = 1; | |
| ret = regcomp(system, sstr, REG_ICASE|REG_NOSUB); | |
| if (ret) { | |
| warning("Bad regular expression '%s'", sstr); | |
| goto out; | |
| } | |
| ret = regcomp(event, estr, REG_ICASE|REG_NOSUB); | |
| if (ret) { | |
| warning("Bad regular expression '%s'", estr); | |
| goto out; | |
| } | |
| out: | |
| free(buf); | |
| return ret; | |
| } | |
| static int read_ftrace_files(struct tracecmd_input *handle, const char *regex) | |
| { | |
| unsigned long long size; | |
| regex_t spreg; | |
| regex_t epreg; | |
| regex_t *sreg = NULL; | |
| regex_t *ereg = NULL; | |
| int print_all = 0; | |
| int unique; | |
| int count; | |
| int ret; | |
| int i; | |
| if (regex) { | |
| sreg = &spreg; | |
| ereg = &epreg; | |
| ret = make_preg_files(regex, sreg, ereg, &unique); | |
| if (ret) | |
| return -1; | |
| if (regexec(sreg, "ftrace", 0, NULL, 0) == 0) { | |
| /* | |
| * If the system matches a regex that did | |
| * not contain a colon, then print all events. | |
| */ | |
| if (!unique) | |
| print_all = 1; | |
| } else if (unique) { | |
| /* | |
| * The user specified a unique event that did | |
| * not match the ftrace system. Don't print any | |
| * events here. | |
| */ | |
| regfree(sreg); | |
| regfree(ereg); | |
| sreg = NULL; | |
| ereg = NULL; | |
| } | |
| } | |
| count = read4(handle); | |
| if (count < 0) | |
| return -1; | |
| for (i = 0; i < count; i++) { | |
| size = read8(handle); | |
| if (size < 0) | |
| return -1; | |
| ret = read_ftrace_file(handle, size, print_all, ereg); | |
| if (ret < 0) | |
| return -1; | |
| } | |
| handle->event_files_start = | |
| lseek64(handle->fd, 0, SEEK_CUR); | |
| if (sreg) { | |
| regfree(sreg); | |
| regfree(ereg); | |
| } | |
| return 0; | |
| } | |
| static int read_event_files(struct tracecmd_input *handle, const char *regex) | |
| { | |
| unsigned long long size; | |
| char *system; | |
| regex_t spreg; | |
| regex_t epreg; | |
| regex_t *sreg = NULL; | |
| regex_t *ereg = NULL; | |
| regex_t *reg; | |
| int systems; | |
| int print_all; | |
| int sys_printed; | |
| int count; | |
| int unique; | |
| int ret; | |
| int i,x; | |
| if (regex) { | |
| sreg = &spreg; | |
| ereg = &epreg; | |
| ret = make_preg_files(regex, sreg, ereg, &unique); | |
| if (ret) | |
| return -1; | |
| } | |
| systems = read4(handle); | |
| if (systems < 0) | |
| return -1; | |
| for (i = 0; i < systems; i++) { | |
| system = read_string(handle); | |
| if (!system) | |
| return -1; | |
| sys_printed = 0; | |
| print_all = 0; | |
| reg = ereg; | |
| if (sreg) { | |
| if (regexec(sreg, system, 0, NULL, 0) == 0) { | |
| /* | |
| * If the user passed in a regex that | |
| * did not contain a colon, then we can | |
| * print all the events of this system. | |
| */ | |
| if (!unique) | |
| print_all = 1; | |
| } else if (unique) { | |
| /* | |
| * The user passed in a unique event that | |
| * specified a specific system and event. | |
| * Since this system doesn't match this | |
| * event, then we don't print any events | |
| * for this system. | |
| */ | |
| reg = NULL; | |
| } | |
| } | |
| count = read4(handle); | |
| if (count < 0) | |
| goto failed; | |
| for (x=0; x < count; x++) { | |
| size = read8(handle); | |
| if (size < 0) | |
| goto failed; | |
| ret = read_event_file(handle, system, size, | |
| print_all, &sys_printed, | |
| reg); | |
| if (ret < 0) | |
| goto failed; | |
| } | |
| free(system); | |
| } | |
| if (sreg) { | |
| regfree(sreg); | |
| regfree(ereg); | |
| } | |
| return 0; | |
| failed: | |
| if (sreg) { | |
| regfree(sreg); | |
| regfree(ereg); | |
| } | |
| free(system); | |
| return -1; | |
| } | |
| static int read_proc_kallsyms(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| int size; | |
| char *buf; | |
| size = read4(handle); | |
| if (!size) | |
| return 0; /* OK? */ | |
| if (size < 0) | |
| return -1; | |
| buf = malloc(size+1); | |
| if (!buf) | |
| return -1; | |
| if (do_read_check(handle, buf, size)){ | |
| free(buf); | |
| return -1; | |
| } | |
| buf[size] = 0; | |
| parse_proc_kallsyms(pevent, buf, size); | |
| free(buf); | |
| return 0; | |
| } | |
| static int read_ftrace_printk(struct tracecmd_input *handle) | |
| { | |
| int size; | |
| char *buf; | |
| size = read4(handle); | |
| if (!size) | |
| return 0; /* OK? */ | |
| if (size < 0) | |
| return -1; | |
| buf = malloc(size + 1); | |
| if (!buf) | |
| return -1; | |
| if (do_read_check(handle, buf, size)) { | |
| free(buf); | |
| return -1; | |
| } | |
| buf[size] = 0; | |
| parse_ftrace_printk(handle->pevent, buf, size); | |
| free(buf); | |
| return 0; | |
| } | |
| static int read_and_parse_cmdlines(struct tracecmd_input *handle); | |
| /** | |
| * tracecmd_read_headers - read the header information from trace.dat | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * This reads the trace.dat file for various information. Like the | |
| * format of the ring buffer, event formats, ftrace formats, kallsyms | |
| * and printk. | |
| */ | |
| int tracecmd_read_headers(struct tracecmd_input *handle) | |
| { | |
| int ret; | |
| ret = read_header_files(handle); | |
| if (ret < 0) | |
| return -1; | |
| ret = read_ftrace_files(handle, NULL); | |
| if (ret < 0) | |
| return -1; | |
| ret = read_event_files(handle, NULL); | |
| if (ret < 0) | |
| return -1; | |
| ret = read_proc_kallsyms(handle); | |
| if (ret < 0) | |
| return -1; | |
| ret = read_ftrace_printk(handle); | |
| if (ret < 0) | |
| return -1; | |
| if (read_and_parse_cmdlines(handle) < 0) | |
| return -1; | |
| pevent_set_long_size(handle->pevent, handle->long_size); | |
| return 0; | |
| } | |
| static unsigned long long calc_page_offset(struct tracecmd_input *handle, | |
| unsigned long long offset) | |
| { | |
| return offset & ~(handle->page_size - 1); | |
| } | |
| static int read_page(struct tracecmd_input *handle, off64_t offset, | |
| int cpu, void *map) | |
| { | |
| off64_t save_seek; | |
| off64_t ret; | |
| if (handle->use_pipe) { | |
| ret = read(handle->cpu_data[cpu].pipe_fd, map, handle->page_size); | |
| /* Set EAGAIN if the pipe is empty */ | |
| if (ret < 0) { | |
| errno = EAGAIN; | |
| return -1; | |
| } else if (ret == 0) { | |
| /* Set EINVAL when the pipe has closed */ | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* other parts of the code may expect the pointer to not move */ | |
| save_seek = lseek64(handle->fd, 0, SEEK_CUR); | |
| ret = lseek64(handle->fd, offset, SEEK_SET); | |
| if (ret < 0) | |
| return -1; | |
| ret = read(handle->fd, map, handle->page_size); | |
| if (ret < 0) | |
| return -1; | |
| /* reset the file pointer back */ | |
| lseek64(handle->fd, save_seek, SEEK_SET); | |
| return 0; | |
| } | |
| static struct page *allocate_page(struct tracecmd_input *handle, | |
| int cpu, off64_t offset) | |
| { | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| struct page *page; | |
| int ret; | |
| list_for_each_entry(page, &cpu_data->pages, list) { | |
| if (page->offset == offset) { | |
| page->ref_count++; | |
| return page; | |
| } | |
| } | |
| page = malloc(sizeof(*page)); | |
| if (!page) | |
| return NULL; | |
| memset(page, 0, sizeof(*page)); | |
| page->offset = offset; | |
| page->handle = handle; | |
| if (handle->read_page) { | |
| page->map = malloc(handle->page_size); | |
| if (page->map) { | |
| ret = read_page(handle, offset, cpu, page->map); | |
| if (ret < 0) { | |
| free(page->map); | |
| page->map = NULL; | |
| } | |
| } | |
| } else { | |
| page->map = mmap(NULL, handle->page_size, PROT_READ, MAP_PRIVATE, | |
| handle->fd, offset); | |
| if (page->map == MAP_FAILED) | |
| page->map = NULL; | |
| } | |
| if (!page->map) { | |
| free(page); | |
| return NULL; | |
| } | |
| list_add(&page->list, &cpu_data->pages); | |
| page->ref_count = 1; | |
| return page; | |
| } | |
| static void __free_page(struct tracecmd_input *handle, struct page *page) | |
| { | |
| if (!page->ref_count) | |
| die("Page ref count is zero!\n"); | |
| page->ref_count--; | |
| if (page->ref_count) | |
| return; | |
| if (handle->read_page) | |
| free(page->map); | |
| else | |
| munmap(page->map, handle->page_size); | |
| list_del(&page->list); | |
| free(page); | |
| } | |
| static void free_page(struct tracecmd_input *handle, int cpu) | |
| { | |
| if (!handle->cpu_data || cpu >= handle->cpus || | |
| !handle->cpu_data[cpu].page) | |
| return; | |
| __free_page(handle, handle->cpu_data[cpu].page); | |
| handle->cpu_data[cpu].page = NULL; | |
| } | |
| static void __free_record(struct pevent_record *record) | |
| { | |
| if (record->priv) { | |
| struct page *page = record->priv; | |
| remove_record(page, record); | |
| __free_page(page->handle, page); | |
| } | |
| free(record); | |
| } | |
| void free_record(struct pevent_record *record) | |
| { | |
| if (!record) | |
| return; | |
| if (!record->ref_count) | |
| die("record ref count is zero!"); | |
| record->ref_count--; | |
| if (record->ref_count) | |
| return; | |
| if (record->locked) | |
| die("freeing record when it is locked!"); | |
| record->data = NULL; | |
| __free_record(record); | |
| } | |
| void tracecmd_record_ref(struct pevent_record *record) | |
| { | |
| record->ref_count++; | |
| #if DEBUG_RECORD | |
| /* Update locating of last reference */ | |
| record->alloc_addr = (unsigned long)__builtin_return_address(0); | |
| #endif | |
| } | |
| static void free_next(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct pevent_record *record; | |
| if (!handle->cpu_data || cpu >= handle->cpus) | |
| return; | |
| record = handle->cpu_data[cpu].next; | |
| if (!record) | |
| return; | |
| handle->cpu_data[cpu].next = NULL; | |
| record->locked = 0; | |
| free_record(record); | |
| } | |
| /* | |
| * Page is mapped, now read in the page header info. | |
| */ | |
| static int update_page_info(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| void *ptr = handle->cpu_data[cpu].page->map; | |
| struct kbuffer *kbuf = handle->cpu_data[cpu].kbuf; | |
| /* FIXME: handle header page */ | |
| if (pevent->header_page_ts_size != 8) { | |
| warning("expected a long long type for timestamp"); | |
| return -1; | |
| } | |
| kbuffer_load_subbuffer(kbuf, ptr); | |
| if (kbuffer_subbuffer_size(kbuf) > handle->page_size) | |
| die("bad page read, with size of %d", | |
| kbuffer_subbuffer_size(kbuf)); | |
| handle->cpu_data[cpu].timestamp = kbuffer_timestamp(kbuf) + handle->ts_offset; | |
| return 0; | |
| } | |
| /* | |
| * get_page maps a page for a given cpu. | |
| * | |
| * Returns 1 if the page was already mapped, | |
| * 0 if it mapped successfully | |
| * -1 on error | |
| */ | |
| static int get_page(struct tracecmd_input *handle, int cpu, | |
| off64_t offset) | |
| { | |
| /* Don't map if the page is already where we want */ | |
| if (handle->cpu_data[cpu].offset == offset && | |
| handle->cpu_data[cpu].page) | |
| return 1; | |
| /* Do not map no data for CPU */ | |
| if (!handle->cpu_data[cpu].size) | |
| return -1; | |
| if (offset & (handle->page_size - 1)) { | |
| errno = -EINVAL; | |
| die("bad page offset %llx", offset); | |
| return -1; | |
| } | |
| if (offset < handle->cpu_data[cpu].file_offset || | |
| offset > handle->cpu_data[cpu].file_offset + | |
| handle->cpu_data[cpu].file_size) { | |
| errno = -EINVAL; | |
| die("bad page offset %llx", offset); | |
| return -1; | |
| } | |
| handle->cpu_data[cpu].offset = offset; | |
| handle->cpu_data[cpu].size = (handle->cpu_data[cpu].file_offset + | |
| handle->cpu_data[cpu].file_size) - | |
| offset; | |
| free_page(handle, cpu); | |
| handle->cpu_data[cpu].page = allocate_page(handle, cpu, offset); | |
| if (!handle->cpu_data[cpu].page) | |
| return -1; | |
| if (update_page_info(handle, cpu)) | |
| return -1; | |
| return 0; | |
| } | |
| static int get_next_page(struct tracecmd_input *handle, int cpu) | |
| { | |
| off64_t offset; | |
| if (!handle->cpu_data[cpu].page && !handle->use_pipe) | |
| return 0; | |
| free_page(handle, cpu); | |
| if (handle->cpu_data[cpu].size <= handle->page_size) { | |
| handle->cpu_data[cpu].offset = 0; | |
| return 0; | |
| } | |
| offset = handle->cpu_data[cpu].offset + handle->page_size; | |
| return get_page(handle, cpu, offset); | |
| } | |
| static struct pevent_record * | |
| peek_event(struct tracecmd_input *handle, unsigned long long offset, | |
| int cpu) | |
| { | |
| struct pevent_record *record = NULL; | |
| /* | |
| * Since the timestamp is calculated from the beginning | |
| * of the page and through each event, we reset the | |
| * page to the beginning. This is just used by | |
| * tracecmd_read_at. | |
| */ | |
| update_page_info(handle, cpu); | |
| do { | |
| free_next(handle, cpu); | |
| record = tracecmd_peek_data(handle, cpu); | |
| if (record && (record->offset + record->record_size) > offset) | |
| break; | |
| } while (record); | |
| return record; | |
| } | |
| static struct pevent_record * | |
| read_event(struct tracecmd_input *handle, unsigned long long offset, | |
| int cpu) | |
| { | |
| struct pevent_record *record; | |
| record = peek_event(handle, offset, cpu); | |
| if (record) | |
| record = tracecmd_read_data(handle, cpu); | |
| return record; | |
| } | |
| static struct pevent_record * | |
| find_and_peek_event(struct tracecmd_input *handle, unsigned long long offset, | |
| int *pcpu) | |
| { | |
| unsigned long long page_offset; | |
| int cpu; | |
| /* find the cpu that this offset exists in */ | |
| for (cpu = 0; cpu < handle->cpus; cpu++) { | |
| if (offset >= handle->cpu_data[cpu].file_offset && | |
| offset < handle->cpu_data[cpu].file_offset + | |
| handle->cpu_data[cpu].file_size) | |
| break; | |
| } | |
| /* Not found? */ | |
| if (cpu == handle->cpus) | |
| return NULL; | |
| /* Move this cpu index to point to this offest */ | |
| page_offset = calc_page_offset(handle, offset); | |
| if (get_page(handle, cpu, page_offset) < 0) | |
| return NULL; | |
| if (pcpu) | |
| *pcpu = cpu; | |
| return peek_event(handle, offset, cpu); | |
| } | |
| static struct pevent_record * | |
| find_and_read_event(struct tracecmd_input *handle, unsigned long long offset, | |
| int *pcpu) | |
| { | |
| struct pevent_record *record; | |
| int cpu; | |
| record = find_and_peek_event(handle, offset, &cpu); | |
| if (record) { | |
| record = tracecmd_read_data(handle, cpu); | |
| if (pcpu) | |
| *pcpu = cpu; | |
| } | |
| return record; | |
| } | |
| /** | |
| * tracecmd_read_at - read a record from a specific offset | |
| * @handle: input handle for the trace.dat file | |
| * @offset: the offset into the file to find the record | |
| * @pcpu: pointer to a variable to store the CPU id the record was found in | |
| * | |
| * This function is useful when looking for a previous record. | |
| * You can store the offset of the record "record->offset" and use that | |
| * offset to retreive the record again without needing to store any | |
| * other information about the record. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_at(struct tracecmd_input *handle, unsigned long long offset, | |
| int *pcpu) | |
| { | |
| unsigned long long page_offset; | |
| int cpu; | |
| page_offset = calc_page_offset(handle, offset); | |
| /* check to see if we have this page already */ | |
| for (cpu = 0; cpu < handle->cpus; cpu++) { | |
| if (handle->cpu_data[cpu].offset == page_offset && | |
| handle->cpu_data[cpu].file_size) | |
| break; | |
| } | |
| if (cpu < handle->cpus) { | |
| if (pcpu) | |
| *pcpu = cpu; | |
| return read_event(handle, offset, cpu); | |
| } else | |
| return find_and_read_event(handle, offset, pcpu); | |
| } | |
| /** | |
| * tracecmd_refresh_record - remaps the records data | |
| * @handle: input handle for the trace.dat file | |
| * @record: the record to be refreshed | |
| * | |
| * A record data points to a mmap section of memory. | |
| * by reading new records the mmap section may be unmapped. | |
| * This will refresh the record's data mapping. | |
| * | |
| * ===== OBSOLETED BY PAGE REFERENCES ===== | |
| * | |
| * Returns 1 if page is still mapped (does not modify CPU iterator) | |
| * 0 on successful mapping (was not mapped before, | |
| * This will update CPU iterator to point to | |
| * the next record) | |
| * -1 on error. | |
| */ | |
| int tracecmd_refresh_record(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| unsigned long long page_offset; | |
| int cpu = record->cpu; | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| int index; | |
| int ret; | |
| page_offset = calc_page_offset(handle, record->offset); | |
| index = record->offset & (handle->page_size - 1); | |
| ret = get_page(handle, record->cpu, page_offset); | |
| if (ret < 0) | |
| return -1; | |
| /* If the page is still mapped, there's nothing to do */ | |
| if (ret) | |
| return 1; | |
| record->data = kbuffer_read_at_offset(cpu_data->kbuf, index, &record->ts); | |
| cpu_data->timestamp = record->ts; | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_read_cpu_first - get the first record in a CPU | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU to search | |
| * | |
| * This returns the first (by time) record entry in a given CPU. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_cpu_first(struct tracecmd_input *handle, int cpu) | |
| { | |
| int ret; | |
| ret = get_page(handle, cpu, handle->cpu_data[cpu].file_offset); | |
| if (ret < 0) | |
| return NULL; | |
| /* If the page was already mapped, we need to reset it */ | |
| if (ret) | |
| update_page_info(handle, cpu); | |
| free_next(handle, cpu); | |
| return tracecmd_read_data(handle, cpu); | |
| } | |
| /** | |
| * tracecmd_read_cpu_last - get the last record in a CPU | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU to search | |
| * | |
| * This returns the last (by time) record entry in a given CPU. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_cpu_last(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct pevent_record *record = NULL; | |
| off64_t offset, page_offset; | |
| offset = handle->cpu_data[cpu].file_offset + | |
| handle->cpu_data[cpu].file_size; | |
| if (offset & (handle->page_size - 1)) | |
| offset &= ~(handle->page_size - 1); | |
| else | |
| offset -= handle->page_size; | |
| page_offset = offset; | |
| again: | |
| if (get_page(handle, cpu, page_offset) < 0) | |
| return NULL; | |
| offset = page_offset; | |
| do { | |
| free_record(record); | |
| record = tracecmd_read_data(handle, cpu); | |
| if (record) | |
| offset = record->offset; | |
| } while (record); | |
| record = tracecmd_read_at(handle, offset, NULL); | |
| /* | |
| * It is possible that a page has just a timestamp | |
| * or just padding on it. | |
| */ | |
| if (!record) { | |
| if (page_offset == handle->cpu_data[cpu].file_offset) | |
| return NULL; | |
| page_offset -= handle->page_size; | |
| goto again; | |
| } | |
| return record; | |
| } | |
| /** | |
| * tracecmd_set_cpu_to_timestamp - set the CPU iterator to a given time | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU pointer to set | |
| * @ts: the timestamp to set the CPU at. | |
| * | |
| * This sets the CPU iterator used by tracecmd_read_data and | |
| * tracecmd_peek_data to a location in the CPU storage near | |
| * a given timestamp. It will try to set the iterator to a time before | |
| * the time stamp and not actually at a given time. | |
| * | |
| * To use this to find a record in a time field, call this function | |
| * first, than iterate with tracecmd_read_data to find the records | |
| * you need. | |
| */ | |
| int | |
| tracecmd_set_cpu_to_timestamp(struct tracecmd_input *handle, int cpu, | |
| unsigned long long ts) | |
| { | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| off64_t start, end, next; | |
| if (cpu < 0 || cpu >= handle->cpus) { | |
| errno = -EINVAL; | |
| return -1; | |
| } | |
| if (!cpu_data->size) | |
| return -1; | |
| if (!cpu_data->page) { | |
| if (init_cpu(handle, cpu)) | |
| return -1; | |
| } | |
| if (cpu_data->timestamp == ts) { | |
| /* | |
| * If a record is cached, then that record is most | |
| * likely the matching timestamp. Otherwise we need | |
| * to start from the beginning of the index; | |
| */ | |
| if (!cpu_data->next || | |
| cpu_data->next->ts != ts) | |
| update_page_info(handle, cpu); | |
| return 0; | |
| } | |
| /* Set to the first record on current page */ | |
| update_page_info(handle, cpu); | |
| if (cpu_data->timestamp < ts) { | |
| start = cpu_data->offset; | |
| end = cpu_data->file_offset + cpu_data->file_size; | |
| if (end & (handle->page_size - 1)) | |
| end &= ~(handle->page_size - 1); | |
| else | |
| end -= handle->page_size; | |
| next = end; | |
| } else { | |
| end = cpu_data->offset; | |
| start = cpu_data->file_offset; | |
| next = start; | |
| } | |
| while (start < end) { | |
| if (get_page(handle, cpu, next) < 0) | |
| return -1; | |
| if (cpu_data->timestamp == ts) | |
| break; | |
| if (cpu_data->timestamp < ts) | |
| start = next; | |
| else | |
| end = next; | |
| next = start + (end - start) / 2; | |
| next = calc_page_offset(handle, next); | |
| /* Prevent an infinite loop if start and end are a page off */ | |
| if (next == start) | |
| start = next += handle->page_size; | |
| } | |
| /* | |
| * We need to end up on a page before the time stamp. | |
| * We go back even if the timestamp is the same. This is because | |
| * we want the event with the timestamp, not the page. The page | |
| * can start with the timestamp we are looking for, but the event | |
| * may be on the previous page. | |
| */ | |
| if (cpu_data->timestamp >= ts && | |
| cpu_data->offset > cpu_data->file_offset) | |
| get_page(handle, cpu, cpu_data->offset - handle->page_size); | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_set_all_cpus_to_timestamp - set all CPUs iterator to a given time | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU pointer to set | |
| * @ts: the timestamp to set the CPU at. | |
| * | |
| * This sets the CPU iterator used by tracecmd_read_data and | |
| * tracecmd_peek_data to a location in the CPU storage near | |
| * a given timestamp. It will try to set the iterator to a time before | |
| * the time stamp and not actually at a given time. | |
| * | |
| * To use this to find a record in a time field, call this function | |
| * first, than iterate with tracecmd_read_next_data to find the records | |
| * you need. | |
| */ | |
| void | |
| tracecmd_set_all_cpus_to_timestamp(struct tracecmd_input *handle, | |
| unsigned long long time) | |
| { | |
| int cpu; | |
| for (cpu = 0; cpu < handle->cpus; cpu++) | |
| tracecmd_set_cpu_to_timestamp(handle, cpu, time); | |
| } | |
| /** | |
| * tracecmd_set_cursor - set the offset for the next tracecmd_read_data | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU pointer to set | |
| * @offset: the offset to place the cursor | |
| * | |
| * Set the pointer to the next read or peek. This is useful when | |
| * needing to read sequentially and then look at another record | |
| * out of sequence without breaking the iteration. This is done with: | |
| * | |
| * record = tracecmd_peek_data() | |
| * offset = record->offset; | |
| * record = tracecmd_read_at(); | |
| * - do what ever with record - | |
| * tracecmd_set_cursor(handle, cpu, offset); | |
| * | |
| * Now the next tracecmd_peek_data or tracecmd_read_data will return | |
| * the original record. | |
| */ | |
| int tracecmd_set_cursor(struct tracecmd_input *handle, | |
| int cpu, unsigned long long offset) | |
| { | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| unsigned long long page_offset; | |
| if (cpu < 0 || cpu >= handle->cpus) | |
| return -1; | |
| if (offset < cpu_data->file_offset || | |
| offset > cpu_data->file_offset + cpu_data->file_size) | |
| return -1; /* cpu does not have this offset. */ | |
| /* Move this cpu index to point to this offest */ | |
| page_offset = calc_page_offset(handle, offset); | |
| if (get_page(handle, cpu, page_offset) < 0) | |
| return -1; | |
| peek_event(handle, offset, cpu); | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_get_cursor - get the offset for the next tracecmd_read_data | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU pointer to get the cursor from | |
| * | |
| * Returns the offset of the next record that would be read. | |
| */ | |
| unsigned long long | |
| tracecmd_get_cursor(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| struct kbuffer *kbuf = cpu_data->kbuf; | |
| if (cpu < 0 || cpu >= handle->cpus) | |
| return 0; | |
| /* | |
| * Use the next pointer if it exists and matches the | |
| * current timestamp. | |
| */ | |
| if (cpu_data->next && | |
| cpu_data->next->ts == cpu_data->timestamp) | |
| return cpu_data->next->offset; | |
| /* | |
| * Either the next point does not exist, or it does | |
| * not match the timestamp. The next read will use the | |
| * current page. | |
| * | |
| * If the offset is at the end, then return that. | |
| */ | |
| if (cpu_data->offset >= cpu_data->file_offset + | |
| cpu_data->file_size) | |
| return cpu_data->offset; | |
| return cpu_data->offset + kbuffer_curr_offset(kbuf); | |
| } | |
| /** | |
| * tracecmd_translate_data - create a record from raw data | |
| * @handle: input handle for the trace.dat file | |
| * @ptr: raw data to read | |
| * @size: the size of the data | |
| * | |
| * This function tries to create a record from some given | |
| * raw data. The data does not need to be from the trace.dat file. | |
| * It can be stored from another location. | |
| * | |
| * Note, since the timestamp is calculated from within the trace | |
| * buffer, the timestamp for the record will be zero, since it | |
| * can't calculate it. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_translate_data(struct tracecmd_input *handle, | |
| void *ptr, int size) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| struct pevent_record *record; | |
| unsigned int length; | |
| int swap = 1; | |
| /* minimum record read is 8, (warn?) (TODO: make 8 into macro) */ | |
| if (size < 8) | |
| return NULL; | |
| record = malloc(sizeof(*record)); | |
| if (!record) | |
| return NULL; | |
| memset(record, 0, sizeof(*record)); | |
| record->ref_count = 1; | |
| if (pevent->host_bigendian == pevent->file_bigendian) | |
| swap = 0; | |
| record->data = kbuffer_translate_data(swap, ptr, &length); | |
| record->size = length; | |
| if (record->data) | |
| record->record_size = record->size + (record->data - ptr); | |
| return record; | |
| } | |
| /** | |
| * tracecmd_read_page_record - read a record off of a page | |
| * @pevent: pevent used to parse the page | |
| * @page: the page to read | |
| * @size: the size of the page | |
| * @last_record: last record read from this page. | |
| * | |
| * If a ring buffer page is available, and the need to parse it | |
| * without having a handle, then this function can be used. | |
| * | |
| * The @pevent needs to be initialized to have the page header information | |
| * already available. | |
| * | |
| * The @last_record is used to know where to read the next record from. | |
| * If @last_record is NULL, the first record on the page will be read. | |
| * | |
| * Returns: | |
| * A newly allocated record that must be freed with free_record() if | |
| * a record is found. Otherwise NULL is returned if the record is bad | |
| * or no more records exist. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_page_record(struct pevent *pevent, void *page, int size, | |
| struct pevent_record *last_record) | |
| { | |
| unsigned long long ts; | |
| struct kbuffer *kbuf; | |
| struct pevent_record *record = NULL; | |
| enum kbuffer_long_size long_size; | |
| enum kbuffer_endian endian; | |
| void *ptr; | |
| if (pevent->file_bigendian) | |
| endian = KBUFFER_ENDIAN_BIG; | |
| else | |
| endian = KBUFFER_ENDIAN_LITTLE; | |
| if (pevent->header_page_size_size == 8) | |
| long_size = KBUFFER_LSIZE_8; | |
| else | |
| long_size = KBUFFER_LSIZE_4; | |
| kbuf = kbuffer_alloc(long_size, endian); | |
| if (!kbuf) | |
| return NULL; | |
| kbuffer_load_subbuffer(kbuf, page); | |
| if (kbuffer_subbuffer_size(kbuf) > size) { | |
| warning("tracecmd_read_page_record: page_size > size"); | |
| goto out_free; | |
| } | |
| if (last_record) { | |
| if (last_record->data < page || last_record->data >= (page + size)) { | |
| warning("tracecmd_read_page_record: bad last record (size=%u)", | |
| last_record->size); | |
| goto out_free; | |
| } | |
| do { | |
| ptr = kbuffer_next_event(kbuf, NULL); | |
| if (!ptr) | |
| break; | |
| } while (ptr < last_record->data); | |
| if (ptr != last_record->data) { | |
| warning("tracecmd_read_page_record: could not find last_record"); | |
| goto out_free; | |
| } | |
| } | |
| ptr = kbuffer_read_event(kbuf, &ts); | |
| if (!ptr) | |
| goto out_free; | |
| record = malloc(sizeof(*record)); | |
| if (!record) | |
| return NULL; | |
| memset(record, 0, sizeof(*record)); | |
| record->ts = ts; | |
| record->size = kbuffer_event_size(kbuf); | |
| record->record_size = kbuffer_curr_size(kbuf); | |
| record->cpu = 0; | |
| record->data = ptr; | |
| record->ref_count = 1; | |
| out_free: | |
| kbuffer_free(kbuf); | |
| return record; | |
| } | |
| /** | |
| * tracecmd_peek_data - return the record at the current location. | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU to pull from | |
| * | |
| * This returns the record at the current location of the CPU | |
| * iterator. It does not increment the CPU iterator. | |
| */ | |
| struct pevent_record * | |
| tracecmd_peek_data(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct pevent_record *record; | |
| unsigned long long ts; | |
| struct kbuffer *kbuf; | |
| struct page *page; | |
| int index; | |
| void *data; | |
| if (cpu >= handle->cpus) | |
| return NULL; | |
| page = handle->cpu_data[cpu].page; | |
| kbuf = handle->cpu_data[cpu].kbuf; | |
| /* Hack to work around function graph read ahead */ | |
| tracecmd_curr_thread_handle = handle; | |
| if (handle->cpu_data[cpu].next) { | |
| record = handle->cpu_data[cpu].next; | |
| if (!record->data) | |
| die("Something freed the record"); | |
| if (handle->cpu_data[cpu].timestamp == record->ts) | |
| return record; | |
| /* | |
| * The timestamp changed, which means the cached | |
| * record is no longer valid. Reread a new record. | |
| */ | |
| free_next(handle, cpu); | |
| } | |
| read_again: | |
| if (!page) { | |
| if (handle->use_pipe) { | |
| get_next_page(handle, cpu); | |
| page = handle->cpu_data[cpu].page; | |
| } | |
| if (!page) | |
| return NULL; | |
| } | |
| data = kbuffer_read_event(kbuf, &ts); | |
| if (!data) { | |
| if (get_next_page(handle, cpu)) | |
| return NULL; | |
| page = handle->cpu_data[cpu].page; | |
| goto read_again; | |
| } | |
| handle->cpu_data[cpu].timestamp = ts + handle->ts_offset; | |
| index = kbuffer_curr_offset(kbuf); | |
| record = malloc(sizeof(*record)); | |
| if (!record) | |
| return NULL; | |
| memset(record, 0, sizeof(*record)); | |
| record->ts = handle->cpu_data[cpu].timestamp; | |
| record->size = kbuffer_event_size(kbuf); | |
| record->cpu = cpu; | |
| record->data = data; | |
| record->offset = handle->cpu_data[cpu].offset + index; | |
| record->missed_events = kbuffer_missed_events(kbuf); | |
| record->ref_count = 1; | |
| record->locked = 1; | |
| handle->cpu_data[cpu].next = record; | |
| record->record_size = kbuffer_curr_size(kbuf); | |
| record->priv = page; | |
| add_record(page, record); | |
| page->ref_count++; | |
| kbuffer_next_event(kbuf, NULL); | |
| return record; | |
| } | |
| /** | |
| * tracecmd_read_data - read the next record and increment | |
| * @handle: input handle for the trace.dat file | |
| * @cpu: the CPU to pull from | |
| * | |
| * This returns the record at the current location of the CPU | |
| * iterator and increments the CPU iterator. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_data(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct pevent_record *record; | |
| record = tracecmd_peek_data(handle, cpu); | |
| handle->cpu_data[cpu].next = NULL; | |
| if (record) { | |
| record->locked = 0; | |
| #if DEBUG_RECORD | |
| record->alloc_addr = (unsigned long)__builtin_return_address(0); | |
| #endif | |
| } | |
| return record; | |
| } | |
| /** | |
| * tracecmd_read_next_data - read the next record | |
| * @handle: input handle to the trace.dat file | |
| * @rec_cpu: return pointer to the CPU that the record belongs to | |
| * | |
| * This returns the next record by time. This is different than | |
| * tracecmd_read_data in that it looks at all CPUs. It does a peek | |
| * at each CPU and the record with the earliest time stame is | |
| * returned. If @rec_cpu is not NULL it gets the CPU id the record was | |
| * on. The CPU cursor of the returned record is moved to the | |
| * next record. | |
| * | |
| * Multiple reads of this function will return a serialized list | |
| * of all records for all CPUs in order of time stamp. | |
| * | |
| * The record returned must be freed. | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_next_data(struct tracecmd_input *handle, int *rec_cpu) | |
| { | |
| unsigned long long ts; | |
| struct pevent_record *record; | |
| int first_record = 1; | |
| int next; | |
| int cpu; | |
| if (rec_cpu) | |
| *rec_cpu = -1; | |
| next = -1; | |
| ts = 0; | |
| for (cpu = 0; cpu < handle->cpus; cpu++) { | |
| record = tracecmd_peek_data(handle, cpu); | |
| if (record && (first_record || record->ts < ts)) { | |
| ts = record->ts; | |
| next = cpu; | |
| first_record = 0; | |
| } | |
| } | |
| if (next >= 0) { | |
| if (rec_cpu) | |
| *rec_cpu = next; | |
| return tracecmd_read_data(handle, next); | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * tracecmd_read_prev - read the record before the given record | |
| * @handle: input handle to the trace.dat file | |
| * @record: the record to use to find the previous record. | |
| * | |
| * This returns the record before the @record on its CPU. If | |
| * @record is the first record, NULL is returned. The cursor is set | |
| * as if the previous record was read by tracecmd_read_data(). | |
| * | |
| * @record can not be NULL, otherwise NULL is returned; the | |
| * record ownership goes to this function. | |
| * | |
| * Note, this is not that fast of an algorithm, since it needs | |
| * to build the timestamp for the record. | |
| * | |
| * The record returned must be freed with free_record(). | |
| */ | |
| struct pevent_record * | |
| tracecmd_read_prev(struct tracecmd_input *handle, struct pevent_record *record) | |
| { | |
| unsigned long long offset, page_offset;; | |
| struct cpu_data *cpu_data; | |
| int index; | |
| int cpu; | |
| if (!record) | |
| return NULL; | |
| cpu = record->cpu; | |
| offset = record->offset; | |
| cpu_data = &handle->cpu_data[cpu]; | |
| page_offset = calc_page_offset(handle, offset); | |
| index = offset - page_offset; | |
| /* Note, the record passed in could have been a peek */ | |
| free_next(handle, cpu); | |
| /* Reset the cursor */ | |
| /* Should not happen */ | |
| if (get_page(handle, cpu, page_offset) < 0) | |
| return NULL; | |
| update_page_info(handle, cpu); | |
| /* Find the record before this record */ | |
| index = 0; | |
| for (;;) { | |
| record = tracecmd_read_data(handle, cpu); | |
| /* Should not happen! */ | |
| if (!record) | |
| return NULL; | |
| if (record->offset == offset) | |
| break; | |
| index = record->offset - page_offset; | |
| free_record(record); | |
| } | |
| free_record(record); | |
| if (index) | |
| /* we found our record */ | |
| return tracecmd_read_at(handle, page_offset + index, NULL); | |
| /* reset the index to start at the beginning of the page */ | |
| update_page_info(handle, cpu); | |
| /* The previous record is on the previous page */ | |
| for (;;) { | |
| /* check if this is the first page */ | |
| if (page_offset == cpu_data->file_offset) | |
| return NULL; | |
| page_offset -= handle->page_size; | |
| /* Updating page to a new page will reset index to 0 */ | |
| get_page(handle, cpu, page_offset); | |
| record = NULL; | |
| index = 0; | |
| do { | |
| if (record) { | |
| index = record->offset - page_offset; | |
| free_record(record); | |
| } | |
| record = tracecmd_read_data(handle, cpu); | |
| /* Should not happen */ | |
| if (!record) | |
| return NULL; | |
| } while (record->offset != offset); | |
| free_record(record); | |
| if (index) | |
| /* we found our record */ | |
| return tracecmd_read_at(handle, page_offset + index, NULL); | |
| } | |
| /* Not reached */ | |
| } | |
| static int init_cpu(struct tracecmd_input *handle, int cpu) | |
| { | |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; | |
| int i; | |
| cpu_data->offset = cpu_data->file_offset; | |
| cpu_data->size = cpu_data->file_size; | |
| cpu_data->timestamp = 0; | |
| list_head_init(&cpu_data->pages); | |
| if (!cpu_data->size) { | |
| printf("CPU %d is empty\n", cpu); | |
| return 0; | |
| } | |
| if (handle->use_pipe) { | |
| /* Just make a page, it will be nuked later */ | |
| cpu_data->page = malloc(sizeof(*cpu_data->page)); | |
| if (!cpu_data->page) | |
| return -1; | |
| memset(cpu_data->page, 0, sizeof(*cpu_data->page)); | |
| list_add(&cpu_data->page->list, &cpu_data->pages); | |
| cpu_data->page->ref_count = 1; | |
| return 0; | |
| } | |
| cpu_data->page = allocate_page(handle, cpu, cpu_data->offset); | |
| if (!cpu_data->page && !handle->read_page) { | |
| perror("mmap"); | |
| fprintf(stderr, "Can not mmap file, will read instead\n"); | |
| if (cpu) { | |
| /* | |
| * If the other CPUs had size and was able to mmap | |
| * then bail. | |
| */ | |
| for (i = 0; i < cpu; i++) { | |
| if (handle->cpu_data[i].size) | |
| return -1; | |
| } | |
| } | |
| /* try again without mmapping, just read it directly */ | |
| handle->read_page = true; | |
| cpu_data->page = allocate_page(handle, cpu, cpu_data->offset); | |
| if (!cpu_data->page) | |
| /* Still no luck, bail! */ | |
| return -1; | |
| } | |
| if (update_page_info(handle, cpu)) | |
| return -1; | |
| return 0; | |
| } | |
| static int handle_options(struct tracecmd_input *handle) | |
| { | |
| unsigned long long offset; | |
| unsigned short option; | |
| unsigned int size; | |
| char *cpustats = NULL; | |
| unsigned int cpustats_size = 0; | |
| struct input_buffer_instance *buffer; | |
| struct hook_list *hook; | |
| char *buf; | |
| for (;;) { | |
| if (do_read_check(handle, &option, 2)) | |
| return -1; | |
| if (option == TRACECMD_OPTION_DONE) | |
| break; | |
| /* next 4 bytes is the size of the option */ | |
| if (do_read_check(handle, &size, 4)) | |
| return -1; | |
| size = __data2host4(handle->pevent, size); | |
| buf = malloc_or_die(size); | |
| if (do_read_check(handle, buf, size)) | |
| return -1; | |
| switch (option) { | |
| case TRACECMD_OPTION_DATE: | |
| /* | |
| * A time has been mapped that is the | |
| * difference between the timestamps and | |
| * gtod. It is stored as ASCII with '0x' | |
| * appended. | |
| */ | |
| if (handle->flags & TRACECMD_FL_IGNORE_DATE) | |
| break; | |
| offset = strtoll(buf, NULL, 0); | |
| /* Convert from micro to nano */ | |
| offset *= 1000; | |
| handle->ts_offset = offset; | |
| break; | |
| case TRACECMD_OPTION_CPUSTAT: | |
| buf[size-1] = '\n'; | |
| cpustats = realloc(cpustats, cpustats_size + size + 1); | |
| if (!cpustats) | |
| die("realloc"); | |
| memcpy(cpustats + cpustats_size, buf, size); | |
| cpustats_size += size; | |
| cpustats[cpustats_size] = 0; | |
| break; | |
| case TRACECMD_OPTION_BUFFER: | |
| /* A buffer instance is saved at the end of the file */ | |
| handle->nr_buffers++; | |
| handle->buffers = realloc(handle->buffers, | |
| sizeof(*handle->buffers) * handle->nr_buffers); | |
| if (!handle->buffers) | |
| die("realloc"); | |
| buffer = &handle->buffers[handle->nr_buffers - 1]; | |
| buffer->name = strdup(buf + 8); | |
| if (!buffer->name) | |
| die("strdup"); | |
| offset = *(unsigned long long *)buf; | |
| buffer->offset = __data2host8(handle->pevent, offset); | |
| break; | |
| case TRACECMD_OPTION_TRACECLOCK: | |
| handle->use_trace_clock = true; | |
| break; | |
| case TRACECMD_OPTION_UNAME: | |
| handle->uname = strdup(buf); | |
| break; | |
| case TRACECMD_OPTION_HOOK: | |
| hook = tracecmd_create_event_hook(buf); | |
| hook->next = handle->hooks; | |
| handle->hooks = hook; | |
| break; | |
| default: | |
| warning("unknown option %d", option); | |
| break; | |
| } | |
| free(buf); | |
| } | |
| handle->cpustats = cpustats; | |
| return 0; | |
| } | |
| static int read_cpu_data(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| enum kbuffer_long_size long_size; | |
| enum kbuffer_endian endian; | |
| unsigned long long size; | |
| char buf[10]; | |
| int cpu; | |
| if (do_read_check(handle, buf, 10)) | |
| return -1; | |
| /* check if this handles options */ | |
| if (strncmp(buf, "options", 7) == 0) { | |
| if (handle_options(handle) < 0) | |
| return -1; | |
| if (do_read_check(handle, buf, 10)) | |
| return -1; | |
| } | |
| /* | |
| * Check if this is a latency report or not. | |
| */ | |
| if (strncmp(buf, "latency", 7) == 0) { | |
| handle->flags |= TRACECMD_FL_LATENCY; | |
| return 1; | |
| } | |
| /* We expect this to be flyrecord */ | |
| if (strncmp(buf, "flyrecord", 9) != 0) | |
| return -1; | |
| handle->cpu_data = malloc(sizeof(*handle->cpu_data) * handle->cpus); | |
| if (!handle->cpu_data) | |
| return -1; | |
| memset(handle->cpu_data, 0, sizeof(*handle->cpu_data) * handle->cpus); | |
| if (force_read) | |
| handle->read_page = true; | |
| if (handle->long_size == 8) | |
| long_size = KBUFFER_LSIZE_8; | |
| else | |
| long_size = KBUFFER_LSIZE_4; | |
| if (handle->pevent->file_bigendian) | |
| endian = KBUFFER_ENDIAN_BIG; | |
| else | |
| endian = KBUFFER_ENDIAN_LITTLE; | |
| for (cpu = 0; cpu < handle->cpus; cpu++) { | |
| unsigned long long offset; | |
| handle->cpu_data[cpu].cpu = cpu; | |
| handle->cpu_data[cpu].kbuf = kbuffer_alloc(long_size, endian); | |
| if (!handle->cpu_data[cpu].kbuf) | |
| goto out_free; | |
| if (pevent->old_format) | |
| kbuffer_set_old_format(handle->cpu_data[cpu].kbuf); | |
| offset = read8(handle); | |
| size = read8(handle); | |
| handle->cpu_data[cpu].file_offset = offset; | |
| handle->cpu_data[cpu].file_size = size; | |
| if (size && (offset + size > handle->total_file_size)) { | |
| /* this happens if the file got truncated */ | |
| printf("File possibly truncated. " | |
| "Need at least %llu, but file size is %zu.\n", | |
| offset + size, handle->total_file_size); | |
| errno = EINVAL; | |
| goto out_free; | |
| } | |
| if (init_cpu(handle, cpu)) | |
| goto out_free; | |
| } | |
| return 0; | |
| out_free: | |
| for ( ; cpu >= 0; cpu--) { | |
| free_page(handle, cpu); | |
| kbuffer_free(handle->cpu_data[cpu].kbuf); | |
| handle->cpu_data[cpu].kbuf = NULL; | |
| } | |
| return -1; | |
| } | |
| static int read_data_and_size(struct tracecmd_input *handle, | |
| char **data, unsigned long long *size) | |
| { | |
| *size = read8(handle); | |
| if (*size < 0) | |
| return -1; | |
| *data = malloc(*size + 1); | |
| if (!*data) | |
| return -1; | |
| if (do_read_check(handle, *data, *size)) { | |
| free(*data); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int read_and_parse_cmdlines(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| unsigned long long size; | |
| char *cmdlines; | |
| if (read_data_and_size(handle, &cmdlines, &size) < 0) | |
| return -1; | |
| cmdlines[size] = 0; | |
| parse_cmdlines(pevent, cmdlines, size); | |
| free(cmdlines); | |
| return 0; | |
| } | |
| static int read_and_parse_trace_clock(struct tracecmd_input *handle, | |
| struct pevent *pevent) | |
| { | |
| unsigned long long size; | |
| char *trace_clock; | |
| if (read_data_and_size(handle, &trace_clock, &size) < 0) | |
| return -1; | |
| trace_clock[size] = 0; | |
| parse_trace_clock(pevent, trace_clock, size); | |
| free(trace_clock); | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_init_data - prepare reading the data from trace.dat | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * This prepares reading the data from trace.dat. This is called | |
| * after tracecmd_read_headers() and before tracecmd_read_data(). | |
| */ | |
| int tracecmd_init_data(struct tracecmd_input *handle) | |
| { | |
| struct pevent *pevent = handle->pevent; | |
| int ret; | |
| handle->cpus = read4(handle); | |
| if (handle->cpus < 0) | |
| return -1; | |
| pevent_set_cpus(pevent, handle->cpus); | |
| ret = read_cpu_data(handle); | |
| if (ret < 0) | |
| return ret; | |
| if (handle->use_trace_clock) { | |
| /* | |
| * There was a bug in the original setting of | |
| * the trace_clock file which let it get | |
| * corrupted. If it fails to read, force local | |
| * clock. | |
| */ | |
| if (read_and_parse_trace_clock(handle, pevent) < 0) { | |
| char clock[] = "[local]"; | |
| warning("File has trace_clock bug, using local clock"); | |
| parse_trace_clock(pevent, clock, 8); | |
| } | |
| } | |
| tracecmd_blk_hack(handle); | |
| return ret; | |
| } | |
| /** | |
| * tracecmd_make_pipe - Have the handle read a pipe instead of a file | |
| * @handle: input handle to read from a pipe | |
| * @cpu: the cpu that the pipe represents | |
| * @fd: the read end of the pipe | |
| * @cpus: the total number of cpus for this handle | |
| * | |
| * In order to stream data from the binary trace files and produce | |
| * output or analyze the data, a tracecmd_input descriptor needs to | |
| * be created, and then converted into a form that can act on a | |
| * pipe. | |
| * | |
| * Note, there are limitations to what this descriptor can do. | |
| * Most notibly, it can not read backwards. Once a page is read | |
| * it can not be read at a later time (except if a record is attached | |
| * to it and is holding the page ref). | |
| * | |
| * It is expected that the handle has already been created and | |
| * tracecmd_read_headers() has run on it. | |
| */ | |
| int tracecmd_make_pipe(struct tracecmd_input *handle, int cpu, int fd, int cpus) | |
| { | |
| enum kbuffer_long_size long_size; | |
| enum kbuffer_endian endian; | |
| handle->read_page = true; | |
| handle->use_pipe = true; | |
| if (!handle->cpus) { | |
| handle->cpus = cpus; | |
| handle->cpu_data = malloc(sizeof(*handle->cpu_data) * handle->cpus); | |
| if (!handle->cpu_data) | |
| return -1; | |
| } | |
| if (cpu >= handle->cpus) | |
| return -1; | |
| if (handle->long_size == 8) | |
| long_size = KBUFFER_LSIZE_8; | |
| else | |
| long_size = KBUFFER_LSIZE_4; | |
| if (handle->pevent->file_bigendian) | |
| endian = KBUFFER_ENDIAN_BIG; | |
| else | |
| endian = KBUFFER_ENDIAN_LITTLE; | |
| memset(&handle->cpu_data[cpu], 0, sizeof(handle->cpu_data[cpu])); | |
| handle->cpu_data[cpu].pipe_fd = fd; | |
| handle->cpu_data[cpu].cpu = cpu; | |
| handle->cpu_data[cpu].kbuf = kbuffer_alloc(long_size, endian); | |
| if (!handle->cpu_data[cpu].kbuf) | |
| return -1; | |
| if (handle->pevent->old_format) | |
| kbuffer_set_old_format(handle->cpu_data[cpu].kbuf); | |
| handle->cpu_data[cpu].file_offset = 0; | |
| handle->cpu_data[cpu].file_size = -1; | |
| init_cpu(handle, cpu); | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_print_events - print the events that are stored in trace.dat | |
| * @handle: input handle for the trace.dat file | |
| * @regex: regex of events to print (NULL is all events) | |
| * | |
| * This is a debugging routine to print out the events that | |
| * are stored in a given trace.dat file. | |
| */ | |
| void tracecmd_print_events(struct tracecmd_input *handle, const char *regex) | |
| { | |
| int ret; | |
| if (!regex) | |
| regex = ".*"; | |
| if (!handle->ftrace_files_start) { | |
| lseek64(handle->fd, handle->header_files_start, SEEK_SET); | |
| read_header_files(handle); | |
| } | |
| ret = read_ftrace_files(handle, regex); | |
| if (ret < 0) | |
| return; | |
| read_event_files(handle, regex); | |
| return; | |
| } | |
| /* Show the cpu data stats */ | |
| static void show_cpu_stats(struct tracecmd_input *handle) | |
| { | |
| struct cpu_data *cpu_data; | |
| int i; | |
| for (i = 0; i < handle->cpus; i++) { | |
| cpu_data = &handle->cpu_data[i]; | |
| printf("CPU%d data recorded at offset=0x%llx\n", | |
| i, cpu_data->file_offset); | |
| printf(" %lld bytes in size\n", cpu_data->file_size); | |
| } | |
| } | |
| /** | |
| * tracecmd_print_stats - prints the stats recorded in the options. | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * Looks for the option TRACECMD_OPTION_CPUSTAT and prints out what's | |
| * stored there, if it is found. Otherwise it prints that none were found. | |
| */ | |
| void tracecmd_print_stats(struct tracecmd_input *handle) | |
| { | |
| if (handle->cpustats) | |
| printf("%s\n", handle->cpustats); | |
| else | |
| printf(" No stats in this file\n"); | |
| show_cpu_stats(handle); | |
| } | |
| /** | |
| * tracecmd_print_uname - prints the recorded uname if it was recorded | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * Looks for the option TRACECMD_OPTION_UNAME and prints out what's | |
| * stored there, if it is found. Otherwise it prints that none were found. | |
| */ | |
| void tracecmd_print_uname(struct tracecmd_input *handle) | |
| { | |
| if (handle->uname) | |
| printf("%s\n", handle->uname); | |
| else | |
| printf(" uname was not recorded in this file\n"); | |
| } | |
| /** | |
| * tracecmd_hooks - return the event hooks that were used in record | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * If trace-cmd record used -H to save hooks, they are parsed and | |
| * presented as hooks here. | |
| * | |
| * Returns the hook list (do not free it, they are freed on close) | |
| */ | |
| struct hook_list *tracecmd_hooks(struct tracecmd_input *handle) | |
| { | |
| return handle->hooks; | |
| } | |
| /** | |
| * tracecmd_alloc_fd - create a tracecmd_input handle from a file descriptor | |
| * @fd: the file descriptor for the trace.dat file | |
| * | |
| * Allocate a tracecmd_input handle from a file descriptor and open the | |
| * file. This tests if the file is of trace-cmd format and allocates | |
| * a parse event descriptor. | |
| * | |
| * The returned pointer is not ready to be read yet. A tracecmd_read_headers() | |
| * and tracecmd_init_data() still need to be called on the descriptor. | |
| * | |
| * Unless you know what you are doing with this, you want to use | |
| * tracecmd_open_fd() instead. | |
| */ | |
| struct tracecmd_input *tracecmd_alloc_fd(int fd) | |
| { | |
| struct tracecmd_input *handle; | |
| char test[] = { 23, 8, 68 }; | |
| char *version; | |
| char buf[BUFSIZ]; | |
| handle = malloc(sizeof(*handle)); | |
| if (!handle) | |
| return NULL; | |
| memset(handle, 0, sizeof(*handle)); | |
| handle->fd = fd; | |
| handle->ref = 1; | |
| if (do_read_check(handle, buf, 3)) | |
| goto failed_read; | |
| if (memcmp(buf, test, 3) != 0) | |
| goto failed_read; | |
| if (do_read_check(handle, buf, 7)) | |
| goto failed_read; | |
| if (memcmp(buf, "tracing", 7) != 0) | |
| goto failed_read; | |
| version = read_string(handle); | |
| if (!version) | |
| goto failed_read; | |
| pr_stat("version = %s\n", version); | |
| free(version); | |
| if (do_read_check(handle, buf, 1)) | |
| goto failed_read; | |
| handle->pevent = pevent_alloc(); | |
| if (!handle->pevent) | |
| goto failed_read; | |
| /* register default ftrace functions first */ | |
| tracecmd_ftrace_overrides(handle, &handle->finfo); | |
| handle->plugin_list = tracecmd_load_plugins(handle->pevent); | |
| handle->pevent->file_bigendian = buf[0]; | |
| handle->pevent->host_bigendian = tracecmd_host_bigendian(); | |
| do_read_check(handle, buf, 1); | |
| handle->long_size = buf[0]; | |
| handle->page_size = read4(handle); | |
| handle->header_files_start = | |
| lseek64(handle->fd, 0, SEEK_CUR); | |
| handle->total_file_size = | |
| lseek64(handle->fd, 0, SEEK_END); | |
| handle->header_files_start = | |
| lseek64(handle->fd, handle->header_files_start, SEEK_SET); | |
| return handle; | |
| failed_read: | |
| free(handle); | |
| return NULL; | |
| } | |
| /** | |
| * tracecmd_alloc_fd - create a tracecmd_input handle from a file name | |
| * @file: the file name of the file that is of tracecmd data type. | |
| * | |
| * Allocate a tracecmd_input handle from a given file name and open the | |
| * file. This tests if the file is of trace-cmd format and allocates | |
| * a parse event descriptor. | |
| * | |
| * The returned pointer is not ready to be read yet. A tracecmd_read_headers() | |
| * and tracecmd_init_data() still need to be called on the descriptor. | |
| * | |
| * Unless you know what you are doing with this, you want to use | |
| * tracecmd_open() instead. | |
| */ | |
| struct tracecmd_input *tracecmd_alloc(const char *file) | |
| { | |
| int fd; | |
| fd = open(file, O_RDONLY); | |
| if (fd < 0) | |
| return NULL; | |
| return tracecmd_alloc_fd(fd); | |
| } | |
| /** | |
| * tracecmd_open_fd - create a tracecmd_handle from the trace.dat file descriptor | |
| * @fd: the file descriptor for the trace.dat file | |
| */ | |
| struct tracecmd_input *tracecmd_open_fd(int fd) | |
| { | |
| struct tracecmd_input *handle; | |
| int ret; | |
| handle = tracecmd_alloc_fd(fd); | |
| if (!handle) | |
| return NULL; | |
| if (tracecmd_read_headers(handle) < 0) | |
| goto fail; | |
| if ((ret = tracecmd_init_data(handle)) < 0) | |
| goto fail; | |
| return handle; | |
| fail: | |
| tracecmd_close(handle); | |
| return NULL; | |
| } | |
| /** | |
| * tracecmd_open - create a tracecmd_handle from a given file | |
| * @file: the file name of the file that is of tracecmd data type. | |
| */ | |
| struct tracecmd_input *tracecmd_open(const char *file) | |
| { | |
| int fd; | |
| fd = open(file, O_RDONLY); | |
| if (fd < 0) | |
| return NULL; | |
| return tracecmd_open_fd(fd); | |
| } | |
| /** | |
| * tracecmd_ref - add a reference to the handle | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * Some applications may share a handle between parts of | |
| * the application. Let those parts add reference counters | |
| * to the handle, and the last one to close it will free it. | |
| */ | |
| void tracecmd_ref(struct tracecmd_input *handle) | |
| { | |
| if (!handle) | |
| return; | |
| handle->ref++; | |
| } | |
| /** | |
| * tracecmd_close - close and free the trace.dat handle | |
| * @handle: input handle for the trace.dat file | |
| * | |
| * Close the file descriptor of the handle and frees | |
| * the resources allocated by the handle. | |
| */ | |
| void tracecmd_close(struct tracecmd_input *handle) | |
| { | |
| int cpu; | |
| if (!handle) | |
| return; | |
| if (handle->ref <= 0) { | |
| warning("tracecmd: bad ref count on handle\n"); | |
| return; | |
| } | |
| if (--handle->ref) | |
| return; | |
| for (cpu = 0; cpu < handle->cpus; cpu++) { | |
| /* The tracecmd_peek_data may have cached a record */ | |
| free_next(handle, cpu); | |
| free_page(handle, cpu); | |
| if (handle->cpu_data && handle->cpu_data[cpu].kbuf) { | |
| kbuffer_free(handle->cpu_data[cpu].kbuf); | |
| if (!list_empty(&handle->cpu_data[cpu].pages)) | |
| warning("pages still allocated on cpu %d%s", | |
| cpu, show_records(&handle->cpu_data[cpu].pages)); | |
| } | |
| } | |
| free(handle->cpustats); | |
| free(handle->cpu_data); | |
| free(handle->uname); | |
| close(handle->fd); | |
| tracecmd_free_hooks(handle->hooks); | |
| handle->hooks = NULL; | |
| if (handle->flags & TRACECMD_FL_BUFFER_INSTANCE) | |
| tracecmd_close(handle->parent); | |
| else { | |
| /* Only main handle frees plugins and pevent */ | |
| tracecmd_unload_plugins(handle->plugin_list, handle->pevent); | |
| pevent_free(handle->pevent); | |
| } | |
| free(handle); | |
| } | |
| static long long read_copy_size8(struct tracecmd_input *handle, int fd) | |
| { | |
| long long size; | |
| /* read size */ | |
| if (do_read_check(handle, &size, 8)) | |
| return -1; | |
| if (__do_write_check(fd, &size, 8)) | |
| return -1; | |
| size = __data2host8(handle->pevent, size); | |
| return size; | |
| } | |
| static int read_copy_size4(struct tracecmd_input *handle, int fd) | |
| { | |
| int size; | |
| /* read size */ | |
| if (do_read_check(handle, &size, 4)) | |
| return -1; | |
| if (__do_write_check(fd, &size, 4)) | |
| return -1; | |
| size = __data2host4(handle->pevent, size); | |
| return size; | |
| } | |
| static int read_copy_data(struct tracecmd_input *handle, | |
| unsigned long long size, int fd) | |
| { | |
| char *buf; | |
| buf = malloc(size); | |
| if (!buf) | |
| return -1; | |
| if (do_read_check(handle, buf, size)) | |
| goto failed_read; | |
| if (__do_write_check(fd, buf, size)) | |
| goto failed_read; | |
| free(buf); | |
| return 0; | |
| failed_read: | |
| free(buf); | |
| return -1; | |
| } | |
| static int copy_header_files(struct tracecmd_input *handle, int fd) | |
| { | |
| long long size; | |
| lseek64(handle->fd, handle->header_files_start, SEEK_SET); | |
| /* "header_page" */ | |
| if (read_copy_data(handle, 12, fd) < 0) | |
| return -1; | |
| size = read_copy_size8(handle, fd); | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| /* "header_event" */ | |
| if (read_copy_data(handle, 13, fd) < 0) | |
| return -1; | |
| size = read_copy_size8(handle, fd); | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| static int copy_ftrace_files(struct tracecmd_input *handle, int fd) | |
| { | |
| unsigned long long size; | |
| int count; | |
| int i; | |
| count = read_copy_size4(handle, fd); | |
| if (count < 0) | |
| return -1; | |
| for (i = 0; i < count; i++) { | |
| size = read_copy_size8(handle, fd); | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int copy_event_files(struct tracecmd_input *handle, int fd) | |
| { | |
| unsigned long long size; | |
| char *system; | |
| int systems; | |
| int count; | |
| int ret; | |
| int i,x; | |
| systems = read_copy_size4(handle, fd); | |
| if (systems < 0) | |
| return -1; | |
| for (i = 0; i < systems; i++) { | |
| system = read_string(handle); | |
| if (!system) | |
| return -1; | |
| if (__do_write_check(fd, system, strlen(system) + 1)) { | |
| free(system); | |
| return -1; | |
| } | |
| free(system); | |
| count = read_copy_size4(handle, fd); | |
| if (count < 0) | |
| return -1; | |
| for (x=0; x < count; x++) { | |
| size = read_copy_size8(handle, fd); | |
| if (size < 0) | |
| return -1; | |
| ret = read_copy_data(handle, size, fd); | |
| if (ret < 0) | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int copy_proc_kallsyms(struct tracecmd_input *handle, int fd) | |
| { | |
| int size; | |
| size = read_copy_size4(handle, fd); | |
| if (!size) | |
| return 0; /* OK? */ | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| static int copy_ftrace_printk(struct tracecmd_input *handle, int fd) | |
| { | |
| int size; | |
| size = read_copy_size4(handle, fd); | |
| if (!size) | |
| return 0; /* OK? */ | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| static int copy_command_lines(struct tracecmd_input *handle, int fd) | |
| { | |
| unsigned long size; | |
| size = read_copy_size8(handle, fd); | |
| if (!size) | |
| return 0; /* OK? */ | |
| if (size < 0) | |
| return -1; | |
| if (read_copy_data(handle, size, fd) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| int tracecmd_copy_headers(struct tracecmd_input *handle, int fd) | |
| { | |
| int ret; | |
| ret = copy_header_files(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| ret = copy_ftrace_files(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| ret = copy_event_files(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| ret = copy_proc_kallsyms(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| ret = copy_ftrace_printk(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| ret = copy_command_lines(handle, fd); | |
| if (ret < 0) | |
| return -1; | |
| return 0; | |
| } | |
| /** | |
| * tracecmd_record_at_buffer_start - return true if record is first on subbuffer | |
| * @handle: input handle for the trace.dat file | |
| * @record: The record to test if it is the first record on page | |
| * | |
| * Returns true if the record is the first record on the page. | |
| */ | |
| int tracecmd_record_at_buffer_start(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| struct page *page = record->priv; | |
| struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; | |
| int offset; | |
| if (!page || !kbuf) | |
| return 0; | |
| offset = record->offset - page->offset; | |
| return offset == kbuffer_start_of_data(kbuf); | |
| } | |
| unsigned long long tracecmd_page_ts(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| struct page *page = record->priv; | |
| struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; | |
| if (!page || !kbuf) | |
| return 0; | |
| return kbuffer_subbuf_timestamp(kbuf, page->map); | |
| } | |
| unsigned int tracecmd_record_ts_delta(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; | |
| struct page *page = record->priv; | |
| int offset; | |
| if (!page || !kbuf) | |
| return 0; | |
| offset = record->offset - page->offset; | |
| return kbuffer_ptr_delta(kbuf, page->map + offset); | |
| } | |
| struct kbuffer *tracecmd_record_kbuf(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| return handle->cpu_data[record->cpu].kbuf; | |
| } | |
| void *tracecmd_record_page(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| struct page *page = record->priv; | |
| return page ? page->map : NULL; | |
| } | |
| void *tracecmd_record_offset(struct tracecmd_input *handle, | |
| struct pevent_record *record) | |
| { | |
| struct page *page = record->priv; | |
| int offset; | |
| if (!page) | |
| return NULL; | |
| offset = record->offset - page->offset; | |
| return page->map + offset; | |
| } | |
| int tracecmd_buffer_instances(struct tracecmd_input *handle) | |
| { | |
| return handle->nr_buffers; | |
| } | |
| const char *tracecmd_buffer_instance_name(struct tracecmd_input *handle, int indx) | |
| { | |
| if (indx >= handle->nr_buffers) | |
| return NULL; | |
| return handle->buffers[indx].name; | |
| } | |
| struct tracecmd_input * | |
| tracecmd_buffer_instance_handle(struct tracecmd_input *handle, int indx) | |
| { | |
| struct tracecmd_input *new_handle; | |
| struct input_buffer_instance *buffer = &handle->buffers[indx]; | |
| size_t offset; | |
| ssize_t ret; | |
| if (indx >= handle->nr_buffers) | |
| return NULL; | |
| /* | |
| * We make a copy of the current handle, but we substitute | |
| * the cpu data with the cpu data for this buffer. | |
| */ | |
| new_handle = malloc(sizeof(*handle)); | |
| if (!new_handle) | |
| return NULL; | |
| *new_handle = *handle; | |
| new_handle->cpu_data = NULL; | |
| new_handle->nr_buffers = 0; | |
| new_handle->buffers = NULL; | |
| new_handle->ref = 1; | |
| new_handle->parent = handle; | |
| new_handle->cpustats = NULL; | |
| new_handle->hooks = NULL; | |
| if (handle->uname) | |
| /* Ignore if fails to malloc, no biggy */ | |
| new_handle->uname = strdup(handle->uname); | |
| tracecmd_ref(handle); | |
| new_handle->fd = dup(handle->fd); | |
| new_handle->flags |= TRACECMD_FL_BUFFER_INSTANCE; | |
| /* Save where we currently are */ | |
| offset = lseek64(handle->fd, 0, SEEK_CUR); | |
| ret = lseek64(handle->fd, buffer->offset, SEEK_SET); | |
| if (ret < 0) { | |
| warning("could not seek to buffer %s offset %ld\n", | |
| buffer->name, buffer->offset); | |
| tracecmd_close(new_handle); | |
| return NULL; | |
| } | |
| ret = read_cpu_data(new_handle); | |
| if (ret < 0) { | |
| warning("failed to read sub buffer %s\n", buffer->name); | |
| tracecmd_close(new_handle); | |
| return NULL; | |
| } | |
| ret = lseek64(handle->fd, offset, SEEK_SET); | |
| if (ret < 0) { | |
| warning("could not seek to back to offset %ld\n", offset); | |
| tracecmd_close(new_handle); | |
| return NULL; | |
| } | |
| return new_handle; | |
| } | |
| int tracecmd_is_buffer_instance(struct tracecmd_input *handle) | |
| { | |
| return handle->flags & TRACECMD_FL_BUFFER_INSTANCE; | |
| } | |
| /** | |
| * tracecmd_long_size - return the size of "long" for the arch | |
| * @handle: input handle for the trace.dat file | |
| */ | |
| int tracecmd_long_size(struct tracecmd_input *handle) | |
| { | |
| return handle->long_size; | |
| } | |
| /** | |
| * tracecmd_page_size - return the PAGE_SIZE for the arch | |
| * @handle: input handle for the trace.dat file | |
| */ | |
| int tracecmd_page_size(struct tracecmd_input *handle) | |
| { | |
| return handle->page_size; | |
| } | |
| /** | |
| * tracecmd_page_size - return the number of CPUs recorded | |
| * @handle: input handle for the trace.dat file | |
| */ | |
| int tracecmd_cpus(struct tracecmd_input *handle) | |
| { | |
| return handle->cpus; | |
| } | |
| /** | |
| * tracecmd_get_pevent - return the pevent handle | |
| * @handle: input handle for the trace.dat file | |
| */ | |
| struct pevent *tracecmd_get_pevent(struct tracecmd_input *handle) | |
| { | |
| return handle->pevent; | |
| } | |
| /** | |
| * tracecmd_get_use_trace_clock - return use_trace_clock | |
| * @handle: input handle for the trace.dat file | |
| */ | |
| bool tracecmd_get_use_trace_clock(struct tracecmd_input *handle) | |
| { | |
| return handle->use_trace_clock; | |
| } |