• 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 "base/check.h"
8 #include "base/metrics/histogram.h"
9 #include "base/metrics/histogram_base.h"
10 #include "base/notreached.h"
11 #include "base/strings/string_util.h"
12 #include "base/time/tick_clock.h"
13 #include "base/trace_event/base_tracing.h"
14 
15 namespace base {
16 namespace sequence_manager {
17 namespace internal {
18 
ThreadController(const TickClock * time_source)19 ThreadController::ThreadController(const TickClock* time_source)
20     : associated_thread_(AssociatedThreadId::CreateUnbound()),
21       time_source_(time_source) {}
22 
23 ThreadController::~ThreadController() = default;
24 
SetTickClock(const TickClock * clock)25 void ThreadController::SetTickClock(const TickClock* clock) {
26   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
27   time_source_ = clock;
28 }
29 
RunLevelTracker(const ThreadController & outer)30 ThreadController::RunLevelTracker::RunLevelTracker(
31     const ThreadController& outer)
32     : outer_(outer) {}
33 
~RunLevelTracker()34 ThreadController::RunLevelTracker::~RunLevelTracker() {
35   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
36 
37   // There shouldn't be any remaining |run_levels_| by the time this unwinds.
38   DCHECK_EQ(run_levels_.size(), 0u);
39 }
40 
EnableMessagePumpTimeKeeperMetrics(const char * thread_name)41 void ThreadController::EnableMessagePumpTimeKeeperMetrics(
42     const char* thread_name) {
43   // MessagePump runs too fast, a low-res clock would result in noisy metrics.
44   if (!base::TimeTicks::IsHighResolution())
45     return;
46 
47   run_level_tracker_.EnableTimeKeeperMetrics(thread_name);
48 }
49 
EnableTimeKeeperMetrics(const char * thread_name)50 void ThreadController::RunLevelTracker::EnableTimeKeeperMetrics(
51     const char* thread_name) {
52   time_keeper_.EnableRecording(thread_name);
53 }
54 
EnableRecording(const char * thread_name)55 void ThreadController::RunLevelTracker::TimeKeeper::EnableRecording(
56     const char* thread_name) {
57   DCHECK(!histogram_);
58   histogram_ = LinearHistogram::FactoryGet(
59       JoinString({"Scheduling.MessagePumpTimeKeeper", thread_name}, "."), 1,
60       Phase::kLastPhase, Phase::kLastPhase + 1,
61       base::HistogramBase::kUmaTargetedHistogramFlag);
62 
63 #if BUILDFLAG(ENABLE_BASE_TRACING)
64   perfetto_track_.emplace(
65       reinterpret_cast<uint64_t>(this),
66       // TODO(crbug.com/1006541): Replace with ThreadTrack::Current() after SDK
67       // migration.
68       // In the non-SDK version, ThreadTrack::Current() returns a different
69       // track id on some platforms (for example Mac OS), which results in
70       // async tracks not being associated with their thread.
71       perfetto::ThreadTrack::ForThread(base::PlatformThread::CurrentId()));
72   // TODO(1006541): Use Perfetto library to name this Track.
73   // auto desc = perfetto_track_->Serialize();
74   // desc.set_name(JoinString({"MessagePumpPhases", thread_name}, " "));
75   // perfetto::internal::TrackEventDataSource::SetTrackDescriptor(
76   //     *perfetto_track_, desc);
77 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
78 }
79 
OnRunLoopStarted(State initial_state,LazyNow & lazy_now)80 void ThreadController::RunLevelTracker::OnRunLoopStarted(State initial_state,
81                                                          LazyNow& lazy_now) {
82   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
83 
84   const bool is_nested = !run_levels_.empty();
85   run_levels_.emplace(initial_state, is_nested, time_keeper_, lazy_now
86 #if BUILDFLAG(ENABLE_BASE_TRACING)
87                       ,
88                       terminating_wakeup_lambda_
89 #endif
90   );
91 
92   // In unit tests, RunLoop::Run() acts as the initial wake-up.
93   if (!is_nested && initial_state != kIdle)
94     time_keeper_.RecordWakeUp(lazy_now);
95 }
96 
OnRunLoopEnded()97 void ThreadController::RunLevelTracker::OnRunLoopEnded() {
98   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
99   // Normally this will occur while kIdle or kInBetweenWorkItems but it can also
100   // occur while kRunningWorkItem in rare situations where the owning
101   // ThreadController is deleted from within a task. Ref.
102   // SequenceManagerWithTaskRunnerTest.DeleteSequenceManagerInsideATask. Thus we
103   // can't assert anything about the current state other than that it must be
104   // exiting an existing RunLevel.
105   DCHECK(!run_levels_.empty());
106   LazyNow exit_lazy_now(outer_->time_source_);
107   run_levels_.top().set_exit_lazy_now(&exit_lazy_now);
108   run_levels_.pop();
109 }
110 
OnWorkStarted(LazyNow & lazy_now)111 void ThreadController::RunLevelTracker::OnWorkStarted(LazyNow& lazy_now) {
112   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
113   // Ignore work outside the main run loop.
114   // The only practical case where this would happen is if a native loop is spun
115   // outside the main runloop (e.g. system dialog during startup). We cannot
116   // support this because we are not guaranteed to be able to observe its exit
117   // (like we would inside an application task which is at least guaranteed to
118   // itself notify us when it ends). Some ThreadControllerWithMessagePumpTest
119   // also drive ThreadController outside a RunLoop and hit this.
120   if (run_levels_.empty())
121     return;
122 
123   // Already running a work item? => #work-in-work-implies-nested
124   if (run_levels_.top().state() == kRunningWorkItem) {
125     run_levels_.emplace(kRunningWorkItem, /*nested=*/true, time_keeper_,
126                         lazy_now
127 #if BUILDFLAG(ENABLE_BASE_TRACING)
128                         ,
129                         terminating_wakeup_lambda_
130 #endif
131     );
132   } else {
133     if (run_levels_.top().state() == kIdle) {
134       time_keeper_.RecordWakeUp(lazy_now);
135     } else {
136       time_keeper_.RecordEndOfPhase(kPumpOverhead, lazy_now);
137     }
138 
139     // Going from kIdle or kInBetweenWorkItems to kRunningWorkItem.
140     run_levels_.top().UpdateState(kRunningWorkItem);
141   }
142 }
143 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)144 void ThreadController::RunLevelTracker::OnApplicationTaskSelected(
145     TimeTicks queue_time,
146     LazyNow& lazy_now) {
147   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
148   // As-in OnWorkStarted. Early native loops can result in
149   // ThreadController::DoWork because the lack of a top-level RunLoop means
150   // `task_execution_allowed` wasn't consumed.
151   if (run_levels_.empty())
152     return;
153 
154   // OnWorkStarted() is expected to precede OnApplicationTaskSelected().
155   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
156 
157   time_keeper_.OnApplicationTaskSelected(queue_time, lazy_now);
158 }
159 
OnWorkEnded(LazyNow & lazy_now,int run_level_depth)160 void ThreadController::RunLevelTracker::OnWorkEnded(LazyNow& lazy_now,
161                                                     int run_level_depth) {
162   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
163   if (run_levels_.empty())
164     return;
165 
166   // #done-work-at-lower-runlevel-implies-done-nested
167   if (run_level_depth != static_cast<int>(num_run_levels())) {
168     DCHECK_EQ(run_level_depth + 1, static_cast<int>(num_run_levels()));
169     run_levels_.top().set_exit_lazy_now(&lazy_now);
170     run_levels_.pop();
171   } else {
172     time_keeper_.RecordEndOfPhase(kWorkItem, lazy_now);
173   }
174 
175   // Whether we exited a nested run-level or not: the current run-level is now
176   // transitioning from kRunningWorkItem to kInBetweenWorkItems.
177   DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
178   run_levels_.top().UpdateState(kInBetweenWorkItems);
179 }
180 
OnIdle(LazyNow & lazy_now)181 void ThreadController::RunLevelTracker::OnIdle(LazyNow& lazy_now) {
182   DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
183   if (run_levels_.empty())
184     return;
185 
186   DCHECK_NE(run_levels_.top().state(), kRunningWorkItem);
187   time_keeper_.RecordEndOfPhase(kIdleWork, lazy_now);
188   run_levels_.top().UpdateState(kIdle);
189 }
190 
RecordScheduleWork()191 void ThreadController::RunLevelTracker::RecordScheduleWork() {
192   // Matching TerminatingFlow is found at
193   // ThreadController::RunLevelTracker::RunLevel::UpdateState
194   if (outer_->associated_thread_->IsBoundToCurrentThread()) {
195     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
196   } else {
197     TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWork",
198                         perfetto::Flow::FromPointer(this));
199   }
200 }
201 
202 // static
SetTraceObserverForTesting(TraceObserverForTesting * trace_observer_for_testing)203 void ThreadController::RunLevelTracker::SetTraceObserverForTesting(
204     TraceObserverForTesting* trace_observer_for_testing) {
205   DCHECK_NE(!!trace_observer_for_testing_, !!trace_observer_for_testing);
206   trace_observer_for_testing_ = trace_observer_for_testing;
207 }
208 
209 // static
210 ThreadController::RunLevelTracker::TraceObserverForTesting*
211     ThreadController::RunLevelTracker::trace_observer_for_testing_ = nullptr;
212 
RunLevel(State initial_state,bool is_nested,TimeKeeper & time_keeper,LazyNow & lazy_now,TerminatingFlowLambda & terminating_wakeup_flow_lambda)213 ThreadController::RunLevelTracker::RunLevel::RunLevel(
214     State initial_state,
215     bool is_nested,
216     TimeKeeper& time_keeper,
217     LazyNow& lazy_now
218 #if BUILDFLAG(ENABLE_BASE_TRACING)
219     ,
220     TerminatingFlowLambda& terminating_wakeup_flow_lambda
221 #endif
222     )
223     : is_nested_(is_nested),
224       time_keeper_(time_keeper),
225       thread_controller_sample_metadata_("ThreadController active",
226                                          base::SampleMetadataScope::kThread)
227 #if BUILDFLAG(ENABLE_BASE_TRACING)
228       ,
229       terminating_wakeup_flow_lambda_(terminating_wakeup_flow_lambda)
230 #endif
231 {
232   if (is_nested_) {
233     // Stop the current kWorkItem phase now, it will resume after the kNested
234     // phase ends.
235     time_keeper_->RecordEndOfPhase(kWorkItemSuspendedOnNested, lazy_now);
236   }
237   UpdateState(initial_state);
238 }
239 
~RunLevel()240 ThreadController::RunLevelTracker::RunLevel::~RunLevel() {
241   if (!was_moved_) {
242     DCHECK(exit_lazy_now_);
243     UpdateState(kIdle);
244     if (is_nested_) {
245       // Attribute the entire time in this nested RunLevel to kNested phase. If
246       // this wasn't the last nested RunLevel, this is ignored and will be
247       // applied on the final pop().
248       time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);
249 
250       // Intentionally ordered after UpdateState(kIdle), reinstantiates
251       // thread_controller_sample_metadata_ when yielding back to a parent
252       // RunLevel (which is active by definition as it is currently running this
253       // one).
254       thread_controller_sample_metadata_.Set(
255           static_cast<int64_t>(++thread_controller_active_id_));
256     }
257   }
258 }
259 
260 ThreadController::RunLevelTracker::RunLevel::RunLevel(RunLevel&& other) =
261     default;
262 
UpdateState(State new_state)263 void ThreadController::RunLevelTracker::RunLevel::UpdateState(State new_state) {
264   // The only state that can be redeclared is idle, anything else should be a
265   // transition.
266   DCHECK(state_ != new_state || new_state == kIdle)
267       << state_ << "," << new_state;
268 
269   const bool was_active = state_ != kIdle;
270   const bool is_active = new_state != kIdle;
271 
272   state_ = new_state;
273   if (was_active == is_active)
274     return;
275 
276   // Change of state.
277   if (is_active) {
278     // Flow emission is found at
279     // ThreadController::RunLevelTracker::RecordScheduleWork.
280     TRACE_EVENT_BEGIN("base", "ThreadController active",
281                       *terminating_wakeup_flow_lambda_);
282     // Overriding the annotation from the previous RunLevel is intentional. Only
283     // the top RunLevel is ever updated, which holds the relevant state.
284     thread_controller_sample_metadata_.Set(
285         static_cast<int64_t>(++thread_controller_active_id_));
286   } else {
287     thread_controller_sample_metadata_.Remove();
288     TRACE_EVENT_END("base");
289     // TODO(crbug.com/1021571): Remove this once fixed.
290     PERFETTO_INTERNAL_ADD_EMPTY_EVENT();
291   }
292 
293   if (trace_observer_for_testing_) {
294     if (is_active)
295       trace_observer_for_testing_->OnThreadControllerActiveBegin();
296     else
297       trace_observer_for_testing_->OnThreadControllerActiveEnd();
298   }
299 }
300 
TimeKeeper(const RunLevelTracker & outer)301 ThreadController::RunLevelTracker::TimeKeeper::TimeKeeper(
302     const RunLevelTracker& outer)
303     : outer_(outer) {}
304 
RecordWakeUp(LazyNow & lazy_now)305 void ThreadController::RunLevelTracker::TimeKeeper::RecordWakeUp(
306     LazyNow& lazy_now) {
307   if (!ShouldRecordNow(ShouldRecordReqs::kOnWakeUp))
308     return;
309 
310   // Phase::kScheduled will be accounted against `last_wakeup_` in
311   // OnTaskSelected, if there's an application task in this work cycle.
312   last_wakeup_ = lazy_now.Now();
313   // Account the next phase starting from now.
314   last_phase_end_ = last_wakeup_;
315 
316 #if BUILDFLAG(ENABLE_BASE_TRACING)
317   // Emit the END of the kScheduled phase right away, this avoids incorrect
318   // ordering when kScheduled is later emitted and its END matches the BEGIN of
319   // an already emitted phase (tracing's sort is stable and would keep the late
320   // END for kScheduled after the earlier BEGIN of the next phase):
321   // crbug.com/1333460. As we just woke up, there are no events active at this
322   // point (we don't record MessagePumpPhases while nested). In the absence of
323   // a kScheduled phase, this unmatched END will be ignored.
324   TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
325                   last_wakeup_);
326 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
327 }
328 
OnApplicationTaskSelected(TimeTicks queue_time,LazyNow & lazy_now)329 void ThreadController::RunLevelTracker::TimeKeeper::OnApplicationTaskSelected(
330     TimeTicks queue_time,
331     LazyNow& lazy_now) {
332   if (!ShouldRecordNow())
333     return;
334 
335   if (!last_wakeup_.is_null()) {
336     // `queue_time` can be null on threads that did not
337     // `SetAddQueueTimeToTasks(true)`. `queue_time` can also be ahead of
338     // `last_wakeup` in racy cases where the first chrome task is enqueued
339     // while the pump was already awake (e.g. for native work). Consider the
340     // kScheduled phase inexistent in that case.
341     if (!queue_time.is_null() && queue_time < last_wakeup_) {
342       if (!last_sleep_.is_null() && queue_time < last_sleep_) {
343         // Avoid overlapping kScheduled and kIdleWork phases when work is
344         // scheduled while going to sleep.
345         queue_time = last_sleep_;
346       }
347       RecordTimeInPhase(kScheduled, queue_time, last_wakeup_);
348 #if BUILDFLAG(ENABLE_BASE_TRACING)
349       // Match the END event which was already emitted by RecordWakeUp().
350       TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
351                         perfetto::StaticString(PhaseToEventName(kScheduled)),
352                         *perfetto_track_, queue_time);
353 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
354     }
355     last_wakeup_ = TimeTicks();
356   }
357   RecordEndOfPhase(kSelectingApplicationTask, lazy_now);
358   current_work_item_is_native_ = false;
359 }
360 
RecordEndOfPhase(Phase phase,LazyNow & lazy_now)361 void ThreadController::RunLevelTracker::TimeKeeper::RecordEndOfPhase(
362     Phase phase,
363     LazyNow& lazy_now) {
364   if (!ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
365                                         : ShouldRecordReqs::kRegular)) {
366     return;
367   }
368 
369   if (phase == kWorkItem && !current_work_item_is_native_) {
370     phase = kApplicationTask;
371     // Back to assuming future work is native until OnApplicationTaskSelected()
372     // is invoked.
373     current_work_item_is_native_ = true;
374   } else if (phase == kWorkItemSuspendedOnNested) {
375     // kWorkItemSuspendedOnNested temporarily marks the end of time allocated to
376     // the current work item. It is reported as a separate phase to skip the
377     // above `current_work_item_is_native_ = true` which assumes the work item
378     // is truly complete.
379     phase = current_work_item_is_native_ ? kNativeWork : kApplicationTask;
380   }
381 
382   const TimeTicks phase_end = lazy_now.Now();
383   RecordTimeInPhase(phase, last_phase_end_, phase_end);
384 
385 #if BUILDFLAG(ENABLE_BASE_TRACING)
386   // Ugly hack to name our `perfetto_track_`.
387   bool is_tracing_enabled = false;
388   TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("base"),
389                                      &is_tracing_enabled);
390   if (is_tracing_enabled) {
391     if (!was_tracing_enabled_) {
392       // The first event name on the track hackily names the track...
393       // TODO(1006541): Use the Perfetto library to properly name this Track in
394       // EnableRecording above.
395       TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("base"),
396                           "MessagePumpPhases", *perfetto_track_,
397                           last_phase_end_ - Seconds(1));
398     }
399 
400     const char* event_name = PhaseToEventName(phase);
401     TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
402                       perfetto::StaticString(event_name), *perfetto_track_,
403                       last_phase_end_);
404     TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
405                     phase_end);
406   }
407   was_tracing_enabled_ = is_tracing_enabled;
408 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
409 
410   last_phase_end_ = phase_end;
411 }
412 
ShouldRecordNow(ShouldRecordReqs reqs)413 bool ThreadController::RunLevelTracker::TimeKeeper::ShouldRecordNow(
414     ShouldRecordReqs reqs) {
415   DCHECK_CALLED_ON_VALID_THREAD(
416       outer_->outer_->associated_thread_->thread_checker);
417   // Recording is technically enabled once `histogram_` is set, however
418   // `last_phase_end_` will be null until the next RecordWakeUp in the work
419   // cycle in which `histogram_` is enabled. Only start recording from there.
420   // Ignore any nested phases. `reqs` may indicate exceptions to this.
421   //
422   // TODO(crbug.com/1329717): In a follow-up, we could probably always be
423   // tracking the phases of the pump and merely ignore the reporting if
424   // `histogram_` isn't set.
425   switch (reqs) {
426     case ShouldRecordReqs::kRegular:
427       return histogram_ && !last_phase_end_.is_null() &&
428              outer_->run_levels_.size() == 1;
429     case ShouldRecordReqs::kOnWakeUp:
430       return histogram_ && outer_->run_levels_.size() == 1;
431     case ShouldRecordReqs::kOnEndNested:
432       return histogram_ && !last_phase_end_.is_null() &&
433              outer_->run_levels_.size() <= 2;
434   }
435 }
436 
RecordTimeInPhase(Phase phase,TimeTicks phase_begin,TimeTicks phase_end)437 void ThreadController::RunLevelTracker::TimeKeeper::RecordTimeInPhase(
438     Phase phase,
439     TimeTicks phase_begin,
440     TimeTicks phase_end) {
441   DCHECK(ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
442                                           : ShouldRecordReqs::kRegular));
443 
444   // Report a phase only when at least 100ms has been attributed to it.
445   static constexpr auto kReportInterval = Milliseconds(100);
446 
447   // Above 30s in a single phase, assume suspend-resume and ignore the report.
448   static constexpr auto kSkippedDelta = Seconds(30);
449 
450   const auto delta = phase_end - phase_begin;
451   DCHECK(!delta.is_negative()) << delta;
452   if (delta >= kSkippedDelta)
453     return;
454 
455   deltas_[phase] += delta;
456   if (deltas_[phase] >= kReportInterval) {
457     const int count = deltas_[phase] / Milliseconds(1);
458     histogram_->AddCount(phase, count);
459     deltas_[phase] -= Milliseconds(count);
460   }
461 
462   if (phase == kIdleWork)
463     last_sleep_ = phase_end;
464 
465   if (outer_->trace_observer_for_testing_)
466     outer_->trace_observer_for_testing_->OnPhaseRecorded(phase);
467 }
468 
469 // static
PhaseToEventName(Phase phase)470 const char* ThreadController::RunLevelTracker::TimeKeeper::PhaseToEventName(
471     Phase phase) {
472   switch (phase) {
473     case kScheduled:
474       return "Scheduled";
475     case kPumpOverhead:
476       return "PumpOverhead";
477     case kNativeWork:
478       return "NativeTask";
479     case kSelectingApplicationTask:
480       return "SelectingApplicationTask";
481     case kApplicationTask:
482       return "ApplicationTask";
483     case kIdleWork:
484       return "IdleWork";
485     case kNested:
486       return "Nested";
487     case kWorkItemSuspendedOnNested:
488       // kWorkItemSuspendedOnNested should be transformed into kNativeWork or
489       // kApplicationTask before this point.
490       NOTREACHED();
491       return "";
492   }
493 }
494 
495 }  // namespace internal
496 }  // namespace sequence_manager
497 }  // namespace base
498