Commit 456eb6a9 authored by Alexander Timin's avatar Alexander Timin Committed by Commit Bot

[scheduler] Move audio logic to PageScheduler level.

Instead of disabling throttling for the whole renderer process when
audio is enabled, disable it only for the current Page.

This will allow to use audio signal when making decisions about loading
throttling and throttling dedicated workers.

R=alexclarke@chromium.org

Change-Id: I78ee861da3e46d12c597294ff8c054bc61b9be6a
Reviewed-on: https://chromium-review.googlesource.com/1039426Reviewed-by: default avatarAlex Clarke <alexclarke@chromium.org>
Commit-Queue: Alexander Timin <altimin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555400}
parent 6ba2acd5
......@@ -611,6 +611,8 @@ FrameSchedulerImpl::OnActiveConnectionCreated() {
}
bool FrameSchedulerImpl::ShouldThrottleTimers() const {
if (parent_page_scheduler_ && parent_page_scheduler_->IsAudioPlaying())
return false;
if (page_visibility_ == PageVisibilityState::kHidden)
return true;
return RuntimeEnabledFeatures::TimerThrottlingForHiddenFramesEnabled() &&
......
......@@ -88,6 +88,8 @@ class PLATFORM_EXPORT FrameSchedulerImpl : public FrameScheduler {
scoped_refptr<TaskQueue> ControlTaskQueue();
void SetPageVisibility(PageVisibilityState page_visibility);
void UpdatePolicy();
bool has_active_connection() const { return has_active_connection_; }
void OnTraceLogEnabled() { tracing_controller_.OnTraceLogEnabled(); }
......@@ -128,7 +130,6 @@ class PLATFORM_EXPORT FrameSchedulerImpl : public FrameScheduler {
bool ShouldThrottleTimers() const;
FrameScheduler::ThrottlingState CalculateThrottlingState() const;
void RemoveThrottlingObserver(Observer* observer);
void UpdatePolicy();
void UpdateQueuePolicy(const scoped_refptr<MainThreadTaskQueue>& queue,
TaskQueue::QueueEnabledVoter* voter);
void UpdateThrottling();
......
......@@ -54,9 +54,6 @@ const double kShortIdlePeriodDurationPercentile = 50;
// Amount of idle time left in a frame (as a ratio of the vsync interval) above
// which main thread compositing can be considered fast.
const double kFastCompositingIdleTimeThreshold = .2;
// We do not throttle anything while audio is played and shortly after that.
constexpr base::TimeDelta kThrottlingDelayAfterAudioIsPlayed =
base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kQueueingTimeWindowDuration =
base::TimeDelta::FromSeconds(1);
const double kSamplingRateForTaskUkm = 0.0001;
......@@ -986,16 +983,13 @@ void MainThreadSchedulerImpl::ResumeTimersForAndroidWebView() {
void MainThreadSchedulerImpl::OnAudioStateChanged() {
bool is_audio_playing = false;
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
is_audio_playing = is_audio_playing || page_scheduler->IsPlayingAudio();
is_audio_playing = is_audio_playing || page_scheduler->IsAudioPlaying();
}
if (is_audio_playing == main_thread_only().is_audio_playing)
return;
main_thread_only().last_audio_state_change = helper_.NowTicks();
main_thread_only().is_audio_playing = is_audio_playing;
UpdatePolicy();
}
std::unique_ptr<MainThreadSchedulerImpl::RendererPauseHandle>
......@@ -1390,17 +1384,6 @@ void MainThreadSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
new_policy_duration = touchstart_expected_flag_valid_for_duration;
}
// Do not throttle while audio is playing or for a short period after that
// to make sure that pages playing short audio clips powered by timers
// work.
if (main_thread_only().last_audio_state_change &&
!main_thread_only().is_audio_playing) {
UpdatePolicyDuration(now,
main_thread_only().last_audio_state_change.value() +
kThrottlingDelayAfterAudioIsPlayed,
&new_policy_duration);
}
bool previously_frozen_when_backgrounded =
main_thread_only().frozen_when_backgrounded;
bool newly_frozen = false;
......@@ -1582,7 +1565,6 @@ void MainThreadSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
}
new_policy.should_disable_throttling() =
ShouldDisableThrottlingBecauseOfAudio(now) ||
main_thread_only().use_virtual_time;
// Tracing is done before the early out check, because it's quite possible we
......@@ -2620,19 +2602,6 @@ MainThreadSchedulerImpl::GetVirtualTimeDomain() {
return virtual_time_domain_.get();
}
bool MainThreadSchedulerImpl::ShouldDisableThrottlingBecauseOfAudio(
base::TimeTicks now) {
if (!main_thread_only().last_audio_state_change)
return false;
if (main_thread_only().is_audio_playing)
return true;
return main_thread_only().last_audio_state_change.value() +
kThrottlingDelayAfterAudioIsPlayed >
now;
}
void MainThreadSchedulerImpl::AddQueueToWakeUpBudgetPool(
MainThreadTaskQueue* queue) {
if (!main_thread_only().wake_up_budget_pool) {
......@@ -2668,6 +2637,10 @@ base::WeakPtr<MainThreadSchedulerImpl> MainThreadSchedulerImpl::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
bool MainThreadSchedulerImpl::IsAudioPlaying() const {
return main_thread_only().is_audio_playing;
}
bool MainThreadSchedulerImpl::ShouldRecordTaskUkm() {
// This function returns true with probability of kSamplingRateForTaskUkm.
return main_thread_only().uniform_distribution(
......
......@@ -274,6 +274,8 @@ class PLATFORM_EXPORT MainThreadSchedulerImpl
base::TimeTicks end,
base::Optional<base::TimeDelta> thread_time);
bool IsAudioPlaying() const;
// base::trace_event::TraceLog::EnabledStateObserver implementation:
void OnTraceLogEnabled() override;
void OnTraceLogDisabled() override;
......@@ -557,8 +559,6 @@ class PLATFORM_EXPORT MainThreadSchedulerImpl
static const char* ExpensiveTaskPolicyToString(
ExpensiveTaskPolicy expensive_task_policy);
bool ShouldDisableThrottlingBecauseOfAudio(base::TimeTicks now);
void AddQueueToWakeUpBudgetPool(MainThreadTaskQueue* queue);
void PauseRendererImpl();
......@@ -663,7 +663,6 @@ class PLATFORM_EXPORT MainThreadSchedulerImpl
base::TimeDelta compositor_frame_interval;
TraceableCounter<base::TimeDelta, kTracingCategoryNameDebug>
longest_jank_free_task_duration;
base::Optional<base::TimeTicks> last_audio_state_change;
TraceableCounter<int, kTracingCategoryNameInfo>
renderer_pause_count; // Renderer is paused if non-zero.
TraceableCounter<int, kTracingCategoryNameDebug>
......
......@@ -32,6 +32,9 @@ constexpr double kDefaultMaxBackgroundThrottlingDelayInSeconds = 0;
constexpr base::TimeDelta kMinimalBackgroundThrottlingDurationToReport =
base::TimeDelta::FromSeconds(3);
// We do not throttle anything while audio is played and shortly after that.
constexpr base::TimeDelta kRecentAudioDelay = base::TimeDelta::FromSeconds(5);
// Values coming from the field trial config are interpreted as follows:
// -1 is "not set". Scheduler should use a reasonable default.
// 0 corresponds to base::nullopt.
......@@ -100,7 +103,7 @@ PageSchedulerImpl::PageSchedulerImpl(
: main_thread_scheduler_(main_thread_scheduler),
page_visibility_(kDefaultPageVisibility),
disable_background_timer_throttling_(disable_background_timer_throttling),
is_audio_playing_(false),
audio_state_(AudioState::kSilent),
is_frozen_(false),
reported_background_throttling_since_navigation_(false),
has_active_connection_(false),
......@@ -110,6 +113,8 @@ PageSchedulerImpl::PageSchedulerImpl(
delegate_(delegate),
weak_factory_(this) {
main_thread_scheduler->AddPageScheduler(this);
on_audio_silent_closure_.Reset(base::BindRepeating(
&PageSchedulerImpl::OnAudioSilent, base::Unretained(this)));
}
PageSchedulerImpl::~PageSchedulerImpl() {
......@@ -237,7 +242,28 @@ void PageSchedulerImpl::RemoveVirtualTimeObserver(
}
void PageSchedulerImpl::AudioStateChanged(bool is_audio_playing) {
is_audio_playing_ = is_audio_playing;
if (is_audio_playing) {
audio_state_ = AudioState::kAudible;
on_audio_silent_closure_.Cancel();
UpdateFramePolicies();
} else {
if (audio_state_ != AudioState::kAudible)
return;
on_audio_silent_closure_.Cancel();
audio_state_ = AudioState::kRecentlyAudible;
main_thread_scheduler_->ControlTaskQueue()->PostDelayedTask(
FROM_HERE, on_audio_silent_closure_.GetCallback(), kRecentAudioDelay);
// No need to call UpdateFramePolicies here, as for outside world
// kAudible and kRecentlyAudible are the same thing.
}
main_thread_scheduler_->OnAudioStateChanged();
}
void PageSchedulerImpl::OnAudioSilent() {
DCHECK_EQ(audio_state_, AudioState::kRecentlyAudible);
audio_state_ = AudioState::kSilent;
UpdateFramePolicies();
main_thread_scheduler_->OnAudioStateChanged();
}
......@@ -253,8 +279,9 @@ void PageSchedulerImpl::RequestBeginMainFrameNotExpected(bool new_state) {
delegate_->RequestBeginMainFrameNotExpected(new_state);
}
bool PageSchedulerImpl::IsPlayingAudio() const {
return is_audio_playing_;
bool PageSchedulerImpl::IsAudioPlaying() const {
return audio_state_ == AudioState::kAudible ||
audio_state_ == AudioState::kRecentlyAudible;
}
bool PageSchedulerImpl::IsFrozen() const {
......@@ -286,7 +313,7 @@ void PageSchedulerImpl::AsValueInto(
page_visibility_ == PageVisibilityState::kVisible);
state->SetBoolean("disable_background_timer_throttling",
disable_background_timer_throttling_);
state->SetBoolean("is_audio_playing", is_audio_playing_);
state->SetBoolean("is_audio_playing", IsAudioPlaying());
state->SetBoolean("is_frozen", is_frozen_);
state->SetBoolean("reported_background_throttling_since_navigation",
reported_background_throttling_since_navigation_);
......@@ -374,6 +401,11 @@ void PageSchedulerImpl::UpdateBackgroundBudgetPoolThrottlingState() {
}
}
void PageSchedulerImpl::UpdateFramePolicies() {
for (FrameSchedulerImpl* frame_scheduler : frame_schedulers_)
frame_scheduler->UpdatePolicy();
}
size_t PageSchedulerImpl::FrameCount() const {
return frame_schedulers_.size();
}
......
......@@ -63,7 +63,7 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
void SetMaxVirtualTimeTaskStarvationCount(
int max_task_starvation_count) override;
void AudioStateChanged(bool is_audio_playing) override;
bool IsPlayingAudio() const override;
bool IsAudioPlaying() const override;
bool IsExemptFromBudgetBasedThrottling() const override;
bool HasActiveConnectionForTest() const override;
void RequestBeginMainFrameNotExpected(bool new_state) override;
......@@ -101,6 +101,12 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
private:
friend class FrameSchedulerImpl;
enum class AudioState {
kSilent,
kAudible,
kRecentlyAudible,
};
CPUTimeBudgetPool* BackgroundCPUTimeBudgetPool();
void MaybeInitializeBackgroundCPUTimeBudgetPool();
......@@ -115,13 +121,17 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
// number of active connections.
void UpdateBackgroundBudgetPoolThrottlingState();
void OnAudioSilent();
void UpdateFramePolicies();
TraceableVariableController tracing_controller_;
std::set<FrameSchedulerImpl*> frame_schedulers_;
MainThreadSchedulerImpl* main_thread_scheduler_;
PageVisibilityState page_visibility_;
bool disable_background_timer_throttling_;
bool is_audio_playing_;
AudioState audio_state_;
bool is_frozen_;
bool reported_background_throttling_since_navigation_;
bool has_active_connection_;
......@@ -129,6 +139,7 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
bool is_main_frame_local_;
CPUTimeBudgetPool* background_time_budget_pool_; // Not owned.
PageScheduler::Delegate* delegate_; // Not owned.
CancelableClosureHolder on_audio_silent_closure_;
base::WeakPtrFactory<PageSchedulerImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PageSchedulerImpl);
......
......@@ -1170,6 +1170,58 @@ TEST_F(PageSchedulerImplTest, PageFreeze) {
EXPECT_EQ(5, counter);
}
TEST_F(PageSchedulerImplTest, AudioState) {
page_scheduler_->AudioStateChanged(true);
EXPECT_TRUE(page_scheduler_->IsAudioPlaying());
page_scheduler_->AudioStateChanged(false);
// We are audible for a certain period after raw signal disappearing.
EXPECT_TRUE(page_scheduler_->IsAudioPlaying());
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(3));
page_scheduler_->AudioStateChanged(false);
// We are still audible. A new call to AudioStateChanged shouldn't change
// anything.
EXPECT_TRUE(page_scheduler_->IsAudioPlaying());
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(3));
// Audio is finally silent.
EXPECT_FALSE(page_scheduler_->IsAudioPlaying());
}
TEST_F(PageSchedulerImplTest, PageSchedulerDestroyedWhileAudioChangePending) {
page_scheduler_->AudioStateChanged(true);
EXPECT_TRUE(page_scheduler_->IsAudioPlaying());
page_scheduler_->AudioStateChanged(false);
page_scheduler_.reset();
mock_task_runner_->RunUntilIdle();
}
TEST_F(PageSchedulerImplTest, AudiblePagesAreNotThrottled) {
page_scheduler_->SetPageVisible(false);
EXPECT_TRUE(scheduler_->task_queue_throttler()->IsThrottled(
ThrottleableTaskQueue().get()));
// No throttling when the page is audible.
page_scheduler_->AudioStateChanged(true);
EXPECT_FALSE(scheduler_->task_queue_throttler()->IsThrottled(
ThrottleableTaskQueue().get()));
// No throttling for some time after audio signal disappears.
page_scheduler_->AudioStateChanged(false);
EXPECT_FALSE(scheduler_->task_queue_throttler()->IsThrottled(
ThrottleableTaskQueue().get()));
// Eventually throttling is reenabled again.
mock_task_runner_->RunUntilIdle();
EXPECT_TRUE(scheduler_->task_queue_throttler()->IsThrottled(
ThrottleableTaskQueue().get()));
}
} // namespace page_scheduler_impl_unittest
} // namespace scheduler
} // namespace blink
......@@ -136,7 +136,7 @@ class PLATFORM_EXPORT PageScheduler {
virtual void AudioStateChanged(bool is_audio_playing) = 0;
virtual bool IsPlayingAudio() const = 0;
virtual bool IsAudioPlaying() const = 0;
// Returns true if the page should be exempted from aggressive throttling
// (e.g. due to a page maintaining an active connection).
......
......@@ -41,7 +41,7 @@ FrameThrottlingState GetFrameThrottlingState(
}
PageScheduler* page_scheduler = frame_scheduler.GetPageScheduler();
if (page_scheduler && page_scheduler->IsPlayingAudio()) {
if (page_scheduler && page_scheduler->IsAudioPlaying()) {
if (frame_scheduler.IsFrameVisible())
return FrameThrottlingState::kVisibleService;
return FrameThrottlingState::kHiddenService;
......
......@@ -344,8 +344,7 @@ void RendererMetricsHelper::RecordTaskMetrics(
if (main_thread_scheduler_->main_thread_only().renderer_hidden) {
per_queue_type_reporters_.hidden.RecordTask(queue_type, duration);
if (main_thread_scheduler_->ShouldDisableThrottlingBecauseOfAudio(
start_time)) {
if (main_thread_scheduler_->IsAudioPlaying()) {
per_queue_type_reporters_.hidden_music.RecordTask(queue_type, duration);
}
} else {
......
......@@ -227,7 +227,7 @@ class RendererMetricsHelperTest : public testing::Test {
RendererMetricsHelper* metrics_helper_; // NOT OWNED
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<FakePageScheduler> playing_view_ =
FakePageScheduler::Builder().SetIsPlayingAudio(true).Build();
FakePageScheduler::Builder().SetIsAudioPlaying(true).Build();
std::unique_ptr<FakePageScheduler> throtting_exempt_view_ =
FakePageScheduler::Builder().SetIsThrottlingExempt(true).Build();
......
......@@ -12,16 +12,16 @@ namespace scheduler {
class FakePageScheduler final : public PageScheduler {
public:
FakePageScheduler(bool is_playing_audio, bool is_throttling_exempt)
: is_playing_audio_(is_playing_audio),
FakePageScheduler(bool is_audio_playing, bool is_throttling_exempt)
: is_audio_playing_(is_audio_playing),
is_throttling_exempt_(is_throttling_exempt) {}
class Builder {
public:
Builder() = default;
Builder& SetIsPlayingAudio(bool is_playing_audio) {
is_playing_audio_ = is_playing_audio;
Builder& SetIsAudioPlaying(bool is_audio_playing) {
is_audio_playing_ = is_audio_playing;
return *this;
}
......@@ -31,18 +31,18 @@ class FakePageScheduler final : public PageScheduler {
}
std::unique_ptr<FakePageScheduler> Build() {
return std::make_unique<FakePageScheduler>(is_playing_audio_,
return std::make_unique<FakePageScheduler>(is_audio_playing_,
is_throttling_exempt_);
}
private:
bool is_playing_audio_ = false;
bool is_audio_playing_ = false;
bool is_throttling_exempt_ = false;
DISALLOW_COPY_AND_ASSIGN(Builder);
};
bool IsPlayingAudio() const override { return is_playing_audio_; }
bool IsAudioPlaying() const override { return is_audio_playing_; }
bool IsExemptFromBudgetBasedThrottling() const override {
return is_throttling_exempt_;
......@@ -75,7 +75,7 @@ class FakePageScheduler final : public PageScheduler {
void RequestBeginMainFrameNotExpected(bool new_state) override {}
private:
bool is_playing_audio_;
bool is_audio_playing_;
bool is_throttling_exempt_;
DISALLOW_COPY_AND_ASSIGN(FakePageScheduler);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment