• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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,bool wall_time_based_metrics_enabled_for_testing)119 void ThreadController::EnableMessagePumpTimeKeeperMetrics(
120     const char* thread_name,
121     bool wall_time_based_metrics_enabled_for_testing) {
122   // MessagePump runs too fast, a low-res clock would result in noisy metrics.
123   if (!base::TimeTicks::IsHighResolution())
124     return;
125 
126   run_level_tracker_.EnableTimeKeeperMetrics(
127       thread_name, wall_time_based_metrics_enabled_for_testing);
128 }
129 
EnableTimeKeeperMetrics(const char * thread_name,bool wall_time_based_metrics_enabled_for_testing)130 void ThreadController::RunLevelTracker::EnableTimeKeeperMetrics(
131     const char* thread_name,
132     bool wall_time_based_metrics_enabled_for_testing) {
133   time_keeper_.EnableRecording(thread_name,
134                                wall_time_based_metrics_enabled_for_testing);
135 }
136 
EnableRecording(const char * thread_name,bool wall_time_based_metrics_enabled_for_testing)137 void ThreadController::RunLevelTracker::TimeKeeper::EnableRecording(
138     const char* thread_name,
139     bool wall_time_based_metrics_enabled_for_testing) {
140   DCHECK(!histogram_);
141   thread_name_ = thread_name;
142   wall_time_based_metrics_enabled_for_testing_ =
143       wall_time_based_metrics_enabled_for_testing;
144 
145   histogram_ = LinearHistogram::FactoryGet(
146       JoinString({"Scheduling.MessagePumpTimeKeeper", thread_name}, "."), 1,
147       Phase::kLastPhase, Phase::kLastPhase + 1,
148       base::HistogramBase::kUmaTargetedHistogramFlag);
149 
150 #if BUILDFLAG(ENABLE_BASE_TRACING)
151   perfetto_track_.emplace(
152       reinterpret_cast<uint64_t>(this),
153       // TODO(crbug.com/42050015): Replace with ThreadTrack::Current() after SDK
154       // migration.
155       // In the non-SDK version, ThreadTrack::Current() returns a different
156       // track id on some platforms (for example Mac OS), which results in
157       // async tracks not being associated with their thread.
158       perfetto::ThreadTrack::ForThread(base::PlatformThread::CurrentId()));
159   // TODO(crbug.com/42050015): Use Perfetto library to name this Track.
160   // auto desc = perfetto_track_->Serialize();
161   // desc.set_name(JoinString({"MessagePumpPhases", thread_name}, " "));
162   // perfetto::internal::TrackEventDataSource::SetTrackDescriptor(
163   //     *perfetto_track_, desc);
164 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
165 }
166 
OnRunLoopStarted(State initial_state,LazyNow & lazy_now)167 void ThreadController::RunLevelTracker::OnRunLoopStarted(State initial_state,
168                                                          LazyNow& lazy_now) {
169   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
170 
171   const bool is_nested = !run_levels_.empty();
172   run_levels_.emplace(initial_state, is_nested, time_keeper_, lazy_now);
173 
174   // In unit tests, RunLoop::Run() acts as the initial wake-up.
175   if (!is_nested && initial_state != kIdle)
176     time_keeper_.RecordWakeUp(lazy_now);
177 }
178 
OnRunLoopEnded()179 void ThreadController::RunLevelTracker::OnRunLoopEnded() {
180   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
181   // Normally this will occur while kIdle or kInBetweenWorkItems but it can also
182   // occur while kRunningWorkItem in rare situations where the owning
183   // ThreadController is deleted from within a task. Ref.
184   // SequenceManagerWithTaskRunnerTest.DeleteSequenceManagerInsideATask. Thus we
185   // can't assert anything about the current state other than that it must be
186   // exiting an existing RunLevel.
187   DCHECK(!run_levels_.empty());
188   LazyNow exit_lazy_now(outer_->time_source_);
189   run_levels_.top().set_exit_lazy_now(&exit_lazy_now);
190   run_levels_.pop();
191 }
192 
OnWorkStarted(LazyNow & lazy_now)193 void ThreadController::RunLevelTracker::OnWorkStarted(LazyNow& lazy_now) {
194   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
195   // Ignore work outside the main run loop.
196   // The only practical case where this would happen is if a native loop is spun
197   // outside the main runloop (e.g. system dialog during startup). We cannot
198   // support this because we are not guaranteed to be able to observe its exit
199   // (like we would inside an application task which is at least guaranteed to
200   // itself notify us when it ends). Some ThreadControllerWithMessagePumpTest
201   // also drive ThreadController outside a RunLoop and hit this.
202   if (run_levels_.empty())
203     return;
204 
205   // Already running a work item? => #work-in-work-implies-nested
206   if (run_levels_.top().state() == kRunningWorkItem) {
207     run_levels_.emplace(kRunningWorkItem, /*nested=*/true, time_keeper_,
208                         lazy_now);
209   } else {
210     if (run_levels_.top().state() == kIdle) {
211       time_keeper_.RecordWakeUp(lazy_now);
212     } else {
213       time_keeper_.RecordEndOfPhase(kPumpOverhead, lazy_now);
214     }
215 
216     // Going from kIdle or kInBetweenWorkItems to kRunningWorkItem.
217     run_levels_.top().UpdateState(kRunningWorkItem, lazy_now);
218   }
219 }
220 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)221 void ThreadController::RunLevelTracker::OnApplicationTaskSelected(
222     TimeTicks queue_time,
223     LazyNow& lazy_now) {
224   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
225   // As-in OnWorkStarted. Early native loops can result in
226   // ThreadController::DoWork because the lack of a top-level RunLoop means
227   // `task_execution_allowed` wasn't consumed.
228   if (run_levels_.empty())
229     return;
230 
231   // OnWorkStarted() is expected to precede OnApplicationTaskSelected().
232   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
233 
234   time_keeper_.OnApplicationTaskSelected(queue_time, lazy_now);
235 }
236 
OnWorkEnded(LazyNow & lazy_now,int run_level_depth)237 void ThreadController::RunLevelTracker::OnWorkEnded(LazyNow& lazy_now,
238                                                     int run_level_depth) {
239   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
240   if (run_levels_.empty())
241     return;
242 
243   // #done-work-at-lower-runlevel-implies-done-nested
244   if (run_level_depth != static_cast<int>(num_run_levels())) {
245     DCHECK_EQ(run_level_depth + 1, static_cast<int>(num_run_levels()));
246     run_levels_.top().set_exit_lazy_now(&lazy_now);
247     run_levels_.pop();
248   } else {
249     time_keeper_.RecordEndOfPhase(kWorkItem, lazy_now);
250   }
251 
252   // Whether we exited a nested run-level or not: the current run-level is now
253   // transitioning from kRunningWorkItem to kInBetweenWorkItems.
254   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
255   run_levels_.top().UpdateState(kInBetweenWorkItems, lazy_now);
256 }
257 
OnIdle(LazyNow & lazy_now)258 void ThreadController::RunLevelTracker::OnIdle(LazyNow& lazy_now) {
259   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
260   if (run_levels_.empty())
261     return;
262 
263   DCHECK_NE(run_levels_.top().state(), kRunningWorkItem);
264   time_keeper_.RecordEndOfPhase(kIdleWork, lazy_now);
265   run_levels_.top().UpdateState(kIdle, lazy_now);
266 }
267 
RecordScheduleWork()268 void ThreadController::RunLevelTracker::RecordScheduleWork() {
269   // Matching TerminatingFlow is found at
270   // ThreadController::RunLevelTracker::RunLevel::UpdateState
271   if (outer_->associated_thread_->IsBoundToCurrentThread()) {
272     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
273   } else {
274     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWork",
275                         perfetto::Flow::FromPointer(this));
276   }
277 }
278 
279 // static
SetTraceObserverForTesting(TraceObserverForTesting * trace_observer_for_testing)280 void ThreadController::RunLevelTracker::SetTraceObserverForTesting(
281     TraceObserverForTesting* trace_observer_for_testing) {
282   DCHECK_NE(!!trace_observer_for_testing_, !!trace_observer_for_testing);
283   trace_observer_for_testing_ = trace_observer_for_testing;
284 }
285 
286 // static
287 ThreadController::RunLevelTracker::TraceObserverForTesting*
288     ThreadController::RunLevelTracker::trace_observer_for_testing_ = nullptr;
289 
RunLevel(State initial_state,bool is_nested,TimeKeeper & time_keeper,LazyNow & lazy_now)290 ThreadController::RunLevelTracker::RunLevel::RunLevel(State initial_state,
291                                                       bool is_nested,
292                                                       TimeKeeper& time_keeper,
293                                                       LazyNow& lazy_now)
294     : is_nested_(is_nested),
295       time_keeper_(time_keeper),
296       thread_controller_sample_metadata_("ThreadController active",
297                                          base::SampleMetadataScope::kThread) {
298   if (is_nested_) {
299     // Stop the current kWorkItem phase now, it will resume after the kNested
300     // phase ends.
301     time_keeper_->RecordEndOfPhase(kWorkItemSuspendedOnNested, lazy_now);
302   }
303   UpdateState(initial_state, lazy_now);
304 }
305 
~RunLevel()306 ThreadController::RunLevelTracker::RunLevel::~RunLevel() {
307   if (!was_moved_) {
308     DCHECK(exit_lazy_now_);
309     UpdateState(kIdle, *exit_lazy_now_);
310     if (is_nested_) {
311       // Attribute the entire time in this nested RunLevel to kNested phase. If
312       // this wasn't the last nested RunLevel, this is ignored and will be
313       // applied on the final pop().
314       time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);
315 
316       if (ShouldRecordSampleMetadata()) {
317         // Intentionally ordered after UpdateState(kIdle), reinstantiates
318         // thread_controller_sample_metadata_ when yielding back to a parent
319         // RunLevel (which is active by definition as it is currently running
320         // this one).
321         thread_controller_sample_metadata_.Set(
322             static_cast<int64_t>(++thread_controller_active_id_));
323       }
324     }
325   }
326 }
327 
328 ThreadController::RunLevelTracker::RunLevel::RunLevel(RunLevel&& other) =
329     default;
330 
LogPercentageMetric(const char * name,int percentage)331 void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
332     const char* name,
333     int percentage) {
334   UmaHistogramPercentage(base::StrCat({name, ".", GetThreadName()}),
335                          percentage);
336 }
337 
LogPercentageMetric(const char * name,int percentage,base::TimeDelta interval_duration)338 void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
339     const char* name,
340     int percentage,
341     base::TimeDelta interval_duration) {
342   UmaHistogramPercentage(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
343                          percentage);
344   UmaHistogramPercentage(
345       base::StrCat({name, GetSuffixForHistogram(interval_duration)}),
346       percentage);
347 }
348 
LogIntervalMetric(const char * name,base::TimeDelta value,base::TimeDelta interval_duration)349 void ThreadController::RunLevelTracker::RunLevel::LogIntervalMetric(
350     const char* name,
351     base::TimeDelta value,
352     base::TimeDelta interval_duration) {
353   // Log towards "Any" time suffix first.
354   UmaHistogramTimes(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
355                     value);
356   if (interval_duration < kNonTrivialActiveIntervalLength) {
357     UmaHistogramCustomMicrosecondsTimes(
358         base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
359         base::Microseconds(1), kNonTrivialActiveIntervalLength, 100);
360   } else if (interval_duration < kMediumActiveIntervalLength) {
361     UmaHistogramCustomTimes(
362         base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
363         kNonTrivialActiveIntervalLength, kMediumActiveIntervalLength, 100);
364   }
365 }
366 
LogOnActiveMetrics(LazyNow & lazy_now)367 void ThreadController::RunLevelTracker::RunLevel::LogOnActiveMetrics(
368     LazyNow& lazy_now) {
369   CHECK(last_active_start_.is_null());
370   CHECK(last_active_threadtick_start_.is_null());
371 
372   if (!last_active_end_.is_null()) {
373     const base::TimeDelta idle_time = lazy_now.Now() - last_active_end_;
374     LogIntervalMetric("Scheduling.ThreadController.IdleDuration", idle_time,
375                       idle_time);
376     last_active_end_ = base::TimeTicks();
377     accumulated_idle_time_ += idle_time;
378   }
379 
380   // Taking thread ticks can be expensive. Make sure to do it rarely enough to
381   // not have a discernible impact on performance.
382   static const bool thread_ticks_supported = ThreadTicks::IsSupported();
383   // Disable subsampling to support wall-time based metrics. Only supported for
384   // testing purposes. By default, the subsampling probability is 0.1%.
385   const double probability =
386       time_keeper_->wall_time_based_metrics_enabled_for_testing() ? 1.0 : 0.001;
387   if (thread_ticks_supported &&
388       metrics_sub_sampler_.ShouldSample(probability)) {
389     last_active_start_ = lazy_now.Now();
390     last_active_threadtick_start_ = ThreadTicks::Now();
391   }
392 }
393 
LogOnIdleMetrics(LazyNow & lazy_now)394 void ThreadController::RunLevelTracker::RunLevel::LogOnIdleMetrics(
395     LazyNow& lazy_now) {
396   if (!last_active_start_.is_null()) {
397     const base::TimeDelta elapsed_ticks = lazy_now.Now() - last_active_start_;
398     base::TimeDelta elapsed_thread_ticks =
399         ThreadTicks::Now() - last_active_threadtick_start_;
400 
401     // Round to 100% in case of clock imprecisions making it look like
402     // there's impossibly more ThreadTicks than TimeTicks elapsed.
403     elapsed_thread_ticks = std::min(elapsed_thread_ticks, elapsed_ticks);
404 
405     LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalDuration",
406                       elapsed_ticks, elapsed_ticks);
407     LogIntervalMetric(
408         "Scheduling.ThreadController.ActiveIntervalOffCpuDuration",
409         elapsed_ticks - elapsed_thread_ticks, elapsed_ticks);
410     LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalOnCpuDuration",
411                       elapsed_thread_ticks, elapsed_ticks);
412 
413     // If the interval was shorter than a tick, 100% on-cpu time is assumed.
414     int active_interval_cpu_percentage =
415         elapsed_ticks.is_zero()
416             ? 100
417             : static_cast<int>(
418                   (elapsed_thread_ticks * 100).IntDiv(elapsed_ticks));
419 
420     LogPercentageMetric(
421         "Scheduling.ThreadController.ActiveIntervalOnCpuPercentage",
422         active_interval_cpu_percentage, elapsed_ticks);
423 
424     if (time_keeper_->wall_time_based_metrics_enabled_for_testing()) {
425       accumulated_active_time_ += elapsed_ticks;
426       accumulated_active_on_cpu_time_ += elapsed_thread_ticks;
427       accumulated_active_off_cpu_time_ +=
428           (elapsed_ticks - elapsed_thread_ticks);
429 
430       // Accumulated wall-time since last wall-time based metric was stored.
431       const base::TimeDelta accumulated_wall_time =
432           accumulated_active_time_ + accumulated_idle_time_;
433 
434       // Add wall-time based ratio metrics (in percent) when the total sum of
435       // active and idle times is larger than one second.
436       if (accumulated_wall_time > Seconds(1)) {
437         const int active_vs_wall_time_percentage = checked_cast<int>(
438             (accumulated_active_time_ * 100).IntDiv(accumulated_wall_time));
439         LogPercentageMetric(
440             "Scheduling.ThreadController.ActiveVsWallTimePercentage",
441             active_vs_wall_time_percentage);
442         const int active_on_cpu_vs_wall_time_percentage =
443             checked_cast<int>((accumulated_active_on_cpu_time_ * 100)
444                                   .IntDiv(accumulated_wall_time));
445         LogPercentageMetric(
446             "Scheduling.ThreadController.ActiveOnCpuVsWallTimePercentage",
447             active_on_cpu_vs_wall_time_percentage);
448         const int active_off_cpu_vs_wall_time_percentage =
449             checked_cast<int>((accumulated_active_off_cpu_time_ * 100)
450                                   .IntDiv(accumulated_wall_time));
451         LogPercentageMetric(
452             "Scheduling.ThreadController.ActiveOffCpuVsWallTimePercentage",
453             active_off_cpu_vs_wall_time_percentage);
454 
455         accumulated_idle_time_ = base::TimeDelta();
456         accumulated_active_time_ = base::TimeDelta();
457         accumulated_active_on_cpu_time_ = base::TimeDelta();
458         accumulated_active_off_cpu_time_ = base::TimeDelta();
459       }
460     }
461 
462     // Reset timings.
463     last_active_start_ = base::TimeTicks();
464     last_active_threadtick_start_ = base::ThreadTicks();
465     last_active_end_ = lazy_now.Now();
466   }
467 }
468 
UpdateState(State new_state,LazyNow & lazy_now)469 void ThreadController::RunLevelTracker::RunLevel::UpdateState(
470     State new_state,
471     LazyNow& lazy_now) {
472   // The only state that can be redeclared is idle, anything else should be a
473   // transition.
474   DCHECK(state_ != new_state || new_state == kIdle)
475       << state_ << "," << new_state;
476 
477   const bool was_active = state_ != kIdle;
478   const bool is_active = new_state != kIdle;
479 
480   state_ = new_state;
481   if (was_active == is_active)
482     return;
483 
484   // Change of state.
485   if (is_active) {
486     LogOnActiveMetrics(lazy_now);
487 
488     // Flow emission is found at
489     // ThreadController::RunLevelTracker::RecordScheduleWork.
490     TRACE_EVENT_BEGIN("base", "ThreadController active", lazy_now.Now(),
491                       [&](perfetto::EventContext& ctx) {
492                         time_keeper_->MaybeEmitIncomingWakeupFlow(ctx);
493                       });
494 
495     if (ShouldRecordSampleMetadata()) {
496       // Overriding the annotation from the previous RunLevel is intentional.
497       // Only the top RunLevel is ever updated, which holds the relevant state.
498       thread_controller_sample_metadata_.Set(
499           static_cast<int64_t>(++thread_controller_active_id_));
500     }
501   } else {
502     if (ShouldRecordSampleMetadata()) {
503       thread_controller_sample_metadata_.Remove();
504     }
505 
506     LogOnIdleMetrics(lazy_now);
507 
508     TRACE_EVENT_END("base", lazy_now.Now());
509   }
510 
511   if (trace_observer_for_testing_) {
512     if (is_active)
513       trace_observer_for_testing_->OnThreadControllerActiveBegin();
514     else
515       trace_observer_for_testing_->OnThreadControllerActiveEnd();
516   }
517 }
518 
TimeKeeper(const RunLevelTracker & outer)519 ThreadController::RunLevelTracker::TimeKeeper::TimeKeeper(
520     const RunLevelTracker& outer)
521     : outer_(outer) {}
522 
RecordWakeUp(LazyNow & lazy_now)523 void ThreadController::RunLevelTracker::TimeKeeper::RecordWakeUp(
524     LazyNow& lazy_now) {
525   if (!ShouldRecordNow(ShouldRecordReqs::kOnWakeUp))
526     return;
527 
528   // Phase::kScheduled will be accounted against `last_wakeup_` in
529   // OnTaskSelected, if there's an application task in this work cycle.
530   last_wakeup_ = lazy_now.Now();
531   // Account the next phase starting from now.
532   last_phase_end_ = last_wakeup_;
533 
534 #if BUILDFLAG(ENABLE_BASE_TRACING)
535   // Emit the END of the kScheduled phase right away, this avoids incorrect
536   // ordering when kScheduled is later emitted and its END matches the BEGIN of
537   // an already emitted phase (tracing's sort is stable and would keep the late
538   // END for kScheduled after the earlier BEGIN of the next phase):
539   // crbug.com/1333460. As we just woke up, there are no events active at this
540   // point (we don't record MessagePumpPhases while nested). In the absence of
541   // a kScheduled phase, this unmatched END will be ignored.
542   TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
543                   last_wakeup_);
544 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
545 }
546 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)547 void ThreadController::RunLevelTracker::TimeKeeper::OnApplicationTaskSelected(
548     TimeTicks queue_time,
549     LazyNow& lazy_now) {
550   if (!ShouldRecordNow())
551     return;
552 
553   if (!last_wakeup_.is_null()) {
554     // `queue_time` can be null on threads that did not
555     // `SetAddQueueTimeToTasks(true)`. `queue_time` can also be ahead of
556     // `last_wakeup` in racy cases where the first chrome task is enqueued
557     // while the pump was already awake (e.g. for native work). Consider the
558     // kScheduled phase inexistent in that case.
559     if (!queue_time.is_null() && queue_time < last_wakeup_) {
560       if (!last_sleep_.is_null() && queue_time < last_sleep_) {
561         // Avoid overlapping kScheduled and kIdleWork phases when work is
562         // scheduled while going to sleep.
563         queue_time = last_sleep_;
564       }
565       RecordTimeInPhase(kScheduled, queue_time, last_wakeup_);
566 #if BUILDFLAG(ENABLE_BASE_TRACING)
567       // Match the END event which was already emitted by RecordWakeUp().
568       TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
569                         perfetto::StaticString(PhaseToEventName(kScheduled)),
570                         *perfetto_track_, queue_time);
571 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
572     }
573     last_wakeup_ = TimeTicks();
574   }
575   RecordEndOfPhase(kSelectingApplicationTask, lazy_now);
576   current_work_item_is_native_ = false;
577 }
578 
RecordEndOfPhase(Phase phase,LazyNow & lazy_now)579 void ThreadController::RunLevelTracker::TimeKeeper::RecordEndOfPhase(
580     Phase phase,
581     LazyNow& lazy_now) {
582   if (!ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
583                                         : ShouldRecordReqs::kRegular)) {
584     return;
585   }
586 
587   if (phase == kWorkItem && !current_work_item_is_native_) {
588     phase = kApplicationTask;
589     // Back to assuming future work is native until OnApplicationTaskSelected()
590     // is invoked.
591     current_work_item_is_native_ = true;
592   } else if (phase == kWorkItemSuspendedOnNested) {
593     // kWorkItemSuspendedOnNested temporarily marks the end of time allocated to
594     // the current work item. It is reported as a separate phase to skip the
595     // above `current_work_item_is_native_ = true` which assumes the work item
596     // is truly complete.
597     phase = current_work_item_is_native_ ? kNativeWork : kApplicationTask;
598   }
599 
600   const TimeTicks phase_end = lazy_now.Now();
601   RecordTimeInPhase(phase, last_phase_end_, phase_end);
602 
603 #if BUILDFLAG(ENABLE_BASE_TRACING)
604   // Ugly hack to name our `perfetto_track_`.
605   bool is_tracing_enabled = false;
606   TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("base"),
607                                      &is_tracing_enabled);
608   if (is_tracing_enabled) {
609     if (!was_tracing_enabled_) {
610       // The first event name on the track hackily names the track...
611       // TODO(crbug.com/42050015): Use the Perfetto library to properly name
612       // this Track in EnableRecording above.
613       TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("base"),
614                           "MessagePumpPhases", *perfetto_track_,
615                           last_phase_end_ - Seconds(1));
616     }
617 
618     const char* event_name = PhaseToEventName(phase);
619     TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
620                       perfetto::StaticString(event_name), *perfetto_track_,
621                       last_phase_end_);
622     TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
623                     phase_end);
624   }
625   was_tracing_enabled_ = is_tracing_enabled;
626 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
627 
628   last_phase_end_ = phase_end;
629 }
630 
MaybeEmitIncomingWakeupFlow(perfetto::EventContext & ctx)631 void ThreadController::RunLevelTracker::TimeKeeper::MaybeEmitIncomingWakeupFlow(
632     perfetto::EventContext& ctx) {
633 #if BUILDFLAG(ENABLE_BASE_TRACING)
634   static const uint8_t* flow_enabled =
635       TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("wakeup.flow");
636   if (!*flow_enabled) {
637     return;
638   }
639 
640   perfetto::TerminatingFlow::ProcessScoped(
641       reinterpret_cast<uint64_t>(&(outer_.get())))(ctx);
642 #endif
643 }
644 
ShouldRecordNow(ShouldRecordReqs reqs)645 bool ThreadController::RunLevelTracker::TimeKeeper::ShouldRecordNow(
646     ShouldRecordReqs reqs) {
647   DCHECK_CALLED_ON_VALID_THREAD(
648       outer_->outer_->associated_thread_->thread_checker);
649   // Recording is technically enabled once `histogram_` is set, however
650   // `last_phase_end_` will be null until the next RecordWakeUp in the work
651   // cycle in which `histogram_` is enabled. Only start recording from there.
652   // Ignore any nested phases. `reqs` may indicate exceptions to this.
653   //
654   // TODO(crbug.com/40226913): In a follow-up, we could probably always be
655   // tracking the phases of the pump and merely ignore the reporting if
656   // `histogram_` isn't set.
657   switch (reqs) {
658     case ShouldRecordReqs::kRegular:
659       return histogram_ && !last_phase_end_.is_null() &&
660              outer_->run_levels_.size() == 1;
661     case ShouldRecordReqs::kOnWakeUp:
662       return histogram_ && outer_->run_levels_.size() == 1;
663     case ShouldRecordReqs::kOnEndNested:
664       return histogram_ && !last_phase_end_.is_null() &&
665              outer_->run_levels_.size() <= 2;
666   }
667 }
668 
RecordTimeInPhase(Phase phase,TimeTicks phase_begin,TimeTicks phase_end)669 void ThreadController::RunLevelTracker::TimeKeeper::RecordTimeInPhase(
670     Phase phase,
671     TimeTicks phase_begin,
672     TimeTicks phase_end) {
673   DCHECK(ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
674                                           : ShouldRecordReqs::kRegular));
675 
676   // Report a phase only when at least 100ms has been attributed to it.
677   static constexpr auto kReportInterval = Milliseconds(100);
678 
679   // Above 30s in a single phase, assume suspend-resume and ignore the report.
680   static constexpr auto kSkippedDelta = Seconds(30);
681 
682   const auto delta = phase_end - phase_begin;
683   DCHECK(!delta.is_negative()) << delta;
684   if (delta >= kSkippedDelta)
685     return;
686 
687   deltas_[phase] += delta;
688   if (deltas_[phase] >= kReportInterval) {
689     const int count = deltas_[phase] / Milliseconds(1);
690     histogram_->AddCount(phase, count);
691     deltas_[phase] -= Milliseconds(count);
692   }
693 
694   if (phase == kIdleWork)
695     last_sleep_ = phase_end;
696 
697   if (outer_->trace_observer_for_testing_)
698     outer_->trace_observer_for_testing_->OnPhaseRecorded(phase);
699 }
700 
701 // static
PhaseToEventName(Phase phase)702 const char* ThreadController::RunLevelTracker::TimeKeeper::PhaseToEventName(
703     Phase phase) {
704   switch (phase) {
705     case kScheduled:
706       return "Scheduled";
707     case kPumpOverhead:
708       return "PumpOverhead";
709     case kNativeWork:
710       return "NativeTask";
711     case kSelectingApplicationTask:
712       return "SelectingApplicationTask";
713     case kApplicationTask:
714       return "ApplicationTask";
715     case kIdleWork:
716       return "IdleWork";
717     case kNested:
718       return "Nested";
719     case kWorkItemSuspendedOnNested:
720       // kWorkItemSuspendedOnNested should be transformed into kNativeWork or
721       // kApplicationTask before this point.
722       NOTREACHED();
723   }
724 }
725 
726 }  // namespace internal
727 }  // namespace sequence_manager
728 }  // namespace base
729