// 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 "chromiumos-wide-profiling/perf_serializer.h"

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

#include <bitset>
#include <utility>

#include "base/logging.h"

#include "chromiumos-wide-profiling/compat/string.h"
#include "chromiumos-wide-profiling/utils.h"

namespace quipper {

PerfSerializer::PerfSerializer() : serialize_sorted_events_(true) {
}

PerfSerializer::~PerfSerializer() {
}

bool PerfSerializer::SerializeFromFile(const string& filename,
                                       PerfDataProto* perf_data_proto) {
  if (!ReadFile(filename))
    return false;
  return Serialize(perf_data_proto);
}

bool PerfSerializer::Serialize(PerfDataProto* perf_data_proto) {
  if (!SerializePerfFileAttrs(attrs_, perf_data_proto->mutable_file_attrs())) {
    return false;
  }

  if (HaveEventNames() &&
      !SerializePerfEventTypes(
          attrs_, perf_data_proto->mutable_event_types())) {
    return false;
  }

  if (!parser_.ParseRawEvents()) {
    return false;
  }

  // Serialize events in either chronological (if possible) or raw data order.
  if (!serialize_sorted_events_ &&
      !SerializeEvents(parser_.parsed_events(),
                       perf_data_proto->mutable_events())) {
    return false;
  } else if (serialize_sorted_events_ &&
      !SerializeEventPointers(
          parser_.GetEventsSortedByTime(),
          perf_data_proto->mutable_events())) {
    return false;
  }

  perf_data_proto->add_metadata_mask(metadata_mask_);

  if (!SerializeMetadata(perf_data_proto)) {
    return false;
  }

  // Add a timestamp_sec to the protobuf.
  struct timeval timestamp_sec;
  if (!gettimeofday(&timestamp_sec, NULL))
    perf_data_proto->set_timestamp_sec(timestamp_sec.tv_sec);

  const PerfEventStats& stats = parser_.stats();
  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);
  return true;
}

bool PerfSerializer::DeserializeToFile(const PerfDataProto& perf_data_proto,
                                       const string& filename) {
  return Deserialize(perf_data_proto) && WriteFile(filename);
}

bool PerfSerializer::Deserialize(const PerfDataProto& perf_data_proto) {
  if (!DeserializePerfFileAttrs(perf_data_proto.file_attrs(), &attrs_))
    return false;
  if (static_cast<size_t>(perf_data_proto.event_types().size()) ==
      attrs_.size() &&
      !DeserializePerfEventTypes(perf_data_proto.event_types(), &attrs_)) {
    return false;
  }

  // Make sure all event types (attrs) have the same sample type.
  for (size_t i = 0; i < attrs_.size(); ++i) {
    CHECK_EQ(attrs_[i].attr.sample_type, attrs_[0].attr.sample_type)
        << "Sample type for attribute #" << i
        << " (" << std::hex << attrs_[i].attr.sample_type << ")"
        << " does not match that of attribute 0"
        << " (" << std::hex << attrs_[0].attr.sample_type << ")";
  }
  CHECK_GT(attrs_.size(), 0U);
  sample_type_ = attrs_[0].attr.sample_type;

  if (!DeserializeEvents(perf_data_proto.events(), &events_)) {
    return false;
  }

  if (!parser_.ParseRawEvents())
    return false;

  if (perf_data_proto.metadata_mask_size())
    metadata_mask_ = perf_data_proto.metadata_mask(0);

  if (!DeserializeMetadata(perf_data_proto)) {
    return false;
  }

  const PerfDataProto_PerfEventStats& stats_pb = perf_data_proto.stats();
  PerfEventStats stats = {0};
  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();
  parser_.mutable_stats() = stats;

  return true;
}

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);  // TODO(dhsharp): or config1?
  S(bp_len);   // TODO(dhsharp): or config2?
  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);  // TODO(dhsharp): or config1?
  S(bp_len);   // TODO(dhsharp): or config2?
  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) << "Deserializing event types, id did not match attr.config. "
                  "Not deserializing the event name!";
    return false;
  }
  event_attr->name = event_type_proto.name();
  return true;
}

bool PerfSerializer::SerializeEventPointer(
    const ParsedEvent* event_ptr,
    PerfDataProto_PerfEvent* event_proto) const {
  const ParsedEvent& event = *event_ptr;
  return SerializeEvent(event, event_proto);
}

bool PerfSerializer::SerializeEvent(
    const ParsedEvent& event,
    PerfDataProto_PerfEvent* event_proto) const {
  const event_t& raw_event = *event.raw_event;
  if (!SerializeEventHeader(raw_event.header, event_proto->mutable_header()))
    return false;
  switch (raw_event.header.type) {
    case PERF_RECORD_SAMPLE:
      if (!SerializeRecordSample(raw_event,
                                 event_proto->mutable_sample_event())) {
        return false;
      }
      break;
    case PERF_RECORD_MMAP:
      if (!SerializeMMapSample(raw_event, event_proto->mutable_mmap_event()))
        return false;
      break;
    case PERF_RECORD_MMAP2:
      if (!SerializeMMap2Sample(raw_event, event_proto->mutable_mmap_event()))
        return false;
      break;
    case PERF_RECORD_COMM:
      if (!SerializeCommSample(raw_event, event_proto->mutable_comm_event()))
        return false;
      break;
    case PERF_RECORD_EXIT:
    case PERF_RECORD_FORK:
      if (!SerializeForkSample(raw_event, event_proto->mutable_fork_event()))
        return false;
      break;
    case PERF_RECORD_LOST:
      if (!SerializeLostSample(raw_event, event_proto->mutable_lost_event()))
        return false;
      break;
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
      if (!SerializeThrottleSample(raw_event,
                                   event_proto->mutable_throttle_event())) {
        return false;
      }
      break;
    case PERF_RECORD_READ:
      if (!SerializeReadSample(raw_event, event_proto->mutable_read_event()))
        return false;
      break;
    default:
      LOG(ERROR) << "Unknown raw_event type: " << raw_event.header.type;
      break;
  }
  return true;
}

bool PerfSerializer::DeserializeEvent(
    const PerfDataProto_PerfEvent& event_proto,
    malloced_unique_ptr<event_t>* event) const {
  // The deserialized header.size should not be completely trusted, because the
  // event content may have been changed (eg, md5 string replacement).
  // Since we don't know how much memory to allocate, use an oversized event_t
  // until the final size is known.
  // TODO(dhsharp): Come up with a better size prediction. We don't want to
  // overrun the allocated space. sizeof(event_t) is almost meaningless wrt the
  // space necessary for the event--it is the size of the largest member of the
  // union. For better or worse, this is dominated by the PATH_MAX-sized array
  // in struct mmap2_event. However, events also have a "sample id" placed
  // immediately after the struct. Previously to this, an on-stack event_t was
  // used, which is dangerous because the stack could have been overrun. Since
  // no issues were reported, we can presume that amount of space was
  // sufficient.
  perf_event_header header = {};
  if (!DeserializeEventHeader(event_proto.header(), &header))
    return false;
  const size_t alloc_event_size = header.size + 4096;
  malloced_unique_ptr<event_t> temp_event(
      CallocMemoryForEvent(alloc_event_size));
  temp_event->header = header;
  bool event_deserialized = true;
  switch (event_proto.header().type()) {
    case PERF_RECORD_SAMPLE:
      if (!DeserializeRecordSample(event_proto.sample_event(),
                                   temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_MMAP:
      if (!DeserializeMMapSample(event_proto.mmap_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_MMAP2:
      if (!DeserializeMMap2Sample(event_proto.mmap_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_COMM:
      if (!DeserializeCommSample(event_proto.comm_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_EXIT:
    case PERF_RECORD_FORK:
      if (!DeserializeForkSample(event_proto.fork_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_LOST:
      if (!DeserializeLostSample(event_proto.lost_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
      if (!DeserializeThrottleSample(event_proto.throttle_event(),
                                     temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_READ:
      if (!DeserializeReadSample(event_proto.read_event(), temp_event.get()))
        event_deserialized = false;
      break;
    case PERF_RECORD_MAX:
    default:
      event_deserialized = false;
      break;
  }
  if (!event_deserialized) {
    event->reset();
    return false;
  }
  // Reallocate the event down to its final size.
  const size_t final_event_size = temp_event->header.size;
  CHECK_LE(final_event_size, alloc_event_size)
      << "Likely overran the event buffer.";
  temp_event.reset(ReallocMemoryForEvent(temp_event.release(),
                                         final_event_size));

  *event = std::move(temp_event);
  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::SerializeRecordSample(
    const event_t& event,
    PerfDataProto_SampleEvent* sample) const {
  perf_sample sample_info;
  if (!ReadPerfSampleInfo(event, &sample_info))
    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->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);
  // TODO(sque): We don't have a use case for the raw data so just store the
  // size.  The data is assumed to be all zeroes.  So far it has been such.
  if (sample_type_ & PERF_SAMPLE_RAW)
    sample->set_raw_size(sample_info.raw_size);
  if (sample_type_ & PERF_SAMPLE_CALLCHAIN) {
    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);
    }
  }
  return true;
}

bool PerfSerializer::DeserializeRecordSample(
    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.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;
    }
  }
  return WritePerfSampleInfo(sample_info, event);
}

bool PerfSerializer::SerializeMMapSample(
    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::DeserializeMMapSample(
    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::SerializeMMap2Sample(
    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::DeserializeMMap2Sample(
    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::SerializeCommSample(
      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::DeserializeCommSample(
    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.
  uint64_t sample_fields =
      GetSampleFieldsForEventType(comm.header.type, sample_type_);
  std::bitset<sizeof(sample_fields) * CHAR_BIT> sample_type_bits(sample_fields);
  comm.header.size = GetPerfSampleDataOffset(*event) +
                     sample_type_bits.count() * sizeof(uint64_t);

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

bool PerfSerializer::SerializeForkSample(
    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.ppid);
  sample->set_fork_time_ns(fork.time);

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

bool PerfSerializer::DeserializeForkSample(
    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::SerializeLostSample(
    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::DeserializeLostSample(
    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::SerializeThrottleSample(
    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::DeserializeThrottleSample(
    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::SerializeReadSample(
    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::DeserializeReadSample(
    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::SerializeSampleInfo(
    const event_t& event,
    PerfDataProto_SampleInfo* sample) const {
  perf_sample sample_info;
  if (!ReadPerfSampleInfo(event, &sample_info))
    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->set_id(sample_info.id);
  if (sample_type_ & PERF_SAMPLE_CPU)
    sample->set_cpu(sample_info.cpu);
  return true;
}

bool PerfSerializer::DeserializeSampleInfo(
    const PerfDataProto_SampleInfo& sample,
    event_t* event) const {
  perf_sample sample_info;
  size_t sample_info_size = 0;
  if (sample.has_tid()) {
    sample_info.pid = sample.pid();
    sample_info.tid = sample.tid();
    sample_info_size += sizeof(uint64_t);
  }
  if (sample.has_sample_time_ns()) {
    sample_info.time = sample.sample_time_ns();
    sample_info_size += sizeof(uint64_t);
  }
  if (sample.has_id()) {
    sample_info.id = sample.id();
    sample_info_size += sizeof(uint64_t);
  }
  if (sample.has_cpu()) {
    sample_info.cpu = sample.cpu();
    sample_info_size += sizeof(uint64_t);
  }

  // The event info may have changed (e.g. strings replaced with Md5sum), so
  // adjust the size accordingly.
  event->header.size = GetPerfSampleDataOffset(*event) + sample_info_size;

  return 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::SerializeBuildIDs(
    const std::vector<build_id_event*>& from,
    RepeatedPtrField<PerfDataProto_PerfBuildID>* to) const {
  return SerializeBuildIDEvents(from, to);
}

bool PerfSerializer::DeserializeBuildIDs(
    const RepeatedPtrField<PerfDataProto_PerfBuildID>& from,
    std::vector<build_id_event*>* to) const {
  // Free any existing build id events.
  for (size_t i = 0; i < to->size(); ++i)
    free(to->at(i));
  to->clear();

  return DeserializeBuildIDEvents(from, to);
}

bool PerfSerializer::SerializeMetadata(PerfDataProto* to) const {
  if (!SerializeTracingMetadata(tracing_data_, to) ||
      !SerializeBuildIDs(build_id_events_, to->mutable_build_ids()) ||
      !SerializeUint32Metadata(uint32_metadata_,
                               to->mutable_uint32_metadata()) ||
      !SerializeUint64Metadata(uint64_metadata_,
                               to->mutable_uint64_metadata()) ||
      !SerializeCPUTopologyMetadata(cpu_topology_,
                                    to->mutable_cpu_topology()) ||
      !SerializeNUMATopologyMetadata(numa_topology_,
                                     to->mutable_numa_topology())) {
    return false;
  }
  typedef PerfDataProto_StringMetadata_StringAndMd5sumPrefix
      StringAndMd5sumPrefix;
  // Handle the string metadata specially.
  for (size_t i = 0; i < string_metadata_.size(); ++i) {
    StringAndMd5sumPrefix* to_metadata = NULL;
    uint32_t type = string_metadata_[i].type;
    PerfDataProto_StringMetadata* proto_string_metadata =
        to->mutable_string_metadata();
    bool is_command_line = false;
    switch (type) {
    case HEADER_HOSTNAME:
      to_metadata = proto_string_metadata->mutable_hostname();
      break;
    case HEADER_OSRELEASE:
      to_metadata = proto_string_metadata->mutable_kernel_version();
      break;
    case HEADER_VERSION:
      to_metadata = proto_string_metadata->mutable_perf_version();
      break;
    case HEADER_ARCH:
      to_metadata = proto_string_metadata->mutable_architecture();
      break;
    case HEADER_CPUDESC:
      to_metadata = proto_string_metadata->mutable_cpu_description();
      break;
    case HEADER_CPUID:
      to_metadata = proto_string_metadata->mutable_cpu_id();
      break;
    case HEADER_CMDLINE:
      is_command_line = true;
      to_metadata = proto_string_metadata->mutable_perf_command_line_whole();
      break;
    default:
      LOG(ERROR) << "Unsupported string metadata type: " << type;
      continue;
    }
    if (is_command_line) {
      // Handle command lines as a special case. It has two protobuf fields, one
      // of which is a repeated field.
      string full_command_line;
      for (size_t j = 0; j < string_metadata_[i].data.size(); ++j) {
        StringAndMd5sumPrefix* command_line_token =
                proto_string_metadata->add_perf_command_line_token();
        command_line_token->set_value(string_metadata_[i].data[j].str);
        command_line_token->
            set_value_md5_prefix(Md5Prefix(command_line_token->value()));
        full_command_line += string_metadata_[i].data[j].str + " ";
      }
      // Delete the extra space at the end of the newly created command string.
      TrimWhitespace(&full_command_line);
      to_metadata->set_value(full_command_line);
      to_metadata->set_value_md5_prefix(Md5Prefix(full_command_line));
    } else {
      DCHECK(to_metadata);  // Make sure a valid destination metadata was found.
      // In some cases there is a null or empty string metadata value in the
      // perf data. Make sure not to access |string_metadata_[i].data[0]| if
      // that is the case.
      if (!string_metadata_[i].data.empty()) {
        to_metadata->set_value(string_metadata_[i].data[0].str);
      } else {
        to_metadata->set_value(string());
      }
      to_metadata->set_value_md5_prefix(Md5Prefix(to_metadata->value()));
    }
  }
  return true;
}

bool PerfSerializer::DeserializeMetadata(const PerfDataProto& from) {
  if (!DeserializeTracingMetadata(from, &tracing_data_) ||
      !DeserializeBuildIDs(from.build_ids(), &build_id_events_) ||
      !DeserializeUint32Metadata(from.uint32_metadata(), &uint32_metadata_) ||
      !DeserializeUint64Metadata(from.uint64_metadata(), &uint64_metadata_) ||
      !DeserializeCPUTopologyMetadata(from.cpu_topology(), &cpu_topology_) ||
      !DeserializeNUMATopologyMetadata(from.numa_topology(), &numa_topology_)) {
    return false;
  }

  // Handle the string metadata specially.
  typedef PerfDataProto_StringMetadata_StringAndMd5sumPrefix
      StringAndMd5sumPrefix;
  const PerfDataProto_StringMetadata& data = from.string_metadata();
  std::vector<std::pair<u32, StringAndMd5sumPrefix> > metadata_strings;
  if (data.has_hostname()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_HOSTNAME), data.hostname()));
  }
  if (data.has_kernel_version()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_OSRELEASE),
                       data.kernel_version()));
  }
  if (data.has_perf_version()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_VERSION), data.perf_version()));
  }
  if (data.has_architecture()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_ARCH), data.architecture()));
  }
  if (data.has_cpu_description()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_CPUDESC),
                       data.cpu_description()));
  }
  if (data.has_cpu_id()) {
    metadata_strings.push_back(
        std::make_pair(static_cast<u32>(HEADER_CPUID), data.cpu_id()));
  }

  // Add each string metadata element to |string_metadata_|.
  for (size_t i = 0; i < metadata_strings.size(); ++i) {
    PerfStringMetadata metadata;
    metadata.type = metadata_strings[i].first;
    CStringWithLength cstring;
    cstring.str = metadata_strings[i].second.value();
    cstring.len = cstring.str.size() + 1;   // Include the null terminator.
    metadata.data.push_back(cstring);

    string_metadata_.push_back(metadata);
  }

  // Add the command line tokens as a special case (repeated field).
  if (data.perf_command_line_token_size() > 0) {
    PerfStringMetadata metadata;
    metadata.type = HEADER_CMDLINE;
    for (int i = 0; i < data.perf_command_line_token_size(); ++i) {
      CStringWithLength cstring;
      cstring.str = data.perf_command_line_token(i).value();
      cstring.len = cstring.str.size() + 1;   // Include the null terminator.
      metadata.data.push_back(cstring);
    }
    string_metadata_.push_back(metadata);
  }

  return true;
}

bool PerfSerializer::SerializeBuildIDEvent(
    const build_id_event* from,
    PerfDataProto_PerfBuildID* to) const {
  to->set_misc(from->header.misc);
  to->set_pid(from->pid);
  to->set_build_id_hash(from->build_id, kBuildIDArraySize);
  to->set_filename(from->filename);
  to->set_filename_md5_prefix(Md5Prefix(from->filename));
  return true;
}

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

  build_id_event* event = CallocMemoryForBuildID(size);
  *to = event;
  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(), kBuildIDArraySize);

  if (from.has_filename() && !filename.empty()) {
    CHECK_GT(snprintf(event->filename, filename.size() + 1, "%s",
                      filename.c_str()),
             0);
  }
  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 (size_t i = 0; i < metadata.core_siblings.size(); ++i) {
    const string& str = metadata.core_siblings[i].str;
    proto_metadata->add_core_siblings(str);
    proto_metadata->add_core_siblings_md5_prefix(Md5Prefix(str));
  }

  for (size_t i = 0; i < metadata.thread_siblings.size(); ++i) {
    const string& str = metadata.thread_siblings[i].str;
    proto_metadata->add_thread_siblings(str);
    proto_metadata->add_thread_siblings_md5_prefix(Md5Prefix(str));
  }
  return true;
}

bool PerfSerializer::DeserializeCPUTopologyMetadata(
    const PerfDataProto_PerfCPUTopologyMetadata& proto_metadata,
    PerfCPUTopologyMetadata* metadata) const {
  for (int i = 0; i < proto_metadata.core_siblings_size(); ++i) {
    CStringWithLength core;
    core.str = proto_metadata.core_siblings(i);
    core.len = GetUint64AlignedStringLength(core.str);
    metadata->core_siblings.push_back(core);
  }

  for (int i = 0; i < proto_metadata.thread_siblings_size(); ++i) {
    CStringWithLength thread;
    thread.str = proto_metadata.thread_siblings(i);
    thread.len = GetUint64AlignedStringLength(thread.str);
    metadata->thread_siblings.push_back(thread);
  }
  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.str);
  proto_metadata->set_cpu_list_md5_prefix(Md5Prefix(metadata.cpu_list.str));
  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.str = proto_metadata.cpu_list();
  metadata->cpu_list.len = GetUint64AlignedStringLength(metadata->cpu_list.str);
  return true;
}

}  // namespace quipper
