xref: /aosp_15_r20/external/cronet/base/task/sequence_manager/thread_controller.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/task/sequence_manager/thread_controller.h"
6 
7 #include <atomic>
8 #include <string_view>
9 
10 #include "base/check.h"
11 #include "base/feature_list.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/histogram_base.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/notreached.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_util.h"
19 #include "base/time/tick_clock.h"
20 #include "base/time/time.h"
21 #include "base/trace_event/base_tracing.h"
22 
23 namespace base {
24 namespace sequence_manager {
25 namespace internal {
26 
27 namespace {
28 // Enable sample metadata recording in this class, if it's currently disabled.
29 // Note that even if `kThreadControllerSetsProfilerMetadata` is disabled, sample
30 // metadata may still be recorded.
31 BASE_FEATURE(kThreadControllerSetsProfilerMetadata,
32              "ThreadControllerSetsProfilerMetadata",
33              base::FEATURE_DISABLED_BY_DEFAULT);
34 
35 // Thread safe copy to be updated once feature list is available. This
36 // defaults to true to make sure that no metadata is lost on clients that
37 // need to record. This leads to some overeporting before feature list
38 // initialization on other clients but that's still way better than the current
39 // situation which is reporting all the time.
40 std::atomic<bool> g_thread_controller_sets_profiler_metadata{true};
41 
42 // ThreadController interval metrics are mostly of interest for intervals that
43 // are not trivially short. Under a certain threshold it's unlikely that
44 // intervention from developers would move metrics. Log with suffix for
45 // intervals under a threshold chosen via tracing data. To validate the
46 // threshold makes sense and does not filter out too many samples
47 // ThreadController.ActiveIntervalDuration can be used.
48 constexpr TimeDelta kNonTrivialActiveIntervalLength = Milliseconds(1);
49 constexpr TimeDelta kMediumActiveIntervalLength = Milliseconds(100);
50 
MakeSuffix(std::string_view time_suffix,std::string_view thread_name)51 std::string MakeSuffix(std::string_view time_suffix,
52                        std::string_view thread_name) {
53   return base::StrCat({".", time_suffix, ".", thread_name});
54 }
55 
56 }  // namespace
57 
ThreadController(const TickClock * time_source)58 ThreadController::ThreadController(const TickClock* time_source)
59     : associated_thread_(AssociatedThreadId::CreateUnbound()),
60       time_source_(time_source) {}
61 
62 ThreadController::~ThreadController() = default;
63 
SetTickClock(const TickClock * clock)64 void ThreadController::SetTickClock(const TickClock* clock) {
65   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
66   time_source_ = clock;
67 }
68 
RunLevelTracker(const ThreadController & outer)69 ThreadController::RunLevelTracker::RunLevelTracker(
70     const ThreadController& outer)
71     : outer_(outer) {}
72 
~RunLevelTracker()73 ThreadController::RunLevelTracker::~RunLevelTracker() {
74   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
75 
76   // There shouldn't be any remaining |run_levels_| by the time this unwinds.
77   DCHECK_EQ(run_levels_.size(), 0u);
78 }
79 
80 // static
InitializeFeatures(features::EmitThreadControllerProfilerMetadata emit_profiler_metadata)81 void ThreadController::InitializeFeatures(
82     features::EmitThreadControllerProfilerMetadata emit_profiler_metadata) {
83   g_thread_controller_sets_profiler_metadata.store(
84       emit_profiler_metadata ==
85               features::EmitThreadControllerProfilerMetadata::kForce ||
86           base::FeatureList::IsEnabled(kThreadControllerSetsProfilerMetadata),
87       std::memory_order_relaxed);
88 }
89 
ShouldRecordSampleMetadata()90 bool ThreadController::RunLevelTracker::RunLevel::ShouldRecordSampleMetadata() {
91   return g_thread_controller_sets_profiler_metadata.load(
92       std::memory_order_relaxed);
93 }
94 
GetThreadName()95 std::string_view ThreadController::RunLevelTracker::RunLevel::GetThreadName() {
96   std::string_view thread_name = "Other";
97   if (!time_keeper_->thread_name().empty()) {
98     thread_name = time_keeper_->thread_name();
99   }
100   return thread_name;
101 }
102 
103 std::string
GetSuffixForCatchAllHistogram()104 ThreadController::RunLevelTracker::RunLevel::GetSuffixForCatchAllHistogram() {
105   return MakeSuffix("Any", GetThreadName());
106 }
107 
GetSuffixForHistogram(TimeDelta duration)108 std::string ThreadController::RunLevelTracker::RunLevel::GetSuffixForHistogram(
109     TimeDelta duration) {
110   std::string_view time_suffix;
111   if (duration < kNonTrivialActiveIntervalLength) {
112     time_suffix = "Short";
113   } else if (duration < kMediumActiveIntervalLength) {
114     time_suffix = "Medium";
115   }
116   return MakeSuffix(time_suffix, GetThreadName());
117 }
118 
EnableMessagePumpTimeKeeperMetrics(const char * thread_name)119 void ThreadController::EnableMessagePumpTimeKeeperMetrics(
120     const char* thread_name) {
121   // MessagePump runs too fast, a low-res clock would result in noisy metrics.
122   if (!base::TimeTicks::IsHighResolution())
123     return;
124 
125   run_level_tracker_.EnableTimeKeeperMetrics(thread_name);
126 }
127 
EnableTimeKeeperMetrics(const char * thread_name)128 void ThreadController::RunLevelTracker::EnableTimeKeeperMetrics(
129     const char* thread_name) {
130   time_keeper_.EnableRecording(thread_name);
131 }
132 
EnableRecording(const char * thread_name)133 void ThreadController::RunLevelTracker::TimeKeeper::EnableRecording(
134     const char* thread_name) {
135   DCHECK(!histogram_);
136   thread_name_ = thread_name;
137 
138   histogram_ = LinearHistogram::FactoryGet(
139       JoinString({"Scheduling.MessagePumpTimeKeeper", thread_name}, "."), 1,
140       Phase::kLastPhase, Phase::kLastPhase + 1,
141       base::HistogramBase::kUmaTargetedHistogramFlag);
142 
143 #if BUILDFLAG(ENABLE_BASE_TRACING)
144   perfetto_track_.emplace(
145       reinterpret_cast<uint64_t>(this),
146       // TODO(crbug.com/1006541): Replace with ThreadTrack::Current() after SDK
147       // migration.
148       // In the non-SDK version, ThreadTrack::Current() returns a different
149       // track id on some platforms (for example Mac OS), which results in
150       // async tracks not being associated with their thread.
151       perfetto::ThreadTrack::ForThread(base::PlatformThread::CurrentId()));
152   // TODO(1006541): Use Perfetto library to name this Track.
153   // auto desc = perfetto_track_->Serialize();
154   // desc.set_name(JoinString({"MessagePumpPhases", thread_name}, " "));
155   // perfetto::internal::TrackEventDataSource::SetTrackDescriptor(
156   //     *perfetto_track_, desc);
157 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
158 }
159 
OnRunLoopStarted(State initial_state,LazyNow & lazy_now)160 void ThreadController::RunLevelTracker::OnRunLoopStarted(State initial_state,
161                                                          LazyNow& lazy_now) {
162   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
163 
164   const bool is_nested = !run_levels_.empty();
165   run_levels_.emplace(initial_state, is_nested, time_keeper_, lazy_now
166   );
167 
168   // In unit tests, RunLoop::Run() acts as the initial wake-up.
169   if (!is_nested && initial_state != kIdle)
170     time_keeper_.RecordWakeUp(lazy_now);
171 }
172 
OnRunLoopEnded()173 void ThreadController::RunLevelTracker::OnRunLoopEnded() {
174   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
175   // Normally this will occur while kIdle or kInBetweenWorkItems but it can also
176   // occur while kRunningWorkItem in rare situations where the owning
177   // ThreadController is deleted from within a task. Ref.
178   // SequenceManagerWithTaskRunnerTest.DeleteSequenceManagerInsideATask. Thus we
179   // can't assert anything about the current state other than that it must be
180   // exiting an existing RunLevel.
181   DCHECK(!run_levels_.empty());
182   LazyNow exit_lazy_now(outer_->time_source_);
183   run_levels_.top().set_exit_lazy_now(&exit_lazy_now);
184   run_levels_.pop();
185 }
186 
OnWorkStarted(LazyNow & lazy_now)187 void ThreadController::RunLevelTracker::OnWorkStarted(LazyNow& lazy_now) {
188   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
189   // Ignore work outside the main run loop.
190   // The only practical case where this would happen is if a native loop is spun
191   // outside the main runloop (e.g. system dialog during startup). We cannot
192   // support this because we are not guaranteed to be able to observe its exit
193   // (like we would inside an application task which is at least guaranteed to
194   // itself notify us when it ends). Some ThreadControllerWithMessagePumpTest
195   // also drive ThreadController outside a RunLoop and hit this.
196   if (run_levels_.empty())
197     return;
198 
199   // Already running a work item? => #work-in-work-implies-nested
200   if (run_levels_.top().state() == kRunningWorkItem) {
201     run_levels_.emplace(kRunningWorkItem, /*nested=*/true, time_keeper_,
202                         lazy_now);
203   } else {
204     if (run_levels_.top().state() == kIdle) {
205       time_keeper_.RecordWakeUp(lazy_now);
206     } else {
207       time_keeper_.RecordEndOfPhase(kPumpOverhead, lazy_now);
208     }
209 
210     // Going from kIdle or kInBetweenWorkItems to kRunningWorkItem.
211     run_levels_.top().UpdateState(kRunningWorkItem, lazy_now);
212   }
213 }
214 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)215 void ThreadController::RunLevelTracker::OnApplicationTaskSelected(
216     TimeTicks queue_time,
217     LazyNow& lazy_now) {
218   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
219   // As-in OnWorkStarted. Early native loops can result in
220   // ThreadController::DoWork because the lack of a top-level RunLoop means
221   // `task_execution_allowed` wasn't consumed.
222   if (run_levels_.empty())
223     return;
224 
225   // OnWorkStarted() is expected to precede OnApplicationTaskSelected().
226   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
227 
228   time_keeper_.OnApplicationTaskSelected(queue_time, lazy_now);
229 }
230 
OnWorkEnded(LazyNow & lazy_now,int run_level_depth)231 void ThreadController::RunLevelTracker::OnWorkEnded(LazyNow& lazy_now,
232                                                     int run_level_depth) {
233   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
234   if (run_levels_.empty())
235     return;
236 
237   // #done-work-at-lower-runlevel-implies-done-nested
238   if (run_level_depth != static_cast<int>(num_run_levels())) {
239     DCHECK_EQ(run_level_depth + 1, static_cast<int>(num_run_levels()));
240     run_levels_.top().set_exit_lazy_now(&lazy_now);
241     run_levels_.pop();
242   } else {
243     time_keeper_.RecordEndOfPhase(kWorkItem, lazy_now);
244   }
245 
246   // Whether we exited a nested run-level or not: the current run-level is now
247   // transitioning from kRunningWorkItem to kInBetweenWorkItems.
248   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
249   run_levels_.top().UpdateState(kInBetweenWorkItems, lazy_now);
250 }
251 
OnIdle(LazyNow & lazy_now)252 void ThreadController::RunLevelTracker::OnIdle(LazyNow& lazy_now) {
253   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
254   if (run_levels_.empty())
255     return;
256 
257   DCHECK_NE(run_levels_.top().state(), kRunningWorkItem);
258   time_keeper_.RecordEndOfPhase(kIdleWork, lazy_now);
259   run_levels_.top().UpdateState(kIdle, lazy_now);
260 }
261 
RecordScheduleWork()262 void ThreadController::RunLevelTracker::RecordScheduleWork() {
263   // Matching TerminatingFlow is found at
264   // ThreadController::RunLevelTracker::RunLevel::UpdateState
265   if (outer_->associated_thread_->IsBoundToCurrentThread()) {
266     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
267   } else {
268     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWork",
269                         perfetto::Flow::FromPointer(this));
270   }
271 }
272 
273 // static
SetTraceObserverForTesting(TraceObserverForTesting * trace_observer_for_testing)274 void ThreadController::RunLevelTracker::SetTraceObserverForTesting(
275     TraceObserverForTesting* trace_observer_for_testing) {
276   DCHECK_NE(!!trace_observer_for_testing_, !!trace_observer_for_testing);
277   trace_observer_for_testing_ = trace_observer_for_testing;
278 }
279 
280 // static
281 ThreadController::RunLevelTracker::TraceObserverForTesting*
282     ThreadController::RunLevelTracker::trace_observer_for_testing_ = nullptr;
283 
RunLevel(State initial_state,bool is_nested,TimeKeeper & time_keeper,LazyNow & lazy_now)284 ThreadController::RunLevelTracker::RunLevel::RunLevel(State initial_state,
285                                                       bool is_nested,
286                                                       TimeKeeper& time_keeper,
287                                                       LazyNow& lazy_now)
288     : is_nested_(is_nested),
289       time_keeper_(time_keeper),
290       thread_controller_sample_metadata_("ThreadController active",
291                                          base::SampleMetadataScope::kThread) {
292   if (is_nested_) {
293     // Stop the current kWorkItem phase now, it will resume after the kNested
294     // phase ends.
295     time_keeper_->RecordEndOfPhase(kWorkItemSuspendedOnNested, lazy_now);
296   }
297   UpdateState(initial_state, lazy_now);
298 }
299 
~RunLevel()300 ThreadController::RunLevelTracker::RunLevel::~RunLevel() {
301   if (!was_moved_) {
302     DCHECK(exit_lazy_now_);
303     UpdateState(kIdle, *exit_lazy_now_);
304     if (is_nested_) {
305       // Attribute the entire time in this nested RunLevel to kNested phase. If
306       // this wasn't the last nested RunLevel, this is ignored and will be
307       // applied on the final pop().
308       time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);
309 
310       if (ShouldRecordSampleMetadata()) {
311         // Intentionally ordered after UpdateState(kIdle), reinstantiates
312         // thread_controller_sample_metadata_ when yielding back to a parent
313         // RunLevel (which is active by definition as it is currently running
314         // this one).
315         thread_controller_sample_metadata_.Set(
316             static_cast<int64_t>(++thread_controller_active_id_));
317       }
318     }
319   }
320 }
321 
322 ThreadController::RunLevelTracker::RunLevel::RunLevel(RunLevel&& other) =
323     default;
324 
LogPercentageMetric(const char * name,int percentage,base::TimeDelta interval_duration)325 void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
326     const char* name,
327     int percentage,
328     base::TimeDelta interval_duration) {
329   UmaHistogramPercentage(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
330                          percentage);
331   UmaHistogramPercentage(
332       base::StrCat({name, GetSuffixForHistogram(interval_duration)}),
333       percentage);
334 }
335 
LogIntervalMetric(const char * name,base::TimeDelta value,base::TimeDelta interval_duration)336 void ThreadController::RunLevelTracker::RunLevel::LogIntervalMetric(
337     const char* name,
338     base::TimeDelta value,
339     base::TimeDelta interval_duration) {
340   // Log towards "Any" time suffix first.
341   UmaHistogramTimes(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
342                     value);
343   if (interval_duration < kNonTrivialActiveIntervalLength) {
344     UmaHistogramCustomMicrosecondsTimes(
345         base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
346         base::Microseconds(1), kNonTrivialActiveIntervalLength, 100);
347   } else if (interval_duration < kMediumActiveIntervalLength) {
348     UmaHistogramCustomTimes(
349         base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
350         kNonTrivialActiveIntervalLength, kMediumActiveIntervalLength, 100);
351   }
352 }
353 
LogOnActiveMetrics(LazyNow & lazy_now)354 void ThreadController::RunLevelTracker::RunLevel::LogOnActiveMetrics(
355     LazyNow& lazy_now) {
356   CHECK(last_active_start_.is_null());
357   CHECK(last_active_threadtick_start_.is_null());
358 
359   if (!last_active_end_.is_null()) {
360     const base::TimeDelta idle_time = lazy_now.Now() - last_active_end_;
361     LogIntervalMetric("Scheduling.ThreadController.IdleDuration", idle_time,
362                       idle_time);
363     last_active_end_ = base::TimeTicks();
364   }
365 
366   // Taking thread ticks can be expensive. Make sure to do it rarely enough to
367   // not have a discernible impact on performance.
368   static const bool thread_ticks_supported = ThreadTicks::IsSupported();
369   if (thread_ticks_supported && metrics_sub_sampler_.ShouldSample(0.001)) {
370     last_active_start_ = lazy_now.Now();
371     last_active_threadtick_start_ = ThreadTicks::Now();
372   }
373 }
374 
LogOnIdleMetrics(LazyNow & lazy_now)375 void ThreadController::RunLevelTracker::RunLevel::LogOnIdleMetrics(
376     LazyNow& lazy_now) {
377   if (!last_active_start_.is_null()) {
378     const base::TimeDelta elapsed_ticks = lazy_now.Now() - last_active_start_;
379     base::TimeDelta elapsed_thread_ticks =
380         ThreadTicks::Now() - last_active_threadtick_start_;
381 
382     // Round to 100% in case of clock imprecisions making it look like
383     // there's impossibly more ThreadTicks than TimeTicks elapsed.
384     elapsed_thread_ticks = std::min(elapsed_thread_ticks, elapsed_ticks);
385 
386     LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalDuration",
387                       elapsed_ticks, elapsed_ticks);
388     LogIntervalMetric(
389         "Scheduling.ThreadController.ActiveIntervalOffCpuDuration",
390         elapsed_ticks - elapsed_thread_ticks, elapsed_ticks);
391     LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalOnCpuDuration",
392                       elapsed_thread_ticks, elapsed_ticks);
393 
394     // If the interval was shorter than a tick, 100% on-cpu time is assumed.
395     int active_interval_cpu_percentage =
396         elapsed_ticks.is_zero()
397             ? 100
398             : static_cast<int>(
399                   (elapsed_thread_ticks * 100).IntDiv(elapsed_ticks));
400 
401     LogPercentageMetric(
402         "Scheduling.ThreadController.ActiveIntervalOnCpuPercentage",
403         active_interval_cpu_percentage, elapsed_ticks);
404 
405     // Reset timings.
406     last_active_start_ = base::TimeTicks();
407     last_active_threadtick_start_ = base::ThreadTicks();
408     last_active_end_ = lazy_now.Now();
409   }
410 }
411 
UpdateState(State new_state,LazyNow & lazy_now)412 void ThreadController::RunLevelTracker::RunLevel::UpdateState(
413     State new_state,
414     LazyNow& lazy_now) {
415   // The only state that can be redeclared is idle, anything else should be a
416   // transition.
417   DCHECK(state_ != new_state || new_state == kIdle)
418       << state_ << "," << new_state;
419 
420   const bool was_active = state_ != kIdle;
421   const bool is_active = new_state != kIdle;
422 
423   state_ = new_state;
424   if (was_active == is_active)
425     return;
426 
427   // Change of state.
428   if (is_active) {
429     LogOnActiveMetrics(lazy_now);
430 
431     // Flow emission is found at
432     // ThreadController::RunLevelTracker::RecordScheduleWork.
433     TRACE_EVENT_BEGIN("base", "ThreadController active", lazy_now.Now(),
434                       [&](perfetto::EventContext& ctx) {
435                         time_keeper_->MaybeEmitIncomingWakeupFlow(ctx);
436                       });
437 
438     if (ShouldRecordSampleMetadata()) {
439       // Overriding the annotation from the previous RunLevel is intentional.
440       // Only the top RunLevel is ever updated, which holds the relevant state.
441       thread_controller_sample_metadata_.Set(
442           static_cast<int64_t>(++thread_controller_active_id_));
443     }
444   } else {
445     if (ShouldRecordSampleMetadata()) {
446       thread_controller_sample_metadata_.Remove();
447     }
448 
449     LogOnIdleMetrics(lazy_now);
450 
451     TRACE_EVENT_END("base", lazy_now.Now());
452     // TODO(crbug.com/1021571): Remove this once fixed.
453     PERFETTO_INTERNAL_ADD_EMPTY_EVENT();
454   }
455 
456   if (trace_observer_for_testing_) {
457     if (is_active)
458       trace_observer_for_testing_->OnThreadControllerActiveBegin();
459     else
460       trace_observer_for_testing_->OnThreadControllerActiveEnd();
461   }
462 }
463 
TimeKeeper(const RunLevelTracker & outer)464 ThreadController::RunLevelTracker::TimeKeeper::TimeKeeper(
465     const RunLevelTracker& outer)
466     : outer_(outer) {}
467 
RecordWakeUp(LazyNow & lazy_now)468 void ThreadController::RunLevelTracker::TimeKeeper::RecordWakeUp(
469     LazyNow& lazy_now) {
470   if (!ShouldRecordNow(ShouldRecordReqs::kOnWakeUp))
471     return;
472 
473   // Phase::kScheduled will be accounted against `last_wakeup_` in
474   // OnTaskSelected, if there's an application task in this work cycle.
475   last_wakeup_ = lazy_now.Now();
476   // Account the next phase starting from now.
477   last_phase_end_ = last_wakeup_;
478 
479 #if BUILDFLAG(ENABLE_BASE_TRACING)
480   // Emit the END of the kScheduled phase right away, this avoids incorrect
481   // ordering when kScheduled is later emitted and its END matches the BEGIN of
482   // an already emitted phase (tracing's sort is stable and would keep the late
483   // END for kScheduled after the earlier BEGIN of the next phase):
484   // crbug.com/1333460. As we just woke up, there are no events active at this
485   // point (we don't record MessagePumpPhases while nested). In the absence of
486   // a kScheduled phase, this unmatched END will be ignored.
487   TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
488                   last_wakeup_);
489 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
490 }
491 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)492 void ThreadController::RunLevelTracker::TimeKeeper::OnApplicationTaskSelected(
493     TimeTicks queue_time,
494     LazyNow& lazy_now) {
495   if (!ShouldRecordNow())
496     return;
497 
498   if (!last_wakeup_.is_null()) {
499     // `queue_time` can be null on threads that did not
500     // `SetAddQueueTimeToTasks(true)`. `queue_time` can also be ahead of
501     // `last_wakeup` in racy cases where the first chrome task is enqueued
502     // while the pump was already awake (e.g. for native work). Consider the
503     // kScheduled phase inexistent in that case.
504     if (!queue_time.is_null() && queue_time < last_wakeup_) {
505       if (!last_sleep_.is_null() && queue_time < last_sleep_) {
506         // Avoid overlapping kScheduled and kIdleWork phases when work is
507         // scheduled while going to sleep.
508         queue_time = last_sleep_;
509       }
510       RecordTimeInPhase(kScheduled, queue_time, last_wakeup_);
511 #if BUILDFLAG(ENABLE_BASE_TRACING)
512       // Match the END event which was already emitted by RecordWakeUp().
513       TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
514                         perfetto::StaticString(PhaseToEventName(kScheduled)),
515                         *perfetto_track_, queue_time);
516 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
517     }
518     last_wakeup_ = TimeTicks();
519   }
520   RecordEndOfPhase(kSelectingApplicationTask, lazy_now);
521   current_work_item_is_native_ = false;
522 }
523 
RecordEndOfPhase(Phase phase,LazyNow & lazy_now)524 void ThreadController::RunLevelTracker::TimeKeeper::RecordEndOfPhase(
525     Phase phase,
526     LazyNow& lazy_now) {
527   if (!ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
528                                         : ShouldRecordReqs::kRegular)) {
529     return;
530   }
531 
532   if (phase == kWorkItem && !current_work_item_is_native_) {
533     phase = kApplicationTask;
534     // Back to assuming future work is native until OnApplicationTaskSelected()
535     // is invoked.
536     current_work_item_is_native_ = true;
537   } else if (phase == kWorkItemSuspendedOnNested) {
538     // kWorkItemSuspendedOnNested temporarily marks the end of time allocated to
539     // the current work item. It is reported as a separate phase to skip the
540     // above `current_work_item_is_native_ = true` which assumes the work item
541     // is truly complete.
542     phase = current_work_item_is_native_ ? kNativeWork : kApplicationTask;
543   }
544 
545   const TimeTicks phase_end = lazy_now.Now();
546   RecordTimeInPhase(phase, last_phase_end_, phase_end);
547 
548 #if BUILDFLAG(ENABLE_BASE_TRACING)
549   // Ugly hack to name our `perfetto_track_`.
550   bool is_tracing_enabled = false;
551   TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("base"),
552                                      &is_tracing_enabled);
553   if (is_tracing_enabled) {
554     if (!was_tracing_enabled_) {
555       // The first event name on the track hackily names the track...
556       // TODO(1006541): Use the Perfetto library to properly name this Track in
557       // EnableRecording above.
558       TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("base"),
559                           "MessagePumpPhases", *perfetto_track_,
560                           last_phase_end_ - Seconds(1));
561     }
562 
563     const char* event_name = PhaseToEventName(phase);
564     TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
565                       perfetto::StaticString(event_name), *perfetto_track_,
566                       last_phase_end_);
567     TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
568                     phase_end);
569   }
570   was_tracing_enabled_ = is_tracing_enabled;
571 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
572 
573   last_phase_end_ = phase_end;
574 }
575 
MaybeEmitIncomingWakeupFlow(perfetto::EventContext & ctx)576 void ThreadController::RunLevelTracker::TimeKeeper::MaybeEmitIncomingWakeupFlow(
577     perfetto::EventContext& ctx) {
578 #if BUILDFLAG(ENABLE_BASE_TRACING)
579   static const uint8_t* flow_enabled =
580       TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("wakeup.flow");
581   if (!*flow_enabled) {
582     return;
583   }
584 
585   perfetto::Flow::ProcessScoped(reinterpret_cast<uint64_t>(&(outer_.get())))(
586       ctx);
587 #endif
588 }
589 
ShouldRecordNow(ShouldRecordReqs reqs)590 bool ThreadController::RunLevelTracker::TimeKeeper::ShouldRecordNow(
591     ShouldRecordReqs reqs) {
592   DCHECK_CALLED_ON_VALID_THREAD(
593       outer_->outer_->associated_thread_->thread_checker);
594   // Recording is technically enabled once `histogram_` is set, however
595   // `last_phase_end_` will be null until the next RecordWakeUp in the work
596   // cycle in which `histogram_` is enabled. Only start recording from there.
597   // Ignore any nested phases. `reqs` may indicate exceptions to this.
598   //
599   // TODO(crbug.com/1329717): In a follow-up, we could probably always be
600   // tracking the phases of the pump and merely ignore the reporting if
601   // `histogram_` isn't set.
602   switch (reqs) {
603     case ShouldRecordReqs::kRegular:
604       return histogram_ && !last_phase_end_.is_null() &&
605              outer_->run_levels_.size() == 1;
606     case ShouldRecordReqs::kOnWakeUp:
607       return histogram_ && outer_->run_levels_.size() == 1;
608     case ShouldRecordReqs::kOnEndNested:
609       return histogram_ && !last_phase_end_.is_null() &&
610              outer_->run_levels_.size() <= 2;
611   }
612 }
613 
RecordTimeInPhase(Phase phase,TimeTicks phase_begin,TimeTicks phase_end)614 void ThreadController::RunLevelTracker::TimeKeeper::RecordTimeInPhase(
615     Phase phase,
616     TimeTicks phase_begin,
617     TimeTicks phase_end) {
618   DCHECK(ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
619                                           : ShouldRecordReqs::kRegular));
620 
621   // Report a phase only when at least 100ms has been attributed to it.
622   static constexpr auto kReportInterval = Milliseconds(100);
623 
624   // Above 30s in a single phase, assume suspend-resume and ignore the report.
625   static constexpr auto kSkippedDelta = Seconds(30);
626 
627   const auto delta = phase_end - phase_begin;
628   DCHECK(!delta.is_negative()) << delta;
629   if (delta >= kSkippedDelta)
630     return;
631 
632   deltas_[phase] += delta;
633   if (deltas_[phase] >= kReportInterval) {
634     const int count = deltas_[phase] / Milliseconds(1);
635     histogram_->AddCount(phase, count);
636     deltas_[phase] -= Milliseconds(count);
637   }
638 
639   if (phase == kIdleWork)
640     last_sleep_ = phase_end;
641 
642   if (outer_->trace_observer_for_testing_)
643     outer_->trace_observer_for_testing_->OnPhaseRecorded(phase);
644 }
645 
646 // static
PhaseToEventName(Phase phase)647 const char* ThreadController::RunLevelTracker::TimeKeeper::PhaseToEventName(
648     Phase phase) {
649   switch (phase) {
650     case kScheduled:
651       return "Scheduled";
652     case kPumpOverhead:
653       return "PumpOverhead";
654     case kNativeWork:
655       return "NativeTask";
656     case kSelectingApplicationTask:
657       return "SelectingApplicationTask";
658     case kApplicationTask:
659       return "ApplicationTask";
660     case kIdleWork:
661       return "IdleWork";
662     case kNested:
663       return "Nested";
664     case kWorkItemSuspendedOnNested:
665       // kWorkItemSuspendedOnNested should be transformed into kNativeWork or
666       // kApplicationTask before this point.
667       NOTREACHED();
668       return "";
669   }
670 }
671 
672 }  // namespace internal
673 }  // namespace sequence_manager
674 }  // namespace base
675