// Copyright 2018 The Chromium 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 "base/message_loop/message_loop_task_runner.h"

#include <string>
#include <utility>

#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/debug/task_annotator.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/incoming_task_queue.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_task_runner.h"
#include "base/message_loop/message_pump.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"

namespace base {

namespace {

// Tests below will post tasks in a loop until |kPostTaskPerfTestDuration| has
// elapsed.
constexpr TimeDelta kPostTaskPerfTestDuration =
    base::TimeDelta::FromSeconds(30);

}  // namespace

class FakeObserver : public internal::IncomingTaskQueue::Observer {
 public:
  // IncomingTaskQueue::Observer
  void WillQueueTask(PendingTask* task) override {}
  void DidQueueTask(bool was_empty) override {}

  virtual void RunTask(PendingTask* task) { std::move(task->task).Run(); }
};

// Exercises MessageLoopTaskRunner's multi-threaded queue in isolation.
class BasicPostTaskPerfTest : public testing::Test {
 public:
  void Run(int batch_size,
           int tasks_per_reload,
           std::unique_ptr<FakeObserver> task_source_observer) {
    base::TimeTicks start = base::TimeTicks::Now();
    base::TimeTicks now;
    FakeObserver* task_source_observer_raw = task_source_observer.get();
    scoped_refptr<internal::IncomingTaskQueue> queue(
        base::MakeRefCounted<internal::IncomingTaskQueue>(
            std::move(task_source_observer)));
    scoped_refptr<SingleThreadTaskRunner> task_runner(
        base::MakeRefCounted<internal::MessageLoopTaskRunner>(queue));
    uint32_t num_posted = 0;
    do {
      for (int i = 0; i < batch_size; ++i) {
        for (int j = 0; j < tasks_per_reload; ++j) {
          task_runner->PostTask(FROM_HERE, DoNothing());
          num_posted++;
        }
        TaskQueue loop_local_queue;
        queue->ReloadWorkQueue(&loop_local_queue);
        while (!loop_local_queue.empty()) {
          PendingTask t = std::move(loop_local_queue.front());
          loop_local_queue.pop();
          task_source_observer_raw->RunTask(&t);
        }
      }

      now = base::TimeTicks::Now();
    } while (now - start < kPostTaskPerfTestDuration);
    std::string trace = StringPrintf("%d_tasks_per_reload", tasks_per_reload);
    perf_test::PrintResult(
        "task", "", trace,
        (now - start).InMicroseconds() / static_cast<double>(num_posted),
        "us/task", true);
  }
};

TEST_F(BasicPostTaskPerfTest, OneTaskPerReload) {
  Run(10000, 1, std::make_unique<FakeObserver>());
}

TEST_F(BasicPostTaskPerfTest, TenTasksPerReload) {
  Run(10000, 10, std::make_unique<FakeObserver>());
}

TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReload) {
  Run(1000, 100, std::make_unique<FakeObserver>());
}

class StubMessagePump : public MessagePump {
 public:
  StubMessagePump() = default;
  ~StubMessagePump() override = default;

  // MessagePump:
  void Run(Delegate* delegate) override {}
  void Quit() override {}
  void ScheduleWork() override {}
  void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override {}
};

// Simulates the overhead of hooking TaskAnnotator and ScheduleWork() to the
// post task machinery.
class FakeObserverSimulatingOverhead : public FakeObserver {
 public:
  FakeObserverSimulatingOverhead() = default;

  // FakeObserver:
  void WillQueueTask(PendingTask* task) final {
    task_annotator_.WillQueueTask("MessageLoop::PostTask", task);
  }

  void DidQueueTask(bool was_empty) final {
    AutoLock scoped_lock(message_loop_lock_);
    pump_->ScheduleWork();
  }

  void RunTask(PendingTask* task) final {
    task_annotator_.RunTask("MessageLoop::PostTask", task);
  }

 private:
  // Simulates overhead from ScheduleWork() and TaskAnnotator calls involved in
  // a real PostTask (stores the StubMessagePump in a pointer to force a virtual
  // dispatch for ScheduleWork() and be closer to reality).
  Lock message_loop_lock_;
  std::unique_ptr<MessagePump> pump_{std::make_unique<StubMessagePump>()};
  debug::TaskAnnotator task_annotator_;

  DISALLOW_COPY_AND_ASSIGN(FakeObserverSimulatingOverhead);
};

TEST_F(BasicPostTaskPerfTest, OneTaskPerReloadWithOverhead) {
  Run(10000, 1, std::make_unique<FakeObserverSimulatingOverhead>());
}

TEST_F(BasicPostTaskPerfTest, TenTasksPerReloadWithOverhead) {
  Run(10000, 10, std::make_unique<FakeObserverSimulatingOverhead>());
}

TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReloadWithOverhead) {
  Run(1000, 100, std::make_unique<FakeObserverSimulatingOverhead>());
}

// Exercises the full MessageLoop/RunLoop machinery.
class IntegratedPostTaskPerfTest : public testing::Test {
 public:
  void Run(int batch_size, int tasks_per_reload) {
    base::TimeTicks start = base::TimeTicks::Now();
    base::TimeTicks now;
    MessageLoop loop;
    uint32_t num_posted = 0;
    do {
      for (int i = 0; i < batch_size; ++i) {
        for (int j = 0; j < tasks_per_reload; ++j) {
          loop->task_runner()->PostTask(FROM_HERE, DoNothing());
          num_posted++;
        }
        RunLoop().RunUntilIdle();
      }

      now = base::TimeTicks::Now();
    } while (now - start < kPostTaskPerfTestDuration);
    std::string trace = StringPrintf("%d_tasks_per_reload", tasks_per_reload);
    perf_test::PrintResult(
        "task", "", trace,
        (now - start).InMicroseconds() / static_cast<double>(num_posted),
        "us/task", true);
  }
};

TEST_F(IntegratedPostTaskPerfTest, OneTaskPerReload) {
  Run(10000, 1);
}

TEST_F(IntegratedPostTaskPerfTest, TenTasksPerReload) {
  Run(10000, 10);
}

TEST_F(IntegratedPostTaskPerfTest, OneHundredTasksPerReload) {
  Run(1000, 100);
}

}  // namespace base