910e62b5创建于 1月15日历史提交
// Copyright 2020 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/task/sequence_manager/thread_controller.h"

#include <atomic>
#include <string_view>

#include "base/check.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"

namespace base::sequence_manager::internal {

namespace {

// TODO(crbug.com/458682617): Remove this. It is an artifact of a feature which
// had the unintentional side-effect of adding a memory barrier at the end of
// each "ThreadController active". Hiding some TSAN failures which need fixing
// before we can lift this. It is disabled when FeatureList gets initialized (as
// the feature was), but FeatureList isn't initialized in all test suites...
std::atomic<bool> g_fortuitous_memory_barrier_on_sleep{
#if defined(THREAD_SANITIZER)
    true};
#else
    false};
#endif

void PerformFortuitousMemoryBarrierIfNecessary() {
  if (g_fortuitous_memory_barrier_on_sleep.load(std::memory_order_relaxed)) {
    static constinit std::atomic_uint shared_int{0};
    // This is the minimum requirement (side-effect from the previous
    // AutoLock) to reproduce crbug.com/458682617 (and mask TSAN failures).
    // Toggling this to std::memory_order_relaxed exposes the TSAN failures.
    // Note: We use `fetch_add` because a `store` isn't allowed to have
    // `acquire` semantics and we need acquire-release semantics to force any
    // kind of synchronization. We use an atomic variable instead of a full
    // `std::atomic_thread_fence` as this only aligns threads which pass
    // through this code path (as AutoLock would) instead of aligning with
    // other threads and their own unrelated memory barriers.
    shared_int.fetch_add(1, std::memory_order_acq_rel);
  }
}

// ThreadController interval metrics are mostly of interest for intervals that
// are not trivially short. Under a certain threshold it's unlikely that
// intervention from developers would move metrics. Log with suffix for
// intervals under a threshold chosen via tracing data. To validate the
// threshold makes sense and does not filter out too many samples
// ThreadController.ActiveIntervalDuration can be used.
constexpr TimeDelta kNonTrivialActiveIntervalLength = Milliseconds(1);
constexpr TimeDelta kMediumActiveIntervalLength = Milliseconds(100);

std::string MakeSuffix(std::string_view time_suffix,
                       std::string_view thread_name) {
  return base::StrCat({".", time_suffix, ".", thread_name});
}

}  // namespace

ThreadController::ThreadController(const TickClock* time_source)
    : associated_thread_(AssociatedThreadId::CreateUnbound()),
      time_source_(time_source) {}

ThreadController::~ThreadController() = default;

void ThreadController::SetTickClock(const TickClock* clock) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  time_source_ = clock;
}

ThreadController::RunLevelTracker::RunLevelTracker(
    const ThreadController& outer)
    : outer_(outer) {}

ThreadController::RunLevelTracker::~RunLevelTracker() {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);

  // There shouldn't be any remaining |run_levels_| by the time this unwinds.
  DCHECK_EQ(run_levels_.size(), 0u);
}

// static
void ThreadController::InitializeFeatures() {
  // Disable fortuitous barriers whenever FeatureList is initialized to minimize
  // the surface where this is applied.
  g_fortuitous_memory_barrier_on_sleep.store(false, std::memory_order_relaxed);
}

std::string_view ThreadController::RunLevelTracker::RunLevel::GetThreadName() {
  std::string_view thread_name = "Other";
  if (!time_keeper_->thread_name().empty()) {
    thread_name = time_keeper_->thread_name();
  }
  return thread_name;
}

std::string
ThreadController::RunLevelTracker::RunLevel::GetSuffixForCatchAllHistogram() {
  return MakeSuffix("Any", GetThreadName());
}

std::string ThreadController::RunLevelTracker::RunLevel::GetSuffixForHistogram(
    TimeDelta duration) {
  std::string_view time_suffix;
  if (duration < kNonTrivialActiveIntervalLength) {
    time_suffix = "Short";
  } else if (duration < kMediumActiveIntervalLength) {
    time_suffix = "Medium";
  }
  return MakeSuffix(time_suffix, GetThreadName());
}

void ThreadController::EnableMessagePumpTimeKeeperMetrics(
    const char* thread_name,
    bool wall_time_based_metrics_enabled_for_testing) {
  // MessagePump runs too fast, a low-res clock would result in noisy metrics.
  if (!base::TimeTicks::IsHighResolution()) {
    return;
  }

  run_level_tracker_.EnableTimeKeeperMetrics(
      thread_name, wall_time_based_metrics_enabled_for_testing);
}

void ThreadController::RunLevelTracker::EnableTimeKeeperMetrics(
    const char* thread_name,
    bool wall_time_based_metrics_enabled_for_testing) {
  time_keeper_.EnableRecording(thread_name,
                               wall_time_based_metrics_enabled_for_testing);
}

void ThreadController::RunLevelTracker::TimeKeeper::EnableRecording(
    const char* thread_name,
    bool wall_time_based_metrics_enabled_for_testing) {
  DCHECK(!histogram_);
  thread_name_ = thread_name;
  wall_time_based_metrics_enabled_for_testing_ =
      wall_time_based_metrics_enabled_for_testing;

  histogram_ = LinearHistogram::FactoryGet(
      JoinString({"Scheduling.MessagePumpTimeKeeper", thread_name}, "."), 1,
      Phase::kLastPhase, Phase::kLastPhase + 1,
      base::HistogramBase::kUmaTargetedHistogramFlag);

  perfetto_track_.emplace("MessagePumpPhases", 0,
                          perfetto::ThreadTrack::Current());
}

void ThreadController::RunLevelTracker::OnRunLoopStarted(State initial_state,
                                                         LazyNow& lazy_now) {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);

  const bool is_nested = !run_levels_.empty();
  run_levels_.emplace(initial_state, is_nested, time_keeper_, lazy_now);

  // In unit tests, RunLoop::Run() acts as the initial wake-up.
  if (!is_nested && initial_state != kIdle) {
    time_keeper_.RecordWakeUp(lazy_now);
  }
}

void ThreadController::RunLevelTracker::OnRunLoopEnded() {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
  // Normally this will occur while kIdle or kInBetweenWorkItems but it can also
  // occur while kRunningWorkItem in rare situations where the owning
  // ThreadController is deleted from within a task. Ref.
  // SequenceManagerWithTaskRunnerTest.DeleteSequenceManagerInsideATask. Thus we
  // can't assert anything about the current state other than that it must be
  // exiting an existing RunLevel.
  DCHECK(!run_levels_.empty());
  LazyNow exit_lazy_now(outer_->time_source_);
  run_levels_.top().set_exit_lazy_now(&exit_lazy_now);
  run_levels_.pop();
}

void ThreadController::RunLevelTracker::OnWorkStarted(LazyNow& lazy_now) {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
  // Ignore work outside the main run loop.
  // The only practical case where this would happen is if a native loop is spun
  // outside the main runloop (e.g. system dialog during startup). We cannot
  // support this because we are not guaranteed to be able to observe its exit
  // (like we would inside an application task which is at least guaranteed to
  // itself notify us when it ends). Some ThreadControllerWithMessagePumpTest
  // also drive ThreadController outside a RunLoop and hit this.
  if (run_levels_.empty()) {
    return;
  }

  // Already running a work item? => #work-in-work-implies-nested
  if (run_levels_.top().state() == kRunningWorkItem) {
    run_levels_.emplace(kRunningWorkItem, /*nested=*/true, time_keeper_,
                        lazy_now);
  } else {
    if (run_levels_.top().state() == kIdle) {
      time_keeper_.RecordWakeUp(lazy_now);
    } else {
      time_keeper_.RecordEndOfPhase(kPumpOverhead, lazy_now);
    }

    // Going from kIdle or kInBetweenWorkItems to kRunningWorkItem.
    run_levels_.top().UpdateState(kRunningWorkItem, lazy_now);
  }
}

void ThreadController::RunLevelTracker::OnApplicationTaskSelected(
    TimeTicks queue_time,
    LazyNow& lazy_now) {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
  // As-in OnWorkStarted. Early native loops can result in
  // ThreadController::DoWork because the lack of a top-level RunLoop means
  // `task_execution_allowed` wasn't consumed.
  if (run_levels_.empty()) {
    return;
  }

  // OnWorkStarted() is expected to precede OnApplicationTaskSelected().
  DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);

  time_keeper_.OnApplicationTaskSelected(queue_time, lazy_now);
}

void ThreadController::RunLevelTracker::OnWorkEnded(LazyNow& lazy_now,
                                                    int run_level_depth) {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
  if (run_levels_.empty()) {
    return;
  }

  // #done-work-at-lower-runlevel-implies-done-nested
  if (run_level_depth != static_cast<int>(num_run_levels())) {
    DCHECK_EQ(run_level_depth + 1, static_cast<int>(num_run_levels()));
    run_levels_.top().set_exit_lazy_now(&lazy_now);
    run_levels_.pop();
  } else {
    time_keeper_.RecordEndOfPhase(kWorkItem, lazy_now);
  }

  // Whether we exited a nested run-level or not: the current run-level is now
  // transitioning from kRunningWorkItem to kInBetweenWorkItems.
  DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
  run_levels_.top().UpdateState(kInBetweenWorkItems, lazy_now);
}

void ThreadController::RunLevelTracker::OnIdle(LazyNow& lazy_now) {
  DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
  if (run_levels_.empty()) {
    return;
  }

  DCHECK_NE(run_levels_.top().state(), kRunningWorkItem);
  time_keeper_.RecordEndOfPhase(kIdleWork, lazy_now);
  run_levels_.top().UpdateState(kIdle, lazy_now);
}

void ThreadController::RunLevelTracker::RecordScheduleWork() {
  // Matching TerminatingFlow is found at
  // ThreadController::RunLevelTracker::RunLevel::UpdateState
  if (outer_->associated_thread_->IsBoundToCurrentThread()) {
    TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
  } else {
    TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWork",
                        perfetto::Flow::FromPointer(this));
  }
}

// static
void ThreadController::RunLevelTracker::SetTraceObserverForTesting(
    TraceObserverForTesting* trace_observer_for_testing) {
  DCHECK_NE(!!trace_observer_for_testing_, !!trace_observer_for_testing);
  trace_observer_for_testing_ = trace_observer_for_testing;
}

// static
ThreadController::RunLevelTracker::TraceObserverForTesting*
    ThreadController::RunLevelTracker::trace_observer_for_testing_ = nullptr;

ThreadController::RunLevelTracker::RunLevel::RunLevel(State initial_state,
                                                      bool is_nested,
                                                      TimeKeeper& time_keeper,
                                                      LazyNow& lazy_now)
    : is_nested_(is_nested), time_keeper_(time_keeper) {
  if (is_nested_) {
    // Stop the current kWorkItem phase now, it will resume after the kNested
    // phase ends.
    time_keeper_->RecordEndOfPhase(kWorkItemSuspendedOnNested, lazy_now);
  }
  UpdateState(initial_state, lazy_now);
}

ThreadController::RunLevelTracker::RunLevel::~RunLevel() {
  if (!was_moved_) {
    DCHECK(exit_lazy_now_);
    UpdateState(kIdle, *exit_lazy_now_);
    if (is_nested_) {
      // Attribute the entire time in this nested RunLevel to kNested phase. If
      // this wasn't the last nested RunLevel, this is ignored and will be
      // applied on the final pop().
      time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);

      // TODO(crbug.com/458682617): Remove this.
      // This one is surprisingly necessary as a single racy test fails without
      // (GamepadServiceSimulationTest.SurfaceIdNotFound).
      PerformFortuitousMemoryBarrierIfNecessary();
    }
  }
}

ThreadController::RunLevelTracker::RunLevel::RunLevel(RunLevel&& other) =
    default;

void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
    const char* name,
    int percentage) {
  UmaHistogramPercentage(base::StrCat({name, ".", GetThreadName()}),
                         percentage);
}

void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
    const char* name,
    int percentage,
    base::TimeDelta interval_duration) {
  UmaHistogramPercentage(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
                         percentage);
  UmaHistogramPercentage(
      base::StrCat({name, GetSuffixForHistogram(interval_duration)}),
      percentage);
}

void ThreadController::RunLevelTracker::RunLevel::LogIntervalMetric(
    const char* name,
    base::TimeDelta value,
    base::TimeDelta interval_duration) {
  // Log towards "Any" time suffix first.
  UmaHistogramTimes(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
                    value);
  if (interval_duration < kNonTrivialActiveIntervalLength) {
    UmaHistogramCustomMicrosecondsTimes(
        base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
        base::Microseconds(1), kNonTrivialActiveIntervalLength, 100);
  } else if (interval_duration < kMediumActiveIntervalLength) {
    UmaHistogramCustomTimes(
        base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
        kNonTrivialActiveIntervalLength, kMediumActiveIntervalLength, 100);
  }
}

void ThreadController::RunLevelTracker::RunLevel::LogOnActiveMetrics(
    LazyNow& lazy_now) {
  CHECK(last_active_start_.is_null());
  CHECK(last_active_threadtick_start_.is_null());

  if (!last_active_end_.is_null()) {
    const base::TimeDelta idle_time = lazy_now.Now() - last_active_end_;
    LogIntervalMetric("Scheduling.ThreadController.IdleDuration", idle_time,
                      idle_time);
    last_active_end_ = base::TimeTicks();
    accumulated_idle_time_ += idle_time;
  }

  // Taking thread ticks can be expensive. Make sure to do it rarely enough to
  // not have a discernible impact on performance.
  static const bool thread_ticks_supported = ThreadTicks::IsSupported();
  // Disable subsampling to support wall-time based metrics. Only supported for
  // testing purposes. By default, the subsampling probability is 0.1%.
  const double probability =
      time_keeper_->wall_time_based_metrics_enabled_for_testing() ? 1.0 : 0.001;
  if (thread_ticks_supported &&
      metrics_sub_sampler_.ShouldSample(probability)) {
    last_active_start_ = lazy_now.Now();
    last_active_threadtick_start_ = ThreadTicks::Now();
  }
}

void ThreadController::RunLevelTracker::RunLevel::LogOnIdleMetrics(
    LazyNow& lazy_now) {
  if (!last_active_start_.is_null()) {
    const base::TimeDelta elapsed_ticks = lazy_now.Now() - last_active_start_;
    base::TimeDelta elapsed_thread_ticks =
        ThreadTicks::Now() - last_active_threadtick_start_;

    // Round to 100% in case of clock imprecisions making it look like
    // there's impossibly more ThreadTicks than TimeTicks elapsed.
    elapsed_thread_ticks = std::min(elapsed_thread_ticks, elapsed_ticks);

    LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalDuration",
                      elapsed_ticks, elapsed_ticks);
    LogIntervalMetric(
        "Scheduling.ThreadController.ActiveIntervalOffCpuDuration",
        elapsed_ticks - elapsed_thread_ticks, elapsed_ticks);
    LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalOnCpuDuration",
                      elapsed_thread_ticks, elapsed_ticks);

    // If the interval was shorter than a tick, 100% on-cpu time is assumed.
    int active_interval_cpu_percentage =
        elapsed_ticks.is_zero()
            ? 100
            : static_cast<int>(
                  (elapsed_thread_ticks * 100).IntDiv(elapsed_ticks));

    LogPercentageMetric(
        "Scheduling.ThreadController.ActiveIntervalOnCpuPercentage",
        active_interval_cpu_percentage, elapsed_ticks);

    if (time_keeper_->wall_time_based_metrics_enabled_for_testing()) {
      accumulated_active_time_ += elapsed_ticks;
      accumulated_active_on_cpu_time_ += elapsed_thread_ticks;
      accumulated_active_off_cpu_time_ +=
          (elapsed_ticks - elapsed_thread_ticks);

      // Accumulated wall-time since last wall-time based metric was stored.
      const base::TimeDelta accumulated_wall_time =
          accumulated_active_time_ + accumulated_idle_time_;

      // Add wall-time based ratio metrics (in percent) when the total sum of
      // active and idle times is larger than one second.
      if (accumulated_wall_time > Seconds(1)) {
        const int active_vs_wall_time_percentage = checked_cast<int>(
            (accumulated_active_time_ * 100).IntDiv(accumulated_wall_time));
        LogPercentageMetric(
            "Scheduling.ThreadController.ActiveVsWallTimePercentage",
            active_vs_wall_time_percentage);
        const int active_on_cpu_vs_wall_time_percentage =
            checked_cast<int>((accumulated_active_on_cpu_time_ * 100)
                                  .IntDiv(accumulated_wall_time));
        LogPercentageMetric(
            "Scheduling.ThreadController.ActiveOnCpuVsWallTimePercentage",
            active_on_cpu_vs_wall_time_percentage);
        const int active_off_cpu_vs_wall_time_percentage =
            checked_cast<int>((accumulated_active_off_cpu_time_ * 100)
                                  .IntDiv(accumulated_wall_time));
        LogPercentageMetric(
            "Scheduling.ThreadController.ActiveOffCpuVsWallTimePercentage",
            active_off_cpu_vs_wall_time_percentage);

        accumulated_idle_time_ = base::TimeDelta();
        accumulated_active_time_ = base::TimeDelta();
        accumulated_active_on_cpu_time_ = base::TimeDelta();
        accumulated_active_off_cpu_time_ = base::TimeDelta();
      }
    }

    // Reset timings.
    last_active_start_ = base::TimeTicks();
    last_active_threadtick_start_ = base::ThreadTicks();
    last_active_end_ = lazy_now.Now();
  }
}

void ThreadController::RunLevelTracker::RunLevel::UpdateState(
    State new_state,
    LazyNow& lazy_now) {
  // The only state that can be redeclared is idle, anything else should be a
  // transition.
  DCHECK(state_ != new_state || new_state == kIdle)
      << state_ << "," << new_state;

  const bool was_active = state_ != kIdle;
  const bool is_active = new_state != kIdle;

  state_ = new_state;
  if (was_active == is_active) {
    return;
  }

  // Change of state.
  if (is_active) {
    LogOnActiveMetrics(lazy_now);

    // Flow emission is found at
    // ThreadController::RunLevelTracker::RecordScheduleWork.
    TRACE_EVENT_BEGIN("base", "ThreadController active", lazy_now.Now(),
                      [&](perfetto::EventContext& ctx) {
                        time_keeper_->MaybeEmitIncomingWakeupFlow(ctx);
                      });

    PerformFortuitousMemoryBarrierIfNecessary();
  } else {
    PerformFortuitousMemoryBarrierIfNecessary();

    LogOnIdleMetrics(lazy_now);

    TRACE_EVENT_END("base", lazy_now.Now());
  }

  if (trace_observer_for_testing_) {
    if (is_active) {
      trace_observer_for_testing_->OnThreadControllerActiveBegin();
    } else {
      trace_observer_for_testing_->OnThreadControllerActiveEnd();
    }
  }
}

ThreadController::RunLevelTracker::TimeKeeper::TimeKeeper(
    const RunLevelTracker& outer)
    : outer_(outer) {}

void ThreadController::RunLevelTracker::TimeKeeper::RecordWakeUp(
    LazyNow& lazy_now) {
  if (!ShouldRecordNow(ShouldRecordReqs::kOnWakeUp)) {
    return;
  }

  // Phase::kScheduled will be accounted against `last_wakeup_` in
  // OnTaskSelected, if there's an application task in this work cycle.
  last_wakeup_ = lazy_now.Now();
  // Account the next phase starting from now.
  last_phase_end_ = last_wakeup_;

  // Emit the END of the kScheduled phase right away, this avoids incorrect
  // ordering when kScheduled is later emitted and its END matches the BEGIN of
  // an already emitted phase (tracing's sort is stable and would keep the late
  // END for kScheduled after the earlier BEGIN of the next phase):
  // crbug.com/1333460. As we just woke up, there are no events active at this
  // point (we don't record MessagePumpPhases while nested). In the absence of
  // a kScheduled phase, this unmatched END will be ignored.
  TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
                  last_wakeup_);
}

void ThreadController::RunLevelTracker::TimeKeeper::OnApplicationTaskSelected(
    TimeTicks queue_time,
    LazyNow& lazy_now) {
  if (!ShouldRecordNow()) {
    return;
  }

  if (!last_wakeup_.is_null()) {
    // `queue_time` can be null on threads that did not
    // `SetAddQueueTimeToTasks(true)`. `queue_time` can also be ahead of
    // `last_wakeup` in racy cases where the first chrome task is enqueued
    // while the pump was already awake (e.g. for native work). Consider the
    // kScheduled phase inexistent in that case.
    if (!queue_time.is_null() && queue_time < last_wakeup_) {
      if (!last_sleep_.is_null() && queue_time < last_sleep_) {
        // Avoid overlapping kScheduled and kIdleWork phases when work is
        // scheduled while going to sleep.
        queue_time = last_sleep_;
      }
      RecordTimeInPhase(kScheduled, queue_time, last_wakeup_);
      // Match the END event which was already emitted by RecordWakeUp().
      TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
                        perfetto::StaticString(PhaseToEventName(kScheduled)),
                        *perfetto_track_, queue_time);
    }
    last_wakeup_ = TimeTicks();
  }
  RecordEndOfPhase(kSelectingApplicationTask, lazy_now);
  current_work_item_is_native_ = false;
}

void ThreadController::RunLevelTracker::TimeKeeper::RecordEndOfPhase(
    Phase phase,
    LazyNow& lazy_now) {
  if (!ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
                                        : ShouldRecordReqs::kRegular)) {
    return;
  }

  if (phase == kWorkItem && !current_work_item_is_native_) {
    phase = kApplicationTask;
    // Back to assuming future work is native until OnApplicationTaskSelected()
    // is invoked.
    current_work_item_is_native_ = true;
  } else if (phase == kWorkItemSuspendedOnNested) {
    // kWorkItemSuspendedOnNested temporarily marks the end of time allocated to
    // the current work item. It is reported as a separate phase to skip the
    // above `current_work_item_is_native_ = true` which assumes the work item
    // is truly complete.
    phase = current_work_item_is_native_ ? kNativeWork : kApplicationTask;
  }

  const TimeTicks phase_end = lazy_now.Now();
  RecordTimeInPhase(phase, last_phase_end_, phase_end);

  const char* event_name = PhaseToEventName(phase);
  TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
                    perfetto::StaticString(event_name), *perfetto_track_,
                    last_phase_end_);
  TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
                  phase_end);

  last_phase_end_ = phase_end;
}

void ThreadController::RunLevelTracker::TimeKeeper::MaybeEmitIncomingWakeupFlow(
    perfetto::EventContext& ctx) {
  static const uint8_t* flow_enabled =
      TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("wakeup.flow");
  if (!*flow_enabled) {
    return;
  }

  perfetto::TerminatingFlow::ProcessScoped(
      reinterpret_cast<uint64_t>(&(outer_.get())))(ctx);
}

bool ThreadController::RunLevelTracker::TimeKeeper::ShouldRecordNow(
    ShouldRecordReqs reqs) {
  DCHECK_CALLED_ON_VALID_THREAD(
      outer_->outer_->associated_thread_->thread_checker);
  // Recording is technically enabled once `histogram_` is set, however
  // `last_phase_end_` will be null until the next RecordWakeUp in the work
  // cycle in which `histogram_` is enabled. Only start recording from there.
  // Ignore any nested phases. `reqs` may indicate exceptions to this.
  //
  // TODO(crbug.com/40226913): In a follow-up, we could probably always be
  // tracking the phases of the pump and merely ignore the reporting if
  // `histogram_` isn't set.
  switch (reqs) {
    case ShouldRecordReqs::kRegular:
      return histogram_ && !last_phase_end_.is_null() &&
             outer_->run_levels_.size() == 1;
    case ShouldRecordReqs::kOnWakeUp:
      return histogram_ && outer_->run_levels_.size() == 1;
    case ShouldRecordReqs::kOnEndNested:
      return histogram_ && !last_phase_end_.is_null() &&
             outer_->run_levels_.size() <= 2;
  }
}

void ThreadController::RunLevelTracker::TimeKeeper::RecordTimeInPhase(
    Phase phase,
    TimeTicks phase_begin,
    TimeTicks phase_end) {
  DCHECK(ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
                                          : ShouldRecordReqs::kRegular));

  // Report a phase only when at least 100ms has been attributed to it.
  static constexpr auto kReportInterval = Milliseconds(100);

  // Above 30s in a single phase, assume suspend-resume and ignore the report.
  static constexpr auto kSkippedDelta = Seconds(30);

  const auto delta = phase_end - phase_begin;
  DCHECK(!delta.is_negative()) << delta;
  if (delta >= kSkippedDelta) {
    return;
  }

  deltas_[phase] += delta;
  if (deltas_[phase] >= kReportInterval) {
    const int count = deltas_[phase] / Milliseconds(1);
    histogram_->AddCount(phase, count);
    deltas_[phase] -= Milliseconds(count);
  }

  if (phase == kIdleWork) {
    last_sleep_ = phase_end;
  }

  if (outer_->trace_observer_for_testing_) {
    outer_->trace_observer_for_testing_->OnPhaseRecorded(phase);
  }
}

// static
const char* ThreadController::RunLevelTracker::TimeKeeper::PhaseToEventName(
    Phase phase) {
  switch (phase) {
    case kScheduled:
      return "Scheduled";
    case kPumpOverhead:
      return "PumpOverhead";
    case kNativeWork:
      return "NativeTask";
    case kSelectingApplicationTask:
      return "SelectingApplicationTask";
    case kApplicationTask:
      return "ApplicationTask";
    case kIdleWork:
      return "IdleWork";
    case kNested:
      return "Nested";
    case kWorkItemSuspendedOnNested:
      // kWorkItemSuspendedOnNested should be transformed into kNativeWork or
      // kApplicationTask before this point.
      NOTREACHED();
  }
}

}  // namespace base::sequence_manager::internal