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

#include "gpu/command_buffer/service/scheduler.h"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/common/scheduling_priority.h"
#include "gpu/config/gpu_preferences.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"

namespace gpu {
namespace {
uint64_t GetTaskFlowId(uint32_t sequence_id, uint64_t order_num) {
  // Xor with a mask to reduce likelihood of flow id collision with
  // non-scheduler tasks. First 64-bits of SHA256 hash of "gpu::Scheduler",
  // interpreted as a big-endian integer. Python snippet:
  // hashlib.sha256(b'gpu::Scheduler').hexdigest()[:16]
  static constexpr uint64_t kMask = 0x03af62470b040902;
  return kMask ^ (static_cast<uint64_t>(sequence_id) << 32) ^ order_num;
}

perfetto::NamedTrack GetSchedulerTraceTrack(const Scheduler* ptr) {
  return perfetto::NamedTrack::FromPointer("gpu::Scheduler", ptr);
}
}  // namespace

Scheduler::Task::Task(SequenceId sequence_id,
                      TaskCallback task_callback,
                      std::vector<SyncToken> sync_token_fences,
                      const SyncToken& release,
                      ReportingCallback report_callback)
    : sequence_id(sequence_id),
      task_callback(std::move(task_callback)),
      sync_token_fences(std::move(sync_token_fences)),
      release(release),
      report_callback(std::move(report_callback)) {}

Scheduler::Task::Task(SequenceId sequence_id,
                      base::OnceClosure task_closure,
                      std::vector<SyncToken> sync_token_fences,
                      const SyncToken& release,
                      ReportingCallback report_callback)
    : sequence_id(sequence_id),
      task_closure(std::move(task_closure)),
      sync_token_fences(std::move(sync_token_fences)),
      release(release),
      report_callback(std::move(report_callback)) {}

Scheduler::Task::Task(SequenceId sequence_id,
                      base::OnceClosure task_closure,
                      std::vector<SyncToken> sync_token_fences,
                      ReportingCallback report_callback)
    : Task(sequence_id,
           std::move(task_closure),
           std::move(sync_token_fences),
           /*release=*/{},
           std::move(report_callback)) {}

Scheduler::Task::Task(Task&& other) = default;
Scheduler::Task::~Task() = default;
Scheduler::Task& Scheduler::Task::operator=(Task&& other) = default;

Scheduler::ScopedSetSequencePriority::ScopedSetSequencePriority(
    Scheduler* scheduler,
    SequenceId sequence_id,
    SchedulingPriority priority)
    : scheduler_(scheduler), sequence_id_(sequence_id) {
  scheduler_->SetSequencePriority(sequence_id, priority);
}
Scheduler::ScopedSetSequencePriority::~ScopedSetSequencePriority() {
  scheduler_->SetSequencePriority(
      sequence_id_, scheduler_->GetSequenceDefaultPriority(sequence_id_));
}

Scheduler::SchedulingState::SchedulingState() = default;
Scheduler::SchedulingState::SchedulingState(const SchedulingState& other) =
    default;
Scheduler::SchedulingState::~SchedulingState() = default;

void Scheduler::SchedulingState::WriteIntoTrace(
    perfetto::TracedValue context) const {
  auto dict = std::move(context).WriteDictionary();
  dict.Add("sequence_id", sequence_id.GetUnsafeValue());
  dict.Add("priority", SchedulingPriorityToString(priority));
  dict.Add("order_num", order_num);
}

bool Scheduler::SchedulingState::operator==(
    const Scheduler::SchedulingState& rhs) const {
  return std::tie(sequence_id, priority, order_num) ==
         std::tie(rhs.sequence_id, rhs.priority, rhs.order_num);
}

Scheduler::PerThreadState::PerThreadState() = default;
Scheduler::PerThreadState::PerThreadState(PerThreadState&& other) = default;
Scheduler::PerThreadState::~PerThreadState() = default;
Scheduler::PerThreadState& Scheduler::PerThreadState::operator=(
    PerThreadState&& other) = default;

Scheduler::Sequence::Sequence(
    Scheduler* scheduler,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    SchedulingPriority priority,
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id)
    : TaskGraph::Sequence(&scheduler->task_graph_,
                          task_runner,
                          namespace_id,
                          command_buffer_id),
      scheduler_(scheduler),
      task_runner_(std::move(task_runner)),
      default_priority_(priority),
      current_priority_(priority) {}

Scheduler::Sequence::~Sequence() {
  for (const auto& wait_fence : wait_fences_) {
    Sequence* release_sequence =
        scheduler_->GetSequence(wait_fence.release_sequence_id);
    if (release_sequence) {
      scheduler_->TryScheduleSequence(release_sequence);
    }
  }
}

bool Scheduler::Sequence::ShouldYieldTo(const Sequence* other) const {
  if (task_runner() != other->task_runner())
    return false;
  if (!running()) {
    return false;
  }
  return SchedulingState::RunsBefore(other->scheduling_state_,
                                     scheduling_state_);
}

void Scheduler::Sequence::SetEnabled(bool enabled) {
  if (enabled_ == enabled)
    return;
  enabled_ = enabled;
  auto track =
      perfetto::NamedTrack::FromPointer("gpu::Scheduler::Sequence", this);
  if (enabled) {
    TRACE_EVENT_BEGIN("gpu", "SequenceEnabled", track, "sequence_id",
                      sequence_id_.GetUnsafeValue());
  } else {
    TRACE_EVENT_END("gpu", track, "sequence_id", sequence_id_.GetUnsafeValue());
  }
  scheduler_->TryScheduleSequence(this);
}

Scheduler::SchedulingState Scheduler::Sequence::SetScheduled() {
  DCHECK(HasTasksAndEnabled());
  DCHECK_NE(running_state_, RUNNING);

  running_state_ = SCHEDULED;

  scheduling_state_.sequence_id = sequence_id_;
  scheduling_state_.priority = current_priority();
  scheduling_state_.order_num = tasks_.front().order_num;

  return scheduling_state_;
}

void Scheduler::Sequence::UpdateRunningPriority() {
  DCHECK_EQ(running_state_, RUNNING);
  scheduling_state_.priority = current_priority();
}

void Scheduler::Sequence::ContinueTask(base::OnceClosure task_closure) {
  DCHECK_EQ(running_state_, RUNNING);
  TRACE_EVENT("gpu,toplevel.flow", "Scheduler::ContinueTask",
              perfetto::Flow::Global(GetTaskFlowId(
                  sequence_id_.value(), order_data_->current_order_num())));
  TaskGraph::Sequence::ContinueTask(std::move(task_closure));
}

uint64_t Scheduler::Sequence::BeginTask(base::OnceClosure* task_closure) {
  DCHECK_EQ(running_state_, SCHEDULED);
  running_state_ = RUNNING;
  return TaskGraph::Sequence::BeginTask(task_closure);
}

void Scheduler::Sequence::FinishTask() {
  DCHECK_EQ(running_state_, RUNNING);
  running_state_ = SCHEDULED;
  TaskGraph::Sequence::FinishTask();
}

void Scheduler::Sequence::OnFrontTaskUnblocked(uint64_t order_num) {
  TRACE_EVENT(
      "gpu,toplevel.flow", "Scheduler::SequenceUnblocked",
      perfetto::Flow::Global(GetTaskFlowId(sequence_id_.value(), order_num)));
  scheduler_->TryScheduleSequence(this);
}

Scheduler::Scheduler(SyncPointManager* sync_point_manager)
    : task_graph_(sync_point_manager) {}

Scheduler::~Scheduler() {
  base::AutoLock auto_lock(lock());

  // Sequences as well as tasks posted to the threads have "this" pointer of the
  // Scheduler. Hence adding DCHECKS to make sure sequences are
  // finished/destroyed and none of the threads are running by the time
  // scheduler is destroyed.
  DCHECK(scheduler_sequence_map_.empty());
  for (const auto& per_thread_state : per_thread_state_map_)
    DCHECK(!per_thread_state.second.running);
}

SequenceId Scheduler::CreateSequence(
    SchedulingPriority priority,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  return CreateSequence(priority, std::move(task_runner),
                        CommandBufferNamespace::INVALID,
                        /*command_buffer_id=*/{});
}

SequenceId Scheduler::CreateSequence(
    SchedulingPriority priority,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id) {
  auto sequence = std::make_unique<Sequence>(
      this, std::move(task_runner), priority, namespace_id, command_buffer_id);
  SequenceId id = sequence->sequence_id();
  Sequence* sequence_ptr = sequence.get();
  task_graph_.AddSequence(std::move(sequence));

  {
    base::AutoLock auto_lock(lock());
    CHECK_EQ(task_graph_.GetSequence(id), sequence_ptr);
    scheduler_sequence_map_.emplace(id, sequence_ptr);

    per_thread_state_map_[sequence_ptr->task_runner()].num_sequences++;
  }
  return id;
}

void Scheduler::DestroySequence(SequenceId sequence_id) {
  {
    base::AutoLock auto_lock(lock());
    auto sequence_it = scheduler_sequence_map_.find(sequence_id);
    CHECK(sequence_it != scheduler_sequence_map_.end());
    Sequence* sequence = sequence_it->second;

    auto per_thread_it = per_thread_state_map_.find(sequence->task_runner());
    CHECK(per_thread_it != per_thread_state_map_.end());
    auto& thread_state = per_thread_it->second;
    thread_state.num_sequences--;
    if (thread_state.num_sequences == 0) {
      per_thread_state_map_.erase(per_thread_it);
    }

    scheduler_sequence_map_.erase(sequence_it);
  }

  task_graph_.DestroySequence(sequence_id);
}

ScopedSyncPointClientState Scheduler::CreateSyncPointClientState(
    SequenceId sequence_id,
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id) {
  return task_graph_.CreateSyncPointClientState(sequence_id, namespace_id,
                                                command_buffer_id);
}

Scheduler::Sequence* Scheduler::GetSequence(SequenceId sequence_id) {
  auto it = scheduler_sequence_map_.find(sequence_id);
  if (it != scheduler_sequence_map_.end()) {
    return it->second;
  }
  return nullptr;
}

Scheduler::PerThreadState& Scheduler::GetThreadState(
    base::SingleThreadTaskRunner* task_runner) {
  auto it = per_thread_state_map_.find(task_runner);
  CHECK(it != per_thread_state_map_.end());
  return it->second;
}

void Scheduler::EnableSequence(SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);
  sequence->SetEnabled(true);
}

void Scheduler::DisableSequence(SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);
  sequence->SetEnabled(false);
}

SchedulingPriority Scheduler::GetSequenceDefaultPriority(
    SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  if (sequence) {
    return sequence->default_priority_;
  }
  return SchedulingPriority::kNormal;
}

void Scheduler::SetSequencePriority(SequenceId sequence_id,
                                    SchedulingPriority priority) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  if (sequence) {
    sequence->current_priority_ = priority;
  }
}

void Scheduler::ScheduleTask(Task task) {
  base::AutoLock auto_lock(lock());
  ScheduleTaskHelper(std::move(task));
}

void Scheduler::ScheduleTasks(std::vector<Task> tasks) {
  base::AutoLock auto_lock(lock());
  for (auto& task : tasks)
    ScheduleTaskHelper(std::move(task));
}

void Scheduler::ScheduleTaskHelper(Task task) {
  SequenceId sequence_id = task.sequence_id;
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);

  uint64_t order_num;
  if (task.task_callback) {
    order_num = sequence->AddTask(
        std::move(task.task_callback), std::move(task.sync_token_fences),
        task.release, std::move(task.report_callback));
  } else {
    order_num = sequence->AddTask(
        std::move(task.task_closure), std::move(task.sync_token_fences),
        task.release, std::move(task.report_callback));
  }

  TRACE_EVENT("gpu,toplevel.flow", "Scheduler::ScheduleTask",
              perfetto::Flow::Global(
                  GetTaskFlowId(sequence->sequence_id().value(), order_num)));

  TryScheduleSequence(sequence);
}

void Scheduler::ContinueTask(SequenceId sequence_id,
                             TaskCallback task_callback) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);
  DCHECK(sequence->task_runner()->BelongsToCurrentThread());
  sequence->ContinueTask(std::move(task_callback));
}

void Scheduler::ContinueTask(SequenceId sequence_id,
                             base::OnceClosure task_closure) {
  base::AutoLock auto_lock(lock());
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);
  DCHECK(sequence->task_runner()->BelongsToCurrentThread());
  sequence->ContinueTask(std::move(task_closure));
}

bool Scheduler::ShouldYield(SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());

  Sequence* running_sequence = GetSequence(sequence_id);
  DCHECK(running_sequence);
  DCHECK(running_sequence->running());
  DCHECK(running_sequence->task_runner()->BelongsToCurrentThread());

  // Call FindNextTask to find the sequence that will run next. This can
  // potentially return nullptr if the only dependency on this thread is a
  // sequence tied to another thread.
  // TODO(elgarawany): Remove ShouldYield entirely and make CommandBufferStub,
  // the only user of ShouldYield, always pause, and leave the scheduling
  // decision to the scheduler.
  Sequence* next_sequence = FindNextTask();
  if (next_sequence == nullptr) {
    return false;
  }

  return running_sequence->ShouldYieldTo(next_sequence);
}

base::SingleThreadTaskRunner* Scheduler::GetTaskRunnerForTesting(
    SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());
  return GetSequence(sequence_id)->task_runner();
}

void Scheduler::TryScheduleSequence(Sequence* sequence) {
  auto* task_runner = sequence->task_runner();
  auto& thread_state = GetThreadState(task_runner);

  DVLOG(10) << "Trying to schedule or wake up sequence "
            << sequence->sequence_id().value()
            << ". running: " << sequence->running() << ".";

  if (sequence->running()) {
    // Update priority of running sequence because of sync token releases.
    DCHECK(thread_state.running);
    sequence->UpdateRunningPriority();
  } else {
    // Insert into scheduling queue if sequence isn't already scheduled.
    if (!sequence->scheduled() && sequence->HasTasksAndEnabled()) {
      sequence->SetScheduled();
    }
    // Wake up RunNextTask if the sequence has work to do. (If the thread is not
    // running, that means that all other sequences were either empty, or
    // waiting for work to be done on another thread).
    if (!thread_state.running && HasAnyUnblockedTasksOnRunner(task_runner)) {
      TRACE_EVENT_BEGIN("gpu", "Scheduler::Running",
                        GetSchedulerTraceTrack(this));
      DVLOG(10) << "Waking up thread because there is work to do.";
      thread_state.running = true;
      thread_state.run_next_task_scheduled = base::TimeTicks::Now();
      task_runner->PostTask(FROM_HERE, base::BindOnce(&Scheduler::RunNextTask,
                                                      base::Unretained(this)));
    }
  }
}

const std::vector<Scheduler::SchedulingState>&
Scheduler::GetSortedRunnableSequences(PerThreadState& thread_state) {
  std::vector<SchedulingState>& sorted_sequences =
      thread_state.sorted_sequences;

  sorted_sequences.clear();
  for (const auto& kv : scheduler_sequence_map_) {
    Sequence* sequence = kv.second;
    // Add any sequence that is enabled, not already running, and has any tasks.
    if (sequence->IsRunnable()) {
      SchedulingState scheduling_state = sequence->SetScheduled();
      sorted_sequences.push_back(scheduling_state);
    }
  }

  // Sort the sequence. We never have more than a few handful of sequences - so
  // this is pretty cheap to do.
  std::stable_sort(sorted_sequences.begin(), sorted_sequences.end(),
                   &SchedulingState::RunsBefore);
  return sorted_sequences;
}

bool Scheduler::HasAnyUnblockedTasksOnRunner(
    const base::SingleThreadTaskRunner* task_runner) const {
  // Loop over all sequences and check if any of them are unblocked and belong
  // to |task_runner|.
  for (const auto& [_, sequence] : scheduler_sequence_map_) {
    CHECK(sequence);
    if (sequence->task_runner() == task_runner && sequence->enabled() &&
        sequence->IsFrontTaskUnblocked()) {
      return true;
    }
  }
  // Either we don't have any enabled sequences, or they are all blocked (this
  // can happen if DrDC is enabled).
  return false;
}

Scheduler::Sequence* Scheduler::FindNextTaskFromRoot(Sequence* root_sequence) {
  if (!root_sequence) {
    return nullptr;
  }

  VLOG_IF(10, !root_sequence->enabled())
      << "Sequence " << root_sequence->sequence_id() << " is not enabled!";
  DVLOG_IF(10, !root_sequence->HasTasks())
      << "Sequence " << root_sequence->sequence_id()
      << " does not have any tasks!";

  // Don't bother looking at disabled sequence, sequences that don't have tasks,
  // and (leaf) sequences that are already running. We don't look at running
  // sequences because their order number is updated *before* they finish, which
  // can make dependencies appear circular.
  if (!root_sequence->IsRunnable()) {
    return nullptr;
  }

  // First, recurse into any dependency that needs to run before the first
  // task in |root_sequence|. The dependencies are sorted by their order num
  // (because of WaitFence ordering).
  const uint64_t first_task_order_num = root_sequence->tasks_.front().order_num;
  DVLOG(10) << "Sequence " << root_sequence->sequence_id()
            << " (order_num: " << first_task_order_num << ") has "
            << root_sequence->wait_fences_.size() << " waits.";

  for (auto fence_iter = root_sequence->wait_fences_.begin();
       fence_iter != root_sequence->wait_fences_.end() &&
       fence_iter->order_num <= first_task_order_num;
       ++fence_iter) {
    // Recurse into the dependent sequence. If a subtask was found, then
    // we're done.
    DVLOG(10) << "Recursing into dependency in sequence "
              << fence_iter->release_sequence_id
              << " (order_num: " << fence_iter->order_num << ").";
    Sequence* release_sequence = GetSequence(fence_iter->release_sequence_id);
    // ShouldYield might be calling this function, and a dependency might depend
    // on the calling sequence, which might have not released its fences yet.
    if (release_sequence && release_sequence->HasTasksAndEnabled() &&
        release_sequence->tasks_.front().order_num >= fence_iter->order_num) {
      continue;
    }
    if (Sequence* result = FindNextTaskFromRoot(release_sequence);
        result != nullptr) {
      return result;
    }
  }
  // It's possible that none of root_sequence's dependencies can be run
  // because they are tied to another thread.
  const bool are_dependencies_done =
      root_sequence->wait_fences_.empty() ||
      root_sequence->wait_fences_.begin()->order_num > first_task_order_num;
  // Return |root_sequence| only if its dependencies are done, and if it can
  // run on the current thread.
  DVLOG_IF(10, root_sequence->task_runner() !=
                   base::SingleThreadTaskRunner::GetCurrentDefault().get())
      << "Will not run sequence because it does not belong to this thread.";
  if (are_dependencies_done &&
      root_sequence->task_runner() ==
          base::SingleThreadTaskRunner::GetCurrentDefault().get()) {
    return root_sequence;
  } else {
    DVLOG_IF(10, !are_dependencies_done)
        << "Sequence " << root_sequence->sequence_id()
        << "'s dependencies are not yet done.";
    return nullptr;
  }
}

Scheduler::Sequence* Scheduler::FindNextTask() {
  auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
  auto& thread_state = GetThreadState(task_runner);
  auto& sorted_sequences = GetSortedRunnableSequences(thread_state);

  // Walk the scheduling queue starting with the highest priority sequence and
  // find the first sequence that can be run. The loop will iterate more than
  // once only if DrDC is enabled and the first sequence contains a single
  // dependency tied to another thread.
  for (const SchedulingState& state : sorted_sequences) {
    Sequence* root_sequence = GetSequence(state.sequence_id);
    DVLOG(10) << "FindNextTask: Calling FindNextTaskFromRoot on sequence "
              << root_sequence->sequence_id().value();
    if (Sequence* sequence = FindNextTaskFromRoot(root_sequence);
        sequence != nullptr) {
      return sequence;
    }
  }

  return nullptr;
}

// See comments in scheduler.h for a high-level overview of the algorithm.
void Scheduler::RunNextTask() {
  SequenceId sequence_id;
  DCHECK(sequence_id.is_null());

  {
    base::AutoLock auto_lock(lock());
    auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();

    DVLOG(10) << "RunNextTask: Task runner is " << (uint64_t)task_runner;

    auto it = per_thread_state_map_.find(task_runner);
    if (it == per_thread_state_map_.end()) {
      TRACE_EVENT_END("gpu", /*"Scheduler::Running"*/
                      GetSchedulerTraceTrack(this));

      DVLOG(10) << "Thread has no sequences. Sleeping.";
      return;
    }

    // Walk the job graph starting from the highest priority roots to find a
    // task to run.
    Sequence* sequence = FindNextTask();

    if (sequence == nullptr) {
      // If there is no sequence to run, it should mean that there are no
      // runnable sequences.
      // TODO(elgarawany): We shouldn't have run RunNextTask if there were no
      // runnable sequences. Change logic to check for that too (that changes
      // old behavior - so leaving for now).

      auto& thread_state = it->second;

      // TODO(crbug.com/40278526): this assert is firing frequently on
      // Release builds with dcheck_always_on on Intel Macs. It looks
      // like it happens when the browser drops frames.
      /*
      DCHECK(GetSortedRunnableSequences(thread_state).empty())
          << "RunNextTask should not have been called "
             "if it did not have any unblocked tasks.";
      */

      TRACE_EVENT_END("gpu", /*"Scheduler::Running"*/
                      GetSchedulerTraceTrack(this));

      DVLOG(10) << "Empty scheduling queue. Sleeping.";
      thread_state.running = false;
      return;
    }

    DCHECK(sequence->task_runner() == task_runner)
        << "FindNextTaskFromRoot returned sequence that does not belong to "
           "this thread.";
    sequence_id = sequence->sequence_id();
  }

  // Now, execute the sequence's task.
  ExecuteSequence(sequence_id);

  // Finally, reschedule RunNextTask if there is any potential remaining work.
  {
    base::AutoLock auto_lock(lock());
    auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();

    auto it = per_thread_state_map_.find(task_runner);
    if (it == per_thread_state_map_.end()) {
      DVLOG(10) << "Thread has no sequences. Sleeping.";
      return;
    }
    auto& thread_state = it->second;

    if (!HasAnyUnblockedTasksOnRunner(task_runner)) {
      TRACE_EVENT_END("gpu", /*"Scheduler::Running"*/
                      GetSchedulerTraceTrack(this));
      DVLOG(10) << "Thread has no runnable sequences. Sleeping.";
      thread_state.running = false;
      return;
    }
    thread_state.run_next_task_scheduled = base::TimeTicks::Now();
    task_runner->PostTask(FROM_HERE, base::BindOnce(&Scheduler::RunNextTask,
                                                    base::Unretained(this)));
  }
}

void Scheduler::ExecuteSequence(const SequenceId sequence_id) {
  base::AutoLock auto_lock(lock());
  auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
  auto& thread_state = GetThreadState(task_runner);

  DVLOG(10) << "Executing sequence " << sequence_id.value() << ".";
  Sequence* sequence = GetSequence(sequence_id);
  DCHECK(sequence);
  DCHECK(sequence->HasTasksAndEnabled());
  DCHECK_EQ(sequence->task_runner(), task_runner);

  // Subsampling these metrics reduced CPU utilization (crbug.com/1295441).
  const bool log_histograms = base::ShouldRecordSubsampledMetric(0.001);
  const std::string_view priority_str =
      SchedulingPriorityToString(sequence->default_priority_);

  if (log_histograms) {
    base::TimeTicks now = base::TimeTicks::Now();

    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "GPU.Scheduler.ThreadSuspendedTime",
        now - thread_state.run_next_task_scheduled, base::Microseconds(10),
        base::Seconds(30), 100);
    base::UmaHistogramCustomMicrosecondsTimes(
        base::StrCat(
            {"GPU.Scheduler.ThreadSuspendedTime.", priority_str, "Priority"}),
        now - thread_state.run_next_task_scheduled, base::Microseconds(10),
        base::Seconds(30), 100);

    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "GPU.Scheduler.TaskDependencyTime",
        sequence->FrontTaskWaitingDependencyDelta(), base::Microseconds(10),
        base::Seconds(30), 100);
    base::UmaHistogramCustomMicrosecondsTimes(
        base::StrCat(
            {"GPU.Scheduler.TaskDependencyTime.", priority_str, "Priority"}),
        sequence->FrontTaskWaitingDependencyDelta(), base::Microseconds(10),
        base::Seconds(30), 100);

    // The delay between when the front task was ready to run (no more
    // dependencies) and now.
    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "GPU.Scheduler.TaskSchedulingDelayTime",
        now - sequence->tasks_.front().running_ready, base::Microseconds(10),
        base::Seconds(30), 100);
    base::UmaHistogramCustomMicrosecondsTimes(
        base::StrCat({"GPU.Scheduler.TaskSchedulingDelayTime.", priority_str,
                      "Priority"}),
        now - sequence->tasks_.front().running_ready, base::Microseconds(10),
        base::Seconds(30), 100);

    // The delay between when the front task was registered and now.
    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "GPU.Scheduler.TaskTotalDelayTime",
        now - sequence->tasks_.front().registration, base::Microseconds(10),
        base::Seconds(30), 100);
    base::UmaHistogramCustomMicrosecondsTimes(
        base::StrCat(
            {"GPU.Scheduler.TaskTotalDelayTime.", priority_str, "Priority"}),
        now - sequence->tasks_.front().registration, base::Microseconds(10),
        base::Seconds(30), 100);
  }

  base::OnceClosure task_closure;
  const uint64_t order_num = sequence->BeginTask(&task_closure);
  const SyncToken release = sequence->current_task_release();
  const uint64_t task_flow_id = GetTaskFlowId(sequence_id.value(), order_num);

  // Begin/FinishProcessingOrderNumber must be called with the lock released
  // because they can renter the scheduler in Enable/DisableSequence.
  scoped_refptr<SyncPointOrderData> order_data = sequence->order_data();

  // Unset pointers before releasing the lock to prevent accidental data race.
  sequence = nullptr;

  base::TimeDelta blocked_time;
  {
    base::AutoUnlock auto_unlock(lock());

    TRACE_EVENT("gpu,toplevel.flow", "Scheduler::RunTask",
                perfetto::Flow::Global(task_flow_id));

    order_data->BeginProcessingOrderNumber(order_num);

    std::move(task_closure).Run();

    if (order_data->IsProcessingOrderNumber()) {
      if (release.HasData()) {
        task_graph_.sync_point_manager()->EnsureFenceSyncReleased(
            release, ReleaseCause::kTaskCompletionRelease);
      }

      order_data->FinishProcessingOrderNumber(order_num);

      TRACE_EVENT("gpu,toplevel.flow", "Scheduler::FinishTask",
                  perfetto::TerminatingFlow::Global(task_flow_id));
    }
  }
  total_blocked_time_ += blocked_time;

  // Reset pointers after reacquiring the lock.
  sequence = GetSequence(sequence_id);
  if (sequence) {
    sequence->FinishTask();
    if (log_histograms) {
      int percent_concurrent =
          std::round(sequence->last_task_concurrency_ratio_ * 100);
      UMA_HISTOGRAM_PERCENTAGE("GPU.Scheduler.PercentConcurrentTaskExecution",
                               percent_concurrent);
      base::UmaHistogramPercentage(
          base::StrCat({"GPU.Scheduler.PercentConcurrentTaskExecution.",
                        priority_str, "Priority"}),
          percent_concurrent);
    }
  }
}

}  // namespace gpu
