1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/scheduler/child/idle_helper.h"
7 #include "base/time/time.h"
8 #include "base/trace_event/trace_event.h"
9 #include "base/trace_event/trace_event_argument.h"
10 #include "components/scheduler/child/scheduler_helper.h"
11 #include "components/scheduler/child/task_queue.h"
12 #include "components/scheduler/child/task_queue_manager.h"
16 IdleHelper::IdleHelper(
17 SchedulerHelper
* helper
,
19 const char* tracing_category
,
20 const char* disabled_by_default_tracing_category
,
21 const char* idle_period_tracing_name
,
22 base::TimeDelta required_quiescence_duration_before_long_idle_period
)
26 helper_
->NewTaskQueue(TaskQueue::Spec("idle_tq").SetPumpPolicy(
27 TaskQueue::PumpPolicy::MANUAL
))),
31 disabled_by_default_tracing_category
,
32 idle_period_tracing_name
),
33 required_quiescence_duration_before_long_idle_period_(
34 required_quiescence_duration_before_long_idle_period
),
35 disabled_by_default_tracing_category_(
36 disabled_by_default_tracing_category
),
38 weak_idle_helper_ptr_
= weak_factory_
.GetWeakPtr();
39 enable_next_long_idle_period_closure_
.Reset(
40 base::Bind(&IdleHelper::EnableLongIdlePeriod
, weak_idle_helper_ptr_
));
41 on_idle_task_posted_closure_
.Reset(base::Bind(
42 &IdleHelper::OnIdleTaskPostedOnMainThread
, weak_idle_helper_ptr_
));
44 idle_task_runner_
= make_scoped_refptr(new SingleThreadIdleTaskRunner(
45 idle_queue_
, helper_
->ControlAfterWakeUpTaskRunner(), this,
48 idle_queue_
->SetQueuePriority(TaskQueue::DISABLED_PRIORITY
);
50 helper_
->AddTaskObserver(this);
53 IdleHelper::~IdleHelper() {
54 helper_
->RemoveTaskObserver(this);
57 IdleHelper::Delegate::Delegate() {
60 IdleHelper::Delegate::~Delegate() {
63 scoped_refptr
<SingleThreadIdleTaskRunner
> IdleHelper::IdleTaskRunner() {
64 helper_
->CheckOnValidThread();
65 return idle_task_runner_
;
68 IdleHelper::IdlePeriodState
IdleHelper::ComputeNewLongIdlePeriodState(
69 const base::TimeTicks now
,
70 base::TimeDelta
* next_long_idle_period_delay_out
) {
71 helper_
->CheckOnValidThread();
73 if (!delegate_
->CanEnterLongIdlePeriod(now
,
74 next_long_idle_period_delay_out
)) {
75 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
78 base::TimeTicks next_pending_delayed_task
=
79 helper_
->NextPendingDelayedTaskRunTime();
80 base::TimeDelta max_long_idle_period_duration
=
81 base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis
);
82 base::TimeDelta long_idle_period_duration
;
83 if (next_pending_delayed_task
.is_null()) {
84 long_idle_period_duration
= max_long_idle_period_duration
;
86 // Limit the idle period duration to be before the next pending task.
87 long_idle_period_duration
= std::min(next_pending_delayed_task
- now
,
88 max_long_idle_period_duration
);
91 if (long_idle_period_duration
>=
92 base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis
)) {
93 *next_long_idle_period_delay_out
= long_idle_period_duration
;
94 if (idle_queue_
->IsQueueEmpty()) {
95 return IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
;
96 } else if (long_idle_period_duration
== max_long_idle_period_duration
) {
97 return IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
;
99 return IdlePeriodState::IN_LONG_IDLE_PERIOD
;
102 // If we can't start the idle period yet then try again after wakeup.
103 *next_long_idle_period_delay_out
= base::TimeDelta::FromMilliseconds(
104 kRetryEnableLongIdlePeriodDelayMillis
);
105 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
109 bool IdleHelper::ShouldWaitForQuiescence() {
110 helper_
->CheckOnValidThread();
112 if (helper_
->IsShutdown())
115 if (required_quiescence_duration_before_long_idle_period_
==
119 bool system_is_quiescent
= helper_
->GetAndClearSystemIsQuiescentBit();
120 TRACE_EVENT1(disabled_by_default_tracing_category_
, "ShouldWaitForQuiescence",
121 "system_is_quiescent", system_is_quiescent
);
122 return !system_is_quiescent
;
125 void IdleHelper::EnableLongIdlePeriod() {
126 TRACE_EVENT0(disabled_by_default_tracing_category_
, "EnableLongIdlePeriod");
127 helper_
->CheckOnValidThread();
128 if (helper_
->IsShutdown())
131 // End any previous idle period.
134 if (ShouldWaitForQuiescence()) {
135 helper_
->ControlTaskRunner()->PostDelayedTask(
136 FROM_HERE
, enable_next_long_idle_period_closure_
.callback(),
137 required_quiescence_duration_before_long_idle_period_
);
138 delegate_
->IsNotQuiescent();
142 base::TimeTicks
now(helper_
->Now());
143 base::TimeDelta next_long_idle_period_delay
;
144 IdlePeriodState new_idle_period_state
=
145 ComputeNewLongIdlePeriodState(now
, &next_long_idle_period_delay
);
146 if (IsInIdlePeriod(new_idle_period_state
)) {
147 StartIdlePeriod(new_idle_period_state
, now
,
148 now
+ next_long_idle_period_delay
);
150 // Otherwise wait for the next long idle period delay before trying again.
151 helper_
->ControlTaskRunner()->PostDelayedTask(
152 FROM_HERE
, enable_next_long_idle_period_closure_
.callback(),
153 next_long_idle_period_delay
);
157 void IdleHelper::StartIdlePeriod(IdlePeriodState new_state
,
159 base::TimeTicks idle_period_deadline
) {
160 DCHECK_GT(idle_period_deadline
, now
);
161 helper_
->CheckOnValidThread();
162 DCHECK(IsInIdlePeriod(new_state
));
164 base::TimeDelta
idle_period_duration(idle_period_deadline
- now
);
165 if (idle_period_duration
<
166 base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis
)) {
167 TRACE_EVENT1(disabled_by_default_tracing_category_
,
168 "NotStartingIdlePeriodBecauseDeadlineIsTooClose",
169 "idle_period_duration_ms",
170 idle_period_duration
.InMillisecondsF());
174 TRACE_EVENT0(disabled_by_default_tracing_category_
, "StartIdlePeriod");
175 idle_queue_
->SetQueuePriority(TaskQueue::BEST_EFFORT_PRIORITY
);
176 idle_queue_
->PumpQueue();
178 state_
.UpdateState(new_state
, idle_period_deadline
, now
);
181 void IdleHelper::EndIdlePeriod() {
182 helper_
->CheckOnValidThread();
183 TRACE_EVENT0(disabled_by_default_tracing_category_
, "EndIdlePeriod");
185 enable_next_long_idle_period_closure_
.Cancel();
186 on_idle_task_posted_closure_
.Cancel();
188 // If we weren't already within an idle period then early-out.
189 if (!IsInIdlePeriod(state_
.idle_period_state()))
192 idle_queue_
->SetQueuePriority(TaskQueue::DISABLED_PRIORITY
);
193 state_
.UpdateState(IdlePeriodState::NOT_IN_IDLE_PERIOD
, base::TimeTicks(),
197 void IdleHelper::WillProcessTask(const base::PendingTask
& pending_task
) {
200 void IdleHelper::DidProcessTask(const base::PendingTask
& pending_task
) {
201 helper_
->CheckOnValidThread();
202 TRACE_EVENT0(disabled_by_default_tracing_category_
, "DidProcessTask");
203 if (IsInIdlePeriod(state_
.idle_period_state()) &&
204 state_
.idle_period_state() !=
205 IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
&&
206 helper_
->Now() >= state_
.idle_period_deadline()) {
207 // If the idle period deadline has now been reached, either end the idle
208 // period or trigger a new long-idle period.
209 if (IsInLongIdlePeriod(state_
.idle_period_state())) {
210 EnableLongIdlePeriod();
212 DCHECK(IdlePeriodState::IN_SHORT_IDLE_PERIOD
==
213 state_
.idle_period_state());
219 void IdleHelper::UpdateLongIdlePeriodStateAfterIdleTask() {
220 helper_
->CheckOnValidThread();
221 DCHECK(IsInLongIdlePeriod(state_
.idle_period_state()));
222 TRACE_EVENT0(disabled_by_default_tracing_category_
,
223 "UpdateLongIdlePeriodStateAfterIdleTask");
224 TaskQueue::QueueState queue_state
= idle_queue_
->GetQueueState();
225 if (queue_state
== TaskQueue::QueueState::EMPTY
) {
226 // If there are no more idle tasks then pause long idle period ticks until a
227 // new idle task is posted.
228 state_
.UpdateState(IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
,
229 state_
.idle_period_deadline(), base::TimeTicks());
230 } else if (queue_state
== TaskQueue::QueueState::NEEDS_PUMPING
) {
231 // If there is still idle work to do then just start the next idle period.
232 base::TimeDelta next_long_idle_period_delay
;
233 if (state_
.idle_period_state() ==
234 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
) {
235 // If we are in a max deadline long idle period then start the next
236 // idle period immediately.
237 next_long_idle_period_delay
= base::TimeDelta();
239 // Otherwise ensure that we kick the scheduler at the right time to
240 // initiate the next idle period.
241 next_long_idle_period_delay
= std::max(
242 base::TimeDelta(), state_
.idle_period_deadline() - helper_
->Now());
244 if (next_long_idle_period_delay
== base::TimeDelta()) {
245 EnableLongIdlePeriod();
247 helper_
->ControlTaskRunner()->PostDelayedTask(
248 FROM_HERE
, enable_next_long_idle_period_closure_
.callback(),
249 next_long_idle_period_delay
);
254 base::TimeTicks
IdleHelper::CurrentIdleTaskDeadline() const {
255 helper_
->CheckOnValidThread();
256 return state_
.idle_period_deadline();
259 void IdleHelper::OnIdleTaskPosted() {
260 TRACE_EVENT0(disabled_by_default_tracing_category_
, "OnIdleTaskPosted");
261 if (idle_task_runner_
->RunsTasksOnCurrentThread()) {
262 OnIdleTaskPostedOnMainThread();
264 helper_
->ControlTaskRunner()->PostTask(
265 FROM_HERE
, on_idle_task_posted_closure_
.callback());
269 void IdleHelper::OnIdleTaskPostedOnMainThread() {
270 TRACE_EVENT0(disabled_by_default_tracing_category_
,
271 "OnIdleTaskPostedOnMainThread");
272 if (state_
.idle_period_state() ==
273 IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
) {
274 // Restart long idle period ticks.
275 helper_
->ControlTaskRunner()->PostTask(
276 FROM_HERE
, enable_next_long_idle_period_closure_
.callback());
280 base::TimeTicks
IdleHelper::WillProcessIdleTask() {
281 helper_
->CheckOnValidThread();
282 DCHECK(IsInIdlePeriod(state_
.idle_period_state()));
284 state_
.TraceIdleIdleTaskStart();
285 return CurrentIdleTaskDeadline();
288 void IdleHelper::DidProcessIdleTask() {
289 helper_
->CheckOnValidThread();
290 state_
.TraceIdleIdleTaskEnd();
291 if (IsInLongIdlePeriod(state_
.idle_period_state())) {
292 UpdateLongIdlePeriodStateAfterIdleTask();
297 bool IdleHelper::IsInIdlePeriod(IdlePeriodState state
) {
298 return state
!= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
302 bool IdleHelper::IsInLongIdlePeriod(IdlePeriodState state
) {
303 return state
== IdlePeriodState::IN_LONG_IDLE_PERIOD
||
304 state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
||
305 state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
;
308 bool IdleHelper::CanExceedIdleDeadlineIfRequired() const {
309 TRACE_EVENT0(disabled_by_default_tracing_category_
,
310 "CanExceedIdleDeadlineIfRequired");
311 helper_
->CheckOnValidThread();
312 return state_
.idle_period_state() ==
313 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
;
316 IdleHelper::IdlePeriodState
IdleHelper::SchedulerIdlePeriodState() const {
317 return state_
.idle_period_state();
320 IdleHelper::State::State(SchedulerHelper
* helper
,
322 const char* tracing_category
,
323 const char* disabled_by_default_tracing_category
,
324 const char* idle_period_tracing_name
)
327 idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD
),
328 nestable_events_started_(false),
329 tracing_category_(tracing_category
),
330 disabled_by_default_tracing_category_(
331 disabled_by_default_tracing_category
),
332 idle_period_tracing_name_(idle_period_tracing_name
) {
335 IdleHelper::State::~State() {
338 IdleHelper::IdlePeriodState
IdleHelper::State::idle_period_state() const {
339 helper_
->CheckOnValidThread();
340 return idle_period_state_
;
343 base::TimeTicks
IdleHelper::State::idle_period_deadline() const {
344 helper_
->CheckOnValidThread();
345 return idle_period_deadline_
;
348 void IdleHelper::State::UpdateState(IdlePeriodState new_state
,
349 base::TimeTicks new_deadline
,
350 base::TimeTicks optional_now
) {
351 IdlePeriodState old_idle_period_state
= idle_period_state_
;
353 helper_
->CheckOnValidThread();
354 if (new_state
== idle_period_state_
) {
355 DCHECK_EQ(new_deadline
, idle_period_deadline_
);
360 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
362 base::TimeTicks
now(optional_now
.is_null() ? helper_
->Now() : optional_now
);
363 TraceEventIdlePeriodStateChange(new_state
, new_deadline
, now
);
364 idle_period_deadline_for_tracing_
=
365 base::TraceTicks::Now() + (new_deadline
- now
);
368 idle_period_state_
= new_state
;
369 idle_period_deadline_
= new_deadline
;
371 // Inform the delegate if we are starting or ending an idle period.
372 if (IsInIdlePeriod(new_state
) && !IsInIdlePeriod(old_idle_period_state
)) {
373 delegate_
->OnIdlePeriodStarted();
374 } else if (!IsInIdlePeriod(new_state
) &&
375 IsInIdlePeriod(old_idle_period_state
)) {
376 delegate_
->OnIdlePeriodEnded();
380 void IdleHelper::State::TraceIdleIdleTaskStart() {
381 helper_
->CheckOnValidThread();
384 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
385 if (is_tracing
&& nestable_events_started_
) {
386 last_idle_task_trace_time_
= base::TraceTicks::Now();
387 TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
388 tracing_category_
, "RunningIdleTask", this,
389 last_idle_task_trace_time_
.ToInternalValue());
393 void IdleHelper::State::TraceIdleIdleTaskEnd() {
394 helper_
->CheckOnValidThread();
397 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
398 if (is_tracing
&& nestable_events_started_
) {
399 if (!idle_period_deadline_for_tracing_
.is_null() &&
400 base::TraceTicks::Now() > idle_period_deadline_for_tracing_
) {
401 TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
402 tracing_category_
, "DeadlineOverrun", this,
403 std::max(idle_period_deadline_for_tracing_
,
404 last_idle_task_trace_time_
).ToInternalValue());
405 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
, "DeadlineOverrun",
408 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
, "RunningIdleTask", this);
412 void IdleHelper::State::TraceEventIdlePeriodStateChange(
413 IdlePeriodState new_state
,
414 base::TimeTicks new_deadline
,
415 base::TimeTicks now
) {
416 TRACE_EVENT2(disabled_by_default_tracing_category_
, "SetIdlePeriodState",
418 IdleHelper::IdlePeriodStateToString(idle_period_state_
),
419 "new_state", IdleHelper::IdlePeriodStateToString(new_state
));
420 if (nestable_events_started_
) {
421 // End async tracing events for the state we are leaving.
422 if (idle_period_state_
== IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
) {
423 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
, "LongIdlePeriodPaused",
426 if (IsInLongIdlePeriod(idle_period_state_
) &&
427 !IsInLongIdlePeriod(new_state
)) {
428 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
, "LongIdlePeriod",
431 if (idle_period_state_
== IdlePeriodState::IN_SHORT_IDLE_PERIOD
) {
432 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
, "ShortIdlePeriod",
435 if (IsInIdlePeriod(idle_period_state_
) && !IsInIdlePeriod(new_state
)) {
436 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_
,
437 idle_period_tracing_name_
, this);
438 nestable_events_started_
= false;
442 // Start async tracing events for the state we are entering.
443 if (IsInIdlePeriod(new_state
) && !IsInIdlePeriod(idle_period_state_
)) {
444 nestable_events_started_
= true;
445 TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
446 tracing_category_
, idle_period_tracing_name_
, this,
447 "idle_period_length_ms", (new_deadline
- now
).ToInternalValue());
449 if (new_state
== IdlePeriodState::IN_SHORT_IDLE_PERIOD
) {
450 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_
, "ShortIdlePeriod",
453 if (IsInLongIdlePeriod(new_state
) &&
454 !IsInLongIdlePeriod(idle_period_state_
)) {
455 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_
, "LongIdlePeriod",
458 if (new_state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
) {
459 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_
, "LongIdlePeriodPaused",
465 const char* IdleHelper::IdlePeriodStateToString(
466 IdlePeriodState idle_period_state
) {
467 switch (idle_period_state
) {
468 case IdlePeriodState::NOT_IN_IDLE_PERIOD
:
469 return "not_in_idle_period";
470 case IdlePeriodState::IN_SHORT_IDLE_PERIOD
:
471 return "in_short_idle_period";
472 case IdlePeriodState::IN_LONG_IDLE_PERIOD
:
473 return "in_long_idle_period";
474 case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
:
475 return "in_long_idle_period_with_max_deadline";
476 case IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
:
477 return "in_long_idle_period_paused";
484 } // namespace scheduler