blob: b93ac14a06965ad1904cd119bb005b69ca9626b1 [file] [log] [blame]
/* Copyright (c) 2015-2016 Google Inc
* Davide Libenzi <dlibenzi@google.com>
* Barret Rhoden <brho@cs.berkeley.edu>
*
* See LICENSE for details.
*
* Converts kprof profiler files into Linux perf ones. The Linux Perf file
* format has bee illustrated here:
*
* https://lwn.net/Articles/644919/
* https://openlab-mu-internal.web.cern.ch/openlab-mu-internal/03_Documents/
* 3_Technical_Documents/Technical_Reports/2011/Urs_Fassler_report.pdf
*
*/
#include <ros/common.h>
#include <ros/memops.h>
#include <ros/profiler_records.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include "perf_format.h"
#include "xlib.h"
#include "perf_core.h"
#include "perfconv.h"
#include "elf.h"
#define MAX_PERF_RECORD_SIZE (32 * 1024 * 1024)
#define PERF_RECORD_BUFFER_SIZE 1024
#define OFFSET_NORELOC ((uint64_t) -1)
#define MEMFILE_BLOCK_SIZE (64 * 1024)
#define MBWR_SOLID (1 << 0)
char *cmd_line_save;
struct perf_record {
uint64_t type;
uint64_t size;
char *data;
char buffer[PERF_RECORD_BUFFER_SIZE];
};
static void dbg_print(struct perfconv_context *cctx, int level, FILE *file,
const char *fmt, ...)
{
if (cctx->debug_level >= level) {
va_list args;
va_start(args, fmt);
vfprintf(file, fmt, args);
va_end(args);
}
}
void perfconv_set_dbglevel(int level, struct perfconv_context *cctx)
{
cctx->debug_level = level;
}
static void free_record(struct perf_record *pr)
{
if (pr->data != pr->buffer)
free(pr->data);
pr->data = NULL;
}
static int read_record(FILE *file, struct perf_record *pr)
{
if (vb_fdecode_uint64(file, &pr->type) == EOF ||
vb_fdecode_uint64(file, &pr->size) == EOF)
return EOF;
if (pr->size > MAX_PERF_RECORD_SIZE) {
fprintf(stderr, "Invalid record size: type=%lu size=%lu\n",
pr->type, pr->size);
exit(1);
}
if (pr->size > sizeof(pr->buffer))
pr->data = xmalloc((size_t) pr->size);
else
pr->data = pr->buffer;
if (fread(pr->data, 1, (size_t) pr->size, file) != (size_t) pr->size) {
fprintf(stderr, "Unable to read record memory: size=%lu\n",
pr->size);
return EOF;
}
return 0;
}
static struct mem_block *mem_block_alloc(size_t size)
{
struct mem_block *mb = xmalloc(sizeof(struct mem_block) + size);
mb->next = NULL;
mb->base = mb->wptr = (char *) mb + sizeof(struct mem_block);
mb->top = mb->base + size;
return mb;
}
static char *mem_block_write(struct mem_block *mb, const void *data,
size_t size)
{
char *wrbase = mb->wptr;
always_assert(size <= mb->top - mb->wptr);
memcpy(mb->wptr, data, size);
mb->wptr += size;
return wrbase;
}
static void mem_file_init(struct mem_file *mf)
{
ZERO_DATA(*mf);
}
static int mem_block_can_write(struct mem_block *mb, size_t size, int flags)
{
size_t space = mb->top - mb->wptr;
return (flags & MBWR_SOLID) ? (space >= size) : (space > 0);
}
static void *mem_file_write(struct mem_file *mf, const void *data, size_t size,
int flags)
{
void *wrbase = NULL;
while (size > 0) {
size_t space, csize;
struct mem_block *mb = mf->tail;
if (!mb || !mem_block_can_write(mb, size, flags)) {
mb = mem_block_alloc(max(MEMFILE_BLOCK_SIZE, size));
if (!mf->tail)
mf->head = mb;
else
mf->tail->next = mb;
mf->tail = mb;
}
space = mb->top - mb->wptr;
csize = min(size, space);
wrbase = mem_block_write(mb, data, csize);
mf->size += csize;
size -= csize;
data = (const char *) data + csize;
}
return wrbase;
}
static void mem_file_sync(struct mem_file *mf, FILE *file, uint64_t rel_offset)
{
struct mem_block *mb;
if (rel_offset != 0) {
struct mem_file_reloc *rel;
always_assert(!mf->relocs || rel_offset != OFFSET_NORELOC);
for (rel = mf->relocs; rel; rel = rel->next)
*rel->ptr += rel_offset;
}
for (mb = mf->head; mb; mb = mb->next)
xfwrite(mb->base, mb->wptr - mb->base, file);
}
static struct mem_file_reloc *mem_file_add_reloc(struct mem_file *mf,
uint64_t *ptr)
{
struct mem_file_reloc *rel = xzmalloc(sizeof(struct mem_file_reloc));
rel->ptr = ptr;
rel->next = mf->relocs;
mf->relocs = rel;
return rel;
}
void perfconv_add_kernel_mmap(struct perfconv_context *cctx)
{
char path[] = "[kernel.kallsyms]";
struct static_mmap64 *mm;
mm = xzmalloc(sizeof(struct static_mmap64));
mm->pid = -1; /* Linux HOST_KERNEL_ID == -1 */
mm->tid = 0; /* Default thread: swapper */
mm->header_misc = PERF_RECORD_MISC_KERNEL;
/* Linux sets addr = 0, size = 0xffffffff9fffffff, off =
* 0xffffffff81000000
*
* Their mmap record is also called [kernel.kallsyms]_text (I think).
* They also have a _text symbol in kallsyms at ffffffff81000000 (equiv
* to our KERN_LOAD_ADDR (which is 0xffffffffc0000000)). Either way,
* this seems to work for us; we'll see. It's also arch-independent
* (for now). */
mm->addr = 0;
mm->size = 0xffffffffffffffff;
mm->offset = 0x0;
mm->path = xstrdup(path);
mm->next = cctx->static_mmaps;
cctx->static_mmaps = mm;
}
static void headers_init(struct perf_headers *hdrs)
{
ZERO_DATA(*hdrs);
}
/* Prepends a header (the mem_block) to the list of memblocks for the given
* HEADER type. They will all be concatted together during finalization. */
static void headers_add_header(struct perf_header *ph,
struct perf_headers *hdrs, size_t nhdr,
struct mem_block *mb)
{
always_assert(nhdr < HEADER_FEAT_BITS);
mb->next = hdrs->headers[nhdr];
hdrs->headers[nhdr] = mb;
set_bitno(ph->adds_features, nhdr);
}
/* Emits the headers contents to the mem_file */
static void headers_finalize(struct perf_headers *hdrs, struct mem_file *mf)
{
struct perf_file_section *header_file_secs, *file_sec;
struct perf_file_section *file_sec_reloc;
size_t nr_hdrs = 0;
size_t hdr_off;
struct mem_block *mb;
size_t mb_sz;
/* For each header, we need a perf_file_section. These header file
* sections are right after the main perf header, and they point to
* actual header. */
for (int i = 0; i < HEADER_FEAT_BITS; i++)
if (hdrs->headers[i])
nr_hdrs++;
if (!nr_hdrs)
return;
header_file_secs = xmalloc(sizeof(struct perf_file_section) * nr_hdrs);
hdr_off = sizeof(struct perf_file_section) * nr_hdrs;
file_sec = header_file_secs;
/* Spit out the perf_file_sections first and track relocations for all
* of the offsets. */
for (int i = 0; i < HEADER_FEAT_BITS; i++) {
mb = hdrs->headers[i];
if (!mb)
continue;
mb_sz = mb->wptr - mb->base;
/* headers[i] is a chain of memblocks */
while (mb->next) {
mb = mb->next;
mb_sz += mb->wptr - mb->base;
}
file_sec->size = mb_sz;
file_sec->offset = hdr_off; /* offset rel to this memfile */
/* When we sync the memfile, we'll need to relocate each of the
* offsets so that they are relative to the final file.
* mem_file_write() should be returning the location of where it
* wrote our file section within the memfile. that's the offset
* that we'll reloc later. */
file_sec_reloc = mem_file_write(mf, file_sec,
sizeof(struct perf_file_section), 0);
assert(file_sec->size == file_sec_reloc->size);
assert(file_sec->offset == file_sec_reloc->offset);
mem_file_add_reloc(mf, &file_sec_reloc->offset);
hdr_off += mb_sz;
file_sec++;
}
free(header_file_secs);
/* Spit out the actual headers */
for (int i = 0; i < HEADER_FEAT_BITS; i++) {
mb = hdrs->headers[i];
while (mb) {
mem_file_write(mf, mb->base, mb->wptr - mb->base, 0);
mb = mb->next;
}
}
}
/* Builds a struct perf_header_string from str and returns it in a mem_block */
static struct mem_block *header_make_string(const char *str)
{
struct perf_header_string *hdr;
struct mem_block *mb;
size_t str_sz = strlen(str) + 1;
size_t hdr_sz = ROUNDUP(str_sz + sizeof(struct perf_header_string),
PERF_STRING_ALIGN);
mb = mem_block_alloc(hdr_sz);
/* Manually writing to the block to avoid another alloc. I guess I
* could do two writes (len and string) and try to not screw it up, but
* that'd be a mess. */
hdr = (struct perf_header_string*)mb->wptr;
mb->wptr += hdr_sz;
hdr->len = str_sz;
memcpy(hdr->string, str, str_sz);
return mb;
}
/* Opens and reads filename, returning the contents. Free the ret. */
static char *get_str_from_os(const char *filename)
{
int fd, ret;
struct stat fd_stat;
char *buf;
size_t buf_sz;
fd = open(filename, O_RDONLY);
if (fd < 0)
return 0;
ret = fstat(fd, &fd_stat);
if (ret) {
close(fd);
return 0;
}
buf_sz = fd_stat.st_size + 1;
buf = xmalloc(buf_sz);
ret = read(fd, buf, buf_sz - 1);
if (ret <= 0) {
free(buf);
close(fd);
return 0;
}
close(fd);
/* OS strings should be null terminated, but let's be defensive */
buf[ret] = 0;
return buf;
}
static void hdr_do_osrelease(struct perf_header *ph, struct perf_headers *hdrs)
{
char *str;
str = get_str_from_os("#version/version_name");
if (!str)
return;
headers_add_header(ph, hdrs, HEADER_OSRELEASE, header_make_string(str));
free(str);
}
static void hdr_do_nrcpus(struct perf_header *ph, struct perf_headers *hdrs)
{
char *str;
uint32_t nr_cores;
struct mem_block *mb;
struct nr_cpus *hdr;
str = get_str_from_os("#vars/num_cores!dw");
if (!str)
return;
nr_cores = atoi(str);
free(str);
mb = mem_block_alloc(sizeof(struct nr_cpus));
hdr = (struct nr_cpus*)mb->wptr;
mb->wptr += sizeof(struct nr_cpus);
hdr->nr_cpus_online = nr_cores;
hdr->nr_cpus_available = nr_cores;
headers_add_header(ph, hdrs, HEADER_NRCPUS, mb);
}
/* Unfortunately, HEADER_CMDLINE doesn't just take a string. It takes a list of
* null-terminated perf_header_strings (i.e. argv), with the a u32 for the
* number of strings. We can just send one string for the entire cmd line. */
static void hdr_do_cmdline(struct perf_header *ph, struct perf_headers *hdrs)
{
struct perf_header_string *hdr;
struct mem_block *mb;
size_t str_sz = strlen(cmd_line_save) + 1;
size_t hdr_sz = sizeof(uint32_t) +
ROUNDUP(str_sz + sizeof(struct perf_header_string),
PERF_STRING_ALIGN);
mb = mem_block_alloc(hdr_sz);
/* emit the nr strings (1) */
*(uint32_t*)mb->wptr = 1;
mb->wptr += sizeof(uint32_t);
/* emit the perf_header_string, as usual */
hdr = (struct perf_header_string*)mb->wptr;
mb->wptr += hdr_sz - sizeof(uint32_t);
hdr->len = str_sz;
memcpy(hdr->string, cmd_line_save, str_sz);
headers_add_header(ph, hdrs, HEADER_CMDLINE, mb);
}
/* Returns TRUE if we already emitted a build-id for path. If this gets too
* slow (which we'll know because of perf!) we can use a hash table (don't hash
* on the first few bytes btw - they are usually either /bin or /lib). */
static bool lookup_buildid(struct perfconv_context *cctx, const char *path)
{
struct build_id_event *b_evt;
struct mem_block *mb;
size_t b_evt_path_sz;
mb = cctx->hdrs.headers[HEADER_BUILD_ID];
while (mb) {
b_evt = (struct build_id_event*)mb->base;
b_evt_path_sz = b_evt->header.size -
offsetof(struct build_id_event, filename);
/* ignoring the last byte since we forced it to be \0 earlier.*/
if (!strncmp(b_evt->filename, path, b_evt_path_sz - 1))
return TRUE;
mb = mb->next;
}
return FALSE;
}
/* Helper: given a path, allocs and inits a build_id_event within a mem_block,
* returning both via parameters. Caller needs to set header.misc and fill in
* the actual build_id. */
static void build_id_alloc(const char *path, struct build_id_event **b_evt_p,
struct mem_block **mb_p)
{
struct build_id_event *b_evt;
struct mem_block *mb;
size_t path_sz, b_evt_sz;
path_sz = strlen(path) + 1;
b_evt_sz = path_sz + sizeof(struct build_id_event);
mb = mem_block_alloc(b_evt_sz);
b_evt = (struct build_id_event*)mb->wptr;
mb->wptr += b_evt_sz;
b_evt->header.type = 0; /* if this fails, try 67 (synthetic build id) */
/* header.size filled in by the caller, depending on the type */
b_evt->header.size = b_evt_sz;
strlcpy(b_evt->filename, path, path_sz);
*b_evt_p = b_evt;
*mb_p = mb;
}
/* Add a build-id header. Unlike many of the other headers, this one is built
* on the fly as we emit other records. */
static void hdr_add_buildid(struct perfconv_context *cctx, const char *path,
int pid)
{
struct build_id_event *b_evt;
struct mem_block *mb;
int ret;
if (lookup_buildid(cctx, path))
return;
build_id_alloc(path, &b_evt, &mb);
b_evt->header.misc = PERF_RECORD_MISC_USER;
b_evt->pid = pid;
ret = filename__read_build_id(path, b_evt->build_id, BUILD_ID_SIZE);
if (ret <= 0)
free(mb);
else
headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
}
static void convert_str_to_binary(char *b_id_str, uint8_t *b_id_raw)
{
char *c = b_id_str;
for (int i = 0; i < BUILD_ID_SIZE; i++) {
b_id_raw[i] = nibble_to_num(*c) << 4 | nibble_to_num(*(c + 1));
c += 2;
}
}
void perfconv_add_kernel_buildid(struct perfconv_context *cctx)
{
struct build_id_event *b_evt;
struct mem_block *mb;
int ret, fd;
char build_id[BUILD_ID_SIZE * 2 + 1] = {0};
build_id_alloc("[kernel.kallsyms]", &b_evt, &mb);
b_evt->header.misc = PERF_RECORD_MISC_KERNEL;
b_evt->pid = -1;
fd = xopen("#version/build_id", O_RDONLY, 0);
ret = read(fd, build_id, sizeof(build_id));
if (ret <= 0) {
free(mb);
} else {
convert_str_to_binary(build_id, b_evt->build_id);
headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
}
}
/* Helper: adds all the headers, marking them in PH and storing them in
* feat_hdrs. */
static void headers_build(struct perf_header *ph, struct perf_headers *hdrs,
struct mem_file *feat_hdrs)
{
hdr_do_osrelease(ph, hdrs);
hdr_do_nrcpus(ph, hdrs);
hdr_do_cmdline(ph, hdrs);
headers_finalize(hdrs, feat_hdrs);
}
static void perf_header_init(struct perf_header *ph)
{
ZERO_DATA(*ph);
ph->magic = PERF_MAGIC2;
ph->size = sizeof(*ph);
ph->attr_size = sizeof(struct perf_event_attr);
}
/* For each attr we emit, we push out the attr, then a perf_file_section for the
* id(s) for that attr. This wasn't mentioned in
* https://lwn.net/Articles/644919/, but it's what Linux perf expects
* (util/header.c). It looks like you can have multiple IDs per event attr.
* We'll only emit one. The *contents* of the perf_file_section for the ids
* aren't in the attr perf_file_section, they are in a separate one
* (attr_id_mf).
*
* Note that *attr_mf*'s relocs are relative to the base of *attr_id_mf*, which
* we'll need to sort out later. */
static void emit_attr(struct mem_file *attr_mf, struct mem_file *attr_id_mf,
const struct perf_event_attr *attr, uint64_t id)
{
struct perf_file_section *psids;
struct perf_file_section sids;
mem_file_write(attr_mf, attr, sizeof(*attr), 0);
sids.offset = attr_id_mf->size;
sids.size = sizeof(uint64_t);
mem_file_write(attr_id_mf, &id, sizeof(uint64_t), 0);
psids = mem_file_write(attr_mf, &sids, sizeof(sids), MBWR_SOLID);
mem_file_add_reloc(attr_mf, &psids->offset);
}
/* Given raw_info, which is what the kernel sends as user_data for a particular
* sample, look up the 'id' for the event/sample. The 'id' identifies the event
* stream that the sample is a part of. There are many samples per event
* stream, all identified by 'id.' It doesn't matter what 'id', so long as it
* is unique. We happen to use the pointer to the sample's eventsel.
*
* If this is the first time we've seen 'raw_info', we'll also add an attribute
* to the perf ctx. There is one attr per 'id' / event stream. */
static uint64_t perfconv_get_event_id(struct perfconv_context *cctx,
uint64_t raw_info)
{
struct perf_eventsel *sel = (struct perf_eventsel*)raw_info;
struct perf_event_attr attr;
uint64_t raw_event;
assert(sel);
if (sel->attr_emitted)
return raw_info;
raw_event = sel->ev.event;
ZERO_DATA(attr);
attr.size = sizeof(attr);
attr.mmap = 1;
attr.comm = 1;
attr.sample_period = sel->ev.trigger_count;
/* Closely coupled with struct perf_record_sample */
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
PERF_SAMPLE_ADDR | PERF_SAMPLE_IDENTIFIER |
PERF_SAMPLE_CPU | PERF_SAMPLE_CALLCHAIN;
attr.exclude_guest = 1; /* we can't trace VMs yet */
attr.exclude_hv = 1; /* we aren't tracing our hypervisor, AFAIK */
attr.exclude_user = !PMEV_GET_USR(raw_event);
attr.exclude_kernel = !PMEV_GET_OS(raw_event);
attr.type = sel->type;
attr.config = sel->config;
emit_attr(&cctx->attrs, &cctx->attr_ids, &attr, raw_info);
sel->attr_emitted = TRUE;
return raw_info;
}
static void emit_static_mmaps(struct perfconv_context *cctx)
{
struct static_mmap64 *mm;
for (mm = cctx->static_mmaps; mm; mm = mm->next) {
size_t size = sizeof(struct perf_record_mmap)
+ strlen(mm->path) + 1;
struct perf_record_mmap *xrec = xzmalloc(size);
xrec->header.type = PERF_RECORD_MMAP;
xrec->header.misc = mm->header_misc;
xrec->header.size = size;
xrec->pid = mm->pid;
xrec->tid = mm->tid;
xrec->addr = mm->addr;
xrec->len = mm->size;
xrec->pgoff = mm->offset;
strcpy(xrec->filename, mm->path);
mem_file_write(&cctx->data, xrec, size, 0);
free(xrec);
}
}
static void emit_comm(uint32_t pid, const char *comm,
struct perfconv_context *cctx)
{
size_t size = sizeof(struct perf_record_comm) + strlen(comm) + 1;
struct perf_record_comm *xrec = xzmalloc(size);
xrec->header.type = PERF_RECORD_COMM;
xrec->header.misc = PERF_RECORD_MISC_USER;
xrec->header.size = size;
xrec->pid = xrec->tid = pid;
strcpy(xrec->comm, comm);
mem_file_write(&cctx->data, xrec, size, 0);
free(xrec);
}
static void emit_pid_mmap64(struct perf_record *pr,
struct perfconv_context *cctx)
{
struct proftype_pid_mmap64 *rec =
(struct proftype_pid_mmap64 *) pr->data;
size_t size = sizeof(struct perf_record_mmap) +
strlen((char*)rec->path) + 1;
struct perf_record_mmap *xrec = xzmalloc(size);
hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
xrec->header.type = PERF_RECORD_MMAP;
xrec->header.misc = PERF_RECORD_MISC_USER;
xrec->header.size = size;
xrec->pid = xrec->tid = rec->pid;
xrec->addr = rec->addr;
xrec->len = rec->size;
xrec->pgoff = rec->offset;
strcpy(xrec->filename, (char*)rec->path);
mem_file_write(&cctx->data, xrec, size, 0);
free(xrec);
}
static void emit_kernel_trace64(struct perf_record *pr,
struct perfconv_context *cctx)
{
struct proftype_kern_trace64 *rec = (struct proftype_kern_trace64 *)
pr->data;
size_t size = sizeof(struct perf_record_sample) +
(rec->num_traces - 1) * sizeof(uint64_t);
struct perf_record_sample *xrec = xzmalloc(size);
xrec->header.type = PERF_RECORD_SAMPLE;
xrec->header.misc = PERF_RECORD_MISC_KERNEL;
xrec->header.size = size;
xrec->ip = rec->trace[0];
/* TODO: -1 means "not a process". We could track ktasks with IDs, emit
* COMM events for them (probably!) and report them as the tid. For
* now, tid of 0 means [swapper] to Linux. */
if (rec->pid == -1) {
xrec->pid = -1;
xrec->tid = 0;
} else {
xrec->pid = rec->pid;
xrec->tid = rec->pid;
}
xrec->time = rec->tstamp;
xrec->addr = rec->trace[0];
xrec->identifier = perfconv_get_event_id(cctx, rec->info);
xrec->cpu = rec->cpu;
xrec->nr = rec->num_traces - 1;
memcpy(xrec->ips, rec->trace + 1,
(rec->num_traces - 1) * sizeof(uint64_t));
mem_file_write(&cctx->data, xrec, size, 0);
free(xrec);
}
static void emit_user_trace64(struct perf_record *pr,
struct perfconv_context *cctx)
{
struct proftype_user_trace64 *rec = (struct proftype_user_trace64 *)
pr->data;
size_t size = sizeof(struct perf_record_sample) +
(rec->num_traces - 1) * sizeof(uint64_t);
struct perf_record_sample *xrec = xzmalloc(size);
xrec->header.type = PERF_RECORD_SAMPLE;
xrec->header.misc = PERF_RECORD_MISC_USER;
xrec->header.size = size;
xrec->ip = rec->trace[0];
xrec->pid = xrec->tid = rec->pid;
xrec->time = rec->tstamp;
xrec->addr = rec->trace[0];
xrec->identifier = perfconv_get_event_id(cctx, rec->info);
xrec->cpu = rec->cpu;
xrec->nr = rec->num_traces - 1;
memcpy(xrec->ips, rec->trace + 1,
(rec->num_traces - 1) * sizeof(uint64_t));
mem_file_write(&cctx->data, xrec, size, 0);
free(xrec);
}
static void emit_new_process(struct perf_record *pr,
struct perfconv_context *cctx)
{
struct proftype_new_process *rec =
(struct proftype_new_process *) pr->data;
const char *comm;
hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
comm = strrchr((char*)rec->path, '/');
if (!comm)
comm = (char*)rec->path;
else
comm++;
emit_comm(rec->pid, comm, cctx);
}
struct perfconv_context *perfconv_create_context(struct perf_context *pctx)
{
struct perfconv_context *cctx =
xzmalloc(sizeof(struct perfconv_context));
cctx->pctx = pctx;
perf_header_init(&cctx->ph);
headers_init(&cctx->hdrs);
mem_file_init(&cctx->fhdrs);
mem_file_init(&cctx->attr_ids);
mem_file_init(&cctx->attrs);
mem_file_init(&cctx->data);
/* event_types is ignored in newer versions of perf */
mem_file_init(&cctx->event_types);
return cctx;
}
void perfconv_free_context(struct perfconv_context *cctx)
{
if (cctx)
free(cctx);
}
void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
FILE *output)
{
size_t processed_records = 0;
uint64_t offset;
uint64_t attr_ids_off = sizeof(cctx->ph);
struct perf_record pr;
emit_static_mmaps(cctx);
while (read_record(input, &pr) == 0) {
dbg_print(cctx, 8, stderr, "Valid record: type=%lu size=%lu\n",
pr.type, pr.size);
processed_records++;
switch (pr.type) {
case PROFTYPE_KERN_TRACE64:
emit_kernel_trace64(&pr, cctx);
break;
case PROFTYPE_USER_TRACE64:
emit_user_trace64(&pr, cctx);
break;
case PROFTYPE_PID_MMAP64:
emit_pid_mmap64(&pr, cctx);
break;
case PROFTYPE_NEW_PROCESS:
emit_new_process(&pr, cctx);
break;
default:
fprintf(stderr, "Unknown record: type=%lu size=%lu\n",
pr.type, pr.size);
processed_records--;
}
free_record(&pr);
}
/* Add all of the headers before outputting ph */
headers_build(&cctx->ph, &cctx->hdrs, &cctx->fhdrs);
/* attrs, events, and data will come after attr_ids. */
offset = sizeof(cctx->ph) + cctx->attr_ids.size;
/* These are the perf_file_sections in the main perf header. We need
* this sorted out before we emit the PH. */
cctx->ph.event_types.offset = offset;
cctx->ph.event_types.size = cctx->event_types.size;
offset += cctx->event_types.size;
cctx->ph.attrs.offset = offset;
cctx->ph.attrs.size = cctx->attrs.size;
offset += cctx->attrs.size;
cctx->ph.data.offset = offset;
cctx->ph.data.size = cctx->data.size;
offset += cctx->data.size;
xfwrite(&cctx->ph, sizeof(cctx->ph), output);
/* attr_ids comes right after the cctx->ph. We need to put it before
* attrs, since attrs needs to know the offset of the base of attrs_ids
* for its relocs. */
assert(ftell(output) == attr_ids_off);
mem_file_sync(&cctx->attr_ids, output, OFFSET_NORELOC);
mem_file_sync(&cctx->event_types, output, OFFSET_NORELOC);
/* reloc is based off *attr_ids* base */
mem_file_sync(&cctx->attrs, output, attr_ids_off);
/* Keep data last, so we can append the feature headers.*/
mem_file_sync(&cctx->data, output, OFFSET_NORELOC);
/* The feature headers must be right after the data section. I didn't
* see anything in the ABI about this, but Linux's perf has this line:
*
* ph->feat_offset = header->data.offset +
* header->data.size;
*/
mem_file_sync(&cctx->fhdrs, output,
cctx->ph.data.offset + cctx->ph.data.size);
dbg_print(cctx, 2, stderr, "Conversion succeeded: %lu records converted\n",
processed_records);
}