/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/traced/probes/ftrace/proto_translation_table.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "perfetto/trace/ftrace/generic.pbzero.h"
#include "src/traced/probes/ftrace/event_info.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"

using testing::_;
using testing::Values;
using testing::ValuesIn;
using testing::TestWithParam;
using testing::Return;
using testing::AnyNumber;
using testing::IsNull;
using testing::Contains;
using testing::Eq;
using testing::Pointee;

namespace perfetto {
namespace {
using protozero::proto_utils::ProtoSchemaType;

class MockFtraceProcfs : public FtraceProcfs {
 public:
  MockFtraceProcfs() : FtraceProcfs("/root/") {}

  MOCK_CONST_METHOD0(ReadPageHeaderFormat, std::string());
  MOCK_CONST_METHOD2(ReadEventFormat,
                     std::string(const std::string& group,
                                 const std::string& name));
};

class AllTranslationTableTest : public TestWithParam<const char*> {
 public:
  void SetUp() override {
    std::string path =
        "src/traced/probes/ftrace/test/data/" + std::string(GetParam()) + "/";
    FtraceProcfs ftrace_procfs(path);
    table_ = ProtoTranslationTable::Create(&ftrace_procfs, GetStaticEventInfo(),
                                           GetStaticCommonFieldsInfo());
    PERFETTO_CHECK(table_);
  }

  std::unique_ptr<ProtoTranslationTable> table_;
};

class TranslationTableCreationTest : public TestWithParam<uint16_t> {};

const char* kDevices[] = {
    "android_seed_N2F62_3.10.49", "android_hammerhead_MRA59G_3.4.0",
};

TEST_P(AllTranslationTableTest, Create) {
  EXPECT_TRUE(table_);
  EXPECT_TRUE(table_->GetEvent(GroupAndName("ftrace", "print")));
  EXPECT_TRUE(table_->GetEvent(GroupAndName("sched", "sched_switch")));
  EXPECT_TRUE(table_->GetEvent(GroupAndName("sched", "sched_wakeup")));
  EXPECT_TRUE(table_->GetEvent(GroupAndName("ext4", "ext4_da_write_begin")));
  for (const Event& event : table_->events()) {
    if (!event.ftrace_event_id)
      continue;
    EXPECT_TRUE(event.name);
    EXPECT_TRUE(event.group);
    EXPECT_TRUE(event.proto_field_id);
    for (const Field& field : event.fields) {
      EXPECT_TRUE(field.proto_field_id);
      EXPECT_TRUE(field.ftrace_type);
      EXPECT_TRUE(static_cast<int>(field.proto_field_type));
    }
  }
  ASSERT_EQ(table_->common_fields().size(), 1u);
  const Field& pid_field = table_->common_fields().at(0);
  EXPECT_EQ(std::string(pid_field.ftrace_name), "common_pid");
  EXPECT_EQ(pid_field.proto_field_id, 2u);

  {
    auto event = table_->GetEvent(GroupAndName("ftrace", "print"));
    EXPECT_TRUE(event);
    EXPECT_EQ(std::string(event->name), "print");
    EXPECT_EQ(std::string(event->group), "ftrace");
    EXPECT_EQ(event->fields.at(1).proto_field_type, ProtoSchemaType::kString);
    EXPECT_EQ(event->fields.at(1).ftrace_type, kFtraceCString);
    EXPECT_EQ(event->fields.at(1).strategy, kCStringToString);
  }
}

INSTANTIATE_TEST_CASE_P(ByDevice, AllTranslationTableTest, ValuesIn(kDevices));

TEST(TranslationTableTest, Seed) {
  std::string path =
      "src/traced/probes/ftrace/test/data/android_seed_N2F62_3.10.49/";
  FtraceProcfs ftrace_procfs(path);
  auto table = ProtoTranslationTable::Create(
      &ftrace_procfs, GetStaticEventInfo(), GetStaticCommonFieldsInfo());
  PERFETTO_CHECK(table);
  const Field& pid_field = table->common_fields().at(0);
  EXPECT_EQ(std::string(pid_field.ftrace_name), "common_pid");
  EXPECT_EQ(pid_field.proto_field_id, 2u);
  EXPECT_EQ(pid_field.ftrace_offset, 4u);
  EXPECT_EQ(pid_field.ftrace_size, 4u);

  {
    auto event = table->GetEvent(GroupAndName("sched", "sched_switch"));
    EXPECT_EQ(std::string(event->name), "sched_switch");
    EXPECT_EQ(std::string(event->group), "sched");
    EXPECT_EQ(event->ftrace_event_id, 68ul);
    EXPECT_EQ(event->fields.at(0).ftrace_offset, 8u);
    EXPECT_EQ(event->fields.at(0).ftrace_size, 16u);
  }

  {
    auto event = table->GetEvent(GroupAndName("sched", "sched_wakeup"));
    EXPECT_EQ(std::string(event->name), "sched_wakeup");
    EXPECT_EQ(std::string(event->group), "sched");
    EXPECT_EQ(event->ftrace_event_id, 70ul);
    EXPECT_EQ(event->fields.at(0).ftrace_offset, 8u);
    EXPECT_EQ(event->fields.at(0).ftrace_size, 16u);
  }

  {
    auto event = table->GetEvent(GroupAndName("ext4", "ext4_da_write_begin"));
    EXPECT_EQ(std::string(event->name), "ext4_da_write_begin");
    EXPECT_EQ(std::string(event->group), "ext4");
    EXPECT_EQ(event->ftrace_event_id, 303ul);
    EXPECT_EQ(event->fields.at(0).ftrace_offset, 8u);
    EXPECT_EQ(event->fields.at(0).ftrace_size, 4u);
  }
}

TEST_P(TranslationTableCreationTest, Create) {
  MockFtraceProcfs ftrace;
  std::vector<Field> common_fields;
  std::vector<Event> events;

  ON_CALL(ftrace, ReadPageHeaderFormat())
      .WillByDefault(Return(
          R"(	field: u64 timestamp;	offset:0;	size:8;	signed:0;
	field: local_t commit;	offset:8;	size:)" +
          std::to_string(GetParam()) + R"(;	signed:1;
	field: int overwrite;	offset:8;	size:1;	signed:1;
	field: char data;	offset:16;	size:4080;	signed:0;)"));
  ON_CALL(ftrace, ReadEventFormat(_, _)).WillByDefault(Return(""));
  ON_CALL(ftrace, ReadEventFormat("group", "foo"))
      .WillByDefault(Return(R"(name: foo
ID: 42
format:
	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
	field:int common_pid;	offset:4;	size:4;	signed:1;

	field:char field_a[16];	offset:8;	size:16;	signed:0;
	field:int field_b;	offset:24;	size:4;	signed:1;
	field:int field_d;	offset:28;	size:4;	signed:1;
	field:u32 field_e;	offset:32;	size:4;	signed:0;

print fmt: "some format")"));
  ;

  EXPECT_CALL(ftrace, ReadPageHeaderFormat()).Times(AnyNumber());
  EXPECT_CALL(ftrace, ReadEventFormat(_, _)).Times(AnyNumber());

  {
    events.emplace_back(Event{});
    Event* event = &events.back();
    event->name = "foo";
    event->group = "group";
    event->proto_field_id = 21;

    {
      // We should get this field.
      event->fields.emplace_back(Field{});
      Field* field = &event->fields.back();
      field->proto_field_id = 501;
      field->proto_field_type = ProtoSchemaType::kString;
      field->ftrace_name = "field_a";
    }

    {
      // We shouldn't get this field: don't know how to read int -> string.
      event->fields.emplace_back(Field{});
      Field* field = &event->fields.back();
      field->proto_field_id = 502;
      field->proto_field_type = ProtoSchemaType::kString;
      field->ftrace_name = "field_b";
    }

    {
      // We shouldn't get this field: no matching field in the format file.
      event->fields.emplace_back(Field{});
      Field* field = &event->fields.back();
      field->proto_field_id = 503;
      field->proto_field_type = ProtoSchemaType::kString;
      field->ftrace_name = "field_c";
    }

    {
      // We should get this field.
      event->fields.emplace_back(Field{});
      Field* field = &event->fields.back();
      field->proto_field_id = 504;
      field->proto_field_type = ProtoSchemaType::kUint64;
      field->ftrace_name = "field_e";
    }
  }

  {
    events.emplace_back(Event{});
    Event* event = &events.back();
    event->name = "bar";
    event->group = "group";
    event->proto_field_id = 22;
  }

  auto table = ProtoTranslationTable::Create(&ftrace, std::move(events),
                                             std::move(common_fields));
  PERFETTO_CHECK(table);
  EXPECT_EQ(table->largest_id(), 42ul);
  EXPECT_EQ(table->EventToFtraceId(GroupAndName("group", "foo")), 42ul);
  EXPECT_EQ(table->EventToFtraceId(GroupAndName("group", "bar")), 0ul);
  EXPECT_FALSE(table->GetEventById(43ul));
  ASSERT_TRUE(table->GetEventById(42ul));
  EXPECT_EQ(table->ftrace_page_header_spec().timestamp.size, 8);
  EXPECT_EQ(table->ftrace_page_header_spec().size.size, GetParam());
  EXPECT_EQ(table->ftrace_page_header_spec().overwrite.size, 1);
  auto event = table->GetEventById(42);
  EXPECT_EQ(event->ftrace_event_id, 42ul);
  EXPECT_EQ(event->proto_field_id, 21ul);
  EXPECT_EQ(event->size, 36u);
  EXPECT_EQ(std::string(event->name), "foo");
  EXPECT_EQ(std::string(event->group), "group");

  ASSERT_EQ(event->fields.size(), 2ul);
  auto field_a = event->fields.at(0);
  EXPECT_EQ(field_a.proto_field_id, 501ul);
  EXPECT_EQ(field_a.strategy, kFixedCStringToString);

  auto field_e = event->fields.at(1);
  EXPECT_EQ(field_e.proto_field_id, 504ul);
  EXPECT_EQ(field_e.strategy, kUint32ToUint64);
}

INSTANTIATE_TEST_CASE_P(BySize, TranslationTableCreationTest, Values(4, 8));

TEST(TranslationTableTest, InferFtraceType) {
  FtraceFieldType type;

  ASSERT_TRUE(InferFtraceType("char foo[16]", 16, false, &type));
  EXPECT_EQ(type, kFtraceFixedCString);

  ASSERT_TRUE(InferFtraceType("char[] foo", 8, false, &type));
  EXPECT_EQ(type, kFtraceStringPtr);

  ASSERT_TRUE(InferFtraceType("char * foo", 8, false, &type));
  EXPECT_EQ(type, kFtraceStringPtr);

  ASSERT_TRUE(InferFtraceType("char foo[64]", 64, false, &type));
  EXPECT_EQ(type, kFtraceFixedCString);

  ASSERT_TRUE(InferFtraceType("u32 foo", 4, false, &type));
  EXPECT_EQ(type, kFtraceUint32);

  ASSERT_TRUE(InferFtraceType("i_ino foo", 4, false, &type));
  ASSERT_EQ(type, kFtraceInode32);

  ASSERT_TRUE(InferFtraceType("i_ino foo", 8, false, &type));
  ASSERT_EQ(type, kFtraceInode64);

  ASSERT_TRUE(InferFtraceType("ino_t foo", 4, false, &type));
  ASSERT_EQ(type, kFtraceInode32);

  ASSERT_TRUE(InferFtraceType("ino_t foo", 8, false, &type));
  ASSERT_EQ(type, kFtraceInode64);

  ASSERT_TRUE(InferFtraceType("dev_t foo", 4, false, &type));
  ASSERT_EQ(type, kFtraceDevId32);

  ASSERT_TRUE(InferFtraceType("dev_t foo", 8, false, &type));
  ASSERT_EQ(type, kFtraceDevId64);

  ASSERT_TRUE(InferFtraceType("pid_t foo", 4, false, &type));
  ASSERT_EQ(type, kFtracePid32);

  ASSERT_TRUE(InferFtraceType("int common_pid", 4, false, &type));
  ASSERT_EQ(type, kFtraceCommonPid32);

  ASSERT_TRUE(InferFtraceType("char foo", 1, true, &type));
  ASSERT_EQ(type, kFtraceInt8);

  ASSERT_TRUE(InferFtraceType("__data_loc char[] foo", 4, false, &type));
  ASSERT_EQ(type, kFtraceDataLoc);
  ASSERT_FALSE(InferFtraceType("__data_loc char[] foo", 8, false, &type));

  EXPECT_FALSE(InferFtraceType("foo", 64, false, &type));
}

TEST(TranslationTableTest, Getters) {
  MockFtraceProcfs ftrace;
  std::vector<Field> common_fields;
  std::vector<Event> events;

  {
    Event event;
    event.name = "foo";
    event.group = "group_one";
    event.ftrace_event_id = 1;
    events.push_back(event);
  }

  {
    Event event;
    event.name = "bar";
    event.group = "group_one";
    event.ftrace_event_id = 2;
    events.push_back(event);
  }

  {
    Event event;
    event.name = "baz";
    event.group = "group_two";
    event.ftrace_event_id = 100;
    events.push_back(event);
  }

  ProtoTranslationTable table(
      &ftrace, events, std::move(common_fields),
      ProtoTranslationTable::DefaultPageHeaderSpecForTesting());
  EXPECT_EQ(table.largest_id(), 100ul);
  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "foo")), 1ul);
  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_two", "baz")), 100ul);
  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "no_such_event")),
            0ul);
  EXPECT_EQ(table.GetEventById(1)->name, "foo");
  EXPECT_EQ(table.GetEventById(3), nullptr);
  EXPECT_EQ(table.GetEventById(200), nullptr);
  EXPECT_EQ(table.GetEventById(0), nullptr);
  EXPECT_EQ(table.GetEvent(GroupAndName("group_one", "foo"))->ftrace_event_id,
            1u);
  EXPECT_THAT(*table.GetEventsByGroup("group_one"),
              Contains(testing::Field(&Event::name, "foo")));
  EXPECT_THAT(*table.GetEventsByGroup("group_one"),
              Contains(testing::Field(&Event::name, "bar")));
  EXPECT_THAT(*table.GetEventsByGroup("group_two"),
              Contains(testing::Field(&Event::name, "baz")));
  EXPECT_THAT(table.GetEventsByGroup("group_three"), IsNull());
}

TEST(TranslationTableTest, Generic) {
  MockFtraceProcfs ftrace;
  std::vector<Field> common_fields;
  std::vector<Event> events;

  ON_CALL(ftrace, ReadPageHeaderFormat())
      .WillByDefault(Return(
          R"(	field: u64 timestamp;	offset:0;	size:8;	signed:0;
	field: local_t commit;	offset:8;	size:4;	signed:1;
	field: int overwrite;	offset:8;	size:1;	signed:1;
	field: char data;	offset:16;	size:4080;	signed:0;)"));
  ON_CALL(ftrace, ReadEventFormat(_, _)).WillByDefault(Return(""));
  ON_CALL(ftrace, ReadEventFormat("group", "foo"))
      .WillByDefault(Return(R"(name: foo
ID: 42
format:
	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
	field:int common_pid;	offset:4;	size:4;	signed:1;

	field:char field_a[16];	offset:8;	size:16;	signed:0;
	field:bool field_b;	offset:24;	size:1;	signed:0;
	field:int field_c;	offset:25;	size:4;	signed:1;
	field:u32 field_d;	offset:33;	size:4;	signed:0;

print fmt: "some format")"));

  EXPECT_CALL(ftrace, ReadPageHeaderFormat()).Times(AnyNumber());
  EXPECT_CALL(ftrace, ReadEventFormat(_, _)).Times(AnyNumber());

  auto table = ProtoTranslationTable::Create(&ftrace, std::move(events),
                                             std::move(common_fields));
  PERFETTO_CHECK(table);
  EXPECT_EQ(table->largest_id(), 0ul);
  GroupAndName group_and_name("group", "foo");
  const Event* e = table->GetOrCreateEvent(group_and_name);
  EXPECT_EQ(table->largest_id(), 42ul);
  EXPECT_EQ(table->EventToFtraceId(group_and_name), 42ul);

  // Check getters
  EXPECT_EQ(table->GetEventById(42)->proto_field_id,
            protos::pbzero::FtraceEvent::kGenericFieldNumber);
  EXPECT_EQ(table->GetEvent(group_and_name)->proto_field_id,
            protos::pbzero::FtraceEvent::kGenericFieldNumber);
  EXPECT_EQ(table->GetEventsByGroup("group")->front()->name,
            group_and_name.name());

  EXPECT_EQ(e->fields.size(), 4ul);
  const std::vector<Field>& fields = e->fields;
  // Check string field
  const auto& str_field = fields[0];
  EXPECT_STREQ(str_field.ftrace_name, "field_a");
  EXPECT_EQ(str_field.proto_field_id,
            protos::pbzero::GenericFtraceEvent::Field::kStrValueFieldNumber);
  EXPECT_EQ(str_field.proto_field_type, ProtoSchemaType::kString);
  EXPECT_EQ(str_field.ftrace_type, kFtraceFixedCString);
  EXPECT_EQ(str_field.ftrace_size, 16);
  EXPECT_EQ(str_field.ftrace_offset, 8);

  // Check bool field
  const auto& bool_field = fields[1];
  EXPECT_STREQ(bool_field.ftrace_name, "field_b");
  EXPECT_EQ(bool_field.proto_field_id,
            protos::pbzero::GenericFtraceEvent::Field::kUintValueFieldNumber);
  EXPECT_EQ(bool_field.proto_field_type, ProtoSchemaType::kUint64);
  EXPECT_EQ(bool_field.ftrace_type, kFtraceBool);
  EXPECT_EQ(bool_field.ftrace_size, 1);
  EXPECT_EQ(bool_field.ftrace_offset, 24);

  // Check int field
  const auto& int_field = fields[2];
  EXPECT_STREQ(int_field.ftrace_name, "field_c");
  EXPECT_EQ(int_field.proto_field_id,
            protos::pbzero::GenericFtraceEvent::Field::kIntValueFieldNumber);
  EXPECT_EQ(int_field.proto_field_type, ProtoSchemaType::kInt64);
  EXPECT_EQ(int_field.ftrace_type, kFtraceInt32);
  EXPECT_EQ(int_field.ftrace_size, 4);
  EXPECT_EQ(int_field.ftrace_offset, 25);

  // Check uint field
  const auto& uint_field = fields[3];
  EXPECT_STREQ(uint_field.ftrace_name, "field_d");
  EXPECT_EQ(uint_field.proto_field_id,
            protos::pbzero::GenericFtraceEvent::Field::kUintValueFieldNumber);
  EXPECT_EQ(uint_field.proto_field_type, ProtoSchemaType::kUint64);
  EXPECT_EQ(uint_field.ftrace_type, kFtraceUint32);
  EXPECT_EQ(uint_field.ftrace_size, 4);
  EXPECT_EQ(uint_field.ftrace_offset, 33);
}

TEST(EventFilterTest, EnableEventsFrom) {
  EventFilter filter;
  filter.AddEnabledEvent(1);
  filter.AddEnabledEvent(17);

  EventFilter or_filter;
  or_filter.AddEnabledEvent(4);
  or_filter.AddEnabledEvent(17);

  filter.EnableEventsFrom(or_filter);
  EXPECT_TRUE(filter.IsEventEnabled(4));
  EXPECT_TRUE(filter.IsEventEnabled(17));
  EXPECT_TRUE(filter.IsEventEnabled(1));
  EXPECT_FALSE(filter.IsEventEnabled(2));

  EventFilter empty_filter;
  filter.EnableEventsFrom(empty_filter);
  EXPECT_TRUE(filter.IsEventEnabled(4));
  EXPECT_TRUE(filter.IsEventEnabled(17));
  EXPECT_TRUE(filter.IsEventEnabled(1));

  empty_filter.EnableEventsFrom(filter);
  EXPECT_TRUE(empty_filter.IsEventEnabled(4));
  EXPECT_TRUE(empty_filter.IsEventEnabled(17));
  EXPECT_TRUE(empty_filter.IsEventEnabled(1));
}

}  // namespace
}  // namespace perfetto
