// Copyright (c) 2014 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 "test_perf_data.h"

#include <stddef.h>

#include <algorithm>
#include <ostream>  
#include <vector>

#include "base/logging.h"

#include "binary_data_utils.h"
#include "compat/string.h"
#include "kernel/perf_internals.h"
#include "perf_data_utils.h"

namespace quipper {
namespace testing {

namespace {

// Write extra bytes to an output stream.
void WriteExtraBytes(size_t size, std::ostream* out) {
  std::vector<char> padding(size);
  out->write(padding.data(), size);
}
u8 ReverseByte(u8 x) {
  x = (x & 0xf0) >> 4 | (x & 0x0f) << 4;  // exchange nibbles
  x = (x & 0xcc) >> 2 | (x & 0x33) << 2;  // exchange pairs
  x = (x & 0xaa) >> 1 | (x & 0x55) << 1;  // exchange neighbors
  return x;
}

void SwapBitfieldOfBits(u8* field, size_t len) {
  for (size_t i = 0; i < len; i++) {
    field[i] = ReverseByte(field[i]);
  }
}

}  // namespace

ExamplePerfDataFileHeader::ExamplePerfDataFileHeader(
    const unsigned long features) {  
  CHECK_EQ(112U, sizeof(perf_file_attr)) << "perf_file_attr has changed size";
  header_ = {
      .magic = kPerfMagic,
      .size = 104,
      .attr_size = sizeof(struct perf_file_attr),
      .attrs = {.offset = 104, .size = 0},
      .data = {.offset = 104, .size = 0},
      .event_types = {0},
      .adds_features = {features, 0, 0, 0},
  };
}

ExamplePerfDataFileHeader& ExamplePerfDataFileHeader::WithAttrIdsCount(
    size_t n) {
  attr_ids_count_ = n;
  UpdateSectionOffsets();
  return *this;
}

ExamplePerfDataFileHeader& ExamplePerfDataFileHeader::WithAttrCount(size_t n) {
  header_.attrs.size = n * header_.attr_size;
  UpdateSectionOffsets();
  return *this;
}

ExamplePerfDataFileHeader& ExamplePerfDataFileHeader::WithDataSize(size_t sz) {
  header_.data.size = sz;
  UpdateSectionOffsets();
  return *this;
}

ExamplePerfDataFileHeader&
ExamplePerfDataFileHeader::WithCustomPerfEventAttrSize(size_t sz) {
  size_t n_attrs = header_.attrs.size / header_.attr_size;
  // Calculate sizeof(perf_file_attr) given the custom sizeof(perf_event_attr)
  header_.attr_size = sz + sizeof(perf_file_section);
  // Re-calculate the attrs section size and update offsets.
  return WithAttrCount(n_attrs);
}

void ExamplePerfDataFileHeader::UpdateSectionOffsets() {
  u64 offset = header_.size;
  offset += attr_ids_count_ * sizeof(u64);
  header_.attrs.offset = offset;
  offset += header_.attrs.size;
  header_.data.offset = offset;
  offset += header_.data.size;
  CHECK_EQ(data_end_offset(), offset);  // aka, the metadata offset.
}

void ExamplePerfDataFileHeader::WriteTo(std::ostream* out) const {
  struct perf_file_header local_header = {
      .magic = MaybeSwap64(header_.magic),
      .size = MaybeSwap64(header_.size),
      .attr_size = MaybeSwap64(header_.attr_size),
      .attrs = {.offset = MaybeSwap64(header_.attrs.offset),
                .size = MaybeSwap64(header_.attrs.size)},
      .data = {.offset = MaybeSwap64(header_.data.offset),
               .size = MaybeSwap64(header_.data.size)},
      .event_types = {.offset = MaybeSwap64(header_.event_types.offset),
                      .size = MaybeSwap64(header_.event_types.size)},
      .adds_features = {0},
  };
  // Copy over the features bits manually since the byte swapping is more
  // complicated.
  for (size_t i = 0; i < sizeof(header_.adds_features) / sizeof(uint64_t);
       ++i) {
    reinterpret_cast<uint64_t*>(local_header.adds_features)[i] = MaybeSwap64(
        reinterpret_cast<const uint64_t*>(header_.adds_features)[i]);
  }

  out->write(reinterpret_cast<const char*>(&local_header),
             sizeof(local_header));
  // Use original header values that weren't endian-swapped.
  CHECK_EQ(static_cast<u64>(out->tellp()), header_.size);
}

void ExamplePipedPerfDataFileHeader::WriteTo(std::ostream* out) const {
  const perf_pipe_file_header header = {
      .magic = kPerfMagic,
      .size = 16,
  };
  out->write(reinterpret_cast<const char*>(&header), sizeof(header));
  CHECK_EQ(static_cast<u64>(out->tellp()), header.size);
}

void ExamplePerfEventAttrEvent_Hardware::WriteTo(std::ostream* out) const {
  // Due to the unnamed union fields (eg, sample_period), this structure can't
  // be initialized with designated initializers.
  perf_event_attr attr = {};
  attr.type = PERF_TYPE_HARDWARE;
  attr.size = attr_size_;
  attr.config = config_;
  attr.sample_period = 100001;
  attr.sample_type = sample_type_;
  attr.read_format = read_format_;
  attr.sample_id_all = sample_id_all_;

  const size_t event_size = sizeof(perf_event_header) + attr.size +
                            ids_.size() * sizeof(decltype(ids_)::value_type);

  const perf_event_header header = {
      .type = PERF_RECORD_HEADER_ATTR,
      .misc = 0,
      .size = static_cast<u16>(event_size),
  };

  out->write(reinterpret_cast<const char*>(&header), sizeof(header));
  out->write(reinterpret_cast<const char*>(&attr),
             std::min(sizeof(attr), static_cast<size_t>(attr_size_)));
  if (sizeof(attr) < attr_size_)
    WriteExtraBytes(attr_size_ - sizeof(attr), out);
  out->write(reinterpret_cast<const char*>(ids_.data()),
             ids_.size() * sizeof(decltype(ids_)::value_type));
}

void AttrIdsSection::WriteTo(std::ostream* out) const {
  out->write(reinterpret_cast<const char*>(ids_.data()),
             ids_.size() * sizeof(decltype(ids_)::value_type));
}

void ExamplePerfFileAttr_Hardware::WriteTo(std::ostream* out) const {
  // Due to the unnamed union fields (eg, sample_period), this structure can't
  // be initialized with designated initializers.
  perf_event_attr attr = {0};
  attr.type = MaybeSwap32(PERF_TYPE_HARDWARE);
  attr.size = MaybeSwap32(attr_size_);
  attr.config = MaybeSwap64(config_);
  attr.sample_period = MaybeSwap64(1);
  attr.sample_type = MaybeSwap64(sample_type_);
  // Bit fields.
  attr.sample_id_all = sample_id_all_;
  attr.precise_ip = 2;  // For testing a bit field that is more than one bit.

  if (is_cross_endian()) {
    // The order of operations here is for native-to-cross-endian conversion.
    // Contrast with similar code in PerfReader for cross-endian-to-native
    // conversion, which performs these swap operations in reverse order.
    const auto tmp = attr.precise_ip;
    attr.precise_ip = (tmp & 0x2) >> 1 | (tmp & 0x1) << 1;

    auto* const bitfield_start = &attr.read_format + 1;
    SwapBitfieldOfBits(reinterpret_cast<u8*>(bitfield_start), sizeof(u64));
  }

  // perf_event_attr can be of a size other than the static struct size. Thus we
  // cannot simply statically create a perf_file_attr (which contains a
  // perf_event_attr and a perf_file_section). Instead, create and write each
  // component separately.
  out->write(reinterpret_cast<const char*>(&attr),
             std::min(sizeof(attr), static_cast<size_t>(attr_size_)));
  if (sizeof(attr) < attr_size_)
    WriteExtraBytes(attr_size_ - sizeof(attr), out);

  out->write(reinterpret_cast<const char*>(&ids_section_),
             sizeof(ids_section_));
}

void ExamplePerfFileAttr_Tracepoint::WriteTo(std::ostream* out) const {
  // Due to the unnamed union fields (eg, sample_period), this structure can't
  // be initialized with designated initializers.
  perf_event_attr attr = {};
  // See kernel src: tools/perf/util/evsel.c perf_evsel__newtp()
  attr.type = PERF_TYPE_TRACEPOINT;
  attr.size = sizeof(perf_event_attr);
  attr.config = tracepoint_event_id_;
  attr.sample_period = 1;
  attr.sample_type = (PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
                      PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | PERF_SAMPLE_RAW);

  const perf_file_attr file_attr = {
      .attr = attr,
      .ids = {.offset = 104, .size = 0},
  };
  out->write(reinterpret_cast<const char*>(&file_attr), sizeof(file_attr));
}

size_t ExampleMmapEvent::GetSize() const {
  return offsetof(struct mmap_event, filename) +
         GetUint64AlignedStringLength(filename_) +
         sample_id_.size();  // sample_id_all
}

void ExampleMmapEvent::WriteTo(std::ostream* out) const {
  const size_t event_size = GetSize();

  struct mmap_event event = {
      .header =
          {
              .type = MaybeSwap32(PERF_RECORD_MMAP),
              .misc = 0,
              .size = MaybeSwap16(static_cast<u16>(event_size)),
          },
      .pid = MaybeSwap32(pid_),
      .tid = MaybeSwap32(pid_),
      .start = MaybeSwap64(start_),
      .len = MaybeSwap64(len_),
      .pgoff = MaybeSwap64(pgoff_),
      // .filename = ..., // written separately
  };

  const size_t pre_mmap_offset = out->tellp();
  out->write(reinterpret_cast<const char*>(&event),
             offsetof(struct mmap_event, filename));
  const size_t filename_aligned_length =
      GetUint64AlignedStringLength(filename_);
  *out << filename_ << string(filename_aligned_length - filename_.size(), '\0');
  out->write(sample_id_.data(), sample_id_.size());
  const size_t written_event_size =
      static_cast<size_t>(out->tellp()) - pre_mmap_offset;
  CHECK_EQ(event_size, static_cast<u64>(written_event_size));
}

void ExampleMmap2Event::WriteTo(std::ostream* out) const {
  const size_t filename_aligned_length =
      GetUint64AlignedStringLength(filename_);
  const size_t event_size = offsetof(struct mmap2_event, filename) +
                            filename_aligned_length +
                            sample_id_.size();  // sample_id_all

  struct mmap2_event event = {
      .header =
          {
              .type = PERF_RECORD_MMAP2,
              .misc = 0,
              .size = static_cast<u16>(event_size),
          },
      .pid = pid_,
      .tid = tid_,
      .start = start_,
      .len = len_,
      .pgoff = pgoff_,
      .maj = maj_,
      .min = min_,
      .ino = ino_,
      .ino_generation = 9,
      .prot = 1 | 2,  // == PROT_READ | PROT_WRITE
      .flags = 2,     // == MAP_PRIVATE
                      // .filename = ..., // written separately
  };

  const size_t pre_mmap_offset = out->tellp();
  out->write(reinterpret_cast<const char*>(&event),
             offsetof(struct mmap2_event, filename));
  *out << filename_ << string(filename_aligned_length - filename_.size(), '\0');
  out->write(sample_id_.data(), sample_id_.size());
  const size_t written_event_size =
      static_cast<size_t>(out->tellp()) - pre_mmap_offset;
  CHECK_EQ(event.header.size, static_cast<u64>(written_event_size));
}

void ExampleForkExitEvent::WriteTo(std::ostream* out) const {
  const size_t event_size = sizeof(struct fork_event) + sample_id_.size();

  struct fork_event event = {
      .header =
          {
              .type = MaybeSwap32(type_),
              .misc = 0,
              .size = MaybeSwap16(static_cast<u16>(event_size)),
          },
      .pid = MaybeSwap32(pid_),
      .ppid = MaybeSwap32(ppid_),
      .tid = MaybeSwap32(tid_),
      .ptid = MaybeSwap32(ptid_),
      .time = MaybeSwap64(time_),
  };

  const size_t pre_event_offset = out->tellp();
  out->write(reinterpret_cast<const char*>(&event), sizeof(event));
  out->write(sample_id_.data(), sample_id_.size());
  const size_t written_event_size =
      static_cast<size_t>(out->tellp()) - pre_event_offset;
  CHECK_EQ(MaybeSwap16(event.header.size),
           static_cast<u64>(written_event_size));
}

void FinishedRoundEvent::WriteTo(std::ostream* out) const {
  const perf_event_header event = {
      .type = PERF_RECORD_FINISHED_ROUND,
      .misc = 0,
      .size = sizeof(struct perf_event_header),
  };
  out->write(reinterpret_cast<const char*>(&event), sizeof(event));
}

size_t ExamplePerfSampleEvent::GetSize() const {
  return sizeof(struct sample_event) + sample_info_.size();
}

void ExamplePerfSampleEvent::WriteTo(std::ostream* out) const {
  const sample_event event = {
      .header = {
          .type = MaybeSwap32(PERF_RECORD_SAMPLE),
          .misc = MaybeSwap16(PERF_RECORD_MISC_USER),
          .size = MaybeSwap16(static_cast<u16>(GetSize())),
      }};
  out->write(reinterpret_cast<const char*>(&event), sizeof(event));
  out->write(sample_info_.data(), sample_info_.size());
}

ExamplePerfSampleEvent_BranchStack::ExamplePerfSampleEvent_BranchStack()
    : ExamplePerfSampleEvent(
          SampleInfo()
              .BranchStack_nr(16)
              .BranchStack_lbr(0x00007f4a313bb8cc, 0x00007f4a313bdb40, 0x02)
              .BranchStack_lbr(0x00007f4a30ce4de2, 0x00007f4a313bb8b3, 0x02)
              .BranchStack_lbr(0x00007f4a313bb8b0, 0x00007f4a30ce4de0, 0x01)
              .BranchStack_lbr(0x00007f4a30ff45c1, 0x00007f4a313bb8a0, 0x02)
              .BranchStack_lbr(0x00007f4a30ff49f2, 0x00007f4a30ff45bb, 0x02)
              .BranchStack_lbr(0x00007f4a30ff4a98, 0x00007f4a30ff49ed, 0x02)
              .BranchStack_lbr(0x00007f4a30ff4a7c, 0x00007f4a30ff4a91, 0x02)
              .BranchStack_lbr(0x00007f4a30ff4a34, 0x00007f4a30ff4a46, 0x02)
              .BranchStack_lbr(0x00007f4a30ff4c22, 0x00007f4a30ff4a0e, 0x02)
              .BranchStack_lbr(0x00007f4a30ff4bb3, 0x00007f4a30ff4c1b, 0x01)
              .BranchStack_lbr(0x00007f4a30ff4a09, 0x00007f4a30ff4b60, 0x02)
              .BranchStack_lbr(0x00007f4a30ff49e8, 0x00007f4a30ff4a00, 0x02)
              .BranchStack_lbr(0x00007f4a30ff42db, 0x00007f4a30ff49e0, 0x02)
              .BranchStack_lbr(0x00007f4a30ff42bb, 0x00007f4a30ff42d4, 0x02)
              .BranchStack_lbr(0x00007f4a333bf88b, 0x00007f4a30ff42ac, 0x02)
              .BranchStack_lbr(0x00007f4a333bf853, 0x00007f4a333bf885, 0x02)) {}

// Event size matching the event produced above
const size_t ExamplePerfSampleEvent_BranchStack::kEventSize =
    (1 /*perf_event_header*/ + 1 /*nr*/ + 16 * 3 /*lbr*/) * sizeof(u64);

void ExamplePerfSampleEvent_Tracepoint::WriteTo(std::ostream* out) const {
  const sample_event event = {.header = {
                                  .type = PERF_RECORD_SAMPLE,
                                  .misc = PERF_RECORD_MISC_USER,
                                  .size = 0x0078,
                              }};
  const u64 sample_event_array[] = {
      0x00007f999c38d15a,  // IP
      0x0000068d0000068d,  // TID (u32 pid, tid)
      0x0001e0211cbab7b9,  // TIME
      0x0000000000000000,  // CPU
      0x0000000000000001,  // PERIOD
      0x0000004900000044,  // RAW (u32 size = 0x44 = 68 = 4 + 8*sizeof(u64))
      0x000000090000068d,  //  .
      0x0000000000000000,  //  .
      0x0000100000000000,  //  .
      0x0000000300000000,  //  .
      0x0000002200000000,  //  .
      0xffffffff00000000,  //  .
      0x0000000000000000,  //  .
      0x0000000000000000,  //  .
  };
  CHECK_EQ(event.header.size,
           sizeof(event.header) + sizeof(sample_event_array));
  out->write(reinterpret_cast<const char*>(&event), sizeof(event));
  out->write(reinterpret_cast<const char*>(sample_event_array),
             sizeof(sample_event_array));
}

// Event size matching the event produced above
const size_t ExamplePerfSampleEvent_Tracepoint::kEventSize =
    (1 /*perf_event_header*/ + 14 /*sample array*/) * sizeof(u64);

void ExampleStringMetadata::WriteTo(std::ostream* out) const {
  const perf_file_section& index_entry = index_entry_.index_entry_;
  CHECK_EQ(static_cast<u64>(out->tellp()), index_entry.offset);
  const u32 data_size_value = MaybeSwap32(data_.size());
  out->write(reinterpret_cast<const char*>(&data_size_value),
             sizeof(data_size_value));
  out->write(data_.data(), data_.size());

  CHECK_EQ(static_cast<u64>(out->tellp()), index_entry.offset + size());
}

void ExampleStringMetadataEvent::WriteTo(std::ostream* out) const {
  const size_t initial_position = out->tellp();

  const u32 data_size = data_.size();
  const perf_event_header header = {
      .type = type_,
      .misc = 0,
      .size =
          static_cast<u16>(sizeof(header) + sizeof(data_size) + data_.size()),
  };
  out->write(reinterpret_cast<const char*>(&header), sizeof(header));

  out->write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
  out->write(reinterpret_cast<const char*>(data_.data()), data_.size());

  CHECK_EQ(static_cast<u64>(out->tellp()), initial_position + header.size);
}

static const char kTraceMetadataValue[] =
    "\x17\x08\x44tracing0.5BLAHBLAHBLAH....";

const string ExampleTracingMetadata::Data::kTraceMetadata(
    kTraceMetadataValue, sizeof(kTraceMetadataValue) - 1);

void ExampleTracingMetadata::Data::WriteTo(std::ostream* out) const {
  const perf_file_section& index_entry = parent_->index_entry_.index_entry_;
  CHECK_EQ(static_cast<u64>(out->tellp()), index_entry.offset);
  out->write(kTraceMetadata.data(), kTraceMetadata.size());
  CHECK_EQ(static_cast<u64>(out->tellp()),
           index_entry.offset + index_entry.size);
}

size_t ExampleAuxtraceEvent::GetSize() const {
  return sizeof(struct auxtrace_event);
}

size_t ExampleAuxtraceEvent::GetTraceSize() const { return trace_data_.size(); }

void ExampleAuxtraceEvent::WriteTo(std::ostream* out) const {
  const size_t event_size = GetSize();

  struct auxtrace_event event = {
      .header =
          {
              .type = MaybeSwap32(PERF_RECORD_AUXTRACE),
              .misc = 0,
              .size = MaybeSwap16(static_cast<u16>(event_size)),
          },
      .size = MaybeSwap64(size_),
      .offset = MaybeSwap64(offset_),
      .reference = MaybeSwap64(reference_),
      .idx = MaybeSwap32(idx_),
      .tid = MaybeSwap32(tid_),
      .cpu = MaybeSwap32(cpu_),
      .reserved__ = MaybeSwap32(reserved_),
  };

  out->write(reinterpret_cast<const char*>(&event), event_size);
  out->write(trace_data_.data(), trace_data_.size());
}

}  // namespace testing
}  // namespace quipper