blob: 8ffdf04b993473ca30e0c36a9683dd893f2594bd [file] [log] [blame]
// 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;
}