// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "perf_serializer.h"

#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>

#include <algorithm>  // for std::copy

#include "base/logging.h"

#include "binary_data_utils.h"
#include "compat/proto.h"
#include "compat/string.h"
#include "kernel/perf_event.h"
#include "perf_data_structures.h"
#include "perf_data_utils.h"
#include "perf_parser.h"
#include "perf_reader.h"

namespace quipper {

PerfSerializer::PerfSerializer() {}

PerfSerializer::~PerfSerializer() {}

bool PerfSerializer::SerializePerfFileAttr(
    const PerfFileAttr& perf_file_attr,
    PerfDataProto_PerfFileAttr* perf_file_attr_proto) const {
  if (!SerializePerfEventAttr(perf_file_attr.attr,
                              perf_file_attr_proto->mutable_attr())) {
    return false;
  }

  for (size_t i = 0; i < perf_file_attr.ids.size(); i++)
    perf_file_attr_proto->add_ids(perf_file_attr.ids[i]);
  return true;
}

bool PerfSerializer::DeserializePerfFileAttr(
    const PerfDataProto_PerfFileAttr& perf_file_attr_proto,
    PerfFileAttr* perf_file_attr) const {
  if (!DeserializePerfEventAttr(perf_file_attr_proto.attr(),
                                &perf_file_attr->attr)) {
    return false;
  }

  for (int i = 0; i < perf_file_attr_proto.ids_size(); i++)
    perf_file_attr->ids.push_back(perf_file_attr_proto.ids(i));
  return true;
}

bool PerfSerializer::SerializePerfEventAttr(
    const perf_event_attr& perf_event_attr,
    PerfDataProto_PerfEventAttr* perf_event_attr_proto) const {
#define S(x) perf_event_attr_proto->set_##x(perf_event_attr.x)
  S(type);
  S(size);
  S(config);
  if (perf_event_attr_proto->freq())
    S(sample_freq);
  else
    S(sample_period);
  S(sample_type);
  S(read_format);
  S(disabled);
  S(inherit);
  S(pinned);
  S(exclusive);
  S(exclude_user);
  S(exclude_kernel);
  S(exclude_hv);
  S(exclude_idle);
  S(mmap);
  S(comm);
  S(freq);
  S(inherit_stat);
  S(enable_on_exec);
  S(task);
  S(watermark);
  S(precise_ip);
  S(mmap_data);
  S(sample_id_all);
  S(exclude_host);
  S(exclude_guest);
  S(exclude_callchain_kernel);
  S(exclude_callchain_user);
  S(mmap2);
  S(comm_exec);
  if (perf_event_attr_proto->watermark())
    S(wakeup_watermark);
  else
    S(wakeup_events);
  S(bp_type);
  S(bp_addr);
  S(bp_len);
  S(branch_sample_type);
  S(sample_regs_user);
  S(sample_stack_user);
#undef S
  return true;
}

bool PerfSerializer::DeserializePerfEventAttr(
    const PerfDataProto_PerfEventAttr& perf_event_attr_proto,
    perf_event_attr* perf_event_attr) const {
  memset(perf_event_attr, 0, sizeof(*perf_event_attr));
#define S(x) perf_event_attr->x = perf_event_attr_proto.x()
  S(type);
  S(size);
  S(config);
  if (perf_event_attr->freq)
    S(sample_freq);
  else
    S(sample_period);
  S(sample_type);
  S(read_format);
  S(disabled);
  S(inherit);
  S(pinned);
  S(exclusive);
  S(exclude_user);
  S(exclude_kernel);
  S(exclude_hv);
  S(exclude_idle);
  S(mmap);
  S(comm);
  S(freq);
  S(inherit_stat);
  S(enable_on_exec);
  S(task);
  S(watermark);
  S(precise_ip);
  S(mmap_data);
  S(sample_id_all);
  S(exclude_host);
  S(exclude_guest);
  S(exclude_callchain_kernel);
  S(exclude_callchain_user);
  S(mmap2);
  S(comm_exec);
  if (perf_event_attr->watermark)
    S(wakeup_watermark);
  else
    S(wakeup_events);
  S(bp_type);
  S(bp_addr);
  S(bp_len);
  S(branch_sample_type);
  S(sample_regs_user);
  S(sample_stack_user);
#undef S
  return true;
}

bool PerfSerializer::SerializePerfEventType(
    const PerfFileAttr& event_attr,
    quipper::PerfDataProto_PerfEventType* event_type_proto) const {
  event_type_proto->set_id(event_attr.attr.config);
  event_type_proto->set_name(event_attr.name);
  event_type_proto->set_name_md5_prefix(Md5Prefix(event_attr.name));
  return true;
}

bool PerfSerializer::DeserializePerfEventType(
    const quipper::PerfDataProto_PerfEventType& event_type_proto,
    PerfFileAttr* event_attr) const {
  // Attr should have already been deserialized.
  if (event_attr->attr.config != event_type_proto.id()) {
    LOG(ERROR) << "Event type ID " << event_type_proto.id()
               << " does not match attr.config " << event_attr->attr.config
               << ". Not deserializing the event name!";
    return false;
  }
  event_attr->name = event_type_proto.name();
  return true;
}

bool PerfSerializer::SerializeEvent(
    const malloced_unique_ptr<event_t>& event_ptr,
    PerfDataProto_PerfEvent* event_proto) const {
  const event_t& event = *event_ptr;

  if (!SerializeEventHeader(event.header, event_proto->mutable_header()))
    return false;

  if (event.header.type >= PERF_RECORD_USER_TYPE_START) {
    if (!SerializeUserEvent(event, event_proto)) {
      return false;
    }
  } else if (!SerializeKernelEvent(event, event_proto)) {
    return false;
  }

  event_proto->set_timestamp(GetTimeFromPerfEvent(*event_proto));
  return true;
}

bool PerfSerializer::SerializeKernelEvent(
    const event_t& event, PerfDataProto_PerfEvent* event_proto) const {
  switch (event.header.type) {
    case PERF_RECORD_SAMPLE:
      return SerializeSampleEvent(event, event_proto->mutable_sample_event());
    case PERF_RECORD_MMAP:
      return SerializeMMapEvent(event, event_proto->mutable_mmap_event());
    case PERF_RECORD_MMAP2:
      return SerializeMMap2Event(event, event_proto->mutable_mmap_event());
    case PERF_RECORD_COMM:
      return SerializeCommEvent(event, event_proto->mutable_comm_event());
    case PERF_RECORD_EXIT:
      return SerializeForkExitEvent(event, event_proto->mutable_exit_event());
    case PERF_RECORD_FORK:
      return SerializeForkExitEvent(event, event_proto->mutable_fork_event());
    case PERF_RECORD_LOST:
      return SerializeLostEvent(event, event_proto->mutable_lost_event());
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
      return SerializeThrottleEvent(event,
                                    event_proto->mutable_throttle_event());
    case PERF_RECORD_READ:
      return SerializeReadEvent(event, event_proto->mutable_read_event());
    case PERF_RECORD_AUX:
      return SerializeAuxEvent(event, event_proto->mutable_aux_event());
    default:
      LOG(ERROR) << "Unknown event type: " << event.header.type;
  }
  return true;
}

bool PerfSerializer::SerializeUserEvent(
    const event_t& event, PerfDataProto_PerfEvent* event_proto) const {
  switch (event.header.type) {
    case PERF_RECORD_AUXTRACE:
      return SerializeAuxtraceEvent(event,
                                    event_proto->mutable_auxtrace_event());
    default:
      if (event.header.type >= PERF_RECORD_HEADER_MAX) {
        LOG(ERROR) << "Unknown event type: " << event.header.type;
      }
  }
  return true;
}

bool PerfSerializer::DeserializeEvent(
    const PerfDataProto_PerfEvent& event_proto,
    malloced_unique_ptr<event_t>* event_ptr) const {
  event_ptr->reset(CallocMemoryForEvent(event_proto.header().size()));
  event_t* event = event_ptr->get();

  if (!DeserializeEventHeader(event_proto.header(), &event->header))
    return false;

  bool event_deserialized = false;
  if (event_proto.header().type() >= PERF_RECORD_USER_TYPE_START) {
    event_deserialized = DeserializeUserEvent(event_proto, event);
  } else {
    event_deserialized = DeserializeKernelEvent(event_proto, event);
  }

  if (!event_deserialized) {
    LOG(ERROR) << "Could not deserialize event of type "
               << event_proto.header().type();
    return false;
  }

  return true;
}

bool PerfSerializer::DeserializeKernelEvent(
    const PerfDataProto_PerfEvent& event_proto, event_t* event) const {
  switch (event_proto.header().type()) {
    case PERF_RECORD_SAMPLE:
      return DeserializeSampleEvent(event_proto.sample_event(), event);
    case PERF_RECORD_MMAP:
      return DeserializeMMapEvent(event_proto.mmap_event(), event);
    case PERF_RECORD_MMAP2:
      return DeserializeMMap2Event(event_proto.mmap_event(), event);
    case PERF_RECORD_COMM:
      return DeserializeCommEvent(event_proto.comm_event(), event);
    case PERF_RECORD_EXIT:
      return (event_proto.has_exit_event() &&
              DeserializeForkExitEvent(event_proto.exit_event(), event)) ||
             (event_proto.has_fork_event() &&
              DeserializeForkExitEvent(event_proto.fork_event(), event));
    // Some older protobufs use the |fork_event| field to store exit
    // events.
    case PERF_RECORD_FORK:
      return DeserializeForkExitEvent(event_proto.fork_event(), event);
    case PERF_RECORD_LOST:
      return DeserializeLostEvent(event_proto.lost_event(), event);
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
      return DeserializeThrottleEvent(event_proto.throttle_event(), event);
    case PERF_RECORD_READ:
      return DeserializeReadEvent(event_proto.read_event(), event);
    case PERF_RECORD_AUX:
      return DeserializeAuxEvent(event_proto.aux_event(), event);
    case PERF_RECORD_ITRACE_START:
    case PERF_RECORD_LOST_SAMPLES:
    case PERF_RECORD_SWITCH:
    case PERF_RECORD_SWITCH_CPU_WIDE:
    case PERF_RECORD_NAMESPACES:
      LOG(ERROR) << "Event type: " << event_proto.header().type()
                 << ". Not yet supported.";
      return true;
      break;
  }
  return false;
}

bool PerfSerializer::DeserializeUserEvent(
    const PerfDataProto_PerfEvent& event_proto, event_t* event) const {
  switch (event_proto.header().type()) {
    case PERF_RECORD_AUXTRACE:
      return DeserializeAuxtraceEvent(event_proto.auxtrace_event(), event);
    default:
      // User type events are marked as deserialized because they don't
      // have non-header data in perf.data proto.
      if (event_proto.header().type() >= PERF_RECORD_HEADER_MAX) {
        return false;
      }
  }
  return true;
}

bool PerfSerializer::SerializeEventHeader(
    const perf_event_header& header,
    PerfDataProto_EventHeader* header_proto) const {
  header_proto->set_type(header.type);
  header_proto->set_misc(header.misc);
  header_proto->set_size(header.size);
  return true;
}

bool PerfSerializer::DeserializeEventHeader(
    const PerfDataProto_EventHeader& header_proto,
    perf_event_header* header) const {
  header->type = header_proto.type();
  header->misc = header_proto.misc();
  header->size = header_proto.size();
  return true;
}

bool PerfSerializer::SerializeSampleEvent(
    const event_t& event, PerfDataProto_SampleEvent* sample) const {
  perf_sample sample_info;
  uint64_t sample_type = 0;
  if (!ReadPerfSampleInfoAndType(event, &sample_info, &sample_type))
    return false;

  if (sample_type & PERF_SAMPLE_IP) sample->set_ip(sample_info.ip);
  if (sample_type & PERF_SAMPLE_TID) {
    sample->set_pid(sample_info.pid);
    sample->set_tid(sample_info.tid);
  }
  if (sample_type & PERF_SAMPLE_TIME)
    sample->set_sample_time_ns(sample_info.time);
  if (sample_type & PERF_SAMPLE_ADDR) sample->set_addr(sample_info.addr);
  if ((sample_type & PERF_SAMPLE_ID) || (sample_type & PERF_SAMPLE_IDENTIFIER))
    sample->set_id(sample_info.id);
  if (sample_type & PERF_SAMPLE_STREAM_ID)
    sample->set_stream_id(sample_info.stream_id);
  if (sample_type & PERF_SAMPLE_CPU) sample->set_cpu(sample_info.cpu);
  if (sample_type & PERF_SAMPLE_PERIOD) sample->set_period(sample_info.period);
  if (sample_type & PERF_SAMPLE_RAW) sample->set_raw_size(sample_info.raw_size);
  if (sample_type & PERF_SAMPLE_READ) {
    const SampleInfoReader* reader = GetSampleInfoReaderForEvent(event);
    if (reader) {
      PerfDataProto_ReadInfo* read_info = sample->mutable_read_info();
      if (reader->event_attr().read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
        read_info->set_time_enabled(sample_info.read.time_enabled);
      if (reader->event_attr().read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
        read_info->set_time_running(sample_info.read.time_running);
      if (reader->event_attr().read_format & PERF_FORMAT_GROUP) {
        for (size_t i = 0; i < sample_info.read.group.nr; i++) {
          auto read_value = read_info->add_read_value();
          read_value->set_value(sample_info.read.group.values[i].value);
          read_value->set_id(sample_info.read.group.values[i].id);
        }
      } else {
        auto read_value = read_info->add_read_value();
        read_value->set_value(sample_info.read.one.value);
        read_value->set_id(sample_info.read.one.id);
      }
    }
  }
  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
    sample->mutable_callchain()->Reserve(sample_info.callchain->nr);
    for (size_t i = 0; i < sample_info.callchain->nr; ++i)
      sample->add_callchain(sample_info.callchain->ips[i]);
  }
  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
    for (size_t i = 0; i < sample_info.branch_stack->nr; ++i) {
      sample->add_branch_stack();
      const struct branch_entry& entry = sample_info.branch_stack->entries[i];
      sample->mutable_branch_stack(i)->set_from_ip(entry.from);
      sample->mutable_branch_stack(i)->set_to_ip(entry.to);
      sample->mutable_branch_stack(i)->set_mispredicted(entry.flags.mispred);
    }
  }

  if (sample_type & PERF_SAMPLE_WEIGHT) sample->set_weight(sample_info.weight);
  if (sample_type & PERF_SAMPLE_DATA_SRC)
    sample->set_data_src(sample_info.data_src);
  if (sample_type & PERF_SAMPLE_TRANSACTION)
    sample->set_transaction(sample_info.transaction);

  return true;
}

bool PerfSerializer::DeserializeSampleEvent(
    const PerfDataProto_SampleEvent& sample, event_t* event) const {
  perf_sample sample_info;
  if (sample.has_ip()) sample_info.ip = sample.ip();
  if (sample.has_pid()) {
    CHECK(sample.has_tid()) << "Cannot have PID without TID.";
    sample_info.pid = sample.pid();
    sample_info.tid = sample.tid();
  }
  if (sample.has_sample_time_ns()) sample_info.time = sample.sample_time_ns();
  if (sample.has_addr()) sample_info.addr = sample.addr();
  if (sample.has_id()) sample_info.id = sample.id();
  if (sample.has_stream_id()) sample_info.stream_id = sample.stream_id();
  if (sample.has_cpu()) sample_info.cpu = sample.cpu();
  if (sample.has_period()) sample_info.period = sample.period();
  if (sample.has_read_info()) {
    const SampleInfoReader* reader = GetSampleInfoReaderForEvent(*event);
    if (reader) {
      const PerfDataProto_ReadInfo& read_info = sample.read_info();
      if (reader->event_attr().read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
        sample_info.read.time_enabled = read_info.time_enabled();
      if (reader->event_attr().read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
        sample_info.read.time_running = read_info.time_running();
      if (reader->event_attr().read_format & PERF_FORMAT_GROUP) {
        sample_info.read.group.nr = read_info.read_value_size();
        sample_info.read.group.values =
            new sample_read_value[read_info.read_value_size()];
        for (size_t i = 0; i < sample_info.read.group.nr; i++) {
          sample_info.read.group.values[i].value =
              read_info.read_value(i).value();
          sample_info.read.group.values[i].id = read_info.read_value(i).id();
        }
      } else if (read_info.read_value_size() == 1) {
        sample_info.read.one.value = read_info.read_value(0).value();
        sample_info.read.one.id = read_info.read_value(0).id();
      } else {
        LOG(ERROR) << "Expected read_value array size of 1 but got "
                   << read_info.read_value_size();
      }
    }
  }
  if (sample.callchain_size() > 0) {
    uint64_t callchain_size = sample.callchain_size();
    sample_info.callchain = reinterpret_cast<struct ip_callchain*>(
        new uint64_t[callchain_size + 1]);
    sample_info.callchain->nr = callchain_size;
    for (size_t i = 0; i < callchain_size; ++i)
      sample_info.callchain->ips[i] = sample.callchain(i);
  }
  if (sample.raw_size() > 0) {
    sample_info.raw_size = sample.raw_size();
    sample_info.raw_data = new uint8_t[sample.raw_size()];
    memset(sample_info.raw_data, 0, sample.raw_size());
  }
  if (sample.branch_stack_size() > 0) {
    uint64_t branch_stack_size = sample.branch_stack_size();
    sample_info.branch_stack = reinterpret_cast<struct branch_stack*>(
        new uint8_t[sizeof(uint64_t) +
                    branch_stack_size * sizeof(struct branch_entry)]);
    sample_info.branch_stack->nr = branch_stack_size;
    for (size_t i = 0; i < branch_stack_size; ++i) {
      struct branch_entry& entry = sample_info.branch_stack->entries[i];
      memset(&entry, 0, sizeof(entry));
      entry.from = sample.branch_stack(i).from_ip();
      entry.to = sample.branch_stack(i).to_ip();
      entry.flags.mispred = sample.branch_stack(i).mispredicted();
      entry.flags.predicted = !entry.flags.mispred;
    }
  }

  if (sample.has_weight()) sample_info.weight = sample.weight();
  if (sample.has_data_src()) sample_info.data_src = sample.data_src();
  if (sample.has_transaction()) sample_info.transaction = sample.transaction();

  const SampleInfoReader* writer = GetSampleInfoReaderForId(sample.id());
  CHECK(writer);
  return writer->WritePerfSampleInfo(sample_info, event);
}

bool PerfSerializer::SerializeMMapEvent(const event_t& event,
                                        PerfDataProto_MMapEvent* sample) const {
  const struct mmap_event& mmap = event.mmap;
  sample->set_pid(mmap.pid);
  sample->set_tid(mmap.tid);
  sample->set_start(mmap.start);
  sample->set_len(mmap.len);
  sample->set_pgoff(mmap.pgoff);
  sample->set_filename(mmap.filename);
  sample->set_filename_md5_prefix(Md5Prefix(mmap.filename));

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeMMapEvent(const PerfDataProto_MMapEvent& sample,
                                          event_t* event) const {
  struct mmap_event& mmap = event->mmap;
  mmap.pid = sample.pid();
  mmap.tid = sample.tid();
  mmap.start = sample.start();
  mmap.len = sample.len();
  mmap.pgoff = sample.pgoff();
  snprintf(mmap.filename, PATH_MAX, "%s", sample.filename().c_str());

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeMMap2Event(
    const event_t& event, PerfDataProto_MMapEvent* sample) const {
  const struct mmap2_event& mmap = event.mmap2;
  sample->set_pid(mmap.pid);
  sample->set_tid(mmap.tid);
  sample->set_start(mmap.start);
  sample->set_len(mmap.len);
  sample->set_pgoff(mmap.pgoff);
  sample->set_maj(mmap.maj);
  sample->set_min(mmap.min);
  sample->set_ino(mmap.ino);
  sample->set_ino_generation(mmap.ino_generation);
  sample->set_prot(mmap.prot);
  sample->set_flags(mmap.flags);
  sample->set_filename(mmap.filename);
  sample->set_filename_md5_prefix(Md5Prefix(mmap.filename));

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeMMap2Event(
    const PerfDataProto_MMapEvent& sample, event_t* event) const {
  struct mmap2_event& mmap = event->mmap2;
  mmap.pid = sample.pid();
  mmap.tid = sample.tid();
  mmap.start = sample.start();
  mmap.len = sample.len();
  mmap.pgoff = sample.pgoff();
  mmap.maj = sample.maj();
  mmap.min = sample.min();
  mmap.ino = sample.ino();
  mmap.ino_generation = sample.ino_generation();
  mmap.prot = sample.prot();
  mmap.flags = sample.flags();
  snprintf(mmap.filename, PATH_MAX, "%s", sample.filename().c_str());

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeCommEvent(const event_t& event,
                                        PerfDataProto_CommEvent* sample) const {
  const struct comm_event& comm = event.comm;
  sample->set_pid(comm.pid);
  sample->set_tid(comm.tid);
  sample->set_comm(comm.comm);
  sample->set_comm_md5_prefix(Md5Prefix(comm.comm));

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeCommEvent(const PerfDataProto_CommEvent& sample,
                                          event_t* event) const {
  struct comm_event& comm = event->comm;
  comm.pid = sample.pid();
  comm.tid = sample.tid();
  snprintf(comm.comm, sizeof(comm.comm), "%s", sample.comm().c_str());

  // Sometimes the command string will be modified.  e.g. if the original comm
  // string is not recoverable from the Md5sum prefix, then use the latter as a
  // replacement comm string.  However, if the original was < 8 bytes (fit into
  // |sizeof(uint64_t)|), then the size is no longer correct.  This section
  // checks for the size difference and updates the size in the header.
  const SampleInfoReader* reader =
      GetSampleInfoReaderForId(sample.sample_info().id());
  CHECK(reader);
  uint64_t sample_fields = SampleInfoReader::GetSampleFieldsForEventType(
      comm.header.type, reader->event_attr().sample_type);
  comm.header.size = SampleInfoReader::GetPerfSampleDataOffset(*event) +
                     GetNumBits(sample_fields) * sizeof(uint64_t);

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeForkExitEvent(
    const event_t& event, PerfDataProto_ForkEvent* sample) const {
  const struct fork_event& fork = event.fork;
  sample->set_pid(fork.pid);
  sample->set_ppid(fork.ppid);
  sample->set_tid(fork.tid);
  sample->set_ptid(fork.ptid);
  sample->set_fork_time_ns(fork.time);

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeForkExitEvent(
    const PerfDataProto_ForkEvent& sample, event_t* event) const {
  struct fork_event& fork = event->fork;
  fork.pid = sample.pid();
  fork.ppid = sample.ppid();
  fork.tid = sample.tid();
  fork.ptid = sample.ptid();
  fork.time = sample.fork_time_ns();

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeLostEvent(const event_t& event,
                                        PerfDataProto_LostEvent* sample) const {
  const struct lost_event& lost = event.lost;
  sample->set_id(lost.id);
  sample->set_lost(lost.lost);

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeLostEvent(const PerfDataProto_LostEvent& sample,
                                          event_t* event) const {
  struct lost_event& lost = event->lost;
  lost.id = sample.id();
  lost.lost = sample.lost();

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeThrottleEvent(
    const event_t& event, PerfDataProto_ThrottleEvent* sample) const {
  const struct throttle_event& throttle = event.throttle;
  sample->set_time_ns(throttle.time);
  sample->set_id(throttle.id);
  sample->set_stream_id(throttle.stream_id);

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeThrottleEvent(
    const PerfDataProto_ThrottleEvent& sample, event_t* event) const {
  struct throttle_event& throttle = event->throttle;
  throttle.time = sample.time_ns();
  throttle.id = sample.id();
  throttle.stream_id = sample.stream_id();

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeReadEvent(const event_t& event,
                                        PerfDataProto_ReadEvent* sample) const {
  const struct read_event& read = event.read;
  sample->set_pid(read.pid);
  sample->set_tid(read.tid);
  sample->set_value(read.value);
  sample->set_time_enabled(read.time_enabled);
  sample->set_time_running(read.time_running);
  sample->set_id(read.id);

  return true;
}

bool PerfSerializer::DeserializeReadEvent(const PerfDataProto_ReadEvent& sample,
                                          event_t* event) const {
  struct read_event& read = event->read;
  read.pid = sample.pid();
  read.tid = sample.tid();
  read.value = sample.value();
  read.time_enabled = sample.time_enabled();
  read.time_running = sample.time_running();
  read.id = sample.id();

  return true;
}

bool PerfSerializer::SerializeAuxEvent(const event_t& event,
                                       PerfDataProto_AuxEvent* sample) const {
  const struct aux_event& aux = event.aux;
  sample->set_aux_offset(aux.aux_offset);
  sample->set_aux_size(aux.aux_size);
  sample->set_is_truncated(aux.flags & PERF_AUX_FLAG_TRUNCATED ? true : false);
  sample->set_is_overwrite(aux.flags & PERF_AUX_FLAG_OVERWRITE ? true : false);
  sample->set_is_partial(aux.flags & PERF_AUX_FLAG_PARTIAL ? true : false);
  if (aux.flags & ~(PERF_AUX_FLAG_TRUNCATED | PERF_AUX_FLAG_OVERWRITE |
                    PERF_AUX_FLAG_PARTIAL)) {
    LOG(WARNING) << "Ignoring unknown PERF_RECORD_AUX flag: " << aux.flags;
  }

  return SerializeSampleInfo(event, sample->mutable_sample_info());
}

bool PerfSerializer::DeserializeAuxEvent(const PerfDataProto_AuxEvent& sample,
                                         event_t* event) const {
  struct aux_event& aux = event->aux;
  aux.aux_offset = sample.aux_offset();
  aux.aux_size = sample.aux_size();
  aux.flags |= sample.is_truncated() ? PERF_AUX_FLAG_TRUNCATED : 0;
  aux.flags |= sample.is_overwrite() ? PERF_AUX_FLAG_OVERWRITE : 0;
  aux.flags |= sample.is_partial() ? PERF_AUX_FLAG_PARTIAL : 0;

  return DeserializeSampleInfo(sample.sample_info(), event);
}

bool PerfSerializer::SerializeSampleInfo(
    const event_t& event, PerfDataProto_SampleInfo* sample) const {
  if (!SampleIdAll()) return true;

  perf_sample sample_info;
  uint64_t sample_type = 0;
  if (!ReadPerfSampleInfoAndType(event, &sample_info, &sample_type))
    return false;

  if (sample_type & PERF_SAMPLE_TID) {
    sample->set_pid(sample_info.pid);
    sample->set_tid(sample_info.tid);
  }
  if (sample_type & PERF_SAMPLE_TIME)
    sample->set_sample_time_ns(sample_info.time);
  if ((sample_type & PERF_SAMPLE_ID) || (sample_type & PERF_SAMPLE_IDENTIFIER))
    sample->set_id(sample_info.id);
  if (sample_type & PERF_SAMPLE_CPU) sample->set_cpu(sample_info.cpu);
  if (sample_type & PERF_SAMPLE_STREAM_ID)
    sample->set_stream_id(sample_info.stream_id);
  return true;
}

bool PerfSerializer::DeserializeSampleInfo(
    const PerfDataProto_SampleInfo& sample, event_t* event) const {
  if (!SampleIdAll()) return true;

  perf_sample sample_info;
  if (sample.has_tid()) {
    sample_info.pid = sample.pid();
    sample_info.tid = sample.tid();
  }
  if (sample.has_sample_time_ns()) sample_info.time = sample.sample_time_ns();
  if (sample.has_id()) sample_info.id = sample.id();
  if (sample.has_cpu()) sample_info.cpu = sample.cpu();
  if (sample.has_stream_id()) sample_info.stream_id = sample.stream_id();

  const SampleInfoReader* writer = GetSampleInfoReaderForId(sample.id());
  CHECK(writer);
  return writer->WritePerfSampleInfo(sample_info, event);
}

bool PerfSerializer::SerializeTracingMetadata(const std::vector<char>& from,
                                              PerfDataProto* to) const {
  if (from.empty()) {
    return true;
  }
  PerfDataProto_PerfTracingMetadata* data = to->mutable_tracing_data();
  data->set_tracing_data(from.data(), from.size());
  data->set_tracing_data_md5_prefix(Md5Prefix(from));

  return true;
}

bool PerfSerializer::DeserializeTracingMetadata(const PerfDataProto& from,
                                                std::vector<char>* to) const {
  if (!from.has_tracing_data()) {
    to->clear();
    return true;
  }

  const PerfDataProto_PerfTracingMetadata& data = from.tracing_data();
  to->assign(data.tracing_data().begin(), data.tracing_data().end());
  return true;
}

bool PerfSerializer::SerializeBuildIDEvent(
    const malloced_unique_ptr<build_id_event>& from,
    PerfDataProto_PerfBuildID* to) const {
  to->set_misc(from->header.misc);
  to->set_pid(from->pid);
  to->set_filename(from->filename);
  to->set_filename_md5_prefix(Md5Prefix(from->filename));

  // Trim out trailing zeroes from the build ID.
  string build_id = RawDataToHexString(from->build_id, kBuildIDArraySize);
  TrimZeroesFromBuildIDString(&build_id);

  uint8_t build_id_bytes[kBuildIDArraySize];
  if (!HexStringToRawData(build_id, build_id_bytes, sizeof(build_id_bytes)))
    return false;

  // Used to convert build IDs (and possibly other hashes) between raw data
  // format and as string of hex digits.
  const int kHexCharsPerByte = 2;
  to->set_build_id_hash(build_id_bytes, build_id.size() / kHexCharsPerByte);

  return true;
}

bool PerfSerializer::DeserializeBuildIDEvent(
    const PerfDataProto_PerfBuildID& from,
    malloced_unique_ptr<build_id_event>* to) const {
  const string& filename = from.filename();
  size_t size = sizeof(build_id_event) + GetUint64AlignedStringLength(filename);

  malloced_unique_ptr<build_id_event>& event = *to;
  event.reset(CallocMemoryForBuildID(size));
  event->header.type = PERF_RECORD_HEADER_BUILD_ID;
  event->header.size = size;
  event->header.misc = from.misc();
  event->pid = from.pid();
  memcpy(event->build_id, from.build_id_hash().c_str(),
         from.build_id_hash().size());

  if (from.has_filename() && !filename.empty()) {
    CHECK_GT(
        snprintf(event->filename, filename.size() + 1, "%s", filename.c_str()),
        0);
  }
  return true;
}

bool PerfSerializer::SerializeAuxtraceEvent(
    const event_t& event, PerfDataProto_AuxtraceEvent* sample) const {
  const struct auxtrace_event& auxtrace = event.auxtrace;
  sample->set_size(auxtrace.size);
  sample->set_offset(auxtrace.offset);
  sample->set_reference(auxtrace.reference);
  sample->set_idx(auxtrace.idx);
  sample->set_tid(auxtrace.tid);
  sample->set_cpu(auxtrace.cpu);

  return true;
}

bool PerfSerializer::SerializeAuxtraceEventTraceData(
    const std::vector<char>& from, PerfDataProto_AuxtraceEvent* to) const {
  if (from.empty()) {
    return true;
  }
  to->set_trace_data(from.data(), from.size());

  return true;
}

bool PerfSerializer::DeserializeAuxtraceEvent(
    const PerfDataProto_AuxtraceEvent& sample, event_t* event) const {
  struct auxtrace_event& auxtrace = event->auxtrace;
  auxtrace.size = sample.size();
  auxtrace.offset = sample.offset();
  auxtrace.reference = sample.reference();
  auxtrace.idx = sample.idx();
  auxtrace.tid = sample.tid();
  auxtrace.cpu = sample.cpu();

  return true;
}

bool PerfSerializer::DeserializeAuxtraceEventTraceData(
    const PerfDataProto_AuxtraceEvent& from, std::vector<char>* to) const {
  to->assign(from.trace_data().begin(), from.trace_data().end());
  return true;
}

bool PerfSerializer::SerializeSingleUint32Metadata(
    const PerfUint32Metadata& metadata,
    PerfDataProto_PerfUint32Metadata* proto_metadata) const {
  proto_metadata->set_type(metadata.type);
  for (size_t i = 0; i < metadata.data.size(); ++i)
    proto_metadata->add_data(metadata.data[i]);
  return true;
}

bool PerfSerializer::DeserializeSingleUint32Metadata(
    const PerfDataProto_PerfUint32Metadata& proto_metadata,
    PerfUint32Metadata* metadata) const {
  metadata->type = proto_metadata.type();
  for (int i = 0; i < proto_metadata.data_size(); ++i)
    metadata->data.push_back(proto_metadata.data(i));
  return true;
}

bool PerfSerializer::SerializeSingleUint64Metadata(
    const PerfUint64Metadata& metadata,
    PerfDataProto_PerfUint64Metadata* proto_metadata) const {
  proto_metadata->set_type(metadata.type);
  for (size_t i = 0; i < metadata.data.size(); ++i)
    proto_metadata->add_data(metadata.data[i]);
  return true;
}

bool PerfSerializer::DeserializeSingleUint64Metadata(
    const PerfDataProto_PerfUint64Metadata& proto_metadata,
    PerfUint64Metadata* metadata) const {
  metadata->type = proto_metadata.type();
  for (int i = 0; i < proto_metadata.data_size(); ++i)
    metadata->data.push_back(proto_metadata.data(i));
  return true;
}

bool PerfSerializer::SerializeCPUTopologyMetadata(
    const PerfCPUTopologyMetadata& metadata,
    PerfDataProto_PerfCPUTopologyMetadata* proto_metadata) const {
  for (const string& core_name : metadata.core_siblings) {
    proto_metadata->add_core_siblings(core_name);
    proto_metadata->add_core_siblings_md5_prefix(Md5Prefix(core_name));
  }
  for (const string& thread_name : metadata.thread_siblings) {
    proto_metadata->add_thread_siblings(thread_name);
    proto_metadata->add_thread_siblings_md5_prefix(Md5Prefix(thread_name));
  }
  return true;
}

bool PerfSerializer::DeserializeCPUTopologyMetadata(
    const PerfDataProto_PerfCPUTopologyMetadata& proto_metadata,
    PerfCPUTopologyMetadata* metadata) const {
  metadata->core_siblings.clear();
  metadata->core_siblings.reserve(proto_metadata.core_siblings().size());
  std::copy(proto_metadata.core_siblings().begin(),
            proto_metadata.core_siblings().end(),
            std::back_inserter(metadata->core_siblings));

  metadata->thread_siblings.clear();
  metadata->thread_siblings.reserve(proto_metadata.thread_siblings().size());
  std::copy(proto_metadata.thread_siblings().begin(),
            proto_metadata.thread_siblings().end(),
            std::back_inserter(metadata->thread_siblings));
  return true;
}

bool PerfSerializer::SerializeNodeTopologyMetadata(
    const PerfNodeTopologyMetadata& metadata,
    PerfDataProto_PerfNodeTopologyMetadata* proto_metadata) const {
  proto_metadata->set_id(metadata.id);
  proto_metadata->set_total_memory(metadata.total_memory);
  proto_metadata->set_free_memory(metadata.free_memory);
  proto_metadata->set_cpu_list(metadata.cpu_list);
  proto_metadata->set_cpu_list_md5_prefix(Md5Prefix(metadata.cpu_list));
  return true;
}

bool PerfSerializer::DeserializeNodeTopologyMetadata(
    const PerfDataProto_PerfNodeTopologyMetadata& proto_metadata,
    PerfNodeTopologyMetadata* metadata) const {
  metadata->id = proto_metadata.id();
  metadata->total_memory = proto_metadata.total_memory();
  metadata->free_memory = proto_metadata.free_memory();
  metadata->cpu_list = proto_metadata.cpu_list();
  return true;
}

bool PerfSerializer::SerializePMUMappingsMetadata(
    const PerfPMUMappingsMetadata& metadata,
    PerfDataProto_PerfPMUMappingsMetadata* proto_metadata) const {
  proto_metadata->set_type(metadata.type);
  proto_metadata->set_name(metadata.name);
  proto_metadata->set_name_md5_prefix(Md5Prefix(metadata.name));
  return true;
}

bool PerfSerializer::DeserializePMUMappingsMetadata(
    const PerfDataProto_PerfPMUMappingsMetadata& proto_metadata,
    PerfPMUMappingsMetadata* metadata) const {
  metadata->type = proto_metadata.type();
  metadata->name = proto_metadata.name();
  return true;
}

bool PerfSerializer::SerializeGroupDescMetadata(
    const PerfGroupDescMetadata& metadata,
    PerfDataProto_PerfGroupDescMetadata* proto_metadata) const {
  proto_metadata->set_name(metadata.name);
  proto_metadata->set_name_md5_prefix(Md5Prefix(metadata.name));
  proto_metadata->set_leader_idx(metadata.leader_idx);
  proto_metadata->set_num_members(metadata.num_members);
  return true;
}

bool PerfSerializer::DeserializeGroupDescMetadata(
    const PerfDataProto_PerfGroupDescMetadata& proto_metadata,
    PerfGroupDescMetadata* metadata) const {
  metadata->name = proto_metadata.name();
  metadata->leader_idx = proto_metadata.leader_idx();
  metadata->num_members = proto_metadata.num_members();
  return true;
}

// static
void PerfSerializer::SerializeParserStats(const PerfEventStats& stats,
                                          PerfDataProto* perf_data_proto) {
  PerfDataProto_PerfEventStats* stats_pb = perf_data_proto->mutable_stats();
  stats_pb->set_num_sample_events(stats.num_sample_events);
  stats_pb->set_num_mmap_events(stats.num_mmap_events);
  stats_pb->set_num_fork_events(stats.num_fork_events);
  stats_pb->set_num_exit_events(stats.num_exit_events);
  stats_pb->set_did_remap(stats.did_remap);
  stats_pb->set_num_sample_events_mapped(stats.num_sample_events_mapped);
}

// static
void PerfSerializer::DeserializeParserStats(
    const PerfDataProto& perf_data_proto, PerfEventStats* stats) {
  const PerfDataProto_PerfEventStats& stats_pb = perf_data_proto.stats();
  stats->num_sample_events = stats_pb.num_sample_events();
  stats->num_mmap_events = stats_pb.num_mmap_events();
  stats->num_fork_events = stats_pb.num_fork_events();
  stats->num_exit_events = stats_pb.num_exit_events();
  stats->did_remap = stats_pb.did_remap();
  stats->num_sample_events_mapped = stats_pb.num_sample_events_mapped();
}

void PerfSerializer::CreateSampleInfoReader(const PerfFileAttr& attr,
                                            bool read_cross_endian) {
  for (const auto& id :
       (attr.ids.empty() ? std::initializer_list<u64>({0}) : attr.ids)) {
    sample_info_reader_map_[id].reset(
        new SampleInfoReader(attr.attr, read_cross_endian));
  }
  UpdateEventIdPositions(attr.attr);
}

void PerfSerializer::UpdateEventIdPositions(
    const struct perf_event_attr& attr) {
  const u64 sample_type = attr.sample_type;
  ssize_t new_sample_event_id_pos = EventIdPosition::NotPresent;
  ssize_t new_other_event_id_pos = EventIdPosition::NotPresent;
  if (sample_type & PERF_SAMPLE_IDENTIFIER) {
    new_sample_event_id_pos = 0;
    new_other_event_id_pos = 1;
  } else if (sample_type & PERF_SAMPLE_ID) {
    // Increment for IP, TID, TIME, ADDR
    new_sample_event_id_pos = 0;
    if (sample_type & PERF_SAMPLE_IP) new_sample_event_id_pos++;
    if (sample_type & PERF_SAMPLE_TID) new_sample_event_id_pos++;
    if (sample_type & PERF_SAMPLE_TIME) new_sample_event_id_pos++;
    if (sample_type & PERF_SAMPLE_ADDR) new_sample_event_id_pos++;

    // Increment for CPU, STREAM_ID
    new_other_event_id_pos = 1;
    if (sample_type & PERF_SAMPLE_CPU) new_other_event_id_pos++;
    if (sample_type & PERF_SAMPLE_STREAM_ID) new_other_event_id_pos++;
  }

  if (sample_event_id_pos_ == EventIdPosition::Uninitialized) {
    sample_event_id_pos_ = new_sample_event_id_pos;
  } else {
    CHECK_EQ(new_sample_event_id_pos, sample_event_id_pos_)
        << "Event ids must be in a consistent positition";
  }
  if (other_event_id_pos_ == EventIdPosition::Uninitialized) {
    other_event_id_pos_ = new_other_event_id_pos;
  } else {
    CHECK_EQ(new_other_event_id_pos, other_event_id_pos_)
        << "Event ids must be in a consistent positition";
  }
}

bool PerfSerializer::SampleIdAll() const {
  if (sample_info_reader_map_.empty()) {
    return false;
  }
  return sample_info_reader_map_.begin()->second->event_attr().sample_id_all;
}

const SampleInfoReader* PerfSerializer::GetSampleInfoReaderForEvent(
    const event_t& event) const {
  // Where is the event id?
  ssize_t event_id_pos = EventIdPosition::Uninitialized;
  if (event.header.type == PERF_RECORD_SAMPLE) {
    event_id_pos = sample_event_id_pos_;
  } else if (SampleIdAll()) {
    event_id_pos = other_event_id_pos_;
  } else {
    event_id_pos = EventIdPosition::NotPresent;
  }

  // What is the event id?
  u64 event_id;
  switch (event_id_pos) {
    case EventIdPosition::Uninitialized:
      LOG(FATAL) << "Position of the event id was not initialized!";
      return nullptr;
    case EventIdPosition::NotPresent:
      event_id = 0;
      break;
    default:
      if (event.header.type == PERF_RECORD_SAMPLE) {
        event_id = event.sample.array[event_id_pos];
      } else {
        // Pretend this is a sample event--ie, an array of u64. Find the length
        // of the array. The sample id is at the end of the array, and
        // event_id_pos (aka other_event_id_pos_) counts from the end.
        size_t event_end_pos =
            (event.header.size - sizeof(event.header)) / sizeof(u64);
        event_id = event.sample.array[event_end_pos - event_id_pos];
      }
      break;
  }
  return GetSampleInfoReaderForId(event_id);
}

const SampleInfoReader* PerfSerializer::GetSampleInfoReaderForId(
    uint64_t id) const {
  if (id) {
    auto iter = sample_info_reader_map_.find(id);
    if (iter == sample_info_reader_map_.end()) return nullptr;
    return iter->second.get();
  }

  if (sample_info_reader_map_.empty()) return nullptr;
  return sample_info_reader_map_.begin()->second.get();
}

bool PerfSerializer::ReadPerfSampleInfoAndType(const event_t& event,
                                               perf_sample* sample_info,
                                               uint64_t* sample_type) const {
  const SampleInfoReader* reader = GetSampleInfoReaderForEvent(event);
  if (!reader) {
    LOG(ERROR) << "No SampleInfoReader available";
    return false;
  }

  if (!reader->ReadPerfSampleInfo(event, sample_info)) return false;
  *sample_type = reader->event_attr().sample_type;
  return true;
}

}  // namespace quipper