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

#include "content/browser/scheduler/responsiveness/jank_monitor_impl.h"

#include "base/compiler_specific.h"
#include "base/observer_list.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"

namespace content {

JankMonitor::~JankMonitor() = default;

JankMonitor::Observer::~Observer() = default;

// static
scoped_refptr<JankMonitor> JankMonitor::Create() {
  return base::MakeRefCounted<responsiveness::JankMonitorImpl>();
}

namespace responsiveness {

// Interval of the monitor performing jankiness checks against the watched
// threads.
static constexpr int64_t kMonitorCheckIntervalMs = 500;
// A task running for longer than |kJankThresholdMs| is considered janky.
static constexpr int64_t kJankThresholdMs = 1000;
// The threshold (10 sec) for shutting down the monitor timer, in microseconds.
static constexpr int64_t kInactivityThresholdUs =
    10 * base::TimeTicks::kMicrosecondsPerSecond;

JankMonitorImpl::JankMonitorImpl()
    : timer_(std::make_unique<base::RepeatingTimer>()),
      timer_running_(false),
      janky_task_id_(0),
      last_activity_time_us_(0) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DETACH_FROM_SEQUENCE(monitor_sequence_checker_);
}

JankMonitorImpl::~JankMonitorImpl() = default;

void JankMonitorImpl::AddObserver(content::JankMonitor::Observer* observer) {
  base::AutoLock auto_lock(observers_lock_);
  observers_.AddObserver(observer);
}

void JankMonitorImpl::RemoveObserver(content::JankMonitor::Observer* observer) {
  base::AutoLock auto_lock(observers_lock_);
  observers_.RemoveObserver(observer);
}

void JankMonitorImpl::SetUp() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Dependencies in SetUp() and Destroy():
  // * Target thread --(may schedule the timer on)--> Monitor thread.
  // * Monitor thread --(read/write)--> ThreadExecutionState data members.
  // * Target thread --(write)--> ThreadExecutionState data members.

  // ThreadExecutionState data members are created first.
  ui_thread_exec_state_ = std::make_unique<ThreadExecutionState>();
  io_thread_exec_state_ = std::make_unique<ThreadExecutionState>();

  // Then the monitor thread.
  monitor_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});

  // Finally set up the MetricSource.
  metric_source_ = CreateMetricSource();
  metric_source_->SetUp();
}

void JankMonitorImpl::Destroy() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Destroy shuts down the monitor timer and the metric source in parallel.
  // |timer_| is shut down and destroyed on the monitor thread. |metric_source_|
  // is destroyed in calling its Destroy() method. The shared data members,
  // |ui_thread_exec_state_| and |io_thread_exec_state_| is destroyed in the
  // JankMonitor dtor, which can happen on either the monitor or the UI thread.

  monitor_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&JankMonitorImpl::DestroyOnMonitorThread,
                                base::RetainedRef(this)));

  base::ScopedClosureRunner finish_destroy_metric_source(base::BindOnce(
      &JankMonitorImpl::FinishDestroyMetricSource, base::RetainedRef(this)));
  metric_source_->Destroy(std::move(finish_destroy_metric_source));
}

void JankMonitorImpl::FinishDestroyMetricSource() {
  // Destruction of MetricSource takes place on the UI thread.
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  metric_source_ = nullptr;
}

void JankMonitorImpl::SetUpOnIOThread() {}

void JankMonitorImpl::TearDownOnUIThread() {
  // Don't destroy |ui_thread_exec_state_| yet because it might be used if the
  // monitor timer runs.
}

void JankMonitorImpl::TearDownOnIOThread() {
  // Don't destroy |io_thread_exec_state_| yet because it might be used if the
  // monitor timer fires.
}

void JankMonitorImpl::WillRunTaskOnUIThread(
    const base::PendingTask* task,
    bool /* was_blocked_or_low_priority */) {
  DCHECK(ui_thread_exec_state_);
  WillRunTaskOrEvent(ui_thread_exec_state_.get(),
                     reinterpret_cast<uintptr_t>(task));
}

void JankMonitorImpl::DidRunTaskOnUIThread(const base::PendingTask* task) {
  DCHECK(ui_thread_exec_state_);
  DidRunTaskOrEvent(ui_thread_exec_state_.get(),
                    reinterpret_cast<uintptr_t>(task));
}

void JankMonitorImpl::WillRunTaskOnIOThread(
    const base::PendingTask* task,
    bool /* was_blocked_or_low_priority */) {
  DCHECK(io_thread_exec_state_);
  WillRunTaskOrEvent(io_thread_exec_state_.get(),
                     reinterpret_cast<uintptr_t>(task));
}

void JankMonitorImpl::DidRunTaskOnIOThread(const base::PendingTask* task) {
  DCHECK(io_thread_exec_state_);
  DidRunTaskOrEvent(io_thread_exec_state_.get(),
                    reinterpret_cast<uintptr_t>(task));
}

void JankMonitorImpl::WillRunEventOnUIThread(uintptr_t opaque_identifier) {
  DCHECK(ui_thread_exec_state_);
  WillRunTaskOrEvent(ui_thread_exec_state_.get(), opaque_identifier);
}

void JankMonitorImpl::DidRunEventOnUIThread(uintptr_t opaque_identifier) {
  DCHECK(ui_thread_exec_state_);
  DidRunTaskOrEvent(ui_thread_exec_state_.get(), opaque_identifier);
}

void JankMonitorImpl::WillRunTaskOrEvent(
    ThreadExecutionState* thread_exec_state,
    uintptr_t opaque_identifier) {
  thread_exec_state->WillRunTaskOrEvent(opaque_identifier);
  if (!timer_running_) {
    monitor_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&JankMonitorImpl::StartTimerIfNecessary,
                                  base::RetainedRef(this)));
  }
}

void JankMonitorImpl::DidRunTaskOrEvent(ThreadExecutionState* thread_exec_state,
                                        uintptr_t opaque_identifier) {
  thread_exec_state->DidRunTaskOrEvent(opaque_identifier);
  NotifyJankStopIfNecessary(opaque_identifier);

  // This might lead to concurrent writes to |last_activity_time_us_|. Either
  // write is fine, and we don't require it to be monotonically increasing.
  last_activity_time_us_ =
      (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
}

void JankMonitorImpl::StartTimerIfNecessary() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);

  // |timer_| is already destroyed. This function is posted from UI or IO thread
  // after Destroy() is called. Just do nothing.
  if (!timer_)
    return;

  DCHECK_EQ(timer_->IsRunning(), timer_running_);

  // Already running. Maybe both UI and IO threads saw the timer stopped, and
  // one attempt has already succeeded.
  if (timer_->IsRunning())
    return;

  static base::TimeDelta monitor_check_interval =
      base::Milliseconds(kMonitorCheckIntervalMs);
  // RepeatingClosure bound to the timer doesn't hold a ref to |this| because
  // the ref will only be released on timer destruction.
  timer_->Start(FROM_HERE, monitor_check_interval,
                base::BindRepeating(&JankMonitorImpl::OnCheckJankiness,
                                    base::Unretained(this)));
  timer_running_ = true;
}

void JankMonitorImpl::StopTimerIfIdle() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
  DCHECK(timer_->IsRunning());

  auto now_us = (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
  if (now_us - last_activity_time_us_ < kInactivityThresholdUs)
    return;

  timer_->Stop();
  timer_running_ = false;
}

std::unique_ptr<MetricSource> JankMonitorImpl::CreateMetricSource() {
  return std::make_unique<MetricSource>(this);
}

void JankMonitorImpl::DestroyOnMonitorThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
  DCHECK(timer_);

  timer_->Stop();
  timer_ = nullptr;
  timer_running_ = false;
}

bool JankMonitorImpl::timer_running() const {
  return timer_running_;
}

void JankMonitorImpl::OnCheckJankiness() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);

  if (janky_task_id_) {
    return;
  }

  auto task_id = ui_thread_exec_state_->CheckJankiness();
  if (task_id.has_value()) {
    OnJankStarted(*task_id);
    return;
  }

  DCHECK(!janky_task_id_);
  // Jankiness is checked in the order of UI, IO thread.
  task_id = io_thread_exec_state_->CheckJankiness();
  if (task_id.has_value()) {
    OnJankStarted(*task_id);
    return;
  }

  DCHECK(!janky_task_id_);
  StopTimerIfIdle();
}

void JankMonitorImpl::OnJankStarted(uintptr_t opaque_identifier) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);

  janky_task_id_ = opaque_identifier;

  base::AutoLock auto_lock(observers_lock_);
  for (content::JankMonitor::Observer& observer : observers_)
    observer.OnJankStarted();
}

void JankMonitorImpl::OnJankStopped(uintptr_t opaque_identifier) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
  DCHECK_NE(opaque_identifier, 0u);
  if (janky_task_id_ != opaque_identifier)
    return;

  janky_task_id_ = 0;

  base::AutoLock auto_lock(observers_lock_);
  for (content::JankMonitor::Observer& observer : observers_)
    observer.OnJankStopped();
}

void JankMonitorImpl::NotifyJankStopIfNecessary(uintptr_t opaque_identifier) {
  if (!janky_task_id_ || janky_task_id_ != opaque_identifier) [[likely]] {
    // Most tasks are unlikely to be janky.
    return;
  }

  monitor_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&JankMonitorImpl::OnJankStopped,
                                base::RetainedRef(this), opaque_identifier));
}

JankMonitorImpl::ThreadExecutionState::TaskMetadata::~TaskMetadata() = default;

JankMonitorImpl::ThreadExecutionState::ThreadExecutionState() {
  // Constructor is always on the UI thread. Detach |target_sequence_checker_|
  // to make it work on IO thread.
  DETACH_FROM_SEQUENCE(target_sequence_checker_);
  DETACH_FROM_SEQUENCE(monitor_sequence_checker_);
}

JankMonitorImpl::ThreadExecutionState::~ThreadExecutionState() = default;

std::optional<uintptr_t>
JankMonitorImpl::ThreadExecutionState::CheckJankiness() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);

  base::TimeTicks now = base::TimeTicks::Now();
  static base::TimeDelta jank_threshold = base::Milliseconds(kJankThresholdMs);

  base::AutoLock lock(lock_);
  if (task_execution_metadata_.empty() ||
      (now - task_execution_metadata_.back().execution_start_time) <
          jank_threshold) [[likely]] {
    // Most tasks are unlikely to be janky.
    return std::nullopt;
  }

  // Mark that the target thread is janky and notify the monitor thread.
  return task_execution_metadata_.back().identifier;
}

void JankMonitorImpl::ThreadExecutionState::WillRunTaskOrEvent(
    uintptr_t opaque_identifier) {
  AssertOnTargetThread();

  base::TimeTicks now = base::TimeTicks::Now();

  base::AutoLock lock(lock_);
  task_execution_metadata_.emplace_back(now, opaque_identifier);
}

void JankMonitorImpl::ThreadExecutionState::DidRunTaskOrEvent(
    uintptr_t opaque_identifier) {
  AssertOnTargetThread();

  base::AutoLock lock(lock_);
  if (task_execution_metadata_.empty() ||
      opaque_identifier != task_execution_metadata_.back().identifier)
      [[unlikely]] {
    // Mismatches can happen (e.g: on ozone/wayland when Paste button is pressed
    // in context menus, among others). Simply ignore the mismatches for now.
    // See https://crbug.com/929813 for the details of why the mismatch
    // happens.
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE)
    task_execution_metadata_.clear();
#endif
    return;
  }

  task_execution_metadata_.pop_back();
}

void JankMonitorImpl::ThreadExecutionState::AssertOnTargetThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(target_sequence_checker_);
}

}  // namespace responsiveness.
}  // namespace content.
