// Copyright 2015 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 "sample_info_reader.h"

#include <string.h>

#include "base/logging.h"
#include "buffer_reader.h"
#include "buffer_writer.h"
#include "kernel/perf_internals.h"
#include "perf_data_utils.h"

namespace quipper {

namespace {

bool IsSupportedEventType(uint32_t type) {
  switch (type) {
    case PERF_RECORD_SAMPLE:
    case PERF_RECORD_MMAP:
    case PERF_RECORD_MMAP2:
    case PERF_RECORD_FORK:
    case PERF_RECORD_EXIT:
    case PERF_RECORD_COMM:
    case PERF_RECORD_LOST:
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
    case PERF_RECORD_AUX:
      return true;
    case PERF_RECORD_READ:
    case PERF_RECORD_MAX:
      return false;
    default:
      LOG(FATAL) << "Unknown event type " << type;
      return false;
  }
}

// Read read info from perf data.  Corresponds to sample format type
// PERF_SAMPLE_READ in the !PERF_FORMAT_GROUP case.
void ReadReadInfo(DataReader* reader, uint64_t read_format,
                  struct perf_sample* sample) {
  reader->ReadUint64(&sample->read.one.value);
  if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
    reader->ReadUint64(&sample->read.time_enabled);
  if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
    reader->ReadUint64(&sample->read.time_running);
  if (read_format & PERF_FORMAT_ID) reader->ReadUint64(&sample->read.one.id);
}

// Read read info from perf data.  Corresponds to sample format type
// PERF_SAMPLE_READ in the PERF_FORMAT_GROUP case.
void ReadGroupReadInfo(DataReader* reader, uint64_t read_format,
                       struct perf_sample* sample) {
  uint64_t num = 0;
  reader->ReadUint64(&num);
  if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
    reader->ReadUint64(&sample->read.time_enabled);
  if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
    reader->ReadUint64(&sample->read.time_running);

  // Make sure there is no existing allocated memory in
  // |sample->read.group.values|.
  CHECK_EQ(static_cast<void*>(NULL), sample->read.group.values);
  sample_read_value* values = new sample_read_value[num];
  for (uint64_t i = 0; i < num; i++) {
    reader->ReadUint64(&values[i].value);
    if (read_format & PERF_FORMAT_ID) reader->ReadUint64(&values[i].id);
  }
  sample->read.group.nr = num;
  sample->read.group.values = values;
}

// Read call chain info from perf data.  Corresponds to sample format type
// PERF_SAMPLE_CALLCHAIN.
void ReadCallchain(DataReader* reader, struct perf_sample* sample) {
  // Make sure there is no existing allocated memory in |sample->callchain|.
  CHECK_EQ(static_cast<void*>(NULL), sample->callchain);

  // The callgraph data consists of a uint64_t value |nr| followed by |nr|
  // addresses.
  uint64_t callchain_size = 0;
  reader->ReadUint64(&callchain_size);

  struct ip_callchain* callchain =
      reinterpret_cast<struct ip_callchain*>(new uint64_t[callchain_size + 1]);
  callchain->nr = callchain_size;

  for (size_t i = 0; i < callchain_size; ++i)
    reader->ReadUint64(&callchain->ips[i]);

  sample->callchain = callchain;
}

// Read raw info from perf data.  Corresponds to sample format type
// PERF_SAMPLE_RAW.
void ReadRawData(DataReader* reader, struct perf_sample* sample) {
  // Save the original read offset.
  size_t reader_offset = reader->Tell();

  reader->ReadUint32(&sample->raw_size);

  // Allocate space for and read the raw data bytes.
  sample->raw_data = new uint8_t[sample->raw_size];
  reader->ReadData(sample->raw_size, sample->raw_data);

  // Determine the bytes that were read, and align to the next 64 bits.
  reader_offset += Align<uint64_t>(sizeof(sample->raw_size) + sample->raw_size);
  reader->SeekSet(reader_offset);
}

// Read call chain info from perf data.  Corresponds to sample format type
// PERF_SAMPLE_CALLCHAIN.
void ReadBranchStack(DataReader* reader, struct perf_sample* sample) {
  // Make sure there is no existing allocated memory in
  // |sample->branch_stack|.
  CHECK_EQ(static_cast<void*>(NULL), sample->branch_stack);

  // The branch stack data consists of a uint64_t value |nr| followed by |nr|
  // branch_entry structs.
  uint64_t branch_stack_size = 0;
  reader->ReadUint64(&branch_stack_size);

  struct branch_stack* branch_stack = reinterpret_cast<struct branch_stack*>(
      new uint8_t[sizeof(uint64_t) +
                  branch_stack_size * sizeof(struct branch_entry)]);
  branch_stack->nr = branch_stack_size;
  for (size_t i = 0; i < branch_stack_size; ++i) {
    reader->ReadUint64(&branch_stack->entries[i].from);
    reader->ReadUint64(&branch_stack->entries[i].to);
    reader->ReadData(sizeof(branch_stack->entries[i].flags),
                     &branch_stack->entries[i].flags);
    if (reader->is_cross_endian()) {
      LOG(ERROR) << "Byte swapping of branch stack flags is not yet supported.";
    }
  }
  sample->branch_stack = branch_stack;
}

// Reads perf sample info data from |event| into |sample|.
// |attr| is the event attribute struct, which contains info such as which
// sample info fields are present.
// |is_cross_endian| indicates that the data is cross-endian and that the byte
// order should be reversed for each field according to its size.
// Returns number of bytes of data read or skipped.
size_t ReadPerfSampleFromData(const event_t& event,
                              const struct perf_event_attr& attr,
                              bool is_cross_endian,
                              struct perf_sample* sample) {
  BufferReader reader(&event, event.header.size);
  reader.set_is_cross_endian(is_cross_endian);
  reader.SeekSet(SampleInfoReader::GetPerfSampleDataOffset(event));

  if (!(event.header.type == PERF_RECORD_SAMPLE || attr.sample_id_all)) {
    return reader.Tell();
  }

  uint64_t sample_fields = SampleInfoReader::GetSampleFieldsForEventType(
      event.header.type, attr.sample_type);

  // See structure for PERF_RECORD_SAMPLE in kernel/perf_event.h
  // and compare sample_id when sample_id_all is set.

  // NB: For sample_id, sample_fields has already been masked to the set
  // of fields in that struct by GetSampleFieldsForEventType. That set
  // of fields is mostly in the same order as PERF_RECORD_SAMPLE, with
  // the exception of PERF_SAMPLE_IDENTIFIER.

  // PERF_SAMPLE_IDENTIFIER is in a different location depending on
  // if this is a SAMPLE event or the sample_id of another event.
  if (event.header.type == PERF_RECORD_SAMPLE) {
    // { u64                   id;       } && PERF_SAMPLE_IDENTIFIER
    if (sample_fields & PERF_SAMPLE_IDENTIFIER) {
      reader.ReadUint64(&sample->id);
    }
  }

  // { u64                   ip;       } && PERF_SAMPLE_IP
  if (sample_fields & PERF_SAMPLE_IP) {
    reader.ReadUint64(&sample->ip);
  }

  // { u32                   pid, tid; } && PERF_SAMPLE_TID
  if (sample_fields & PERF_SAMPLE_TID) {
    reader.ReadUint32(&sample->pid);
    reader.ReadUint32(&sample->tid);
  }

  // { u64                   time;     } && PERF_SAMPLE_TIME
  if (sample_fields & PERF_SAMPLE_TIME) {
    reader.ReadUint64(&sample->time);
  }

  // { u64                   addr;     } && PERF_SAMPLE_ADDR
  if (sample_fields & PERF_SAMPLE_ADDR) {
    reader.ReadUint64(&sample->addr);
  }

  // { u64                   id;       } && PERF_SAMPLE_ID
  if (sample_fields & PERF_SAMPLE_ID) {
    reader.ReadUint64(&sample->id);
  }

  // { u64                   stream_id;} && PERF_SAMPLE_STREAM_ID
  if (sample_fields & PERF_SAMPLE_STREAM_ID) {
    reader.ReadUint64(&sample->stream_id);
  }

  // { u32                   cpu, res; } && PERF_SAMPLE_CPU
  if (sample_fields & PERF_SAMPLE_CPU) {
    reader.ReadUint32(&sample->cpu);

    // The PERF_SAMPLE_CPU format bit specifies 64-bits of data, but the actual
    // CPU number is really only 32 bits. There is an extra 32-bit word of
    // reserved padding, as the whole field is aligned to 64 bits.

    // reader.ReadUint32(&sample->res);  // reserved
    u32 reserved;
    reader.ReadUint32(&reserved);
  }

  // This is the location of PERF_SAMPLE_IDENTIFIER in struct sample_id.
  if (event.header.type != PERF_RECORD_SAMPLE) {
    // { u64                   id;       } && PERF_SAMPLE_IDENTIFIER
    if (sample_fields & PERF_SAMPLE_IDENTIFIER) {
      reader.ReadUint64(&sample->id);
    }
  }

  //
  // The remaining fields are only in PERF_RECORD_SAMPLE
  //

  // { u64                   period;   } && PERF_SAMPLE_PERIOD
  if (sample_fields & PERF_SAMPLE_PERIOD) {
    reader.ReadUint64(&sample->period);
  }

  // { struct read_format    values;   } && PERF_SAMPLE_READ
  if (sample_fields & PERF_SAMPLE_READ) {
    if (attr.read_format & PERF_FORMAT_GROUP)
      ReadGroupReadInfo(&reader, attr.read_format, sample);
    else
      ReadReadInfo(&reader, attr.read_format, sample);
  }

  // { u64                   nr,
  //   u64                   ips[nr];  } && PERF_SAMPLE_CALLCHAIN
  if (sample_fields & PERF_SAMPLE_CALLCHAIN) {
    ReadCallchain(&reader, sample);
  }

  // { u32                   size;
  //   char                  data[size];}&& PERF_SAMPLE_RAW
  if (sample_fields & PERF_SAMPLE_RAW) {
    ReadRawData(&reader, sample);
  }

  // { u64                   nr;
  //   { u64 from, to, flags } lbr[nr];} && PERF_SAMPLE_BRANCH_STACK
  if (sample_fields & PERF_SAMPLE_BRANCH_STACK) {
    ReadBranchStack(&reader, sample);
  }

  // { u64                   abi; # enum perf_sample_regs_abi
  //   u64                   regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER
  if (sample_fields & PERF_SAMPLE_REGS_USER) {
    VLOG(1) << "Skipping PERF_SAMPLE_REGS_USER data.";
    u64 abi;
    if (!reader.ReadUint64(&abi)) {
      return false;
    }
    size_t weight = abi == 0 ? 0 : __builtin_popcountll(attr.sample_regs_user);
    u64 regs[64];
    if (weight > 0 && !reader.ReadData(weight * sizeof(u64), regs)) {
      return false;
    }
  }

  // { u64                   size;
  //   char                  data[size];
  //   u64                   dyn_size; } && PERF_SAMPLE_STACK_USER
  if (sample_fields & PERF_SAMPLE_STACK_USER) {
    VLOG(1) << "Skipping PERF_SAMPLE_STACK_USER data.";
    u64 size;
    if (!reader.ReadUint64(&size)) {
      return false;
    }
    if (size != 0) {
      std::unique_ptr<char[]> data(new char[size]);
      if (!reader.ReadData(size, data.get())) {
        return false;
      }
      u64 dyn_size;
      reader.ReadUint64(&dyn_size);
    }
  }

  // { u64                   weight;   } && PERF_SAMPLE_WEIGHT
  if (sample_fields & PERF_SAMPLE_WEIGHT) {
    reader.ReadUint64(&sample->weight);
  }

  // { u64                   data_src; } && PERF_SAMPLE_DATA_SRC
  if (sample_fields & PERF_SAMPLE_DATA_SRC) {
    reader.ReadUint64(&sample->data_src);
  }

  // { u64                   transaction; } && PERF_SAMPLE_TRANSACTION
  if (sample_fields & PERF_SAMPLE_TRANSACTION) {
    reader.ReadUint64(&sample->transaction);
  }

  if (sample_fields & ~(PERF_SAMPLE_MAX - 1)) {
    LOG(WARNING) << "Unrecognized sample fields 0x" << std::hex
                 << (sample_fields & ~(PERF_SAMPLE_MAX - 1));
  }

  return reader.Tell();
}

// Writes sample info data data from |sample| into |event|.
// |attr| is the event attribute struct, which contains info such as which
// sample info fields are present.
// |event| is the destination event. Its header should already be filled out.
// Returns the number of bytes written or skipped.
size_t WritePerfSampleToData(const struct perf_sample& sample,
                             const struct perf_event_attr& attr,
                             event_t* event) {
  const uint64_t* initial_array_ptr = reinterpret_cast<const uint64_t*>(event);

  uint64_t offset = SampleInfoReader::GetPerfSampleDataOffset(*event);
  uint64_t* array =
      reinterpret_cast<uint64_t*>(event) + offset / sizeof(uint64_t);

  if (!(event->header.type == PERF_RECORD_SAMPLE || attr.sample_id_all)) {
    return offset;
  }

  uint64_t sample_fields = SampleInfoReader::GetSampleFieldsForEventType(
      event->header.type, attr.sample_type);

  union {
    uint32_t val32[sizeof(uint64_t) / sizeof(uint32_t)];
    uint64_t val64;
  };

  // See notes at the top of ReadPerfSampleFromData regarding the structure
  // of PERF_RECORD_SAMPLE, sample_id, and PERF_SAMPLE_IDENTIFIER, as they
  // all apply here as well.

  // PERF_SAMPLE_IDENTIFIER is in a different location depending on
  // if this is a SAMPLE event or the sample_id of another event.
  if (event->header.type == PERF_RECORD_SAMPLE) {
    // { u64                   id;       } && PERF_SAMPLE_IDENTIFIER
    if (sample_fields & PERF_SAMPLE_IDENTIFIER) {
      *array++ = sample.id;
    }
  }

  // { u64                   ip;       } && PERF_SAMPLE_IP
  if (sample_fields & PERF_SAMPLE_IP) {
    *array++ = sample.ip;
  }

  // { u32                   pid, tid; } && PERF_SAMPLE_TID
  if (sample_fields & PERF_SAMPLE_TID) {
    val32[0] = sample.pid;
    val32[1] = sample.tid;
    *array++ = val64;
  }

  // { u64                   time;     } && PERF_SAMPLE_TIME
  if (sample_fields & PERF_SAMPLE_TIME) {
    *array++ = sample.time;
  }

  // { u64                   addr;     } && PERF_SAMPLE_ADDR
  if (sample_fields & PERF_SAMPLE_ADDR) {
    *array++ = sample.addr;
  }

  // { u64                   id;       } && PERF_SAMPLE_ID
  if (sample_fields & PERF_SAMPLE_ID) {
    *array++ = sample.id;
  }

  // { u64                   stream_id;} && PERF_SAMPLE_STREAM_ID
  if (sample_fields & PERF_SAMPLE_STREAM_ID) {
    *array++ = sample.stream_id;
  }

  // { u32                   cpu, res; } && PERF_SAMPLE_CPU
  if (sample_fields & PERF_SAMPLE_CPU) {
    val32[0] = sample.cpu;
    // val32[1] = sample.res;  // reserved
    val32[1] = 0;
    *array++ = val64;
  }

  // This is the location of PERF_SAMPLE_IDENTIFIER in struct sample_id.
  if (event->header.type != PERF_RECORD_SAMPLE) {
    // { u64                   id;       } && PERF_SAMPLE_IDENTIFIER
    if (sample_fields & PERF_SAMPLE_IDENTIFIER) {
      *array++ = sample.id;
    }
  }

  //
  // The remaining fields are only in PERF_RECORD_SAMPLE
  //

  // { u64                   period;   } && PERF_SAMPLE_PERIOD
  if (sample_fields & PERF_SAMPLE_PERIOD) {
    *array++ = sample.period;
  }

  // { struct read_format    values;   } && PERF_SAMPLE_READ
  if (sample_fields & PERF_SAMPLE_READ) {
    if (attr.read_format & PERF_FORMAT_GROUP) {
      *array++ = sample.read.group.nr;
      if (attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
        *array++ = sample.read.time_enabled;
      if (attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
        *array++ = sample.read.time_running;
      for (size_t i = 0; i < sample.read.group.nr; i++) {
        *array++ = sample.read.group.values[i].value;
        if (attr.read_format & PERF_FORMAT_ID) *array++ = sample.read.one.id;
      }
    } else {
      *array++ = sample.read.one.value;
      if (attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
        *array++ = sample.read.time_enabled;
      if (attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
        *array++ = sample.read.time_running;
      if (attr.read_format & PERF_FORMAT_ID) *array++ = sample.read.one.id;
    }
  }

  // { u64                   nr,
  //   u64                   ips[nr];  } && PERF_SAMPLE_CALLCHAIN
  if (sample_fields & PERF_SAMPLE_CALLCHAIN) {
    if (!sample.callchain) {
      LOG(ERROR) << "Expecting callchain data, but none was found.";
    } else {
      *array++ = sample.callchain->nr;
      for (size_t i = 0; i < sample.callchain->nr; ++i)
        *array++ = sample.callchain->ips[i];
    }
  }

  // { u32                   size;
  //   char                  data[size];}&& PERF_SAMPLE_RAW
  if (sample_fields & PERF_SAMPLE_RAW) {
    uint32_t* ptr = reinterpret_cast<uint32_t*>(array);
    *ptr++ = sample.raw_size;
    memcpy(ptr, sample.raw_data, sample.raw_size);

    // Update the data read pointer after aligning to the next 64 bytes.
    int num_bytes = Align<uint64_t>(sizeof(sample.raw_size) + sample.raw_size);
    array += num_bytes / sizeof(uint64_t);
  }

  // { u64                   nr;
  //   { u64 from, to, flags } lbr[nr];} && PERF_SAMPLE_BRANCH_STACK
  if (sample_fields & PERF_SAMPLE_BRANCH_STACK) {
    if (!sample.branch_stack) {
      LOG(ERROR) << "Expecting branch stack data, but none was found.";
    } else {
      *array++ = sample.branch_stack->nr;
      for (size_t i = 0; i < sample.branch_stack->nr; ++i) {
        *array++ = sample.branch_stack->entries[i].from;
        *array++ = sample.branch_stack->entries[i].to;
        memcpy(array++, &sample.branch_stack->entries[i].flags,
               sizeof(uint64_t));
      }
    }
  }

  // { u64                   abi; # enum perf_sample_regs_abi
  //   u64                   regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER
  if (sample_fields & PERF_SAMPLE_REGS_USER) {
    LOG(ERROR) << "PERF_SAMPLE_REGS_USER is not yet supported.";
    return (array - initial_array_ptr) * sizeof(uint64_t);
  }

  // { u64                   size;
  //   char                  data[size];
  //   u64                   dyn_size; } && PERF_SAMPLE_STACK_USER
  if (sample_fields & PERF_SAMPLE_STACK_USER) {
    LOG(ERROR) << "PERF_SAMPLE_STACK_USER is not yet supported.";
    return (array - initial_array_ptr) * sizeof(uint64_t);
  }

  // { u64                   weight;   } && PERF_SAMPLE_WEIGHT
  if (sample_fields & PERF_SAMPLE_WEIGHT) {
    *array++ = sample.weight;
  }

  // { u64                   data_src; } && PERF_SAMPLE_DATA_SRC
  if (sample_fields & PERF_SAMPLE_DATA_SRC) {
    *array++ = sample.data_src;
  }

  // { u64                   transaction; } && PERF_SAMPLE_TRANSACTION
  if (sample_fields & PERF_SAMPLE_TRANSACTION) {
    *array++ = sample.transaction;
  }

  return (array - initial_array_ptr) * sizeof(uint64_t);
}

}  // namespace

bool SampleInfoReader::ReadPerfSampleInfo(const event_t& event,
                                          struct perf_sample* sample) const {
  CHECK(sample);

  if (!IsSupportedEventType(event.header.type)) {
    LOG(ERROR) << "Unsupported event type " << event.header.type;
    return false;
  }

  size_t size_read_or_skipped =
      ReadPerfSampleFromData(event, event_attr_, read_cross_endian_, sample);

  if (size_read_or_skipped != event.header.size) {
    LOG(ERROR) << "Read/skipped " << size_read_or_skipped << " bytes, expected "
               << event.header.size << " bytes.";
  }

  return (size_read_or_skipped == event.header.size);
}

bool SampleInfoReader::WritePerfSampleInfo(const perf_sample& sample,
                                           event_t* event) const {
  CHECK(event);

  if (!IsSupportedEventType(event->header.type)) {
    LOG(ERROR) << "Unsupported event type " << event->header.type;
    return false;
  }

  size_t size_written_or_skipped =
      WritePerfSampleToData(sample, event_attr_, event);
  if (size_written_or_skipped != event->header.size) {
    LOG(ERROR) << "Wrote/skipped " << size_written_or_skipped
               << " bytes, expected " << event->header.size << " bytes.";
  }

  return (size_written_or_skipped == event->header.size);
}

// static
uint64_t SampleInfoReader::GetSampleFieldsForEventType(uint32_t event_type,
                                                       uint64_t sample_type) {
  uint64_t mask = UINT64_MAX;
  switch (event_type) {
    case PERF_RECORD_MMAP:
    case PERF_RECORD_LOST:
    case PERF_RECORD_COMM:
    case PERF_RECORD_EXIT:
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
    case PERF_RECORD_FORK:
    case PERF_RECORD_READ:
    case PERF_RECORD_MMAP2:
    case PERF_RECORD_AUX:
      // See perf_event.h "struct" sample_id and sample_id_all.
      mask = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_ID |
             PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER;
      break;
    case PERF_RECORD_SAMPLE:
      break;
    default:
      LOG(FATAL) << "Unknown event type " << event_type;
  }
  return sample_type & mask;
}

// static
uint64_t SampleInfoReader::GetPerfSampleDataOffset(const event_t& event) {
  uint64_t offset = UINT64_MAX;
  switch (event.header.type) {
    case PERF_RECORD_SAMPLE:
      offset = offsetof(event_t, sample.array);
      break;
    case PERF_RECORD_MMAP:
      offset = sizeof(event.mmap) - sizeof(event.mmap.filename) +
               GetUint64AlignedStringLength(event.mmap.filename);
      break;
    case PERF_RECORD_FORK:
    case PERF_RECORD_EXIT:
      offset = sizeof(event.fork);
      break;
    case PERF_RECORD_COMM:
      offset = sizeof(event.comm) - sizeof(event.comm.comm) +
               GetUint64AlignedStringLength(event.comm.comm);
      break;
    case PERF_RECORD_LOST:
      offset = sizeof(event.lost);
      break;
    case PERF_RECORD_THROTTLE:
    case PERF_RECORD_UNTHROTTLE:
      offset = sizeof(event.throttle);
      break;
    case PERF_RECORD_READ:
      offset = sizeof(event.read);
      break;
    case PERF_RECORD_MMAP2:
      offset = sizeof(event.mmap2) - sizeof(event.mmap2.filename) +
               GetUint64AlignedStringLength(event.mmap2.filename);
      break;
    case PERF_RECORD_AUX:
      offset = sizeof(event.aux);
      break;
    default:
      LOG(FATAL) << "Unknown event type " << event.header.type;
      break;
  }
  // Make sure the offset was valid
  CHECK_NE(offset, UINT64_MAX);
  CHECK_EQ(offset % sizeof(uint64_t), 0U);
  return offset;
}

}  // namespace quipper