| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> |
| * |
| */ |
| #define _LARGEFILE64_SOURCE |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <regex.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include <linux/time64.h> |
| |
| #include "trace-write-local.h" |
| #include "trace-cmd-local.h" |
| #include "trace-local.h" |
| #include "kbuffer.h" |
| #include "list.h" |
| |
| #define _STRINGIFY(x) #x |
| #define STRINGIFY(x) _STRINGIFY(x) |
| |
| #define MISSING_EVENTS (1 << 31) |
| #define MISSING_STORED (1 << 30) |
| |
| #define COMMIT_MASK ((1 << 27) - 1) |
| |
| /* force uncompressing in memory */ |
| #define INMEMORY_DECOMPRESS |
| |
| /* for debugging read instead of mmap */ |
| static int force_read = 0; |
| |
| struct page_map { |
| struct list_head list; |
| off64_t offset; |
| off64_t size; |
| void *map; |
| int ref_count; |
| }; |
| |
| struct page { |
| struct list_head list; |
| off64_t offset; |
| struct tracecmd_input *handle; |
| struct page_map *page_map; |
| void *map; |
| int ref_count; |
| int cpu; |
| long long lost_events; |
| #if DEBUG_RECORD |
| struct tep_record *records; |
| #endif |
| }; |
| |
| struct zchunk_cache { |
| struct list_head list; |
| struct tracecmd_compress_chunk *chunk; |
| void *map; |
| int ref; |
| }; |
| |
| struct cpu_zdata { |
| /* uncompressed cpu data */ |
| int fd; |
| #ifdef __ANDROID__ |
| char file[37]; /* strlen(COMPR_TEMP_FILE) */ |
| #else /* !__ANDROID__ */ |
| char file[26]; /* strlen(COMPR_TEMP_FILE) */ |
| #endif /* __ANDROID__ */ |
| |
| unsigned int count; |
| unsigned int last_chunk; |
| struct list_head cache; |
| struct tracecmd_compress_chunk *chunks; |
| }; |
| |
| #ifdef __ANDROID__ |
| #define COMPR_TEMP_FILE "/data/local/tmp/trace_cpu_dataXXXXXX" |
| #else /* !__ANDROID__ */ |
| #define COMPR_TEMP_FILE "/tmp/trace_cpu_dataXXXXXX" |
| #endif /* __ANDROID__ */ |
| |
| 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; |
| unsigned long long first_ts; |
| struct list_head page_maps; |
| struct page_map *page_map; |
| struct page **pages; |
| struct tep_record *next; |
| struct page *page; |
| struct kbuffer *kbuf; |
| int nr_pages; |
| int page_cnt; |
| int cpu; |
| int pipe_fd; |
| struct cpu_zdata compress; |
| }; |
| |
| struct cpu_file_data { |
| int cpu; |
| unsigned long long offset; |
| unsigned long long size; |
| }; |
| |
| struct input_buffer_instance { |
| char *name; |
| size_t offset; |
| char *clock; |
| bool latency; |
| int page_size; |
| int cpus; |
| struct cpu_file_data *cpu_data; |
| }; |
| |
| struct ts_offset_sample { |
| long long time; |
| long long offset; |
| long long scaling; |
| long long fraction; |
| }; |
| |
| struct guest_trace_info { |
| struct guest_trace_info *next; |
| char *name; |
| unsigned long long trace_id; |
| int vcpu_count; |
| int *cpu_pid; |
| }; |
| |
| struct timesync_offsets { |
| int ts_samples_count; |
| struct ts_offset_sample *ts_samples; |
| }; |
| |
| struct host_trace_info { |
| unsigned long long peer_trace_id; |
| unsigned int flags; |
| bool sync_enable; |
| int ts_samples_count; |
| struct ts_offset_sample *ts_samples; |
| int cpu_count; |
| struct timesync_offsets *ts_offsets; |
| }; |
| |
| struct tsc2nsec { |
| int mult; |
| int shift; |
| unsigned long long offset; |
| }; |
| |
| struct file_section { |
| unsigned long long section_offset; |
| unsigned long long data_offset; |
| int id; |
| int flags; |
| struct file_section *next; |
| }; |
| |
| struct tracecmd_input { |
| struct tep_handle *pevent; |
| struct tep_plugin_list *plugin_list; |
| struct tracecmd_input *parent; |
| unsigned long file_state; |
| unsigned long long trace_id; |
| unsigned long long next_offset; |
| unsigned long flags; |
| int fd; |
| int long_size; |
| int page_size; |
| int page_map_size; |
| int max_cpu; |
| int cpus; |
| int ref; |
| int nr_buffers; /* buffer instances */ |
| bool use_trace_clock; |
| bool read_page; |
| bool use_pipe; |
| bool read_zpage; /* uncompress pages in memory, do not use tmp files */ |
| bool cpu_compressed; |
| int file_version; |
| unsigned int cpustats_size; |
| struct cpu_zdata latz; |
| struct cpu_data *cpu_data; |
| long long ts_offset; |
| struct tsc2nsec tsc_calc; |
| |
| unsigned int strings_size; /* size of the metadata strings */ |
| char *strings; /* metadata strings */ |
| |
| bool read_compress; |
| struct tracecmd_compression *compress; |
| |
| struct host_trace_info host; |
| double ts2secs; |
| char * cpustats; |
| char * uname; |
| char * version; |
| char * trace_clock; |
| struct input_buffer_instance top_buffer; |
| struct input_buffer_instance *buffers; |
| int parsing_failures; |
| struct guest_trace_info *guest; |
| |
| struct tracecmd_ftrace finfo; |
| |
| struct hook_list *hooks; |
| struct pid_addr_maps *pid_maps; |
| /* file information */ |
| struct file_section *sections; |
| bool options_init; |
| unsigned long long options_start; |
| unsigned long long options_last_offset; |
| size_t total_file_size; |
| |
| /* For custom profilers. */ |
| tracecmd_show_data_func show_data_func; |
| }; |
| |
| __thread struct tracecmd_input *tracecmd_curr_thread_handle; |
| |
| #define CHECK_READ_STATE(H, S) ((H)->file_version < FILE_VERSION_SECTIONS && (H)->file_state >= (S)) |
| #define HAS_SECTIONS(H) ((H)->flags & TRACECMD_FL_SECTIONED) |
| #define HAS_COMPRESSION(H) ((H)->flags & TRACECMD_FL_COMPRESSION) |
| |
| static int read_options_type(struct tracecmd_input *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; |
| } |
| |
| enum tracecmd_file_states tracecmd_get_file_state(struct tracecmd_input *handle) |
| { |
| return handle->file_state; |
| } |
| |
| #if DEBUG_RECORD |
| static void remove_record(struct page *page, struct tep_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 tep_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 page **pages, int nr_pages) |
| { |
| static char buf[BUFSIZ + 1]; |
| struct tep_record *record; |
| struct page *page; |
| int len; |
| int i; |
| |
| memset(buf, 0, sizeof(buf)); |
| len = 0; |
| for (i = 0; i < nr_pages; i++) { |
| page = pages[i]; |
| if (!page) |
| continue; |
| 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 tep_record *record) {} |
| static inline void add_record(struct page *page, struct tep_record *record) {} |
| static const char *show_records(struct page **pages, int nr_pages) |
| { |
| return ""; |
| } |
| #endif |
| |
| static int init_cpu(struct tracecmd_input *handle, int cpu); |
| |
| static ssize_t do_read_fd(int fd, void *data, size_t size) |
| { |
| ssize_t tot = 0; |
| ssize_t r; |
| |
| do { |
| r = read(fd, data + tot, size - tot); |
| tot += r; |
| |
| if (!r) |
| break; |
| if (r < 0) |
| return r; |
| } while (tot != size); |
| |
| return tot; |
| } |
| |
| static inline int do_lseek(struct tracecmd_input *handle, int offset, int whence) |
| { |
| if (handle->read_compress) |
| return tracecmd_compress_lseek(handle->compress, offset, whence); |
| else |
| return lseek(handle->fd, offset, whence); |
| } |
| |
| static inline ssize_t do_read(struct tracecmd_input *handle, void *data, size_t size) |
| { |
| if (handle->read_compress) |
| return tracecmd_compress_buffer_read(handle->compress, data, size); |
| else |
| return do_read_fd(handle->fd, data, size); |
| } |
| |
| static ssize_t |
| do_read_check(struct tracecmd_input *handle, void *data, size_t size) |
| { |
| ssize_t 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; |
| size_t size = 0; |
| ssize_t i; |
| ssize_t r; |
| |
| for (;;) { |
| r = do_read(handle, buf, BUFSIZ); |
| if (r <= 0) |
| 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 = do_lseek(handle, -(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 int read2(struct tracecmd_input *handle, unsigned short *size) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| unsigned short data; |
| |
| if (do_read_check(handle, &data, 2)) |
| return -1; |
| |
| *size = tep_read_number(pevent, &data, 2); |
| return 0; |
| } |
| |
| static int read4(struct tracecmd_input *handle, unsigned int *size) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| unsigned int data; |
| |
| if (do_read_check(handle, &data, 4)) |
| return -1; |
| |
| *size = tep_read_number(pevent, &data, 4); |
| return 0; |
| } |
| |
| static int read8(struct tracecmd_input *handle, unsigned long long *size) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| unsigned long long data; |
| |
| if (do_read_check(handle, &data, 8)) |
| return -1; |
| |
| *size = tep_read_number(pevent, &data, 8); |
| return 0; |
| } |
| |
| __hidden void in_uncompress_reset(struct tracecmd_input *handle) |
| { |
| if (handle->compress) { |
| handle->read_compress = false; |
| tracecmd_compress_reset(handle->compress); |
| } |
| } |
| |
| __hidden int in_uncompress_block(struct tracecmd_input *handle) |
| { |
| int ret = 0; |
| |
| if (handle->compress) { |
| ret = tracecmd_uncompress_block(handle->compress); |
| if (!ret) |
| handle->read_compress = true; |
| } |
| return ret; |
| } |
| |
| static struct file_section *section_get(struct tracecmd_input *handle, int id) |
| { |
| struct file_section *sec; |
| |
| for (sec = handle->sections; sec; sec = sec->next) { |
| if (sec->id == id) |
| return sec; |
| } |
| |
| return NULL; |
| } |
| |
| static struct file_section *section_open(struct tracecmd_input *handle, int id) |
| { |
| struct file_section *sec = section_get(handle, id); |
| |
| if (!sec) |
| return NULL; |
| |
| if (lseek64(handle->fd, sec->data_offset, SEEK_SET) == (off64_t)-1) |
| return NULL; |
| |
| if ((sec->flags & TRACECMD_SEC_FL_COMPRESS) && in_uncompress_block(handle)) |
| return NULL; |
| |
| return sec; |
| } |
| |
| static void section_close(struct tracecmd_input *handle, struct file_section *sec) |
| { |
| if (sec->flags & TRACECMD_SEC_FL_COMPRESS) |
| in_uncompress_reset(handle); |
| } |
| |
| static int section_add_or_update(struct tracecmd_input *handle, int id, int flags, |
| unsigned long long section_offset, |
| unsigned long long data_offset) |
| { |
| struct file_section *sec = section_get(handle, id); |
| |
| if (!sec) { |
| sec = calloc(1, sizeof(struct file_section)); |
| if (!sec) |
| return -1; |
| sec->next = handle->sections; |
| handle->sections = sec; |
| sec->id = id; |
| } |
| |
| if (section_offset) |
| sec->section_offset = section_offset; |
| if (data_offset) |
| sec->data_offset = data_offset; |
| if (flags >= 0) |
| sec->flags = flags; |
| |
| return 0; |
| } |
| |
| static int read_header_files(struct tracecmd_input *handle) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| unsigned long long size; |
| char *header; |
| char buf[BUFSIZ]; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_HEADERS)) |
| return 0; |
| |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_HEADER_INFO, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| if (do_read_check(handle, buf, 12)) |
| return -1; |
| |
| if (memcmp(buf, "header_page", 12) != 0) |
| return -1; |
| |
| if (read8(handle, &size) < 0) |
| return -1; |
| |
| header = malloc(size); |
| if (!header) |
| return -1; |
| |
| if (do_read_check(handle, header, size)) |
| goto failed_read; |
| |
| tep_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 = tep_get_header_page_size(pevent); |
| |
| if (do_read_check(handle, buf, 13)) |
| return -1; |
| |
| if (memcmp(buf, "header_event", 13) != 0) |
| return -1; |
| |
| if (read8(handle, &size) < 0) |
| return -1; |
| |
| header = malloc(size); |
| if (!header) |
| return -1; |
| |
| if (do_read_check(handle, header, size)) |
| goto failed_read; |
| |
| free(header); |
| |
| handle->file_state = TRACECMD_FILE_HEADERS; |
| |
| 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) { |
| tracecmd_warning("Insufficient memory"); |
| return 0; |
| } |
| |
| strncpy(buf, file, size); |
| buf[size] = 0; |
| |
| /* get the name from the first line */ |
| line = strtok(buf, "\n"); |
| if (!line) { |
| tracecmd_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 tep_handle *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 (tep_parse_event(pevent, buf, size, "ftrace")) |
| handle->parsing_failures++; |
| } |
| 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 tep_handle *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 (tep_parse_event(pevent, buf, size, system)) |
| handle->parsing_failures++; |
| } |
| 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) |
| return -ENOMEM; |
| |
| 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) { |
| tracecmd_warning("Bad regular expression '%s'", sstr); |
| goto out; |
| } |
| |
| ret = regcomp(event, estr, REG_ICASE|REG_NOSUB); |
| if (ret) { |
| tracecmd_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; |
| unsigned int count, i; |
| int print_all = 0; |
| int unique; |
| int ret; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_FTRACE_EVENTS)) |
| return 0; |
| |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_FTRACE_EVENTS, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| 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; |
| } |
| } |
| |
| ret = read4(handle, &count); |
| if (ret < 0) |
| goto out; |
| |
| for (i = 0; i < count; i++) { |
| ret = read8(handle, &size); |
| if (ret < 0) |
| goto out; |
| ret = read_ftrace_file(handle, size, print_all, ereg); |
| if (ret < 0) |
| goto out; |
| } |
| |
| handle->file_state = TRACECMD_FILE_FTRACE_EVENTS; |
| ret = 0; |
| out: |
| if (sreg) { |
| regfree(sreg); |
| regfree(ereg); |
| } |
| |
| return ret; |
| } |
| |
| static int read_event_files(struct tracecmd_input *handle, const char *regex) |
| { |
| unsigned long long size; |
| char *system = NULL; |
| regex_t spreg; |
| regex_t epreg; |
| regex_t *sreg = NULL; |
| regex_t *ereg = NULL; |
| regex_t *reg; |
| unsigned int systems; |
| unsigned int count; |
| unsigned int i, x; |
| int print_all; |
| int sys_printed; |
| int unique; |
| int ret; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_ALL_EVENTS)) |
| return 0; |
| |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_EVENT_FORMATS, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| if (regex) { |
| sreg = &spreg; |
| ereg = &epreg; |
| ret = make_preg_files(regex, sreg, ereg, &unique); |
| if (ret) |
| return -1; |
| } |
| |
| ret = read4(handle, &systems); |
| if (ret < 0) |
| goto out; |
| |
| for (i = 0; i < systems; i++) { |
| system = read_string(handle); |
| if (!system) { |
| ret = -1; |
| goto out; |
| } |
| |
| 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; |
| } |
| } |
| |
| ret = read4(handle, &count); |
| if (ret < 0) |
| goto out; |
| |
| for (x=0; x < count; x++) { |
| ret = read8(handle, &size); |
| if (ret < 0) |
| goto out; |
| |
| ret = read_event_file(handle, system, size, |
| print_all, &sys_printed, |
| reg); |
| if (ret < 0) |
| goto out; |
| } |
| free(system); |
| } |
| system = NULL; |
| |
| handle->file_state = TRACECMD_FILE_ALL_EVENTS; |
| ret = 0; |
| out: |
| if (sreg) { |
| regfree(sreg); |
| regfree(ereg); |
| } |
| |
| free(system); |
| return ret; |
| } |
| |
| static int read_proc_kallsyms(struct tracecmd_input *handle) |
| { |
| struct tep_handle *tep = handle->pevent; |
| unsigned int size; |
| char *buf; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_KALLSYMS)) |
| return 0; |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_KALLSYMS, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| if (read4(handle, &size) < 0) |
| return -1; |
| if (!size) { |
| handle->file_state = TRACECMD_FILE_KALLSYMS; |
| return 0; /* OK? */ |
| } |
| |
| buf = malloc(size+1); |
| if (!buf) |
| return -1; |
| if (do_read_check(handle, buf, size)){ |
| free(buf); |
| return -1; |
| } |
| buf[size] = 0; |
| |
| tep_parse_kallsyms(tep, buf); |
| |
| free(buf); |
| |
| handle->file_state = TRACECMD_FILE_KALLSYMS; |
| |
| return 0; |
| } |
| |
| static int read_ftrace_printk(struct tracecmd_input *handle) |
| { |
| unsigned int size; |
| char *buf; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_PRINTK)) |
| return 0; |
| |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_PRINTK, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| if (read4(handle, &size) < 0) |
| return -1; |
| if (!size) { |
| handle->file_state = TRACECMD_FILE_PRINTK; |
| return 0; /* OK? */ |
| } |
| |
| buf = malloc(size + 1); |
| if (!buf) |
| return -1; |
| if (do_read_check(handle, buf, size)) { |
| free(buf); |
| return -1; |
| } |
| |
| buf[size] = 0; |
| |
| tep_parse_printk_formats(handle->pevent, buf); |
| |
| free(buf); |
| |
| handle->file_state = TRACECMD_FILE_PRINTK; |
| |
| return 0; |
| } |
| |
| static int read_and_parse_cmdlines(struct tracecmd_input *handle); |
| |
| /** |
| * tracecmd_get_parsing_failures - get the count of parsing failures |
| * @handle: input handle for the trace.dat file |
| * |
| * This returns the count of failures while parsing the event files |
| */ |
| int tracecmd_get_parsing_failures(struct tracecmd_input *handle) |
| { |
| if (handle) |
| return handle->parsing_failures; |
| return 0; |
| } |
| |
| static int read_cpus(struct tracecmd_input *handle) |
| { |
| unsigned int cpus; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_CPU_COUNT)) |
| return 0; |
| |
| if (read4(handle, &cpus) < 0) |
| return -1; |
| |
| handle->cpus = cpus; |
| handle->max_cpu = cpus; |
| tep_set_cpus(handle->pevent, handle->cpus); |
| handle->file_state = TRACECMD_FILE_CPU_COUNT; |
| |
| return 0; |
| } |
| |
| static int read_headers_v6(struct tracecmd_input *handle, enum tracecmd_file_states state, |
| const char *regex) |
| { |
| int ret; |
| |
| /* Set to read all if state is zero */ |
| if (!state) |
| state = TRACECMD_FILE_OPTIONS; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| handle->parsing_failures = 0; |
| |
| ret = read_header_files(handle); |
| if (ret < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| ret = read_ftrace_files(handle, NULL); |
| if (ret < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| ret = read_event_files(handle, regex); |
| if (ret < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| ret = read_proc_kallsyms(handle); |
| if (ret < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| ret = read_ftrace_printk(handle); |
| if (ret < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| if (read_and_parse_cmdlines(handle) < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| if (read_cpus(handle) < 0) |
| return -1; |
| |
| if (state <= handle->file_state) |
| return 0; |
| |
| if (read_options_type(handle) < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int handle_options(struct tracecmd_input *handle); |
| |
| static const char *get_metadata_string(struct tracecmd_input *handle, int offset) |
| { |
| if (!handle || !handle->strings || offset < 0 || handle->strings_size >= offset) |
| return NULL; |
| |
| return handle->strings + offset; |
| } |
| |
| static int read_section_header(struct tracecmd_input *handle, unsigned short *id, |
| unsigned short *flags, unsigned long long *size, const char **description) |
| { |
| unsigned short fl; |
| unsigned short sec_id; |
| unsigned long long sz; |
| int desc; |
| |
| if (read2(handle, &sec_id)) |
| return -1; |
| if (read2(handle, &fl)) |
| return -1; |
| if (read4(handle, (unsigned int *)&desc)) |
| return -1; |
| if (read8(handle, &sz)) |
| return -1; |
| |
| if (id) |
| *id = sec_id; |
| if (flags) |
| *flags = fl; |
| if (size) |
| *size = sz; |
| if (description) |
| *description = get_metadata_string(handle, desc); |
| |
| return 0; |
| } |
| |
| static int handle_section(struct tracecmd_input *handle, struct file_section *section, |
| const char *regex) |
| { |
| unsigned short id, flags; |
| unsigned long long size; |
| int ret; |
| |
| if (lseek64(handle->fd, section->section_offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| if (read_section_header(handle, &id, &flags, &size, NULL)) |
| return -1; |
| section->flags = flags; |
| if (id != section->id) |
| return -1; |
| |
| section->data_offset = lseek64(handle->fd, 0, SEEK_CUR); |
| if ((section->flags & TRACECMD_SEC_FL_COMPRESS) && in_uncompress_block(handle)) |
| return -1; |
| |
| switch (section->id) { |
| case TRACECMD_OPTION_HEADER_INFO: |
| ret = read_header_files(handle); |
| break; |
| case TRACECMD_OPTION_FTRACE_EVENTS: |
| ret = read_ftrace_files(handle, NULL); |
| break; |
| case TRACECMD_OPTION_EVENT_FORMATS: |
| ret = read_event_files(handle, regex); |
| break; |
| case TRACECMD_OPTION_KALLSYMS: |
| ret = read_proc_kallsyms(handle); |
| break; |
| case TRACECMD_OPTION_PRINTK: |
| ret = read_ftrace_printk(handle); |
| break; |
| case TRACECMD_OPTION_CMDLINES: |
| ret = read_and_parse_cmdlines(handle); |
| break; |
| default: |
| ret = 0; |
| break; |
| } |
| |
| if (section->flags & TRACECMD_SEC_FL_COMPRESS) |
| in_uncompress_reset(handle); |
| |
| return ret; |
| } |
| |
| static int read_headers(struct tracecmd_input *handle, const char *regex) |
| { |
| struct file_section *section; |
| |
| if (handle->options_init) |
| return 0; |
| |
| if (!handle->options_start) |
| return -1; |
| |
| if (lseek64(handle->fd, handle->options_start, SEEK_SET) == (off64_t)-1) { |
| tracecmd_warning("Filed to goto options offset %lld", handle->options_start); |
| return -1; |
| } |
| |
| if (handle_options(handle)) |
| return -1; |
| |
| section = handle->sections; |
| while (section) { |
| if (handle_section(handle, section, NULL)) |
| return -1; |
| section = section->next; |
| } |
| |
| handle->options_init = true; |
| return 0; |
| } |
| |
| /** |
| * tracecmd_read_headers - read the header information from trace.dat |
| * @handle: input handle for the trace.dat file |
| * @state: The state to read up to or zero to read up to options. |
| * |
| * This reads the trace.dat file for various information. Like the |
| * format of the ring buffer, event formats, ftrace formats, kallsyms |
| * and printk. This may be called multiple times with different @state |
| * values, to read partial data at a time. It will always continue |
| * where it left off. |
| */ |
| int tracecmd_read_headers(struct tracecmd_input *handle, |
| enum tracecmd_file_states state) |
| { |
| if (!HAS_SECTIONS(handle)) |
| return read_headers_v6(handle, state, NULL); |
| return read_headers(handle, NULL); |
| } |
| |
| 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; |
| } |
| |
| /* page_map_size must be a power of two */ |
| static unsigned long long normalize_size(unsigned long long size) |
| { |
| /* From Hacker's Delight: or bits after first set bit to all 1s */ |
| size |= (size >> 1); |
| size |= (size >> 2); |
| size |= (size >> 4); |
| size |= (size >> 8); |
| size |= (size >> 16); |
| size |= (size >> 32); |
| |
| /* Clear all bits except first one for previous power of two */ |
| return size - (size >> 1); |
| } |
| |
| static void free_page_map(struct page_map *page_map) |
| { |
| page_map->ref_count--; |
| if (page_map->ref_count) |
| return; |
| |
| munmap(page_map->map, page_map->size); |
| list_del(&page_map->list); |
| free(page_map); |
| } |
| |
| #define CHUNK_CHECK_OFFSET(C, O) ((O) >= (C)->offset && (O) < ((C)->offset + (C)->size)) |
| |
| static int chunk_cmp(const void *A, const void *B) |
| { |
| const struct tracecmd_compress_chunk *a = A; |
| const struct tracecmd_compress_chunk *b = B; |
| |
| if (CHUNK_CHECK_OFFSET(b, a->offset)) |
| return 0; |
| |
| if (b->offset < a->offset) |
| return -1; |
| |
| return 1; |
| } |
| |
| static struct tracecmd_compress_chunk *get_zchunk(struct cpu_data *cpu, off64_t offset) |
| { |
| struct cpu_zdata *cpuz = &cpu->compress; |
| struct tracecmd_compress_chunk *chunk; |
| struct tracecmd_compress_chunk key; |
| |
| if (!cpuz->chunks) |
| return NULL; |
| |
| if (offset > (cpuz->chunks[cpuz->count - 1].offset + cpuz->chunks[cpuz->count - 1].size)) |
| return NULL; |
| |
| /* check if the requested offset is in the last requested chunk or in the next chunk */ |
| if (CHUNK_CHECK_OFFSET(cpuz->chunks + cpuz->last_chunk, offset)) |
| return cpuz->chunks + cpuz->last_chunk; |
| |
| cpuz->last_chunk++; |
| if (cpuz->last_chunk < cpuz->count && |
| CHUNK_CHECK_OFFSET(cpuz->chunks + cpuz->last_chunk, offset)) |
| return cpuz->chunks + cpuz->last_chunk; |
| |
| key.offset = offset; |
| chunk = bsearch(&key, cpuz->chunks, cpuz->count, sizeof(*chunk), chunk_cmp); |
| |
| if (!chunk) /* should never happen */ |
| return NULL; |
| |
| cpuz->last_chunk = chunk - cpuz->chunks; |
| return chunk; |
| } |
| |
| static void free_zpage(struct cpu_data *cpu_data, void *map) |
| { |
| struct zchunk_cache *cache; |
| |
| list_for_each_entry(cache, &cpu_data->compress.cache, list) { |
| if (map <= cache->map && map > (cache->map + cache->chunk->size)) |
| goto found; |
| } |
| return; |
| |
| found: |
| cache->ref--; |
| if (cache->ref) |
| return; |
| list_del(&cache->list); |
| free(cache->map); |
| free(cache); |
| } |
| |
| static void *read_zpage(struct tracecmd_input *handle, int cpu, off64_t offset) |
| { |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; |
| struct tracecmd_compress_chunk *chunk; |
| struct zchunk_cache *cache; |
| void *map = NULL; |
| int pindex; |
| int size; |
| |
| offset -= cpu_data->file_offset; |
| |
| /* Look in the cache of already loaded chunks */ |
| list_for_each_entry(cache, &cpu_data->compress.cache, list) { |
| if (CHUNK_CHECK_OFFSET(cache->chunk, offset)) { |
| cache->ref++; |
| goto out; |
| } |
| } |
| |
| chunk = get_zchunk(cpu_data, offset); |
| if (!chunk) |
| return NULL; |
| |
| size = handle->page_size > chunk->size ? handle->page_size : chunk->size; |
| map = malloc(size); |
| if (!map) |
| return NULL; |
| |
| if (tracecmd_uncompress_chunk(handle->compress, chunk, map) < 0) |
| goto error; |
| |
| cache = calloc(1, sizeof(struct zchunk_cache)); |
| if (!cache) |
| goto error; |
| |
| cache->ref = 1; |
| cache->chunk = chunk; |
| cache->map = map; |
| list_add(&cache->list, &cpu_data->compress.cache); |
| |
| /* a chunk can hold multiple pages, get the requested one */ |
| out: |
| pindex = (offset - cache->chunk->offset) / handle->page_size; |
| return cache->map + (pindex * handle->page_size); |
| error: |
| free(map); |
| return NULL; |
| } |
| |
| static void *allocate_page_map(struct tracecmd_input *handle, |
| struct page *page, int cpu, off64_t offset) |
| { |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; |
| struct page_map *page_map; |
| off64_t map_size; |
| off64_t map_offset; |
| void *map; |
| int ret; |
| int fd; |
| |
| if (handle->cpu_compressed && handle->read_zpage) |
| return read_zpage(handle, cpu, offset); |
| |
| if (handle->read_page) { |
| map = malloc(handle->page_size); |
| if (!map) |
| return NULL; |
| ret = read_page(handle, offset, cpu, map); |
| if (ret < 0) { |
| free(map); |
| return NULL; |
| } |
| return map; |
| } |
| |
| map_size = handle->page_map_size; |
| map_offset = offset & ~(map_size - 1); |
| |
| if (map_offset < cpu_data->file_offset) { |
| map_size -= cpu_data->file_offset - map_offset; |
| map_offset = cpu_data->file_offset; |
| } |
| |
| page_map = cpu_data->page_map; |
| |
| if (page_map && page_map->offset == map_offset) |
| goto out; |
| |
| list_for_each_entry(page_map, &cpu_data->page_maps, list) { |
| if (page_map->offset == map_offset) |
| goto out; |
| } |
| |
| page_map = calloc(1, sizeof(*page_map)); |
| if (!page_map) |
| return NULL; |
| |
| if (map_offset + map_size > cpu_data->file_offset + cpu_data->file_size) |
| map_size -= map_offset + map_size - |
| (cpu_data->file_offset + cpu_data->file_size); |
| |
| if (cpu_data->compress.fd >= 0) { |
| map_offset -= cpu_data->file_offset; |
| fd = cpu_data->compress.fd; |
| } else |
| fd = handle->fd; |
| again: |
| page_map->size = map_size; |
| page_map->offset = map_offset; |
| |
| page_map->map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, map_offset); |
| |
| if (page_map->map == MAP_FAILED) { |
| /* Try a smaller map */ |
| map_size >>= 1; |
| if (map_size < handle->page_size) { |
| free(page_map); |
| return NULL; |
| } |
| handle->page_map_size = map_size; |
| map_offset = offset & ~(map_size - 1); |
| /* |
| * Note, it is now possible to get duplicate memory |
| * maps. But that's fine, the previous maps with |
| * larger sizes will eventually be unmapped. |
| */ |
| goto again; |
| } |
| |
| list_add(&page_map->list, &cpu_data->page_maps); |
| out: |
| if (cpu_data->page_map != page_map) { |
| struct page_map *old_map = cpu_data->page_map; |
| cpu_data->page_map = page_map; |
| page_map->ref_count++; |
| if (old_map) |
| free_page_map(old_map); |
| } |
| page->page_map = page_map; |
| page_map->ref_count++; |
| return page_map->map + offset - page_map->offset; |
| } |
| |
| 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 **pages; |
| struct page *page; |
| int index; |
| |
| index = (offset - cpu_data->file_offset) / handle->page_size; |
| if (index >= cpu_data->nr_pages) { |
| pages = realloc(cpu_data->pages, (index + 1) * sizeof(*cpu_data->pages)); |
| if (!pages) |
| return NULL; |
| memset(pages + cpu_data->nr_pages, 0, |
| (index + 1 - cpu_data->nr_pages) * sizeof(*cpu_data->pages)); |
| cpu_data->pages = pages; |
| cpu_data->nr_pages = index + 1; |
| } |
| if (cpu_data->pages[index]) { |
| cpu_data->pages[index]->ref_count++; |
| return cpu_data->pages[index]; |
| } |
| |
| page = malloc(sizeof(*page)); |
| if (!page) |
| return NULL; |
| |
| memset(page, 0, sizeof(*page)); |
| page->offset = offset; |
| page->handle = handle; |
| page->cpu = cpu; |
| |
| page->map = allocate_page_map(handle, page, cpu, offset); |
| |
| if (!page->map) { |
| free(page); |
| return NULL; |
| } |
| |
| cpu_data->pages[index] = page; |
| cpu_data->page_cnt++; |
| page->ref_count = 1; |
| |
| return page; |
| } |
| |
| static void __free_page(struct tracecmd_input *handle, struct page *page) |
| { |
| struct cpu_data *cpu_data = &handle->cpu_data[page->cpu]; |
| struct page **pages; |
| int index; |
| |
| if (!page->ref_count) { |
| tracecmd_critical("Page ref count is zero!"); |
| return; |
| } |
| |
| page->ref_count--; |
| if (page->ref_count) |
| return; |
| |
| if (handle->read_page) |
| free(page->map); |
| else if (handle->read_zpage) |
| free_zpage(cpu_data, page->map); |
| else |
| free_page_map(page->page_map); |
| |
| index = (page->offset - cpu_data->file_offset) / handle->page_size; |
| cpu_data->pages[index] = NULL; |
| cpu_data->page_cnt--; |
| |
| free(page); |
| |
| if (handle->use_pipe) { |
| for (index = cpu_data->nr_pages - 1; index > 0; index--) |
| if (cpu_data->pages[index]) |
| break; |
| if (index < (cpu_data->nr_pages - 1)) { |
| pages = realloc(cpu_data->pages, (index + 1) * sizeof(*cpu_data->pages)); |
| if (!pages) |
| return; |
| cpu_data->pages = pages; |
| cpu_data->nr_pages = index + 1; |
| } |
| } |
| } |
| |
| 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 tep_record *record) |
| { |
| if (record->priv) { |
| struct page *page = record->priv; |
| remove_record(page, record); |
| __free_page(page->handle, page); |
| } |
| |
| free(record); |
| } |
| |
| void tracecmd_free_record(struct tep_record *record) |
| { |
| if (!record) |
| return; |
| |
| if (!record->ref_count) { |
| tracecmd_critical("record ref count is zero!"); |
| return; |
| } |
| |
| record->ref_count--; |
| |
| if (record->ref_count) |
| return; |
| |
| if (record->locked) { |
| tracecmd_critical("freeing record when it is locked!"); |
| return; |
| } |
| |
| record->data = NULL; |
| |
| __free_record(record); |
| } |
| |
| void tracecmd_record_ref(struct tep_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 tep_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; |
| tracecmd_free_record(record); |
| } |
| |
| /* This functions was taken from the Linux kernel */ |
| static unsigned long long mul_u64_u32_shr(unsigned long long a, |
| unsigned long long mul, unsigned int shift) |
| { |
| unsigned int ah, al; |
| unsigned long long ret; |
| |
| al = a; |
| ah = a >> 32; |
| |
| ret = (al * mul) >> shift; |
| if (ah) |
| ret += (ah * mul) << (32 - shift); |
| |
| return ret; |
| } |
| |
| static inline unsigned long long |
| timestamp_correction_calc(unsigned long long ts, unsigned int flags, |
| struct ts_offset_sample *min, |
| struct ts_offset_sample *max) |
| { |
| long long tscor; |
| |
| if (flags & TRACECMD_TSYNC_FLAG_INTERPOLATE) { |
| long long delta = max->time - min->time; |
| long long offset = ((long long)ts - min->time) * |
| (max->offset - min->offset); |
| |
| tscor = min->offset + (offset + delta / 2) / delta; |
| } else { |
| tscor = min->offset; |
| } |
| |
| ts = (ts * min->scaling) >> min->fraction; |
| if (tscor < 0) |
| return ts - llabs(tscor); |
| |
| return ts + tscor; |
| } |
| |
| static unsigned long long timestamp_host_sync(unsigned long long ts, int cpu, |
| struct tracecmd_input *handle) |
| { |
| struct timesync_offsets *tsync; |
| int min, mid, max; |
| |
| if (cpu >= handle->host.cpu_count) |
| return ts; |
| tsync = &handle->host.ts_offsets[cpu]; |
| |
| /* We have one sample, nothing to calc here */ |
| if (tsync->ts_samples_count == 1) |
| return ts + tsync->ts_samples[0].offset; |
| |
| /* We have two samples, nothing to search here */ |
| if (tsync->ts_samples_count == 2) |
| return timestamp_correction_calc(ts, handle->host.flags, |
| &tsync->ts_samples[0], |
| &tsync->ts_samples[1]); |
| |
| /* We have more than two samples */ |
| if (ts <= tsync->ts_samples[0].time) |
| return timestamp_correction_calc(ts, handle->host.flags, |
| &tsync->ts_samples[0], |
| &tsync->ts_samples[1]); |
| else if (ts >= tsync->ts_samples[tsync->ts_samples_count-1].time) |
| return timestamp_correction_calc(ts, handle->host.flags, |
| &tsync->ts_samples[tsync->ts_samples_count-2], |
| &tsync->ts_samples[tsync->ts_samples_count-1]); |
| min = 0; |
| max = tsync->ts_samples_count-1; |
| mid = (min + max)/2; |
| while (min <= max) { |
| if (ts < tsync->ts_samples[mid].time) |
| max = mid - 1; |
| else if (ts > tsync->ts_samples[mid].time) |
| min = mid + 1; |
| else |
| break; |
| mid = (min + max)/2; |
| } |
| |
| return timestamp_correction_calc(ts, handle->host.flags, |
| &tsync->ts_samples[mid], |
| &tsync->ts_samples[mid+1]); |
| } |
| |
| static unsigned long long timestamp_calc(unsigned long long ts, int cpu, |
| struct tracecmd_input *handle) |
| { |
| /* do not modify raw timestamps */ |
| if (handle->flags & TRACECMD_FL_RAW_TS) |
| return ts; |
| |
| /* Guest trace file, sync with host timestamps */ |
| if (handle->host.sync_enable) |
| ts = timestamp_host_sync(ts, cpu, handle); |
| |
| if (handle->ts2secs) { |
| /* user specified clock frequency */ |
| ts *= handle->ts2secs; |
| } else if (handle->tsc_calc.mult) { |
| /* auto calculated TSC clock frequency */ |
| ts = mul_u64_u32_shr(ts, handle->tsc_calc.mult, handle->tsc_calc.shift); |
| } |
| |
| /* User specified time offset with --ts-offset or --date options */ |
| ts += handle->ts_offset; |
| |
| return ts; |
| } |
| |
| /* |
| * Page is mapped, now read in the page header info. |
| */ |
| static int update_page_info(struct tracecmd_input *handle, int cpu) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| void *ptr = handle->cpu_data[cpu].page->map; |
| struct kbuffer *kbuf = handle->cpu_data[cpu].kbuf; |
| |
| /* FIXME: handle header page */ |
| if (tep_get_header_timestamp_size(pevent) != 8) { |
| tracecmd_warning("expected a long long type for timestamp"); |
| return -1; |
| } |
| |
| kbuffer_load_subbuffer(kbuf, ptr); |
| if (kbuffer_subbuffer_size(kbuf) > handle->page_size) { |
| tracecmd_warning("bad page read, with size of %d", kbuffer_subbuffer_size(kbuf)); |
| return -1; |
| } |
| handle->cpu_data[cpu].timestamp = timestamp_calc(kbuffer_timestamp(kbuf), |
| cpu, handle); |
| |
| 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; |
| tracecmd_critical("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; |
| tracecmd_critical("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 tep_record * |
| peek_event(struct tracecmd_input *handle, unsigned long long offset, |
| int cpu) |
| { |
| struct tep_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 tep_record * |
| read_event(struct tracecmd_input *handle, unsigned long long offset, |
| int cpu) |
| { |
| struct tep_record *record; |
| |
| record = peek_event(handle, offset, cpu); |
| if (record) |
| record = tracecmd_read_data(handle, cpu); |
| return record; |
| } |
| |
| static struct tep_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 tep_record * |
| find_and_read_event(struct tracecmd_input *handle, unsigned long long offset, |
| int *pcpu) |
| { |
| struct tep_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 tep_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 && handle->cpu_data[cpu].page) { |
| 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 tep_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 tep_record * |
| tracecmd_read_cpu_first(struct tracecmd_input *handle, int cpu) |
| { |
| unsigned long long page_offset; |
| int ret; |
| |
| if (cpu >= handle->cpus) |
| return NULL; |
| |
| page_offset = calc_page_offset(handle, handle->cpu_data[cpu].file_offset); |
| |
| ret = get_page(handle, cpu, page_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 tep_record * |
| tracecmd_read_cpu_last(struct tracecmd_input *handle, int cpu) |
| { |
| struct tep_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 { |
| tracecmd_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 tep_record * |
| tracecmd_translate_data(struct tracecmd_input *handle, |
| void *ptr, int size) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| struct tep_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 (tep_is_local_bigendian(pevent) == tep_is_file_bigendian(pevent)) |
| 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_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 tep_record * |
| tracecmd_peek_data(struct tracecmd_input *handle, int cpu) |
| { |
| struct tep_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) { |
| tracecmd_critical("Something freed the record"); |
| return NULL; |
| } |
| |
| 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 = timestamp_calc(ts, cpu, handle); |
| |
| 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 = handle->cpu_data[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 tep_record * |
| tracecmd_read_data(struct tracecmd_input *handle, int cpu) |
| { |
| struct tep_record *record; |
| |
| if (cpu >= handle->cpus) |
| return NULL; |
| |
| 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 tep_record * |
| tracecmd_read_next_data(struct tracecmd_input *handle, int *rec_cpu) |
| { |
| struct tep_record *record; |
| int next_cpu; |
| |
| record = tracecmd_peek_next_data(handle, &next_cpu); |
| if (!record) |
| return NULL; |
| |
| if (rec_cpu) |
| *rec_cpu = next_cpu; |
| |
| return tracecmd_read_data(handle, next_cpu); |
| } |
| |
| /** |
| * tracecmd_peek_next_data - return 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_peek_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. It does not increment the CPU iterator. |
| */ |
| struct tep_record * |
| tracecmd_peek_next_data(struct tracecmd_input *handle, int *rec_cpu) |
| { |
| unsigned long long ts; |
| struct tep_record *record, *next_record = NULL; |
| int next_cpu; |
| int cpu; |
| |
| if (rec_cpu) |
| *rec_cpu = -1; |
| |
| next_cpu = -1; |
| ts = 0; |
| |
| for (cpu = 0; cpu < handle->cpus; cpu++) { |
| record = tracecmd_peek_data(handle, cpu); |
| if (record && (!next_record || record->ts < ts)) { |
| ts = record->ts; |
| next_cpu = cpu; |
| next_record = record; |
| } |
| } |
| |
| if (next_record) { |
| if (rec_cpu) |
| *rec_cpu = next_cpu; |
| return next_record; |
| } |
| |
| 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 tracecmd_free_record(). |
| */ |
| struct tep_record * |
| tracecmd_read_prev(struct tracecmd_input *handle, struct tep_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; |
| tracecmd_free_record(record); |
| } |
| tracecmd_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; |
| tracecmd_free_record(record); |
| } |
| record = tracecmd_read_data(handle, cpu); |
| /* Should not happen */ |
| if (!record) |
| return NULL; |
| } while (record->offset != offset); |
| tracecmd_free_record(record); |
| |
| if (index) |
| /* we found our record */ |
| return tracecmd_read_at(handle, page_offset + index, NULL); |
| } |
| |
| /* Not reached */ |
| } |
| |
| static int init_cpu_zfile(struct tracecmd_input *handle, int cpu) |
| { |
| struct cpu_data *cpu_data; |
| unsigned long long size; |
| off64_t offset; |
| |
| cpu_data = &handle->cpu_data[cpu]; |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| if (lseek64(handle->fd, cpu_data->file_offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| |
| strcpy(cpu_data->compress.file, COMPR_TEMP_FILE); |
| cpu_data->compress.fd = mkstemp(cpu_data->compress.file); |
| if (cpu_data->compress.fd < 0) |
| return -1; |
| |
| if (tracecmd_uncompress_copy_to(handle->compress, cpu_data->compress.fd, NULL, &size)) |
| return -1; |
| |
| if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| |
| cpu_data->file_offset = handle->next_offset; |
| handle->next_offset = (handle->next_offset + size + handle->page_size - 1) & |
| ~(handle->page_size - 1); |
| cpu_data->offset = cpu_data->file_offset; |
| |
| cpu_data->file_size = size; |
| cpu_data->size = size; |
| return 0; |
| } |
| |
| static int init_cpu_zpage(struct tracecmd_input *handle, int cpu) |
| { |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; |
| int count; |
| int i; |
| |
| if (lseek64(handle->fd, cpu_data->file_offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| |
| count = tracecmd_load_chunks_info(handle->compress, &cpu_data->compress.chunks); |
| if (count < 0) |
| return -1; |
| |
| cpu_data->compress.count = count; |
| cpu_data->compress.last_chunk = 0; |
| |
| cpu_data->file_offset = handle->next_offset; |
| |
| for (i = 0; i < count; i++) |
| cpu_data->file_size += cpu_data->compress.chunks[i].size; |
| |
| cpu_data->offset = cpu_data->file_offset; |
| cpu_data->size = cpu_data->file_size; |
| handle->next_offset = (handle->next_offset + cpu_data->size + handle->page_size - 1) & |
| ~(handle->page_size - 1); |
| return 0; |
| } |
| |
| static int init_cpu(struct tracecmd_input *handle, int cpu) |
| { |
| struct cpu_data *cpu_data = &handle->cpu_data[cpu]; |
| int ret; |
| int i; |
| |
| if (handle->cpu_compressed && cpu_data->file_size > 0) { |
| if (handle->read_zpage) |
| ret = init_cpu_zpage(handle, cpu); |
| else |
| ret = init_cpu_zfile(handle, cpu); |
| if (ret) |
| return ret; |
| } else { |
| cpu_data->offset = cpu_data->file_offset; |
| cpu_data->size = cpu_data->file_size; |
| } |
| cpu_data->timestamp = 0; |
| |
| list_head_init(&cpu_data->page_maps); |
| list_head_init(&cpu_data->compress.cache); |
| |
| if (!cpu_data->size) { |
| tracecmd_info("CPU %d is empty", cpu); |
| return 0; |
| } |
| |
| cpu_data->nr_pages = (cpu_data->size + handle->page_size - 1) / handle->page_size; |
| if (!cpu_data->nr_pages) |
| cpu_data->nr_pages = 1; |
| cpu_data->pages = calloc(cpu_data->nr_pages, sizeof(*cpu_data->pages)); |
| if (!cpu_data->pages) |
| return -1; |
| |
| 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) |
| goto fail; |
| |
| memset(cpu_data->page, 0, sizeof(*cpu_data->page)); |
| cpu_data->pages[0] = cpu_data->page; |
| cpu_data->page_cnt = 1; |
| 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) |
| goto fail; |
| } |
| } |
| |
| /* 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! */ |
| goto fail; |
| } |
| |
| if (update_page_info(handle, cpu)) |
| goto fail; |
| cpu_data->first_ts = cpu_data->timestamp; |
| |
| return 0; |
| fail: |
| free(cpu_data->pages); |
| cpu_data->pages = NULL; |
| free(cpu_data->page); |
| cpu_data->page = NULL; |
| return -1; |
| } |
| |
| void tracecmd_set_ts_offset(struct tracecmd_input *handle, |
| long long offset) |
| { |
| handle->ts_offset = offset; |
| } |
| |
| /** |
| * tracecmd_add_ts_offset - Add value to the offset which will be applied to the timestamps of all |
| * events from given trace file |
| * @handle: input handle to the trace.dat file |
| * @offset: value, that will be added to the offset |
| */ |
| void tracecmd_add_ts_offset(struct tracecmd_input *handle, |
| long long offset) |
| { |
| handle->ts_offset += offset; |
| } |
| |
| void tracecmd_set_ts2secs(struct tracecmd_input *handle, |
| unsigned long long hz) |
| { |
| double ts2secs; |
| |
| ts2secs = (double)NSEC_PER_SEC / (double)hz; |
| handle->ts2secs = ts2secs; |
| handle->use_trace_clock = false; |
| } |
| |
| static int tsync_offset_cmp(const void *a, const void *b) |
| { |
| struct ts_offset_sample *ts_a = (struct ts_offset_sample *)a; |
| struct ts_offset_sample *ts_b = (struct ts_offset_sample *)b; |
| |
| if (ts_a->time > ts_b->time) |
| return 1; |
| if (ts_a->time < ts_b->time) |
| return -1; |
| return 0; |
| } |
| |
| #define safe_read(R, C) \ |
| do { \ |
| if ((C) > size) \ |
| return -EFAULT; \ |
| (R) = tep_read_number(tep, buf, (C)); \ |
| buf += (C); \ |
| size -= (C); \ |
| } while (0) |
| |
| #define safe_read_loop(type) \ |
| do { \ |
| int ii; \ |
| for (ii = 0; ii < ts_offsets->ts_samples_count; ii++) \ |
| safe_read(ts_offsets->ts_samples[ii].type, 8); \ |
| } while (0) |
| |
| static int tsync_cpu_offsets_load(struct tracecmd_input *handle, char *buf, int size) |
| { |
| struct tep_handle *tep = handle->pevent; |
| struct timesync_offsets *ts_offsets; |
| int i, j, k; |
| |
| safe_read(handle->host.cpu_count, 4); |
| handle->host.ts_offsets = calloc(handle->host.cpu_count, |
| sizeof(struct timesync_offsets)); |
| if (!handle->host.ts_offsets) |
| return -ENOMEM; |
| for (i = 0; i < handle->host.cpu_count; i++) { |
| ts_offsets = &handle->host.ts_offsets[i]; |
| safe_read(ts_offsets->ts_samples_count, 4); |
| ts_offsets->ts_samples = calloc(ts_offsets->ts_samples_count, |
| sizeof(struct ts_offset_sample)); |
| if (!ts_offsets->ts_samples) |
| return -ENOMEM; |
| safe_read_loop(time); |
| safe_read_loop(offset); |
| safe_read_loop(scaling); |
| } |
| |
| if (size > 0) { |
| for (i = 0; i < handle->host.cpu_count; i++) { |
| ts_offsets = &handle->host.ts_offsets[i]; |
| safe_read_loop(fraction); |
| } |
| } |
| |
| for (i = 0; i < handle->host.cpu_count; i++) { |
| ts_offsets = &handle->host.ts_offsets[i]; |
| qsort(ts_offsets->ts_samples, ts_offsets->ts_samples_count, |
| sizeof(struct ts_offset_sample), tsync_offset_cmp); |
| /* Filter possible samples with equal time */ |
| for (k = 0, j = 0; k < ts_offsets->ts_samples_count; k++) { |
| if (k == 0 || ts_offsets->ts_samples[k].time != ts_offsets->ts_samples[k-1].time) |
| ts_offsets->ts_samples[j++] = ts_offsets->ts_samples[k]; |
| } |
| ts_offsets->ts_samples_count = j; |
| } |
| |
| return 0; |
| } |
| |
| static void trace_tsync_offset_free(struct host_trace_info *host) |
| { |
| int i; |
| |
| if (host->ts_offsets) { |
| for (i = 0; i < host->cpu_count; i++) |
| free(host->ts_offsets[i].ts_samples); |
| free(host->ts_offsets); |
| host->ts_offsets = NULL; |
| } |
| } |
| |
| static int trace_pid_map_cmp(const void *a, const void *b) |
| { |
| struct tracecmd_proc_addr_map *m_a = (struct tracecmd_proc_addr_map *)a; |
| struct tracecmd_proc_addr_map *m_b = (struct tracecmd_proc_addr_map *)b; |
| |
| if (m_a->start > m_b->start) |
| if (m_a->start < m_b->start) |
| return -1; |
| return 0; |
| } |
| |
| static void procmap_free(struct pid_addr_maps *maps) |
| { |
| int i; |
| |
| if (!maps) |
| return; |
| if (maps->lib_maps) { |
| for (i = 0; i < maps->nr_lib_maps; i++) |
| free(maps->lib_maps[i].lib_name); |
| free(maps->lib_maps); |
| } |
| free(maps->proc_name); |
| free(maps); |
| } |
| |
| static void trace_guests_free(struct tracecmd_input *handle) |
| { |
| struct guest_trace_info *guest; |
| |
| while (handle->guest) { |
| guest = handle->guest; |
| handle->guest = handle->guest->next; |
| free(guest->name); |
| free(guest->cpu_pid); |
| free(guest); |
| } |
| } |
| |
| static int trace_guest_load(struct tracecmd_input *handle, char *buf, int size) |
| { |
| struct guest_trace_info *guest = NULL; |
| int cpu; |
| int i; |
| |
| guest = calloc(1, sizeof(struct guest_trace_info)); |
| if (!guest) |
| goto error; |
| |
| /* |
| * Guest name, null terminated string |
| * long long (8 bytes) trace-id |
| * int (4 bytes) number of guest CPUs |
| * array of size number of guest CPUs: |
| * int (4 bytes) Guest CPU id |
| * int (4 bytes) Host PID, running the guest CPU |
| */ |
| |
| guest->name = strndup(buf, size); |
| if (!guest->name) |
| goto error; |
| buf += strlen(guest->name) + 1; |
| size -= strlen(guest->name) + 1; |
| |
| if (size < sizeof(long long)) |
| goto error; |
| guest->trace_id = tep_read_number(handle->pevent, buf, sizeof(long long)); |
| buf += sizeof(long long); |
| size -= sizeof(long long); |
| |
| if (size < sizeof(int)) |
| goto error; |
| guest->vcpu_count = tep_read_number(handle->pevent, buf, sizeof(int)); |
| buf += sizeof(int); |
| size -= sizeof(int); |
| |
| guest->cpu_pid = calloc(guest->vcpu_count, sizeof(int)); |
| if (!guest->cpu_pid) |
| goto error; |
| |
| for (i = 0; i < guest->vcpu_count; i++) { |
| if (size < 2 * sizeof(int)) |
| goto error; |
| cpu = tep_read_number(handle->pevent, buf, sizeof(int)); |
| buf += sizeof(int); |
| if (cpu >= guest->vcpu_count) |
| goto error; |
| guest->cpu_pid[cpu] = tep_read_number(handle->pevent, |
| buf, sizeof(int)); |
| buf += sizeof(int); |
| size -= 2 * sizeof(int); |
| } |
| |
| guest->next = handle->guest; |
| handle->guest = guest; |
| return 0; |
| |
| error: |
| if (guest) { |
| free(guest->cpu_pid); |
| free(guest->name); |
| free(guest); |
| } |
| return -1; |
| } |
| |
| /* Needs to be a constant, and 4K should be good enough */ |
| #define STR_PROCMAP_LINE_MAX 4096 |
| static int trace_pid_map_load(struct tracecmd_input *handle, char *buf) |
| { |
| struct pid_addr_maps *maps = NULL; |
| char mapname[STR_PROCMAP_LINE_MAX+1]; |
| char *line; |
| int res; |
| int ret; |
| int i; |
| |
| maps = calloc(1, sizeof(*maps)); |
| if (!maps) |
| return -ENOMEM; |
| |
| ret = -EINVAL; |
| line = strchr(buf, '\n'); |
| if (!line) |
| goto out_fail; |
| |
| *line = '\0'; |
| if (strlen(buf) > STR_PROCMAP_LINE_MAX) |
| goto out_fail; |
| |
| res = sscanf(buf, "%x %x %"STRINGIFY(STR_PROCMAP_LINE_MAX)"s", &maps->pid, &maps->nr_lib_maps, mapname); |
| if (res != 3) |
| goto out_fail; |
| |
| ret = -ENOMEM; |
| maps->proc_name = strdup(mapname); |
| if (!maps->proc_name) |
| goto out_fail; |
| |
| maps->lib_maps = calloc(maps->nr_lib_maps, sizeof(struct tracecmd_proc_addr_map)); |
| if (!maps->lib_maps) |
| goto out_fail; |
| |
| buf = line + 1; |
| line = strchr(buf, '\n'); |
| for (i = 0; i < maps->nr_lib_maps; i++) { |
| if (!line) |
| break; |
| *line = '\0'; |
| if (strlen(buf) > STR_PROCMAP_LINE_MAX) |
| break; |
| res = sscanf(buf, "%llx %llx %s", &maps->lib_maps[i].start, |
| &maps->lib_maps[i].end, mapname); |
| if (res != 3) |
| break; |
| maps->lib_maps[i].lib_name = strdup(mapname); |
| if (!maps->lib_maps[i].lib_name) |
| goto out_fail; |
| buf = line + 1; |
| line = strchr(buf, '\n'); |
| } |
| |
| ret = -EINVAL; |
| if (i != maps->nr_lib_maps) |
| goto out_fail; |
| |
| qsort(maps->lib_maps, maps->nr_lib_maps, |
| sizeof(*maps->lib_maps), trace_pid_map_cmp); |
| |
| maps->next = handle->pid_maps; |
| handle->pid_maps = maps; |
| |
| return 0; |
| |
| out_fail: |
| procmap_free(maps); |
| return ret; |
| } |
| |
| static void trace_pid_map_free(struct pid_addr_maps *maps) |
| { |
| struct pid_addr_maps *del; |
| |
| while (maps) { |
| del = maps; |
| maps = maps->next; |
| procmap_free(del); |
| } |
| } |
| |
| static int trace_pid_map_search(const void *a, const void *b) |
| { |
| struct tracecmd_proc_addr_map *key = (struct tracecmd_proc_addr_map *)a; |
| struct tracecmd_proc_addr_map *map = (struct tracecmd_proc_addr_map *)b; |
| |
| if (key->start >= map->end) |
| return 1; |
| if (key->start < map->start) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * tracecmd_search_task_map - Search task memory address map |
| * @handle: input handle to the trace.dat file |
| * @pid: pid of the task |
| * @addr: address from the task memory space. |
| * |
| * Map of the task memory can be saved in the trace.dat file, using the option |
| * "--proc-map". If there is such information, this API can be used to look up |
| * into this memory map to find what library is loaded at the given @addr. |
| * |
| * A pointer to struct tracecmd_proc_addr_map is returned, containing the name |
| * of the library at given task @addr and the library start and end addresses. |
| */ |
| struct tracecmd_proc_addr_map * |
| tracecmd_search_task_map(struct tracecmd_input *handle, |
| int pid, unsigned long long addr) |
| { |
| struct tracecmd_proc_addr_map *lib; |
| struct tracecmd_proc_addr_map key; |
| struct pid_addr_maps *maps; |
| |
| if (!handle || !handle->pid_maps) |
| return NULL; |
| |
| maps = handle->pid_maps; |
| while (maps) { |
| if (maps->pid == pid) |
| break; |
| maps = maps->next; |
| } |
| if (!maps || !maps->nr_lib_maps || !maps->lib_maps) |
| return NULL; |
| key.start = addr; |
| lib = bsearch(&key, maps->lib_maps, maps->nr_lib_maps, |
| sizeof(*maps->lib_maps), trace_pid_map_search); |
| |
| return lib; |
| } |
| |
| __hidden unsigned int get_meta_strings_size(struct tracecmd_input *handle) |
| { |
| return handle->strings_size; |
| } |
| |
| __hidden unsigned long long get_last_option_offset(struct tracecmd_input *handle) |
| { |
| return handle->options_last_offset; |
| } |
| |
| static int handle_option_done(struct tracecmd_input *handle, char *buf, int size) |
| { |
| unsigned long long offset; |
| |
| if (size < 8) |
| return -1; |
| |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| if (offset >= size) |
| handle->options_last_offset = offset - size; |
| |
| offset = tep_read_number(handle->pevent, buf, 8); |
| if (!offset) |
| return 0; |
| |
| if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| |
| return handle_options(handle); |
| } |
| |
| static inline int save_read_number(struct tep_handle *tep, char *data, int *data_size, |
| int *read_pos, int bytes, unsigned long long *num) |
| { |
| if (bytes > *data_size) |
| return -1; |
| |
| *num = tep_read_number(tep, (data + *read_pos), bytes); |
| *read_pos += bytes; |
| *data_size -= bytes; |
| return 0; |
| } |
| |
| static inline char *save_read_string(char *data, int *data_size, int *read_pos) |
| { |
| char *str; |
| |
| if (*data_size < 1) |
| return NULL; |
| |
| str = strdup(data + *read_pos); |
| if (!str) |
| return NULL; |
| *data_size -= (strlen(str) + 1); |
| if (*data_size < 0) { |
| free(str); |
| return NULL; |
| } |
| *read_pos += (strlen(str) + 1); |
| |
| return str; |
| } |
| |
| static int handle_buffer_option(struct tracecmd_input *handle, |
| unsigned short id, char *data, int size) |
| { |
| struct input_buffer_instance *buff; |
| struct cpu_file_data *cpu_data; |
| unsigned long long tmp; |
| long long max_cpu = -1; |
| int rsize = 0; |
| char *name; |
| int i; |
| |
| if (save_read_number(handle->pevent, data, &size, &rsize, 8, &tmp)) |
| return -1; |
| |
| name = save_read_string(data, &size, &rsize); |
| if (!name) |
| return -1; |
| |
| if (*name == '\0') { |
| /* top buffer */ |
| buff = &handle->top_buffer; |
| } else { |
| buff = realloc(handle->buffers, sizeof(*handle->buffers) * (handle->nr_buffers + 1)); |
| if (!buff) { |
| free(name); |
| return -1; |
| } |
| handle->buffers = buff; |
| handle->nr_buffers++; |
| |
| buff = &handle->buffers[handle->nr_buffers - 1]; |
| } |
| memset(buff, 0, sizeof(struct input_buffer_instance)); |
| buff->name = name; |
| buff->offset = tmp; |
| |
| if (!HAS_SECTIONS(handle)) |
| return 0; |
| |
| /* file sections specific data */ |
| buff->clock = save_read_string(data, &size, &rsize); |
| if (!buff->clock) |
| return -1; |
| |
| if (*name == '\0' && !handle->trace_clock) |
| handle->trace_clock = strdup(buff->clock); |
| |
| if (id == TRACECMD_OPTION_BUFFER) { |
| if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) |
| return -1; |
| buff->page_size = tmp; |
| |
| if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) |
| return -1; |
| buff->cpus = tmp; |
| if (!buff->cpus) |
| return 0; |
| cpu_data = calloc(buff->cpus, sizeof(*cpu_data)); |
| if (!cpu_data) |
| return -1; |
| for (i = 0; i < buff->cpus; i++) { |
| if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) |
| goto fail; |
| if ((long long)tmp > max_cpu) |
| max_cpu = tmp; |
| cpu_data[i].cpu = tmp; |
| if (save_read_number(handle->pevent, data, |
| &size, &rsize, 8, &cpu_data[i].offset)) |
| goto fail; |
| if (save_read_number(handle->pevent, data, |
| &size, &rsize, 8, &cpu_data[i].size)) |
| goto fail; |
| } |
| if (buff->cpus == max_cpu + 1) { |
| /* Check to make sure cpus match the index */ |
| for (i = 0; i < buff->cpus; i++) { |
| if (cpu_data[i].cpu != i) |
| goto copy_buffer; |
| } |
| buff->cpu_data = cpu_data; |
| } else { |
| copy_buffer: |
| buff->cpu_data = calloc(max_cpu + 1, sizeof(*cpu_data)); |
| if (!buff->cpu_data) |
| goto fail; |
| for (i = 0; i < buff->cpus; i++) { |
| if (buff->cpu_data[cpu_data[i].cpu].size) { |
| tracecmd_warning("More than one buffer defined for CPU %d (buffer %d)\n", |
| cpu_data[i].cpu, i); |
| goto fail; |
| } |
| buff->cpu_data[cpu_data[i].cpu] = cpu_data[i]; |
| } |
| buff->cpus = max_cpu + 1; |
| free(cpu_data); |
| } |
| } else { |
| buff->latency = true; |
| } |
| return 0; |
| fail: |
| free(cpu_data); |
| return -1; |
| } |
| |
| static int handle_options(struct tracecmd_input *handle) |
| { |
| long long offset; |
| unsigned short option; |
| unsigned int size; |
| unsigned short id, flags; |
| char *cpustats = NULL; |
| struct hook_list *hook; |
| bool compress = false; |
| char *buf; |
| int cpus; |
| int ret; |
| |
| if (!HAS_SECTIONS(handle)) { |
| handle->options_start = lseek64(handle->fd, 0, SEEK_CUR); |
| } else { |
| if (read_section_header(handle, &id, &flags, NULL, NULL)) |
| return -1; |
| if (id != TRACECMD_OPTION_DONE) |
| return -1; |
| if (flags & TRACECMD_SEC_FL_COMPRESS) |
| compress = true; |
| } |
| |
| if (compress && in_uncompress_block(handle)) |
| return -1; |
| |
| for (;;) { |
| ret = read2(handle, &option); |
| if (ret) |
| goto out; |
| |
| if (!HAS_SECTIONS(handle) && option == TRACECMD_OPTION_DONE) |
| break; |
| |
| /* next 4 bytes is the size of the option */ |
| ret = read4(handle, &size); |
| if (ret) |
| goto out; |
| buf = malloc(size); |
| if (!buf) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| ret = do_read_check(handle, buf, size); |
| if (ret) |
| goto out; |
| |
| 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 | TRACECMD_FL_RAW_TS)) |
| break; |
| offset = strtoll(buf, NULL, 0); |
| /* Convert from micro to nano */ |
| offset *= 1000; |
| handle->ts_offset += offset; |
| break; |
| case TRACECMD_OPTION_OFFSET: |
| /* |
| * Similar to date option, but just adds an |
| * offset to the timestamp. |
| */ |
| if (handle->flags & TRACECMD_FL_RAW_TS) |
| break; |
| offset = strtoll(buf, NULL, 0); |
| handle->ts_offset += offset; |
| break; |
| case TRACECMD_OPTION_TIME_SHIFT: |
| /* |
| * long long int (8 bytes) trace session ID |
| * int (4 bytes) protocol flags. |
| * int (4 bytes) CPU count. |
| * array of size [CPU count]: |
| * [ |
| * int (4 bytes) count of timestamp offsets. |
| * long long array of size [count] of times, |
| * when the offsets were calculated. |
| * long long array of size [count] of timestamp offsets. |
| * long long array of size [count] of timestamp scaling ratios.* |
| * ] |
| * array of size [CPU count]: |
| * [ |
| * long long array of size [count] of timestamp scaling fraction bits.* |
| * ]* |
| */ |
| if (size < 16 || (handle->flags & TRACECMD_FL_RAW_TS)) |
| break; |
| handle->host.peer_trace_id = tep_read_number(handle->pevent, |
| buf, 8); |
| handle->host.flags = tep_read_number(handle->pevent, |
| buf + 8, 4); |
| ret = tsync_cpu_offsets_load(handle, buf + 12, size - 12); |
| if (ret < 0) |
| goto out; |
| tracecmd_enable_tsync(handle, true); |
| break; |
| case TRACECMD_OPTION_CPUSTAT: |
| buf[size-1] = '\n'; |
| cpustats = realloc(handle->cpustats, |
| handle->cpustats_size + size + 1); |
| if (!cpustats) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| memcpy(cpustats + handle->cpustats_size, buf, size); |
| handle->cpustats_size += size; |
| cpustats[handle->cpustats_size] = 0; |
| handle->cpustats = cpustats; |
| break; |
| case TRACECMD_OPTION_BUFFER: |
| case TRACECMD_OPTION_BUFFER_TEXT: |
| ret = handle_buffer_option(handle, option, buf, size); |
| if (ret < 0) |
| goto out; |
| break; |
| case TRACECMD_OPTION_TRACECLOCK: |
| tracecmd_parse_trace_clock(handle, buf, size); |
| if (!handle->ts2secs) |
| handle->use_trace_clock = true; |
| break; |
| case TRACECMD_OPTION_UNAME: |
| handle->uname = strdup(buf); |
| break; |
| case TRACECMD_OPTION_VERSION: |
| handle->version = strdup(buf); |
| break; |
| case TRACECMD_OPTION_HOOK: |
| hook = tracecmd_create_event_hook(buf); |
| hook->next = handle->hooks; |
| handle->hooks = hook; |
| break; |
| case TRACECMD_OPTION_CPUCOUNT: |
| cpus = *(int *)buf; |
| handle->cpus = tep_read_number(handle->pevent, &cpus, 4); |
| if (handle->cpus > handle->max_cpu) |
| handle->max_cpu = handle->cpus; |
| tep_set_cpus(handle->pevent, handle->cpus); |
| break; |
| case TRACECMD_OPTION_PROCMAPS: |
| if (buf[size-1] == '\0') |
| trace_pid_map_load(handle, buf); |
| break; |
| case TRACECMD_OPTION_TRACEID: |
| if (size < 8) |
| break; |
| handle->trace_id = tep_read_number(handle->pevent, |
| buf, 8); |
| break; |
| case TRACECMD_OPTION_GUEST: |
| trace_guest_load(handle, buf, size); |
| break; |
| case TRACECMD_OPTION_TSC2NSEC: |
| if (size < 16 || (handle->flags & TRACECMD_FL_RAW_TS)) |
| break; |
| handle->tsc_calc.mult = tep_read_number(handle->pevent, |
| buf, 4); |
| handle->tsc_calc.shift = tep_read_number(handle->pevent, |
| buf + 4, 4); |
| handle->tsc_calc.offset = tep_read_number(handle->pevent, |
| buf + 8, 8); |
| break; |
| case TRACECMD_OPTION_HEADER_INFO: |
| case TRACECMD_OPTION_FTRACE_EVENTS: |
| case TRACECMD_OPTION_EVENT_FORMATS: |
| case TRACECMD_OPTION_KALLSYMS: |
| case TRACECMD_OPTION_PRINTK: |
| case TRACECMD_OPTION_CMDLINES: |
| if (size < 8) |
| break; |
| section_add_or_update(handle, option, -1, |
| tep_read_number(handle->pevent, buf, 8), 0); |
| break; |
| case TRACECMD_OPTION_DONE: |
| if (compress) |
| in_uncompress_reset(handle); |
| ret = handle_option_done(handle, buf, size); |
| free(buf); |
| return ret; |
| |
| default: |
| tracecmd_warning("unknown option %d", option); |
| break; |
| } |
| |
| free(buf); |
| |
| } |
| |
| ret = 0; |
| |
| out: |
| if (compress) |
| in_uncompress_reset(handle); |
| return ret; |
| } |
| |
| static int read_options_type(struct tracecmd_input *handle) |
| { |
| char buf[10]; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_CPU_LATENCY)) |
| return 0; |
| |
| 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; |
| handle->file_state = TRACECMD_FILE_OPTIONS; |
| if (do_read_check(handle, buf, 10)) |
| return -1; |
| } |
| |
| /* |
| * Check if this is a latency report or flyrecord. |
| */ |
| if (strncmp(buf, "latency", 7) == 0) |
| handle->file_state = TRACECMD_FILE_CPU_LATENCY; |
| else if (strncmp(buf, "flyrecord", 9) == 0) |
| handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; |
| else |
| return -1; |
| |
| return 0; |
| } |
| |
| int tracecmd_latency_data_read(struct tracecmd_input *handle, char **buf, size_t *size) |
| { |
| struct cpu_zdata *zdata = &handle->latz; |
| void *data; |
| int rsize; |
| int fd = -1; |
| int id; |
| |
| if (!handle || !buf || !size) |
| return -1; |
| if (handle->file_state != TRACECMD_FILE_CPU_LATENCY) |
| return -1; |
| |
| if (!handle->cpu_compressed) { |
| fd = handle->fd; |
| } else if (!handle->read_zpage) { |
| if (zdata->fd < 0) |
| return -1; |
| fd = zdata->fd; |
| } |
| |
| /* Read data from a file */ |
| if (fd >= 0) { |
| if (!(*buf)) { |
| *size = BUFSIZ; |
| *buf = malloc(*size); |
| if (!(*buf)) |
| return -1; |
| } |
| return do_read_fd(fd, *buf, *size); |
| } |
| |
| /* Uncompress data in memory */ |
| if (zdata->last_chunk >= zdata->count) |
| return 0; |
| |
| id = zdata->last_chunk; |
| if (!*buf || *size < zdata->chunks[id].size) { |
| data = realloc(*buf, zdata->chunks[id].size); |
| if (!data) |
| return -1; |
| *buf = data; |
| *size = zdata->chunks[id].size; |
| } |
| |
| if (tracecmd_uncompress_chunk(handle->compress, &zdata->chunks[id], *buf)) |
| return -1; |
| |
| rsize = zdata->chunks[id].size; |
| zdata->last_chunk++; |
| return rsize; |
| } |
| |
| static int init_cpu_data(struct tracecmd_input *handle) |
| { |
| enum kbuffer_long_size long_size; |
| enum kbuffer_endian endian; |
| unsigned long long max_size = 0; |
| unsigned long long pages; |
| int cpu; |
| |
| /* We expect this to be flyrecord */ |
| if (handle->file_state != TRACECMD_FILE_CPU_FLYRECORD) |
| return -1; |
| |
| if (force_read) |
| handle->read_page = true; |
| |
| if (handle->long_size == 8) |
| long_size = KBUFFER_LSIZE_8; |
| else |
| long_size = KBUFFER_LSIZE_4; |
| |
| if (tep_is_file_bigendian(handle->pevent)) |
| endian = KBUFFER_ENDIAN_BIG; |
| else |
| endian = KBUFFER_ENDIAN_LITTLE; |
| |
| for (cpu = 0; cpu < handle->cpus; cpu++) { |
| handle->cpu_data[cpu].compress.fd = -1; |
| handle->cpu_data[cpu].kbuf = kbuffer_alloc(long_size, endian); |
| if (!handle->cpu_data[cpu].kbuf) |
| goto out_free; |
| if (tep_is_old_format(handle->pevent)) |
| kbuffer_set_old_format(handle->cpu_data[cpu].kbuf); |
| |
| if (handle->cpu_data[cpu].file_size > max_size) |
| max_size = handle->cpu_data[cpu].file_size; |
| } |
| |
| /* Calculate about a meg of pages for buffering */ |
| pages = handle->page_size ? max_size / handle->page_size : 0; |
| if (!pages) |
| pages = 1; |
| pages = normalize_size(pages); |
| handle->page_map_size = handle->page_size * pages; |
| if (handle->page_map_size < handle->page_size) |
| handle->page_map_size = handle->page_size; |
| |
| |
| for (cpu = 0; cpu < handle->cpus; cpu++) { |
| 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; |
| } |
| |
| int init_latency_data(struct tracecmd_input *handle) |
| { |
| unsigned long long wsize; |
| int ret; |
| |
| if (!handle->cpu_compressed) |
| return 0; |
| |
| if (handle->read_zpage) { |
| handle->latz.count = tracecmd_load_chunks_info(handle->compress, &handle->latz.chunks); |
| if (handle->latz.count < 0) |
| return -1; |
| } else { |
| strcpy(handle->latz.file, COMPR_TEMP_FILE); |
| handle->latz.fd = mkstemp(handle->latz.file); |
| if (handle->latz.fd < 0) |
| return -1; |
| |
| ret = tracecmd_uncompress_copy_to(handle->compress, handle->latz.fd, NULL, &wsize); |
| if (ret) |
| return -1; |
| |
| lseek64(handle->latz.fd, 0, SEEK_SET); |
| } |
| |
| return 0; |
| } |
| |
| static int init_buffer_cpu_data(struct tracecmd_input *handle, struct input_buffer_instance *buffer) |
| { |
| unsigned long long offset; |
| unsigned long long size; |
| unsigned short id, flags; |
| int cpu; |
| |
| if (handle->cpu_data) |
| return -1; |
| |
| if (lseek64(handle->fd, buffer->offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| if (read_section_header(handle, &id, &flags, NULL, NULL)) |
| return -1; |
| if (flags & TRACECMD_SEC_FL_COMPRESS) |
| handle->cpu_compressed = true; |
| if (buffer->latency) { |
| handle->file_state = TRACECMD_FILE_CPU_LATENCY; |
| return init_latency_data(handle) == 0 ? 1 : -1; |
| } |
| handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; |
| handle->cpus = buffer->cpus; |
| if (handle->max_cpu < handle->cpus) |
| handle->max_cpu = handle->cpus; |
| |
| handle->cpu_data = calloc(handle->cpus, sizeof(*handle->cpu_data)); |
| if (!handle->cpu_data) |
| return -1; |
| |
| for (cpu = 0; cpu < handle->cpus; cpu++) { |
| handle->cpu_data[cpu].cpu = buffer->cpu_data[cpu].cpu; |
| offset = buffer->cpu_data[cpu].offset; |
| size = buffer->cpu_data[cpu].size; |
| 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; |
| return -1; |
| } |
| } |
| |
| return init_cpu_data(handle); |
| } |
| |
| static int read_cpu_data(struct tracecmd_input *handle) |
| { |
| unsigned long long size; |
| int cpus; |
| int cpu; |
| |
| /* |
| * Check if this is a latency report or not. |
| */ |
| if (handle->file_state == TRACECMD_FILE_CPU_LATENCY) |
| return 1; |
| |
| /* We expect this to be flyrecord */ |
| if (handle->file_state != TRACECMD_FILE_CPU_FLYRECORD) |
| return -1; |
| |
| cpus = handle->cpus; |
| |
| 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); |
| |
| for (cpu = 0; cpu < handle->cpus; cpu++) { |
| unsigned long long offset; |
| |
| handle->cpu_data[cpu].cpu = cpu; |
| read8(handle, &offset); |
| read8(handle, &size); |
| 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; |
| return -1; |
| } |
| } |
| |
| /* |
| * It is possible that an option changed the number of CPUs. |
| * If that happened, then there's "empty" cpu data saved for |
| * backward compatibility. |
| */ |
| if (cpus < handle->cpus) { |
| unsigned long long ignore; |
| int once = 0; |
| |
| read8(handle, &ignore); /* offset */ |
| read8(handle, &ignore); /* size */ |
| if (ignore != 0) { |
| if (!once) { |
| tracecmd_warning("ignored CPU data not zero size"); |
| once++; |
| } |
| } |
| } |
| |
| return init_cpu_data(handle); |
| } |
| |
| static int read_data_and_size(struct tracecmd_input *handle, |
| char **data, unsigned long long *size) |
| { |
| if (read8(handle, 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 tep_handle *pevent = handle->pevent; |
| unsigned long long size; |
| char *cmdlines; |
| |
| if (CHECK_READ_STATE(handle, TRACECMD_FILE_CMD_LINES)) |
| return 0; |
| |
| if (!HAS_SECTIONS(handle)) |
| section_add_or_update(handle, TRACECMD_OPTION_CMDLINES, 0, 0, |
| lseek64(handle->fd, 0, SEEK_CUR)); |
| |
| |
| if (read_data_and_size(handle, &cmdlines, &size) < 0) |
| return -1; |
| cmdlines[size] = 0; |
| tep_parse_saved_cmdlines(pevent, cmdlines); |
| free(cmdlines); |
| |
| handle->file_state = TRACECMD_FILE_CMD_LINES; |
| |
| return 0; |
| } |
| |
| static void extract_trace_clock(struct tracecmd_input *handle, char *line) |
| { |
| char *clock = NULL; |
| char *next = NULL; |
| char *data; |
| |
| data = strtok_r(line, "[]", &next); |
| sscanf(data, "%ms", &clock); |
| /* TODO: report if it fails to allocate */ |
| handle->trace_clock = clock; |
| |
| if (!clock) |
| return; |
| |
| /* Clear usecs if raw timestamps are requested */ |
| if (handle->flags & TRACECMD_FL_RAW_TS) |
| handle->flags &= ~TRACECMD_FL_IN_USECS; |
| |
| /* Clear usecs if not one of the specified clocks */ |
| if (strcmp(clock, "local") && strcmp(clock, "global") && |
| strcmp(clock, "uptime") && strcmp(clock, "perf") && |
| strncmp(clock, "mono", 4) && strcmp(clock, TSCNSEC_CLOCK) && |
| strcmp(clock, "tai")) |
| handle->flags &= ~TRACECMD_FL_IN_USECS; |
| } |
| |
| void tracecmd_parse_trace_clock(struct tracecmd_input *handle, |
| char *file, int size __maybe_unused) |
| { |
| char *line; |
| char *next = NULL; |
| |
| line = strtok_r(file, " ", &next); |
| while (line) { |
| /* current trace_clock is shown as "[local]". */ |
| if (*line == '[') |
| return extract_trace_clock(handle, line); |
| line = strtok_r(NULL, " ", &next); |
| } |
| } |
| |
| static int read_and_parse_trace_clock(struct tracecmd_input *handle, |
| struct tep_handle *pevent) |
| { |
| unsigned long long size; |
| char *trace_clock; |
| |
| if (read_data_and_size(handle, &trace_clock, &size) < 0) |
| return -1; |
| trace_clock[size] = 0; |
| tracecmd_parse_trace_clock(handle, trace_clock, size); |
| free(trace_clock); |
| return 0; |
| } |
| |
| static int init_data_v6(struct tracecmd_input *handle) |
| { |
| struct tep_handle *pevent = handle->pevent; |
| int ret; |
| |
| 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]"; |
| tracecmd_warning("File has trace_clock bug, using local clock"); |
| tracecmd_parse_trace_clock(handle, clock, 8); |
| } |
| } |
| return ret; |
| } |
| |
| static int init_data(struct tracecmd_input *handle) |
| { |
| return init_buffer_cpu_data(handle, &handle->top_buffer); |
| } |
| |
| /** |
| * 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) |
| { |
| int ret; |
| |
| if (!HAS_SECTIONS(handle)) |
| ret = init_data_v6(handle); |
| else |
| ret = init_data(handle); |
| 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 (tep_is_file_bigendian(handle->pevent)) |
| 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 (tep_is_old_format(handle->pevent)) |
| 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) |
| { |
| if (!regex) |
| regex = ".*"; |
| |
| if (!HAS_SECTIONS(handle)) |
| read_headers_v6(handle, TRACECMD_FILE_ALL_EVENTS, regex); |
| |
| read_headers(handle, regex); |
| } |
| |
| /* 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_print_uname - prints the recorded uname if it was recorded |
| * @handle: input handle for the trace.dat file |
| * |
| * Looks for the option TRACECMD_OPTION_VERSION and prints out what's |
| * stored there, if it is found. Otherwise it prints that none were found. |
| */ |
| void tracecmd_print_version(struct tracecmd_input *handle) |
| { |
| if (handle->version) |
| printf("%s\n", handle->version); |
| else |
| printf(" version 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; |
| } |
| |
| static int init_metadata_strings(struct tracecmd_input *handle, int size) |
| { |
| char *tmp; |
| |
| tmp = realloc(handle->strings, handle->strings_size + size); |
| if (!tmp) |
| return -1; |
| |
| handle->strings = tmp; |
| if (do_read_check(handle, handle->strings + handle->strings_size, size)) |
| return -1; |
| |
| handle->strings_size += size; |
| |
| return 0; |
| } |
| |
| static int read_metadata_strings(struct tracecmd_input *handle) |
| { |
| unsigned short flags; |
| int found = 0; |
| unsigned short id; |
| unsigned int csize, rsize; |
| unsigned long long size; |
| off64_t offset; |
| |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| do { |
| if (read_section_header(handle, &id, &flags, &size, NULL)) |
| break; |
| if (id == TRACECMD_OPTION_STRINGS) { |
| found++; |
| if ((flags & TRACECMD_SEC_FL_COMPRESS)) { |
| read4(handle, &csize); |
| read4(handle, &rsize); |
| do_lseek(handle, -8, SEEK_CUR); |
| if (in_uncompress_block(handle)) |
| break; |
| } else { |
| rsize = size; |
| } |
| init_metadata_strings(handle, rsize); |
| if (flags & TRACECMD_SEC_FL_COMPRESS) |
| in_uncompress_reset(handle); |
| } else { |
| if (lseek64(handle->fd, size, SEEK_CUR) == (off_t)-1) |
| break; |
| } |
| } while (1); |
| |
| if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) |
| return -1; |
| |
| return found ? 0 : -1; |
| } |
| |
| /** |
| * tracecmd_alloc_fd - create a tracecmd_input handle from a file descriptor |
| * @fd: the file descriptor for the trace.dat file |
| * @flags: bitmask of enum tracecmd_open_flags |
| * |
| * 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, int flags) |
| { |
| struct tracecmd_input *handle; |
| char test[] = TRACECMD_MAGIC; |
| unsigned int page_size; |
| size_t offset; |
| char *version = NULL; |
| char *zver = NULL; |
| char *zname = NULL; |
| char buf[BUFSIZ]; |
| unsigned long ver; |
| |
| handle = malloc(sizeof(*handle)); |
| if (!handle) |
| return NULL; |
| memset(handle, 0, sizeof(*handle)); |
| |
| handle->fd = fd; |
| handle->ref = 1; |
| handle->latz.fd = -1; |
| /* By default, use usecs, unless told otherwise */ |
| handle->flags |= TRACECMD_FL_IN_USECS; |
| |
| #ifdef INMEMORY_DECOMPRESS |
| handle->read_zpage = 1; |
| #endif |
| 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; |
| tracecmd_info("version = %s", version); |
| ver = strtol(version, NULL, 10); |
| if (!ver && errno) |
| goto failed_read; |
| if (!tracecmd_is_version_supported(ver)) { |
| tracecmd_warning("Unsupported file version %lu", ver); |
| goto failed_read; |
| } |
| handle->file_version = ver; |
| free(version); |
| version = NULL; |
| |
| if (handle->file_version >= FILE_VERSION_SECTIONS) |
| handle->flags |= TRACECMD_FL_SECTIONED; |
| if (handle->file_version >= FILE_VERSION_COMPRESSION) |
| handle->flags |= TRACECMD_FL_COMPRESSION; |
| |
| if (do_read_check(handle, buf, 1)) |
| goto failed_read; |
| |
| handle->pevent = tep_alloc(); |
| if (!handle->pevent) |
| goto failed_read; |
| |
| /* register default ftrace functions first */ |
| if (!(flags & TRACECMD_FL_LOAD_NO_PLUGINS) && |
| !(flags & TRACECMD_FL_LOAD_NO_SYSTEM_PLUGINS)) |
| tracecmd_ftrace_overrides(handle, &handle->finfo); |
| |
| handle->plugin_list = trace_load_plugins(handle->pevent, flags); |
| |
| tep_set_file_bigendian(handle->pevent, buf[0]); |
| tep_set_local_bigendian(handle->pevent, tracecmd_host_bigendian()); |
| |
| do_read_check(handle, buf, 1); |
| handle->long_size = buf[0]; |
| tep_set_long_size(handle->pevent, handle->long_size); |
| |
| read4(handle, &page_size); |
| handle->page_size = page_size; |
| handle->next_offset = page_size; |
| |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| handle->total_file_size = lseek64(handle->fd, 0, SEEK_END); |
| lseek64(handle->fd, offset, SEEK_SET); |
| |
| if (HAS_COMPRESSION(handle)) { |
| zname = read_string(handle); |
| if (!zname) |
| goto failed_read; |
| |
| zver = read_string(handle); |
| if (!zver) |
| goto failed_read; |
| |
| if (strcmp(zname, "none") == 0) { |
| handle->read_zpage = false; |
| handle->flags &= ~TRACECMD_FL_COMPRESSION; |
| } else { |
| handle->compress = tracecmd_compress_alloc(zname, zver, |
| handle->fd, |
| handle->pevent, NULL); |
| if (!handle->compress) { |
| tracecmd_warning("Unsupported file compression %s %s", zname, zver); |
| goto failed_read; |
| } |
| } |
| |
| free(zname); |
| free(zver); |
| } |
| |
| if (HAS_SECTIONS(handle)) { |
| if (read8(handle, &(handle->options_start))) { |
| tracecmd_warning("Filed to read the offset of the first option section"); |
| goto failed_read; |
| } |
| read_metadata_strings(handle); |
| } |
| |
| handle->file_state = TRACECMD_FILE_INIT; |
| |
| return handle; |
| |
| failed_read: |
| free(version); |
| free(zname); |
| free(zver); |
| 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. |
| * @flags: bitmask of enum tracecmd_open_flags |
| * |
| * 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 flags) |
| { |
| int fd; |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| return NULL; |
| |
| return tracecmd_alloc_fd(fd, flags); |
| } |
| |
| /** |
| * tracecmd_open_fd - create a tracecmd_handle from the trace.dat file descriptor |
| * @fd: the file descriptor for the trace.dat file |
| * @flags: bitmask of enum tracecmd_open_flags |
| */ |
| struct tracecmd_input *tracecmd_open_fd(int fd, int flags) |
| { |
| struct tracecmd_input *handle; |
| int ret; |
| |
| handle = tracecmd_alloc_fd(fd, flags); |
| if (!handle) |
| return NULL; |
| |
| if (tracecmd_read_headers(handle, 0) < 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. |
| * @flags: bitmask of enum tracecmd_open_flags |
| */ |
| struct tracecmd_input *tracecmd_open(const char *file, int flags) |
| { |
| int fd; |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| return NULL; |
| |
| return tracecmd_open_fd(fd, flags); |
| } |
| |
| /** |
| * tracecmd_open_head - create a tracecmd_handle from a given file, read |
| * and parse only the trace headers from the file |
| * @file: the file name of the file that is of tracecmd data type. |
| * @flags: bitmask of enum tracecmd_open_flags |
| */ |
| struct tracecmd_input *tracecmd_open_head(const char *file, int flags) |
| { |
| struct tracecmd_input *handle; |
| int fd; |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| return NULL; |
| |
| handle = tracecmd_alloc_fd(fd, flags); |
| if (!handle) |
| return NULL; |
| |
| if (tracecmd_read_headers(handle, 0) < 0) |
| goto fail; |
| |
| return handle; |
| |
| fail: |
| tracecmd_close(handle); |
| return NULL; |
| } |
| |
| /** |
| * 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++; |
| } |
| |
| static inline void free_buffer(struct input_buffer_instance *buf) |
| { |
| free(buf->name); |
| free(buf->clock); |
| free(buf->cpu_data); |
| } |
| |
| /** |
| * 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) |
| { |
| struct zchunk_cache *cache; |
| struct file_section *del_sec; |
| struct cpu_data *cpu_data; |
| struct page_map *page_map, *n; |
| int cpu; |
| int i; |
| |
| if (!handle) |
| return; |
| |
| if (handle->ref <= 0) { |
| tracecmd_warning("tracecmd: bad ref count on handle"); |
| 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) { |
| cpu_data = &handle->cpu_data[cpu]; |
| if (cpu_data->kbuf) { |
| kbuffer_free(cpu_data->kbuf); |
| if (cpu_data->page_map) |
| free_page_map(cpu_data->page_map); |
| |
| if (cpu_data->page_cnt) |
| tracecmd_warning("%d pages still allocated on cpu %d%s", |
| cpu_data->page_cnt, cpu, |
| show_records(cpu_data->pages, |
| cpu_data->nr_pages)); |
| free(cpu_data->pages); |
| } |
| if (cpu_data->compress.fd >= 0) { |
| close(cpu_data->compress.fd); |
| unlink(cpu_data->compress.file); |
| } |
| while (!list_empty(&cpu_data->compress.cache)) { |
| cache = container_of(cpu_data->compress.cache.next, |
| struct zchunk_cache, list); |
| list_del(&cache->list); |
| free(cache->map); |
| free(cache); |
| } |
| free(cpu_data->compress.chunks); |
| list_for_each_entry_safe(page_map, n, &cpu_data->page_maps, list) { |
| list_del(&page_map->list); |
| free(page_map); |
| } |
| } |
| } |
| |
| free(handle->cpustats); |
| free(handle->cpu_data); |
| free(handle->uname); |
| free(handle->trace_clock); |
| free(handle->strings); |
| free(handle->version); |
| close(handle->fd); |
| free(handle->latz.chunks); |
| if (handle->latz.fd >= 0) { |
| close(handle->latz.fd); |
| unlink(handle->latz.file); |
| } |
| while (handle->sections) { |
| del_sec = handle->sections; |
| handle->sections = handle->sections->next; |
| free(del_sec); |
| } |
| |
| free_buffer(&handle->top_buffer); |
| for (i = 0; i < handle->nr_buffers; i++) |
| free_buffer(&handle->buffers[i]); |
| free(handle->buffers); |
| |
| tracecmd_free_hooks(handle->hooks); |
| handle->hooks = NULL; |
| |
| trace_pid_map_free(handle->pid_maps); |
| handle->pid_maps = NULL; |
| |
| trace_tsync_offset_free(&handle->host); |
| trace_guests_free(handle); |
| |
| if (handle->flags & TRACECMD_FL_BUFFER_INSTANCE) |
| tracecmd_close(handle->parent); |
| else { |
| /* Only main handle frees plugins, pevent and compression context */ |
| tracecmd_compress_destroy(handle->compress); |
| tep_unload_plugins(handle->plugin_list, handle->pevent); |
| tep_free(handle->pevent); |
| } |
| free(handle); |
| } |
| |
| static int read_copy_size8(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle, unsigned long long *size) |
| { |
| /* read size */ |
| if (do_read_check(in_handle, size, 8)) |
| return -1; |
| |
| if (do_write_check(out_handle, size, 8)) |
| return -1; |
| |
| *size = tep_read_number(in_handle->pevent, size, 8); |
| return 0; |
| } |
| |
| static int read_copy_size4(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle, |
| unsigned int *size) |
| { |
| /* read size */ |
| if (do_read_check(in_handle, size, 4)) |
| return -1; |
| |
| if (do_write_check(out_handle, size, 4)) |
| return -1; |
| |
| *size = tep_read_number(in_handle->pevent, size, 4); |
| return 0; |
| } |
| |
| static int read_copy_data(struct tracecmd_input *in_handle, |
| unsigned long long size, |
| struct tracecmd_output *out_handle) |
| { |
| char *buf; |
| |
| buf = malloc(size); |
| if (!buf) |
| return -1; |
| if (do_read_check(in_handle, buf, size)) |
| goto failed_read; |
| |
| if (do_write_check(out_handle, buf, size)) |
| goto failed_read; |
| |
| free(buf); |
| |
| return 0; |
| |
| failed_read: |
| free(buf); |
| return -1; |
| } |
| |
| |
| static bool check_in_state(struct tracecmd_input *handle, int new_state) |
| { |
| return check_file_state(handle->file_version, handle->file_state, new_state); |
| } |
| |
| static int copy_header_files(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned long long size; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_HEADERS) || |
| !check_out_state(out_handle, TRACECMD_FILE_HEADERS)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_HEADER_INFO); |
| if (!sec) |
| return -1; |
| |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_HEADER_INFO, |
| "headers", TRACECMD_SEC_FL_COMPRESS, true); |
| out_compression_start(out_handle, compress); |
| |
| /* "header_page" */ |
| if (read_copy_data(in_handle, 12, out_handle) < 0) |
| goto error; |
| |
| if (read_copy_size8(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| |
| /* "header_event" */ |
| if (read_copy_data(in_handle, 13, out_handle) < 0) |
| goto error; |
| |
| if (read_copy_size8(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| |
| in_handle->file_state = TRACECMD_FILE_HEADERS; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_ftrace_files(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned long long size; |
| unsigned int count; |
| unsigned int i; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_FTRACE_EVENTS) || |
| !check_out_state(out_handle, TRACECMD_FILE_FTRACE_EVENTS)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_FTRACE_EVENTS); |
| if (!sec) |
| return -1; |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_FTRACE_EVENTS, |
| "ftrace events", TRACECMD_SEC_FL_COMPRESS, true); |
| |
| out_compression_start(out_handle, compress); |
| |
| if (read_copy_size4(in_handle, out_handle, &count) < 0) |
| goto error; |
| |
| for (i = 0; i < count; i++) { |
| |
| if (read_copy_size8(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| } |
| |
| in_handle->file_state = TRACECMD_FILE_FTRACE_EVENTS; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_event_files(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned long long size; |
| char *system; |
| unsigned int systems; |
| unsigned int count; |
| unsigned int i,x; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_ALL_EVENTS) || |
| !check_out_state(out_handle, TRACECMD_FILE_ALL_EVENTS)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_EVENT_FORMATS); |
| if (!sec) |
| return -1; |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_EVENT_FORMATS, |
| "events format", TRACECMD_SEC_FL_COMPRESS, true); |
| |
| out_compression_start(out_handle, compress); |
| |
| if (read_copy_size4(in_handle, out_handle, &systems) < 0) |
| goto error; |
| |
| for (i = 0; i < systems; i++) { |
| system = read_string(in_handle); |
| if (!system) |
| goto error; |
| if (do_write_check(out_handle, system, strlen(system) + 1)) { |
| free(system); |
| goto error; |
| } |
| free(system); |
| |
| if (read_copy_size4(in_handle, out_handle, &count) < 0) |
| goto error; |
| |
| for (x=0; x < count; x++) { |
| if (read_copy_size8(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| } |
| } |
| |
| in_handle->file_state = TRACECMD_FILE_ALL_EVENTS; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_proc_kallsyms(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned int size; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_KALLSYMS) || |
| !check_out_state(out_handle, TRACECMD_FILE_KALLSYMS)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_KALLSYMS); |
| if (!sec) |
| return -1; |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_KALLSYMS, |
| "kallsyms", TRACECMD_SEC_FL_COMPRESS, true); |
| |
| out_compression_start(out_handle, compress); |
| if (read_copy_size4(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (!size) |
| goto out; /* OK? */ |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| out: |
| in_handle->file_state = TRACECMD_FILE_KALLSYMS; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_ftrace_printk(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned int size; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_PRINTK) || |
| !check_out_state(out_handle, TRACECMD_FILE_PRINTK)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_PRINTK); |
| if (!sec) |
| return -1; |
| |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_PRINTK, |
| "printk", TRACECMD_SEC_FL_COMPRESS, true); |
| |
| out_compression_start(out_handle, compress); |
| |
| if (read_copy_size4(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (!size) |
| goto out; /* OK? */ |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| |
| out: |
| in_handle->file_state = TRACECMD_FILE_PRINTK; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_command_lines(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| bool compress = out_check_compression(out_handle); |
| struct file_section *sec; |
| unsigned long long offset; |
| unsigned long long size; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_CMD_LINES) || |
| !check_out_state(out_handle, TRACECMD_FILE_CMD_LINES)) |
| return -1; |
| |
| sec = section_open(in_handle, TRACECMD_OPTION_CMDLINES); |
| if (!sec) |
| return -1; |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_CMDLINES, |
| "command lines", TRACECMD_SEC_FL_COMPRESS, true); |
| |
| out_compression_start(out_handle, compress); |
| |
| if (read_copy_size8(in_handle, out_handle, &size) < 0) |
| goto error; |
| |
| if (!size) |
| goto out; /* OK? */ |
| |
| if (read_copy_data(in_handle, size, out_handle) < 0) |
| goto error; |
| |
| out: |
| in_handle->file_state = TRACECMD_FILE_CMD_LINES; |
| if (out_compression_end(out_handle, compress)) |
| goto error; |
| |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| section_close(in_handle, sec); |
| |
| if (out_update_section_header(out_handle, offset)) |
| goto error; |
| |
| return 0; |
| error: |
| out_compression_reset(out_handle, compress); |
| section_close(in_handle, sec); |
| return -1; |
| } |
| |
| static int copy_cpu_count(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| unsigned int cpus; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_CPU_COUNT) || |
| !check_out_state(out_handle, TRACECMD_FILE_CPU_COUNT)) |
| return -1; |
| |
| if (!HAS_SECTIONS(in_handle)) { |
| if (read4(in_handle, &cpus)) |
| return -1; |
| } else { |
| cpus = in_handle->max_cpu; |
| } |
| |
| if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { |
| cpus = tep_read_number(in_handle->pevent, &cpus, 4); |
| if (do_write_check(out_handle, &cpus, 4)) |
| return -1; |
| } else { |
| tracecmd_add_option(out_handle, TRACECMD_OPTION_CPUCOUNT, sizeof(int), &cpus); |
| } |
| |
| in_handle->file_state = TRACECMD_FILE_CPU_COUNT; |
| out_set_file_state(out_handle, in_handle->file_state); |
| |
| return 0; |
| } |
| |
| /** |
| * tracecmd_copy_headers - Copy headers from a tracecmd_input handle to a file descriptor |
| * @in_handle: input handle for the trace.dat file to copy from. |
| * @out_handle: output handle to the trace.dat file to copy to. |
| * @start_state: The file state to start copying from (zero for the beginnig) |
| * @end_state: The file state to stop at (zero for up to cmdlines) |
| * |
| * This is used to copy trace header data of a trace.dat file to a |
| * file descriptor. Using @start_state and @end_state it may be used |
| * multiple times against the input handle. |
| * |
| * NOTE: The input handle is also modified, and ends at the end |
| * state as well. |
| */ |
| int tracecmd_copy_headers(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle, |
| enum tracecmd_file_states start_state, |
| enum tracecmd_file_states end_state) |
| { |
| struct file_section *sec = NULL; |
| int ret; |
| |
| if (!start_state) |
| start_state = TRACECMD_FILE_HEADERS; |
| if (!end_state) |
| end_state = TRACECMD_FILE_CMD_LINES; |
| |
| if (start_state > end_state) |
| return -1; |
| |
| if (end_state < TRACECMD_FILE_HEADERS) |
| return 0; |
| |
| if (in_handle->file_state >= start_state) { |
| /* Set the handle to just before the start state */ |
| sec = section_open(in_handle, TRACECMD_OPTION_HEADER_INFO); |
| if (!sec) |
| return -1; |
| /* Now that the file handle has moved, change its state */ |
| in_handle->file_state = TRACECMD_FILE_INIT; |
| } |
| |
| /* Try to bring the input up to the start state - 1 */ |
| ret = tracecmd_read_headers(in_handle, start_state - 1); |
| if (sec) |
| section_close(in_handle, sec); |
| if (ret < 0) |
| goto out; |
| |
| switch (start_state) { |
| case TRACECMD_FILE_HEADERS: |
| ret = copy_header_files(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_FTRACE_EVENTS: |
| /* handle's state is now updating with the copies */ |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_ftrace_files(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_ALL_EVENTS: |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_event_files(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_KALLSYMS: |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_proc_kallsyms(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_PRINTK: |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_ftrace_printk(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_CMD_LINES: |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_command_lines(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| case TRACECMD_FILE_CPU_COUNT: |
| if (end_state <= in_handle->file_state) |
| return 0; |
| |
| ret = copy_cpu_count(in_handle, out_handle); |
| if (ret < 0) |
| goto out; |
| |
| /* fallthrough */ |
| default: |
| break; |
| } |
| |
| out: |
| return ret < 0 ? -1 : 0; |
| } |
| |
| int tracecmd_copy_buffer_descr(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| int i; |
| |
| if (tracecmd_get_out_file_version(out_handle) >= FILE_VERSION_SECTIONS) |
| return 0; |
| |
| for (i = 0; i < in_handle->nr_buffers; i++) |
| tracecmd_add_buffer_info(out_handle, in_handle->buffers[i].name, 0); |
| |
| return tracecmd_write_buffer_info(out_handle); |
| } |
| |
| static int copy_options_recursive(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| unsigned short id, flags = 0; |
| unsigned short option, en2; |
| unsigned long long next; |
| unsigned int size, en4; |
| bool skip; |
| |
| for (;;) { |
| if (do_read_check(in_handle, &option, 2)) |
| return -1; |
| |
| en2 = tep_read_number(in_handle->pevent, &option, 2); |
| |
| if (en2 == TRACECMD_OPTION_DONE && !HAS_SECTIONS(in_handle)) |
| return 0; |
| |
| /* next 4 bytes is the size of the option */ |
| if (do_read_check(in_handle, &size, 4)) |
| return -1; |
| |
| en4 = tep_read_number(in_handle->pevent, &size, 4); |
| if (en2 == TRACECMD_OPTION_DONE) { |
| /* option done v7 */ |
| if (en4 < 8) |
| return -1; |
| |
| if (read8(in_handle, &next)) |
| return -1; |
| |
| if (!next) |
| break; |
| |
| if (do_lseek(in_handle, next, SEEK_SET) == (off64_t)-1) |
| return -1; |
| |
| if (read_section_header(in_handle, &id, &flags, NULL, NULL)) |
| return -1; |
| |
| if (id != TRACECMD_OPTION_DONE) |
| return -1; |
| |
| if (flags & TRACECMD_SEC_FL_COMPRESS && in_uncompress_block(in_handle)) |
| return -1; |
| |
| return copy_options_recursive(in_handle, out_handle); |
| } |
| /* Do not copy these, as they have file specific offsets */ |
| switch (en2) { |
| case TRACECMD_OPTION_BUFFER: |
| case TRACECMD_OPTION_BUFFER_TEXT: |
| case TRACECMD_OPTION_HEADER_INFO: |
| case TRACECMD_OPTION_FTRACE_EVENTS: |
| case TRACECMD_OPTION_EVENT_FORMATS: |
| case TRACECMD_OPTION_KALLSYMS: |
| case TRACECMD_OPTION_PRINTK: |
| case TRACECMD_OPTION_CMDLINES: |
| skip = true; |
| break; |
| default: |
| skip = false; |
| break; |
| } |
| if (skip) { |
| do_lseek(in_handle, en4, SEEK_CUR); |
| continue; |
| } |
| if (do_write_check(out_handle, &option, 2)) |
| return -1; |
| |
| if (do_write_check(out_handle, &size, 4)) |
| return -1; |
| |
| if (read_copy_data(in_handle, en4, out_handle)) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int copy_options(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) |
| { |
| unsigned long long offset, start; |
| unsigned short id, en2, flags = 0; |
| int tmp; |
| |
| if (HAS_SECTIONS(in_handle)) { |
| if (read_section_header(in_handle, &id, &flags, NULL, NULL)) |
| return -1; |
| |
| if (id != TRACECMD_OPTION_DONE) |
| return -1; |
| |
| if (flags & TRACECMD_SEC_FL_COMPRESS && in_uncompress_block(in_handle)) |
| return -1; |
| } |
| start = tracecmd_get_out_file_offset(out_handle); |
| if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { |
| if (do_write_check(out_handle, "options ", 10)) |
| return -1; |
| } |
| |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_DONE, "options", 0, false); |
| |
| if (copy_options_recursive(in_handle, out_handle)) |
| goto error; |
| |
| id = TRACECMD_OPTION_DONE; |
| en2 = tep_read_number(in_handle->pevent, &id, 2); |
| if (do_write_check(out_handle, &en2, 2)) |
| goto error; |
| |
| if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { |
| out_save_options_offset(out_handle, start); |
| } else { |
| tmp = 8; |
| if (do_write_check(out_handle, &tmp, 4)) |
| goto error; |
| |
| out_save_options_offset(out_handle, start); |
| start = 0; |
| if (do_write_check(out_handle, &start, 8)) |
| goto error; |
| } |
| out_update_section_header(out_handle, offset); |
| if (flags & TRACECMD_SEC_FL_COMPRESS) |
| in_uncompress_reset(in_handle); |
| in_handle->file_state = TRACECMD_FILE_OPTIONS; |
| out_set_file_state(out_handle, in_handle->file_state); |
| /* Append local options */ |
| return tracecmd_append_options(out_handle); |
| |
| error: |
| if (flags & TRACECMD_SEC_FL_COMPRESS) |
| in_uncompress_reset(in_handle); |
| return 0; |
| } |
| |
| int tracecmd_copy_options(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| if (!check_in_state(in_handle, TRACECMD_FILE_OPTIONS) || |
| !check_out_state(out_handle, TRACECMD_FILE_OPTIONS)) |
| return -1; |
| |
| if (!in_handle->options_start) |
| return 0; |
| |
| if (lseek64(in_handle->fd, in_handle->options_start, SEEK_SET) == (off64_t)-1) |
| return -1; |
| |
| if (copy_options(in_handle, out_handle) < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int copy_trace_latency(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle, const char *buf_name) |
| { |
| int page_size = getpagesize(); |
| unsigned long long wsize; |
| unsigned long long offset; |
| int fd; |
| |
| if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS && |
| do_write_check(out_handle, "latency ", 10)) |
| return -1; |
| |
| offset = tracecmd_get_out_file_offset(out_handle); |
| |
| if (tracecmd_get_out_file_version(out_handle) >= FILE_VERSION_SECTIONS && |
| !out_add_buffer_option(out_handle, buf_name, TRACECMD_OPTION_BUFFER_TEXT, |
| offset, 0, NULL, page_size)) |
| return -1; |
| |
| offset = out_write_section_header(out_handle, TRACECMD_OPTION_BUFFER_TEXT, |
| "buffer latency", TRACECMD_SEC_FL_COMPRESS, false); |
| |
| if (in_handle->latz.fd >= 0) |
| fd = in_handle->latz.fd; |
| else |
| fd = in_handle->fd; |
| |
| if (!out_copy_fd_compress(out_handle, fd, 0, &wsize, page_size)) |
| return -1; |
| |
| if (out_update_section_header(out_handle, offset)) |
| return -1; |
| |
| out_set_file_state(out_handle, TRACECMD_FILE_CPU_LATENCY); |
| return 0; |
| } |
| |
| static int copy_trace_flyrecord_data(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle, const char *buff_name) |
| { |
| struct cpu_data_source *data; |
| int total_size = 0; |
| int cpus; |
| int ret; |
| int i, j; |
| |
| if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) |
| cpus = in_handle->max_cpu; |
| else |
| cpus = in_handle->cpus; |
| |
| data = calloc(cpus, sizeof(struct cpu_data_source)); |
| if (!data) |
| return -1; |
| |
| for (i = 0; i < in_handle->cpus; i++) { |
| j = in_handle->cpu_data[i].cpu; |
| data[j].size = in_handle->cpu_data[i].file_size; |
| total_size += data[j].size; |
| if (in_handle->cpu_data[i].compress.fd >= 0) { |
| data[j].fd = in_handle->cpu_data[i].compress.fd; |
| data[j].offset = 0; |
| } else { |
| data[j].fd = in_handle->fd; |
| data[j].offset = in_handle->cpu_data[i].file_offset; |
| } |
| } |
| if (total_size || tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) |
| ret = out_write_cpu_data(out_handle, cpus, data, buff_name); |
| else |
| ret = 0; |
| free(data); |
| |
| return ret; |
| } |
| |
| static int copy_flyrecord_buffer(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle, int index) |
| { |
| struct tracecmd_input *instance; |
| const char *name; |
| int ret; |
| |
| name = tracecmd_buffer_instance_name(in_handle, index); |
| if (!name) |
| return -1; |
| |
| instance = tracecmd_buffer_instance_handle(in_handle, index); |
| if (!instance) |
| return -1; |
| |
| if (!tracecmd_get_quiet(out_handle) && *name) |
| fprintf(stderr, "\nBuffer: %s\n\n", name); |
| |
| if (in_handle->buffers[index].latency) |
| ret = copy_trace_latency(in_handle, out_handle, name); |
| else |
| ret = copy_trace_flyrecord_data(instance, out_handle, name); |
| tracecmd_close(instance); |
| |
| return ret; |
| } |
| |
| static int copy_trace_data_from_v6(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| char buf[10]; |
| int ret; |
| int i; |
| |
| if (do_read_check(in_handle, buf, 10)) |
| return -1; |
| |
| if (strncmp(buf, "latency", 7) == 0) |
| in_handle->file_state = TRACECMD_FILE_CPU_LATENCY; |
| else if (strncmp(buf, "flyrecord", 9) == 0) |
| in_handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; |
| |
| tracecmd_init_data(in_handle); |
| tracecmd_set_out_clock(out_handle, in_handle->trace_clock); |
| |
| if (in_handle->file_state == TRACECMD_FILE_CPU_LATENCY) |
| return copy_trace_latency(in_handle, out_handle, ""); |
| |
| /* top instance */ |
| ret = copy_trace_flyrecord_data(in_handle, out_handle, ""); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < in_handle->nr_buffers; i++) |
| copy_flyrecord_buffer(in_handle, out_handle, i); |
| |
| return 0; |
| } |
| |
| static int copy_trace_data_from_v7(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| int ret = 0; |
| int i; |
| |
| /* Force using temporary files for trace data decompression */ |
| in_handle->read_zpage = false; |
| tracecmd_init_data(in_handle); |
| tracecmd_set_out_clock(out_handle, in_handle->trace_clock); |
| |
| /* copy top buffer */ |
| if (in_handle->top_buffer.latency) |
| ret = copy_trace_latency(in_handle, out_handle, in_handle->top_buffer.name); |
| else if (in_handle->top_buffer.cpus) |
| ret = copy_trace_flyrecord_data(in_handle, out_handle, |
| in_handle->top_buffer.name); |
| else if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) |
| ret = out_write_emty_cpu_data(out_handle, in_handle->max_cpu); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < in_handle->nr_buffers; i++) |
| copy_flyrecord_buffer(in_handle, out_handle, i); |
| |
| return 0; |
| } |
| |
| __hidden int tracecmd_copy_trace_data(struct tracecmd_input *in_handle, |
| struct tracecmd_output *out_handle) |
| { |
| int ret; |
| |
| if (!check_in_state(in_handle, TRACECMD_FILE_CPU_FLYRECORD) || |
| !check_out_state(out_handle, TRACECMD_FILE_CPU_FLYRECORD)) |
| return -1; |
| |
| if (in_handle->file_version < FILE_VERSION_SECTIONS) |
| ret = copy_trace_data_from_v6(in_handle, out_handle); |
| else |
| ret = copy_trace_data_from_v7(in_handle, out_handle); |
| |
| return ret; |
| } |
| |
| /** |
| * 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 tep_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 tep_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 tep_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 tep_record *record) |
| { |
| return handle->cpu_data[record->cpu].kbuf; |
| } |
| |
| void *tracecmd_record_page(struct tracecmd_input *handle, |
| struct tep_record *record) |
| { |
| struct page *page = record->priv; |
| |
| return page ? page->map : NULL; |
| } |
| |
| void *tracecmd_record_offset(struct tracecmd_input *handle, |
| struct tep_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; |
| memset(&new_handle->top_buffer, 0, sizeof(new_handle->top_buffer)); |
| new_handle->cpu_data = NULL; |
| new_handle->nr_buffers = 0; |
| new_handle->buffers = NULL; |
| new_handle->version = NULL; |
| new_handle->sections = NULL; |
| new_handle->strings = NULL; |
| new_handle->guest = NULL; |
| new_handle->ref = 1; |
| if (handle->trace_clock) { |
| new_handle->trace_clock = strdup(handle->trace_clock); |
| if (!new_handle->trace_clock) { |
| free(new_handle); |
| return NULL; |
| } |
| } |
| memset(&new_handle->host, 0, sizeof(new_handle->host)); |
| 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; |
| |
| new_handle->pid_maps = NULL; |
| if (!HAS_SECTIONS(handle)) { |
| /* Save where we currently are */ |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| |
| ret = lseek64(handle->fd, buffer->offset, SEEK_SET); |
| if (ret == (off64_t)-1) { |
| tracecmd_warning("could not seek to buffer %s offset %ld", |
| buffer->name, buffer->offset); |
| goto error; |
| } |
| /* |
| * read_options_type() is called right after the CPU count so update |
| * file state accordingly. |
| */ |
| new_handle->file_state = TRACECMD_FILE_CPU_COUNT; |
| ret = read_options_type(new_handle); |
| if (!ret) |
| ret = read_cpu_data(new_handle); |
| |
| if (ret < 0) { |
| tracecmd_warning("failed to read sub buffer %s", buffer->name); |
| goto error; |
| } |
| ret = lseek64(handle->fd, offset, SEEK_SET); |
| if (ret < 0) { |
| tracecmd_warning("could not seek to back to offset %ld", offset); |
| goto error; |
| } |
| } else { |
| new_handle->page_size = handle->buffers[indx].page_size; |
| if (init_buffer_cpu_data(new_handle, buffer) < 0) |
| goto error; |
| } |
| |
| return new_handle; |
| |
| error: |
| tracecmd_close(new_handle); |
| return NULL; |
| } |
| |
| 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->max_cpu; |
| } |
| |
| /** |
| * tracecmd_get_tep - return the tep handle |
| * @handle: input handle for the trace.dat file |
| */ |
| struct tep_handle *tracecmd_get_tep(struct tracecmd_input *handle) |
| { |
| return handle->pevent; |
| } |
| |
| /** |
| * tracecmd_get_in_file_version - return the trace.dat file version |
| * @handle: input handle for the trace.dat file |
| */ |
| unsigned long tracecmd_get_in_file_version(struct tracecmd_input *handle) |
| { |
| return handle->file_version; |
| } |
| |
| /** |
| * tracecmd_get_file_compress_proto - get name and version of compression algorithm |
| * @handle: input handle for the trace.dat file |
| * @name: return, name of the compression algorithm. |
| * @version: return, version of the compression algorithm. |
| * |
| * Get the name and the version of the compression algorithm, used to |
| * compress the file associated with @handle. |
| * Returns 0 on success, or -1 in case of an error. If 0 is returned, |
| * the name and version of the algorithm are stored in @name and @version. |
| * The returned strings must *not* be freed. |
| */ |
| int tracecmd_get_file_compress_proto(struct tracecmd_input *handle, |
| const char **name, const char **version) |
| { |
| return tracecmd_compress_proto_get_name(handle->compress, name, version); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * tracecmd_get_options_offset - get offset of the options sections in the file |
| * @handle: input handle for the trace.dat file |
| */ |
| size_t tracecmd_get_options_offset(struct tracecmd_input *handle) |
| { |
| return handle->options_start; |
| } |
| |
| /** |
| * tracecmd_get_trace_clock - return the saved trace clock |
| * @handle: input handle for the trace.dat file |
| * |
| * Returns a string of the clock that was saved in the trace.dat file. |
| * The string should not be freed, as it points to the internal |
| * structure data. |
| */ |
| const char *tracecmd_get_trace_clock(struct tracecmd_input *handle) |
| { |
| return handle->trace_clock; |
| } |
| |
| /** |
| * tracecmd_get_cpustats - return the saved cpu stats |
| * @handle: input handle for the trace.dat file |
| * |
| * Provides a method to extract the cpu stats saved in @handle. |
| * |
| * Returns a string of the cpu stats that was saved in the trace.dat file. |
| * The string should not be freed, as it points to the internal |
| * structure data. |
| */ |
| const char *tracecmd_get_cpustats(struct tracecmd_input *handle) |
| { |
| return handle->cpustats; |
| } |
| |
| /** |
| * tracecmd_get_uname - return the saved name and kernel information |
| * @handle: input handle for the trace.dat file |
| * |
| * Provides a method to extract the system information saved in @handle. |
| * |
| * Returns a string of the system information that was saved in the |
| * trace.dat file. |
| * The string should not be freed, as it points to the internal |
| * structure data. |
| */ |
| const char *tracecmd_get_uname(struct tracecmd_input *handle) |
| { |
| return handle->uname; |
| } |
| |
| /** |
| * tracecmd_get_version - return the saved version information |
| * @handle: input handle for the trace.dat file |
| * |
| * Provides a method to extract the version string saved in @handle. |
| * |
| * Returns a string of the version that was saved in the trace.dat file. |
| * The string should not be freed, as it points to the internal |
| * structure data. |
| */ |
| const char *tracecmd_get_version(struct tracecmd_input *handle) |
| { |
| return handle->version; |
| } |
| |
| /** |
| * tracecmd_get_cpu_file_size - return the saved cpu file size |
| * @handle: input handle for the trace.dat file |
| * @cpu: cpu index |
| * |
| * Provides a method to extract the cpu file size saved in @handle. |
| * |
| * Returns the cpu file size saved in trace.dat file or (off64_t)-1 for |
| * invalid cpu index. |
| */ |
| off64_t tracecmd_get_cpu_file_size(struct tracecmd_input *handle, int cpu) |
| { |
| if (cpu < 0 || cpu >= handle->cpus) |
| return (off64_t)-1; |
| return handle->cpu_data[cpu].file_size; |
| } |
| |
| /** |
| * tracecmd_get_show_data_func - return the show data func |
| * @handle: input handle for the trace.dat file |
| */ |
| tracecmd_show_data_func |
| tracecmd_get_show_data_func(struct tracecmd_input *handle) |
| { |
| return handle->show_data_func; |
| } |
| |
| /** |
| * tracecmd_set_show_data_func - set the show data func |
| * @handle: input handle for the trace.dat file |
| */ |
| void tracecmd_set_show_data_func(struct tracecmd_input *handle, |
| tracecmd_show_data_func func) |
| { |
| handle->show_data_func = func; |
| } |
| |
| /** |
| * tracecmd_get_traceid - get the trace id of the session |
| * @handle: input handle for the trace.dat file |
| * |
| * Returns the trace id, written in the trace file |
| */ |
| unsigned long long tracecmd_get_traceid(struct tracecmd_input *handle) |
| { |
| return handle->trace_id; |
| } |
| |
| /** |
| * tracecmd_get_first_ts - get the timestamp of the first recorded event |
| * @handle: input handle for the trace.dat file |
| * |
| * Returns the timestamp of the first recorded event |
| */ |
| unsigned long long tracecmd_get_first_ts(struct tracecmd_input *handle) |
| { |
| unsigned long long ts = 0; |
| bool first = true; |
| int i; |
| |
| for (i = 0; i < handle->cpus; i++) { |
| /* Ignore empty buffers */ |
| if (!handle->cpu_data[i].size) |
| continue; |
| if (first || ts > handle->cpu_data[i].first_ts) |
| ts = handle->cpu_data[i].first_ts; |
| first = false; |
| } |
| |
| return ts; |
| } |
| |
| /** |
| * tracecmd_get_guest_cpumap - get the mapping of guest VCPU to host process |
| * @handle: input handle for the trace.dat file |
| * @trace_id: ID of the guest tracing session |
| * @name: return, name of the guest |
| * @vcpu_count: return, number of VPUs |
| * @cpu_pid: return, array with guest VCPU to host process mapping |
| * |
| * Returns @name of the guest, number of VPUs (@vcpu_count) |
| * and array @cpu_pid with size @vcpu_count. Array index is VCPU id, array |
| * content is PID of the host process, running this VCPU. |
| * |
| * This information is stored in host trace.dat file |
| */ |
| int tracecmd_get_guest_cpumap(struct tracecmd_input *handle, |
| unsigned long long trace_id, |
| const char **name, |
| int *vcpu_count, const int **cpu_pid) |
| { |
| struct guest_trace_info *guest = handle->guest; |
| |
| while (guest) { |
| if (guest->trace_id == trace_id) |
| break; |
| guest = guest->next; |
| } |
| if (!guest) |
| return -1; |
| |
| if (name) |
| *name = guest->name; |
| if (vcpu_count) |
| *vcpu_count = guest->vcpu_count; |
| if (cpu_pid) |
| *cpu_pid = guest->cpu_pid; |
| return 0; |
| } |
| |
| /** |
| * tracecmd_enable_tsync - enable / disable the timestamps correction |
| * @handle: input handle for the trace.dat file |
| * @enable: enable / disable the timestamps correction |
| * |
| * Enables or disables timestamps correction on file load, using the array of |
| * recorded time offsets. If "enable" is true, but there are no time offsets, |
| * function fails and -1 is returned. |
| * |
| * Returns -1 in case of an error, or 0 otherwise |
| */ |
| int tracecmd_enable_tsync(struct tracecmd_input *handle, bool enable) |
| { |
| if (enable && |
| (!handle->host.ts_offsets || !handle->host.cpu_count)) |
| return -1; |
| |
| handle->host.sync_enable = enable; |
| |
| return 0; |
| } |
| |