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 state_
.TraceIdleIdleTaskStart();
283 return CurrentIdleTaskDeadline();
286 void IdleHelper::DidProcessIdleTask() {
287 helper_
->CheckOnValidThread();
288 state_
.TraceIdleIdleTaskEnd();
289 if (IsInLongIdlePeriod(state_
.idle_period_state())) {
290 UpdateLongIdlePeriodStateAfterIdleTask();
295 bool IdleHelper::IsInIdlePeriod(IdlePeriodState state
) {
296 return state
!= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
300 bool IdleHelper::IsInLongIdlePeriod(IdlePeriodState state
) {
301 return state
== IdlePeriodState::IN_LONG_IDLE_PERIOD
||
302 state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
||
303 state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
;
306 bool IdleHelper::CanExceedIdleDeadlineIfRequired() const {
307 TRACE_EVENT0(disabled_by_default_tracing_category_
,
308 "CanExceedIdleDeadlineIfRequired");
309 helper_
->CheckOnValidThread();
310 return state_
.idle_period_state() ==
311 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
;
314 IdleHelper::IdlePeriodState
IdleHelper::SchedulerIdlePeriodState() const {
315 return state_
.idle_period_state();
318 IdleHelper::State::State(SchedulerHelper
* helper
,
320 const char* tracing_category
,
321 const char* disabled_by_default_tracing_category
,
322 const char* idle_period_tracing_name
)
325 idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD
),
326 idle_period_trace_event_started_(false),
327 running_idle_task_for_tracing_(false),
328 tracing_category_(tracing_category
),
329 disabled_by_default_tracing_category_(
330 disabled_by_default_tracing_category
),
331 idle_period_tracing_name_(idle_period_tracing_name
) {
334 IdleHelper::State::~State() {
337 IdleHelper::IdlePeriodState
IdleHelper::State::idle_period_state() const {
338 helper_
->CheckOnValidThread();
339 return idle_period_state_
;
342 base::TimeTicks
IdleHelper::State::idle_period_deadline() const {
343 helper_
->CheckOnValidThread();
344 return idle_period_deadline_
;
347 void IdleHelper::State::UpdateState(IdlePeriodState new_state
,
348 base::TimeTicks new_deadline
,
349 base::TimeTicks optional_now
) {
350 IdlePeriodState old_idle_period_state
= idle_period_state_
;
352 helper_
->CheckOnValidThread();
353 if (new_state
== idle_period_state_
) {
354 DCHECK_EQ(new_deadline
, idle_period_deadline_
);
359 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
361 base::TimeTicks
now(optional_now
.is_null() ? helper_
->Now() : optional_now
);
362 base::TraceTicks trace_now
= base::TraceTicks::Now();
363 idle_period_deadline_for_tracing_
= trace_now
+ (new_deadline
- now
);
364 TraceEventIdlePeriodStateChange(
365 new_state
, running_idle_task_for_tracing_
,
366 idle_period_deadline_for_tracing_
, trace_now
);
369 idle_period_state_
= new_state
;
370 idle_period_deadline_
= new_deadline
;
372 // Inform the delegate if we are starting or ending an idle period.
373 if (IsInIdlePeriod(new_state
) && !IsInIdlePeriod(old_idle_period_state
)) {
374 delegate_
->OnIdlePeriodStarted();
375 } else if (!IsInIdlePeriod(new_state
) &&
376 IsInIdlePeriod(old_idle_period_state
)) {
377 delegate_
->OnIdlePeriodEnded();
381 void IdleHelper::State::TraceIdleIdleTaskStart() {
382 helper_
->CheckOnValidThread();
385 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
387 TraceEventIdlePeriodStateChange(
388 idle_period_state_
, true, idle_period_deadline_for_tracing_
,
389 base::TraceTicks::Now());
393 void IdleHelper::State::TraceIdleIdleTaskEnd() {
394 helper_
->CheckOnValidThread();
397 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
399 TraceEventIdlePeriodStateChange(
400 idle_period_state_
, false, idle_period_deadline_for_tracing_
,
401 base::TraceTicks::Now());
405 void IdleHelper::State::TraceEventIdlePeriodStateChange(
406 IdlePeriodState new_state
,
407 bool new_running_idle_task
,
408 base::TraceTicks new_deadline
,
409 base::TraceTicks now
) {
410 TRACE_EVENT2(disabled_by_default_tracing_category_
, "SetIdlePeriodState",
412 IdleHelper::IdlePeriodStateToString(idle_period_state_
),
413 "new_state", IdleHelper::IdlePeriodStateToString(new_state
));
415 if (idle_period_trace_event_started_
&& running_idle_task_for_tracing_
&&
416 !new_running_idle_task
) {
417 running_idle_task_for_tracing_
= false;
418 if (!idle_period_deadline_for_tracing_
.is_null() &&
419 now
> idle_period_deadline_for_tracing_
) {
420 TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0(
421 tracing_category_
, idle_period_tracing_name_
, this,
423 std::max(idle_period_deadline_for_tracing_
,
424 last_idle_task_trace_time_
).ToInternalValue());
428 if (IsInIdlePeriod(new_state
)) {
429 if (!idle_period_trace_event_started_
) {
430 idle_period_trace_event_started_
= true;
431 TRACE_EVENT_ASYNC_BEGIN1(
432 tracing_category_
, idle_period_tracing_name_
, this,
433 "idle_period_length_ms", (new_deadline
- now
).ToInternalValue());
436 if (new_running_idle_task
) {
437 last_idle_task_trace_time_
= now
;
438 running_idle_task_for_tracing_
= true;
439 TRACE_EVENT_ASYNC_STEP_INTO0(
440 tracing_category_
, idle_period_tracing_name_
, this,
442 } else if (new_state
== IdlePeriodState::IN_SHORT_IDLE_PERIOD
) {
443 TRACE_EVENT_ASYNC_STEP_INTO0(
444 tracing_category_
, idle_period_tracing_name_
, this,
446 } else if (IsInLongIdlePeriod(new_state
) &&
447 new_state
!= IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
) {
448 TRACE_EVENT_ASYNC_STEP_INTO0(
449 tracing_category_
, idle_period_tracing_name_
, this,
451 } else if (new_state
== IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
) {
452 TRACE_EVENT_ASYNC_STEP_INTO0(
453 tracing_category_
, idle_period_tracing_name_
, this,
454 "LongIdlePeriodPaused");
456 } else if (idle_period_trace_event_started_
) {
457 idle_period_trace_event_started_
= false;
458 TRACE_EVENT_ASYNC_END0(tracing_category_
, idle_period_tracing_name_
, this);
463 const char* IdleHelper::IdlePeriodStateToString(
464 IdlePeriodState idle_period_state
) {
465 switch (idle_period_state
) {
466 case IdlePeriodState::NOT_IN_IDLE_PERIOD
:
467 return "not_in_idle_period";
468 case IdlePeriodState::IN_SHORT_IDLE_PERIOD
:
469 return "in_short_idle_period";
470 case IdlePeriodState::IN_LONG_IDLE_PERIOD
:
471 return "in_long_idle_period";
472 case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
:
473 return "in_long_idle_period_with_max_deadline";
474 case IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED
:
475 return "in_long_idle_period_paused";
482 } // namespace scheduler