// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/observer_list.h"

#include <memory>

#include "base/check_op.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_result_reporter.h"

// Ask the compiler not to use a register for this counter, in case it decides
// to do magic optimizations like |counter += kLaps|.
volatile int g_observer_list_perf_test_counter;

namespace base {

constexpr char kMetricPrefixObserverList[] = "ObserverList.";
constexpr char kMetricNotifyTimePerObserver[] = "notify_time_per_observer";

namespace {

perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
  perf_test::PerfResultReporter reporter(kMetricPrefixObserverList, story_name);
  reporter.RegisterImportantMetric(kMetricNotifyTimePerObserver, "ns");
  return reporter;
}

}  // namespace

class ObserverInterface {
 public:
  ObserverInterface() = default;
  ObserverInterface(const ObserverInterface&) = delete;
  ObserverInterface& operator=(const ObserverInterface&) = delete;
  virtual ~ObserverInterface() = default;
  virtual void Observe() const {
    g_observer_list_perf_test_counter = g_observer_list_perf_test_counter + 1;
  }
};

class UnsafeObserver : public ObserverInterface {};

class TestCheckedObserver : public CheckedObserver, public ObserverInterface {};

template <class ObserverType>
struct Pick {
  // The ObserverList type to use. Checked observers need to be in a checked
  // ObserverList.
  using ObserverListType = ObserverList<ObserverType>;
  static const char* GetName() { return "CheckedObserver"; }
};
template <>
struct Pick<UnsafeObserver> {
  using ObserverListType = ObserverList<ObserverInterface>::Unchecked;
  static const char* GetName() { return "UnsafeObserver"; }
};

template <class ObserverType>
class ObserverListPerfTest : public ::testing::Test {
 public:
  using ObserverListType = typename Pick<ObserverType>::ObserverListType;

  ObserverListPerfTest() = default;
  ObserverListPerfTest(const ObserverListPerfTest&) = delete;
  ObserverListPerfTest& operator=(const ObserverListPerfTest&) = delete;
};

typedef ::testing::Types<UnsafeObserver, TestCheckedObserver> ObserverTypes;
TYPED_TEST_SUITE(ObserverListPerfTest, ObserverTypes);

// Performance test for base::ObserverList and Checked Observers.
TYPED_TEST(ObserverListPerfTest, NotifyPerformance) {
  constexpr int kMaxObservers = 128;
#if DCHECK_IS_ON()
  // The test takes about 100x longer in debug builds, mostly due to sequence
  // checker overheads when WeakPtr gets involved.
  constexpr int kLaps = 1000000;
#else
  constexpr int kLaps = 100000000;
#endif
  constexpr int kWarmupLaps = 100;
  std::vector<std::unique_ptr<TypeParam>> observers;

  for (int observer_count = 0; observer_count <= kMaxObservers;
       observer_count = observer_count ? observer_count * 2 : 1) {
    typename TestFixture::ObserverListType list;
    for (int i = 0; i < observer_count; ++i)
      observers.push_back(std::make_unique<TypeParam>());
    for (auto& o : observers)
      list.AddObserver(o.get());

    for (int i = 0; i < kWarmupLaps; ++i) {
      for (auto& o : list)
        o.Observe();
    }
    g_observer_list_perf_test_counter = 0;
    const int weighted_laps = kLaps / (observer_count + 1);

    TimeTicks start = TimeTicks::Now();
    for (int i = 0; i < weighted_laps; ++i) {
      for (auto& o : list)
        o.Observe();
    }
    TimeDelta duration = TimeTicks::Now() - start;

    observers.clear();

    EXPECT_EQ(observer_count * weighted_laps,
              g_observer_list_perf_test_counter);
    EXPECT_TRUE(observer_count == 0 || !list.empty());

    std::string story_name =
        base::StringPrintf("%s_%d", Pick<TypeParam>::GetName(), observer_count);

    // A typical value is 3-20 nanoseconds per observe in Release, 1000-2000ns
    // in an optimized build with DCHECKs and 3000-6000ns in debug builds.
    auto reporter = SetUpReporter(story_name);
    reporter.AddResult(
        kMetricNotifyTimePerObserver,
        duration.InNanoseconds() /
            static_cast<double>(g_observer_list_perf_test_counter +
                                weighted_laps));
  }
}

}  // namespace base
