PowerMode: Track main thread animations separately
Adds a separate PowerModeVoter to cc::Scheduler to identify when the
main thread is producing frames.
This helps us better identify no-op animations that involve the main
thread (e.g. rAF), while still distinguishing (valid) main-thread frame
production that takes a long time and eventually produces updates.
The prior mechanism that would look at the FrameSkippedReason to
identify whether the main thread was active could not detect no-op
main thread animations where the BeginMainFrame exceeded the BeginFrame
deadline.
Bug: b:187392644
Bug: 1166695
Change-Id: Ia3feb109f0ae498dd378cc7c063790433f21e04d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2877048
Commit-Queue: Eric Seckler <eseckler@chromium.org>
Reviewed-by: Bo <boliu@chromium.org>
Reviewed-by: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#881990}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index a0848a9..eaa675b 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -436,6 +436,7 @@
"//base",
"//base/third_party/dynamic_annotations",
"//build:chromeos_buildflags",
+ "//components/power_scheduler",
"//components/viz/client",
"//device/base/synchronization",
"//gpu",
@@ -829,6 +830,7 @@
"//build:chromeos_buildflags",
"//cc/mojo_embedder",
"//cc/paint",
+ "//components/power_scheduler",
"//components/ukm:test_support",
"//components/viz/client",
"//components/viz/common",
diff --git a/cc/DEPS b/cc/DEPS
index 85414ab..b694c05 100644
--- a/cc/DEPS
+++ b/cc/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+components/power_scheduler",
"+components/ukm/test_ukm_recorder.h",
"+components/viz/client",
"+components/viz/common",
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.cc b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
index e8c3476d..c22f450 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.cc
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
@@ -215,7 +215,8 @@
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"step", "DidNotProduceFrame", "reason", reason);
bool frame_completed = reason == FrameSkippedReason::kNoDamage;
- power_mode_voter_.OnFrameSkipped(frame_completed);
+ bool waiting_on_main = reason == FrameSkippedReason::kWaitingOnMain;
+ power_mode_voter_.OnFrameSkipped(frame_completed, waiting_on_main);
compositor_frame_sink_ptr_->DidNotProduceFrame(ack);
}
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc
index e0ba0fb..ccb984d 100644
--- a/cc/scheduler/scheduler.cc
+++ b/cc/scheduler/scheduler.cc
@@ -19,6 +19,7 @@
#include "cc/metrics/begin_main_frame_metrics.h"
#include "cc/metrics/compositor_frame_reporting_controller.h"
#include "cc/metrics/compositor_timing_history.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
@@ -40,7 +41,8 @@
std::unique_ptr<CompositorTimingHistory> compositor_timing_history,
gfx::RenderingPipeline* main_thread_pipeline,
gfx::RenderingPipeline* compositor_thread_pipeline,
- CompositorFrameReportingController* compositor_frame_reporting_controller)
+ CompositorFrameReportingController* compositor_frame_reporting_controller,
+ power_scheduler::PowerModeArbiter* power_mode_arbiter)
: settings_(settings),
client_(client),
layer_tree_host_id_(layer_tree_host_id),
@@ -51,7 +53,9 @@
begin_impl_frame_tracker_(FROM_HERE),
state_machine_(settings),
main_thread_pipeline_(main_thread_pipeline),
- compositor_thread_pipeline_(compositor_thread_pipeline) {
+ compositor_thread_pipeline_(compositor_thread_pipeline),
+ power_mode_voter_(
+ power_mode_arbiter->NewVoter("PowerModeVoter.MainThreadAnimation")) {
TRACE_EVENT1("cc", "Scheduler::Scheduler", "settings", settings_.AsValue());
DCHECK(client_);
DCHECK(!state_machine_.BeginFrameNeeded());
@@ -948,6 +952,7 @@
PostPendingBeginFrameTask();
StartOrStopBeginFrames();
+ UpdatePowerModeVote();
}
void Scheduler::AsProtozeroInto(
@@ -1095,4 +1100,34 @@
ProcessScheduledActions();
}
+void Scheduler::UpdatePowerModeVote() {
+ // After three aborted BeginMainFrames, consider the main thread's involvement
+ // in frame production unimportant. PowerMode detection for compositor-driven
+ // animation or no-op animation relies on the voter in the frame sink in this
+ // case.
+ constexpr int kMaxAbortedBeginMainFrames = 2;
+
+ bool main_thread_animation =
+ observing_begin_frame_source_ &&
+ (state_machine_.needs_begin_main_frame() ||
+ state_machine_.CommitPending() || state_machine_.has_pending_tree()) &&
+ state_machine_.aborted_begin_main_frame_count() <=
+ kMaxAbortedBeginMainFrames;
+
+ power_scheduler::PowerMode vote =
+ main_thread_animation ? power_scheduler::PowerMode::kMainThreadAnimation
+ : power_scheduler::PowerMode::kIdle;
+
+ if (last_power_mode_vote_ == vote)
+ return;
+
+ last_power_mode_vote_ = vote;
+ if (vote == power_scheduler::PowerMode::kIdle) {
+ power_mode_voter_->ResetVoteAfterTimeout(
+ power_scheduler::PowerModeVoter::kAnimationTimeout);
+ } else {
+ power_mode_voter_->VoteFor(vote);
+ }
+}
+
} // namespace cc
diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h
index 69026a1..fa51f7f6 100644
--- a/cc/scheduler/scheduler.h
+++ b/cc/scheduler/scheduler.h
@@ -19,6 +19,7 @@
#include "cc/scheduler/scheduler_settings.h"
#include "cc/scheduler/scheduler_state_machine.h"
#include "cc/tiles/tile_priority.h"
+#include "components/power_scheduler/power_mode_voter.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"
@@ -95,15 +96,16 @@
class CC_EXPORT Scheduler : public viz::BeginFrameObserverBase {
public:
- Scheduler(SchedulerClient* client,
- const SchedulerSettings& scheduler_settings,
- int layer_tree_host_id,
- base::SingleThreadTaskRunner* task_runner,
- std::unique_ptr<CompositorTimingHistory> compositor_timing_history,
- gfx::RenderingPipeline* main_thread_pipeline,
- gfx::RenderingPipeline* compositor_thread_pipeline,
- CompositorFrameReportingController*
- compositor_frame_reporting_controller);
+ Scheduler(
+ SchedulerClient* client,
+ const SchedulerSettings& scheduler_settings,
+ int layer_tree_host_id,
+ base::SingleThreadTaskRunner* task_runner,
+ std::unique_ptr<CompositorTimingHistory> compositor_timing_history,
+ gfx::RenderingPipeline* main_thread_pipeline,
+ gfx::RenderingPipeline* compositor_thread_pipeline,
+ CompositorFrameReportingController* compositor_frame_reporting_controller,
+ power_scheduler::PowerModeArbiter* power_mode_arbiter);
Scheduler(const Scheduler&) = delete;
~Scheduler() override;
@@ -348,6 +350,10 @@
base::Optional<gfx::RenderingPipeline::ScopedPipelineActive>
compositor_thread_pipeline_active_;
+ std::unique_ptr<power_scheduler::PowerModeVoter> power_mode_voter_;
+ power_scheduler::PowerMode last_power_mode_vote_ =
+ power_scheduler::PowerMode::kIdle;
+
private:
// Posts the deadline task if needed by checking
// SchedulerStateMachine::CurrentBeginImplFrameDeadlineMode(). This only
@@ -403,6 +409,8 @@
bool IsInsideAction(SchedulerStateMachine::Action action) {
return inside_action_ == action;
}
+
+ void UpdatePowerModeVote();
};
} // namespace cc
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
index 84aa13ae..8fc7ba9 100644
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -879,6 +879,10 @@
// need first draw to come through).
active_tree_is_ready_to_draw_ = false;
}
+
+ aborted_begin_main_frame_count_ = 0;
+ } else {
+ aborted_begin_main_frame_count_++;
}
// Update state related to forced draws.
diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h
index 8fa08af31..0f07052 100644
--- a/cc/scheduler/scheduler_state_machine.h
+++ b/cc/scheduler/scheduler_state_machine.h
@@ -349,6 +349,10 @@
}
bool did_commit_during_frame() const { return did_commit_during_frame_; }
+ int aborted_begin_main_frame_count() const {
+ return aborted_begin_main_frame_count_;
+ }
+
protected:
bool BeginFrameRequiredForAction() const;
bool BeginFrameNeededForVideo() const;
@@ -480,6 +484,9 @@
// If set to true, the pending tree must be drawn at least once after
// activation before a new tree can be activated.
bool pending_tree_needs_first_draw_on_activation_ = false;
+
+ // Number of consecutive BeginMainFrames that were aborted without updates.
+ int aborted_begin_main_frame_count_ = 0;
};
} // namespace cc
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc
index 3074f14..950f1ed6 100644
--- a/cc/scheduler/scheduler_unittest.cc
+++ b/cc/scheduler/scheduler_unittest.cc
@@ -20,11 +20,13 @@
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
+#include "base/time/time_override.h"
#include "base/trace_event/trace_event.h"
#include "cc/metrics/begin_main_frame_metrics.h"
#include "cc/metrics/event_metrics.h"
#include "cc/test/fake_compositor_frame_reporting_controller.h"
#include "cc/test/scheduler_test_common.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/fake_delay_based_time_source.h"
@@ -46,6 +48,10 @@
namespace cc {
namespace {
+using power_scheduler::PowerMode;
+using power_scheduler::PowerModeArbiter;
+using power_scheduler::PowerModeVoter;
+
base::TimeDelta kSlowDuration = base::TimeDelta::FromSeconds(1);
base::TimeDelta kFastDuration = base::TimeDelta::FromMilliseconds(1);
@@ -404,7 +410,7 @@
scheduler_ = std::make_unique<TestScheduler>(
task_runner_->GetMockTickClock(), client_.get(), scheduler_settings_, 0,
task_runner_.get(), std::move(fake_compositor_timing_history),
- reporting_controller.get());
+ reporting_controller.get(), &power_mode_arbiter_);
client_->set_scheduler(scheduler_.get());
scheduler_->SetBeginFrameSource(frame_source);
@@ -591,6 +597,7 @@
std::unique_ptr<viz::SyntheticBeginFrameSource> unthrottled_frame_source_;
SchedulerSettings scheduler_settings_;
std::unique_ptr<FakeSchedulerClient> client_;
+ PowerModeArbiter power_mode_arbiter_;
std::unique_ptr<TestScheduler> scheduler_;
FakeCompositorTimingHistory* fake_compositor_timing_history_;
DroppedFrameCounter dropped_counter;
@@ -4435,5 +4442,160 @@
EXPECT_ACTIONS("WillBeginImplFrame");
}
+namespace {
+class FakePowerModeObserver : public PowerModeArbiter::Observer {
+ public:
+ void OnPowerModeChanged(PowerMode old_mode, PowerMode new_mode) override {}
+};
+
+class SchedulerTestForPowerMode : public SchedulerTest {
+ public:
+ SchedulerTestForPowerMode()
+ : time_overrides_(
+ /*time_override=*/nullptr,
+ &SchedulerTestForPowerMode::TimeTicksNow,
+ /*thread_ticks_override=*/nullptr) {
+ DCHECK_EQ(nullptr, current_test_);
+ current_test_ = this;
+
+ // Clear the arbiter's initial kCharging vote.
+ power_mode_arbiter_.SetOnBatteryPowerForTesting(/*on_battery_power=*/true);
+ power_mode_arbiter_.SetTaskRunnerForTesting(task_runner_);
+
+ // Add a fake observer so that reset tasks are executed.
+ power_mode_arbiter_.AddObserver(&observer_);
+ }
+
+ ~SchedulerTestForPowerMode() override {
+ DCHECK_EQ(this, current_test_);
+ current_test_ = nullptr;
+ }
+
+ void AdvanceToArbiterSnapAfter(base::TimeDelta delay) {
+ // Align the mock clock with the phase of the arbiter's reset tasks.
+ base::TimeTicks target_time =
+ (task_runner_->NowTicks() + delay)
+ .SnappedToNextTick(base::TimeTicks(),
+ PowerModeArbiter::kResetVoteTimeResolution);
+ task_runner_->RunUntilTime(target_time);
+ }
+
+ static base::TimeTicks TimeTicksNow() {
+ DCHECK_NE(nullptr, current_test_);
+ return current_test_->task_runner_->NowTicks();
+ }
+
+ private:
+ // The ScopedTimeClockOverrides below require a function pointer (as opposed
+ // to a bound callback). We store the current test instance in this static
+ // variable to access its members from the static TimeTicksNow() method above.
+ static SchedulerTestForPowerMode* current_test_;
+
+ // The arbiter uses base::TimeTicks::Now(), which needs to be overridden by
+ // the test's task runner. Ideally we'd be using base::test::TaskEnvironment
+ // for scheduler unittests, which would do this for us.
+ base::subtle::ScopedTimeClockOverrides time_overrides_;
+
+ FakePowerModeObserver observer_;
+};
+
+// static
+SchedulerTestForPowerMode* SchedulerTestForPowerMode::current_test_;
+} // namespace
+
+TEST_F(SchedulerTestForPowerMode, BeginMainFramePowerModeVoter) {
+ // Arbiter should start out in idle mode.
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+
+ // SetUpScheduler will cause a BeginMainFrame and commit, which should change
+ // the PowerMode vote.
+ SetUpScheduler(EXTERNAL_BFS);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+
+ // The scheduler should now be idle, so the PowerMode vote should be reset
+ // after kAnimationTimeout.
+ AdvanceToArbiterSnapAfter(PowerModeVoter::kAnimationTimeout);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+
+ scheduler_->SetNeedsBeginMainFrame();
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+
+ // While BeginMainFrame is needed, the vote is not reset
+ AdvanceToArbiterSnapAfter(PowerModeVoter::kAnimationTimeout);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+
+ client_->Reset();
+ EXPECT_SCOPED(AdvanceFrame());
+ EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
+
+ client_->Reset();
+ scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
+
+ // While BeginMainFrame is active, the vote is not reset
+ AdvanceToArbiterSnapAfter(PowerModeVoter::kAnimationTimeout);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+
+ scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
+
+ // After aborting, the vote is reset.
+ AdvanceToArbiterSnapAfter(PowerModeVoter::kAnimationTimeout);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+
+ // Go through another two BeginMainFrames that are aborted.
+ scheduler_->SetNeedsBeginMainFrame();
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+ client_->Reset();
+ EXPECT_SCOPED(AdvanceFrame());
+ EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
+ client_->Reset();
+ scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
+ scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
+
+ scheduler_->SetNeedsBeginMainFrame();
+ client_->Reset();
+ EXPECT_SCOPED(AdvanceFrame());
+ EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
+ client_->Reset();
+ scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
+ scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
+
+ // Still in animation mode, but a reset is pending.
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+ AdvanceToArbiterSnapAfter(PowerModeVoter::kAnimationTimeout);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+
+ // Further BeginMainFrame will be ignored, because the main-thread animation
+ // is considered no-op after three consecutive aborted BeginMainFrames.
+ scheduler_->SetNeedsBeginMainFrame();
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+ client_->Reset();
+ EXPECT_SCOPED(AdvanceFrame());
+ EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
+ client_->Reset();
+ scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
+ scheduler_->BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES);
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+
+ // But once the BeginMainFrame produces updates, we vote for animation again.
+ scheduler_->SetNeedsBeginMainFrame();
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(), PowerMode::kIdle);
+ client_->Reset();
+ EXPECT_SCOPED(AdvanceFrame());
+ EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
+ client_->Reset();
+ scheduler_->NotifyBeginMainFrameStarted(task_runner_->NowTicks());
+ scheduler_->NotifyReadyToCommit(nullptr);
+ EXPECT_ACTIONS("ScheduledActionCommit");
+ EXPECT_EQ(power_mode_arbiter_.GetActiveModeForTesting(),
+ PowerMode::kMainThreadAnimation);
+ client_->Reset();
+}
+
} // namespace
} // namespace cc
diff --git a/cc/test/scheduler_test_common.cc b/cc/test/scheduler_test_common.cc
index 68250233..827627d 100644
--- a/cc/test/scheduler_test_common.cc
+++ b/cc/test/scheduler_test_common.cc
@@ -141,7 +141,8 @@
int layer_tree_host_id,
base::SingleThreadTaskRunner* task_runner,
std::unique_ptr<CompositorTimingHistory> compositor_timing_history,
- CompositorFrameReportingController* compositor_frame_reporting_controller)
+ CompositorFrameReportingController* compositor_frame_reporting_controller,
+ power_scheduler::PowerModeArbiter* power_mode_arbiter)
: Scheduler(client,
scheduler_settings,
layer_tree_host_id,
@@ -149,7 +150,8 @@
std::move(compositor_timing_history),
nullptr,
nullptr,
- compositor_frame_reporting_controller),
+ compositor_frame_reporting_controller,
+ power_mode_arbiter),
now_src_(now_src) {}
base::TimeTicks TestScheduler::Now() const {
diff --git a/cc/test/scheduler_test_common.h b/cc/test/scheduler_test_common.h
index 48fff79..b989e63 100644
--- a/cc/test/scheduler_test_common.h
+++ b/cc/test/scheduler_test_common.h
@@ -86,8 +86,8 @@
int layer_tree_host_id,
base::SingleThreadTaskRunner* task_runner,
std::unique_ptr<CompositorTimingHistory> compositor_timing_history,
- CompositorFrameReportingController*
- compositor_frame_reporting_controller);
+ CompositorFrameReportingController* compositor_frame_reporting_controller,
+ power_scheduler::PowerModeArbiter* arbiter);
TestScheduler(const TestScheduler&) = delete;
TestScheduler& operator=(const TestScheduler&) = delete;
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 6e4e0cc..726d6ff12 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -31,6 +31,7 @@
#include "cc/trees/proxy_main.h"
#include "cc/trees/render_frame_metadata_observer.h"
#include "cc/trees/task_runner_provider.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"
#include "components/viz/common/frame_timing_details.h"
#include "components/viz/common/gpu/context_provider.h"
@@ -95,7 +96,8 @@
task_runner_provider_->ImplThreadTaskRunner(),
std::move(compositor_timing_history), layer_tree_host->TakeMainPipeline(),
layer_tree_host->TakeCompositorPipeline(),
- host_impl_->compositor_frame_reporting_controller());
+ host_impl_->compositor_frame_reporting_controller(),
+ power_scheduler::PowerModeArbiter::GetInstance());
DCHECK_EQ(scheduler_->visible(), host_impl_->visible());
}
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 3de362e3..a24ce564 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -29,6 +29,7 @@
#include "cc/trees/mutator_host.h"
#include "cc/trees/render_frame_metadata_observer.h"
#include "cc/trees/scoped_abort_remaining_swap_promises.h"
+#include "components/power_scheduler/power_mode_arbiter.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"
#include "components/viz/common/frame_timing_details.h"
#include "components/viz/common/gpu/context_provider.h"
@@ -92,7 +93,8 @@
std::move(compositor_timing_history),
layer_tree_host_->TakeMainPipeline(),
layer_tree_host_->TakeCompositorPipeline(),
- host_impl_->compositor_frame_reporting_controller());
+ host_impl_->compositor_frame_reporting_controller(),
+ power_scheduler::PowerModeArbiter::GetInstance());
}
}