// 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 <byteswap.h>

#include "compat/test.h"
#include "kernel/perf_event.h"
#include "kernel/perf_internals.h"
#include "test_perf_data.h"
#include "test_utils.h"

namespace quipper {

using testing::PunU32U64;

TEST(SampleInfoReaderTest, ReadSampleEvent) {
  // clang-format off
  uint64_t sample_type =       // * == in sample_id_all
      PERF_SAMPLE_IP |
      PERF_SAMPLE_TID |        // *
      PERF_SAMPLE_TIME |       // *
      PERF_SAMPLE_ADDR |
      PERF_SAMPLE_ID |         // *
      PERF_SAMPLE_STREAM_ID |  // *
      PERF_SAMPLE_CPU |        // *
      PERF_SAMPLE_PERIOD |
      PERF_SAMPLE_WEIGHT |
      PERF_SAMPLE_DATA_SRC |
      PERF_SAMPLE_TRANSACTION;
  // clang-format on
  struct perf_event_attr attr = {0};
  attr.sample_type = sample_type;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      1415837014 * 1000000000ULL,            // TIME
      0x00007f999c38d15a,                    // ADDR
      2,                                     // ID
      1,                                     // STREAM_ID
      8,                                     // CPU
      10001,                                 // PERIOD
      12345,                                 // WEIGHT
      0x68100142,                            // DATA_SRC
      67890,                                 // TRANSACTIONS
  };
  const sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0xffffffff01234567, sample.ip);
  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(1415837014 * 1000000000ULL, sample.time);
  EXPECT_EQ(0x00007f999c38d15a, sample.addr);
  EXPECT_EQ(2, sample.id);
  EXPECT_EQ(1, sample.stream_id);
  EXPECT_EQ(8, sample.cpu);
  EXPECT_EQ(10001, sample.period);
  EXPECT_EQ(12345, sample.weight);
  EXPECT_EQ(0x68100142, sample.data_src);
  EXPECT_EQ(67890, sample.transaction);
}

TEST(SampleInfoReaderTest, ReadSampleEventCrossEndian) {
  // clang-format off
  uint64_t sample_type =      // * == in sample_id_all
      PERF_SAMPLE_IP |
      PERF_SAMPLE_TID |        // *
      PERF_SAMPLE_TIME |       // *
      PERF_SAMPLE_ADDR |
      PERF_SAMPLE_ID |         // *
      PERF_SAMPLE_STREAM_ID |  // *
      PERF_SAMPLE_CPU |        // *
      PERF_SAMPLE_PERIOD;
  // clang-format on
  struct perf_event_attr attr = {0};
  attr.sample_type = sample_type;

  SampleInfoReader reader(attr, true /* read_cross_endian */);

  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      1415837014 * 1000000000ULL,            // TIME
      0x00007f999c38d15a,                    // ADDR
      2,                                     // ID
      1,                                     // STREAM_ID
      8,                                     // CPU
      10001,                                 // PERIOD
  };

  const sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(bswap_64(0xffffffff01234567), sample.ip);
  EXPECT_EQ(bswap_32(0x68d), sample.pid);  // 32-bit
  EXPECT_EQ(bswap_32(0x68e), sample.tid);  // 32-bit
  EXPECT_EQ(bswap_64(1415837014 * 1000000000ULL), sample.time);
  EXPECT_EQ(bswap_64(0x00007f999c38d15a), sample.addr);
  EXPECT_EQ(bswap_64(2), sample.id);
  EXPECT_EQ(bswap_64(1), sample.stream_id);
  EXPECT_EQ(bswap_32(8), sample.cpu);  // 32-bit
  EXPECT_EQ(bswap_64(10001), sample.period);
}

TEST(SampleInfoReaderTest, ReadMmapEvent) {
  // clang-format off
  uint64_t sample_type =      // * == in sample_id_all
      PERF_SAMPLE_IP |
      PERF_SAMPLE_TID |        // *
      PERF_SAMPLE_TIME |       // *
      PERF_SAMPLE_ADDR |
      PERF_SAMPLE_ID |         // *
      PERF_SAMPLE_STREAM_ID |  // *
      PERF_SAMPLE_CPU |        // *
      PERF_SAMPLE_PERIOD;
  // clang-format on
  struct perf_event_attr attr = {0};
  attr.sample_type = sample_type;
  attr.sample_id_all = true;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  // PERF_RECORD_MMAP
  ASSERT_EQ(40, offsetof(struct mmap_event, filename));
  const u64 mmap_sample_id[] = {
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      1415911367 * 1000000000ULL,            // TIME
      3,                                     // ID
      2,                                     // STREAM_ID
      9,                                     // CPU
  };
  // clang-format off
  const size_t mmap_event_size =
      offsetof(struct mmap_event, filename) +
      10+6 /* ==16, nearest 64-bit boundary for filename */ +
      sizeof(mmap_sample_id);
  // clang-format on

  const char mmap_filename[10 + 6] = "/dev/zero";
  struct mmap_event written_mmap_event = {
      .header =
          {
              .type = PERF_RECORD_MMAP,
              .misc = 0,
              .size = mmap_event_size,
          },
      .pid = 0x68d,
      .tid = 0x68d,
      .start = 0x1d000,
      .len = 0x1000,
      .pgoff = 0,
      // .filename = ..., // written separately
  };

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&written_mmap_event),
              offsetof(struct mmap_event, filename));
  input.write(mmap_filename, 10 + 6);
  input.write(reinterpret_cast<const char*>(mmap_sample_id),
              sizeof(mmap_sample_id));

  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(1415911367 * 1000000000ULL, sample.time);
  EXPECT_EQ(3, sample.id);
  EXPECT_EQ(2, sample.stream_id);
  EXPECT_EQ(9, sample.cpu);
}

TEST(SampleInfoReaderTest, ReadReadInfoAllFields) {
  struct perf_event_attr attr = {0};
  attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_READ;
  attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
                     PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  // PERF_RECORD_SAMPLE
  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      // From kernel/perf_event.h:
      // struct read_format {
      //   { u64  value;
      //     { u64  time_enabled; } && PERF_FORMAT_TOTAL_TIME_ENABLED
      //     { u64  time_running; } && PERF_FORMAT_TOTAL_TIME_RUNNING
      //     { u64  id;           } && PERF_FORMAT_ID
      //   } && !PERF_FORMAT_GROUP
      // };
      1000000,                     // READ: value
      1415837014 * 1000000000ULL,  // READ: time_enabled
      1234567890 * 1000000000ULL,  // READ: time_running
      0xabcdef,                    // READ: id
  };
  sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0xffffffff01234567, sample.ip);
  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(1415837014 * 1000000000ULL, sample.read.time_enabled);
  EXPECT_EQ(1234567890 * 1000000000ULL, sample.read.time_running);
  EXPECT_EQ(0xabcdef, sample.read.one.id);
  EXPECT_EQ(1000000, sample.read.one.value);
}

TEST(SampleInfoReaderTest, ReadReadInfoOmitTotalTimeFields) {
  struct perf_event_attr attr = {0};
  attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_READ;
  // Omit the PERF_FORMAT_TOTAL_TIME_* fields.
  attr.read_format = PERF_FORMAT_ID;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  // PERF_RECORD_SAMPLE
  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      1000000,                               // READ: value
      0xabcdef,                              // READ: id
  };
  sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0xffffffff01234567, sample.ip);
  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(0xabcdef, sample.read.one.id);
  EXPECT_EQ(1000000, sample.read.one.value);
}

TEST(SampleInfoReaderTest, ReadReadInfoValueFieldOnly) {
  struct perf_event_attr attr = {0};
  attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_READ;
  // Omit all optional fields. The |value| field still remains.
  attr.read_format = 0;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  // PERF_RECORD_SAMPLE
  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      1000000,                               // READ: value
  };
  sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0xffffffff01234567, sample.ip);
  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(1000000, sample.read.one.value);
}

TEST(SampleInfoReaderTest, ReadReadInfoWithGroups) {
  struct perf_event_attr attr = {0};
  attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_READ;
  // Omit the PERF_FORMAT_TOTAL_TIME_* fields.
  attr.read_format = PERF_FORMAT_ID | PERF_FORMAT_GROUP;

  SampleInfoReader reader(attr, false /* read_cross_endian */);

  // PERF_RECORD_SAMPLE
  const u64 sample_event_array[] = {
      0xffffffff01234567,                    // IP
      PunU32U64{.v32 = {0x68d, 0x68e}}.v64,  // TID (u32 pid, tid)
      3,                                     // READ: nr
      1000000,                               // READ: values[0].value
      0xabcdef,                              // READ: values[0].id
      2000000,                               // READ: values[1].value
      0xdecaf0,                              // READ: values[1].id
      3000000,                               // READ: values[2].value
      0xbeef00,                              // READ: values[2].id
  };
  sample_event sample_event_struct = {
      .header = {
          .type = PERF_RECORD_SAMPLE,
          .misc = 0,
          .size = sizeof(sample_event) + sizeof(sample_event_array),
      }};

  std::stringstream input;
  input.write(reinterpret_cast<const char*>(&sample_event_struct),
              sizeof(sample_event_struct));
  input.write(reinterpret_cast<const char*>(sample_event_array),
              sizeof(sample_event_array));
  string input_string = input.str();
  const event_t& event = *reinterpret_cast<const event_t*>(input_string.data());

  perf_sample sample;
  ASSERT_TRUE(reader.ReadPerfSampleInfo(event, &sample));

  EXPECT_EQ(0xffffffff01234567, sample.ip);
  EXPECT_EQ(0x68d, sample.pid);
  EXPECT_EQ(0x68e, sample.tid);
  EXPECT_EQ(3, sample.read.group.nr);
  ASSERT_NE(static_cast<void*>(NULL), sample.read.group.values);
  EXPECT_EQ(0xabcdef, sample.read.group.values[0].id);
  EXPECT_EQ(1000000, sample.read.group.values[0].value);
  EXPECT_EQ(0xdecaf0, sample.read.group.values[1].id);
  EXPECT_EQ(2000000, sample.read.group.values[1].value);
  EXPECT_EQ(0xbeef00, sample.read.group.values[2].id);
  EXPECT_EQ(3000000, sample.read.group.values[2].value);
}

}  // namespace quipper