blob: 1c467c1dfed964ea02f10f2313342f7370c514ae [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/loader/fetch/ResourceLoadScheduler.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "platform/Histogram.h"
#include "platform/runtime_enabled_features.h"
namespace blink {
namespace {
// Field trial name.
const char kResourceLoadSchedulerTrial[] = "ResourceLoadScheduler";
// Field trial parameter names.
// Note: bg_limit is supported on m61+, but bg_sub_limit is only on m63+.
// If bg_sub_limit param is not found, we should use bg_limit to make the
// study result statistically correct.
const char kOutstandingLimitForBackgroundMainFrameName[] = "bg_limit";
const char kOutstandingLimitForBackgroundSubFrameName[] = "bg_sub_limit";
// Field trial default parameters.
constexpr size_t kOutstandingLimitForBackgroundFrameDefault = 16u;
uint32_t GetFieldTrialUint32Param(const char* name, uint32_t default_param) {
std::map<std::string, std::string> trial_params;
bool result =
base::GetFieldTrialParams(kResourceLoadSchedulerTrial, &trial_params);
if (!result)
return default_param;
const auto& found = trial_params.find(name);
if (found == trial_params.end())
return default_param;
uint32_t param;
if (!base::StringToUint(found->second, &param))
return default_param;
return param;
}
uint32_t GetOutstandingThrottledLimit(FetchContext* context) {
DCHECK(context);
uint32_t main_frame_limit =
GetFieldTrialUint32Param(kOutstandingLimitForBackgroundMainFrameName,
kOutstandingLimitForBackgroundFrameDefault);
if (context->IsMainFrame())
return main_frame_limit;
// We do not have a fixed default limit for sub-frames, but use the limit for
// the main frame so that it works as how previous versions that haven't
// consider sub-frames' specific limit work.
return GetFieldTrialUint32Param(kOutstandingLimitForBackgroundSubFrameName,
main_frame_limit);
}
} // namespace
constexpr ResourceLoadScheduler::ClientId
ResourceLoadScheduler::kInvalidClientId;
ResourceLoadScheduler::ResourceLoadScheduler(FetchContext* context)
: outstanding_throttled_limit_(GetOutstandingThrottledLimit(context)),
context_(context) {
if (!RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled())
return;
auto* scheduler = context->GetFrameScheduler();
if (!scheduler)
return;
is_enabled_ = true;
scheduler->AddThrottlingObserver(WebFrameScheduler::ObserverType::kLoader,
this);
}
DEFINE_TRACE(ResourceLoadScheduler) {
visitor->Trace(pending_request_map_);
visitor->Trace(context_);
}
void ResourceLoadScheduler::Shutdown() {
// Do nothing if the feature is not enabled, or Shutdown() was already called.
if (is_shutdown_)
return;
is_shutdown_ = true;
if (!is_enabled_)
return;
auto* scheduler = context_->GetFrameScheduler();
DCHECK(scheduler);
scheduler->RemoveThrottlingObserver(WebFrameScheduler::ObserverType::kLoader,
this);
}
void ResourceLoadScheduler::Request(ResourceLoadSchedulerClient* client,
ThrottleOption option,
ResourceLoadScheduler::ClientId* id) {
*id = GenerateClientId();
if (is_shutdown_)
return;
if (!is_enabled_ || option == ThrottleOption::kCanNotBeThrottled) {
Run(*id, client);
return;
}
pending_request_map_.insert(*id, client);
pending_request_queue_.push_back(*id);
MaybeRun();
}
bool ResourceLoadScheduler::Release(
ResourceLoadScheduler::ClientId id,
ResourceLoadScheduler::ReleaseOption option) {
// Check kInvalidClientId that can not be passed to the HashSet.
if (id == kInvalidClientId)
return false;
if (running_requests_.find(id) != running_requests_.end()) {
running_requests_.erase(id);
if (option == ReleaseOption::kReleaseAndSchedule)
MaybeRun();
return true;
}
auto found = pending_request_map_.find(id);
if (found != pending_request_map_.end()) {
pending_request_map_.erase(found);
// Intentionally does not remove it from |pending_request_queue_|.
// Didn't release any running requests, but the outstanding limit might be
// changed to allow another request.
if (option == ReleaseOption::kReleaseAndSchedule)
MaybeRun();
return true;
}
return false;
}
void ResourceLoadScheduler::SetOutstandingLimitForTesting(size_t limit) {
SetOutstandingLimitAndMaybeRun(limit);
}
void ResourceLoadScheduler::OnNetworkQuiet() {
DCHECK(IsMainThread());
if (maximum_running_requests_seen_ == 0)
return;
DEFINE_STATIC_LOCAL(
CustomCountHistogram, main_frame_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.MainframeThrottled", 0, 10000,
25));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, main_frame_not_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.MainframeNotThrottled", 0,
10000, 25));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, main_frame_partially_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.MainframePartiallyThrottled",
0, 10000, 25));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, sub_frame_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.SubframeThrottled", 0, 10000,
25));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, sub_frame_not_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.SubframeNotThrottled", 0,
10000, 25));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, sub_frame_partially_throttled,
("Blink.ResourceLoadScheduler.PeakRequests.SubframePartiallyThrottled", 0,
10000, 25));
switch (throttling_history_) {
case ThrottlingHistory::kInitial:
case ThrottlingHistory::kNotThrottled:
if (context_->IsMainFrame())
main_frame_not_throttled.Count(maximum_running_requests_seen_);
else
sub_frame_not_throttled.Count(maximum_running_requests_seen_);
break;
case ThrottlingHistory::kThrottled:
if (context_->IsMainFrame())
main_frame_throttled.Count(maximum_running_requests_seen_);
else
sub_frame_throttled.Count(maximum_running_requests_seen_);
break;
case ThrottlingHistory::kPartiallyThrottled:
if (context_->IsMainFrame())
main_frame_partially_throttled.Count(maximum_running_requests_seen_);
else
sub_frame_partially_throttled.Count(maximum_running_requests_seen_);
break;
}
}
void ResourceLoadScheduler::OnThrottlingStateChanged(
WebFrameScheduler::ThrottlingState state) {
switch (state) {
case WebFrameScheduler::ThrottlingState::kThrottled:
if (throttling_history_ == ThrottlingHistory::kInitial)
throttling_history_ = ThrottlingHistory::kThrottled;
else if (throttling_history_ == ThrottlingHistory::kNotThrottled)
throttling_history_ = ThrottlingHistory::kPartiallyThrottled;
SetOutstandingLimitAndMaybeRun(outstanding_throttled_limit_);
break;
case WebFrameScheduler::ThrottlingState::kNotThrottled:
if (throttling_history_ == ThrottlingHistory::kInitial)
throttling_history_ = ThrottlingHistory::kNotThrottled;
else if (throttling_history_ == ThrottlingHistory::kThrottled)
throttling_history_ = ThrottlingHistory::kPartiallyThrottled;
SetOutstandingLimitAndMaybeRun(kOutstandingUnlimited);
break;
}
}
ResourceLoadScheduler::ClientId ResourceLoadScheduler::GenerateClientId() {
ClientId id = ++current_id_;
CHECK_NE(0u, id);
return id;
}
void ResourceLoadScheduler::MaybeRun() {
// Requests for keep-alive loaders could be remained in the pending queue,
// but ignore them once Shutdown() is called.
if (is_shutdown_)
return;
while (!pending_request_queue_.empty()) {
if (outstanding_limit_ && running_requests_.size() >= outstanding_limit_)
return;
ClientId id = pending_request_queue_.TakeFirst();
auto found = pending_request_map_.find(id);
if (found == pending_request_map_.end())
continue; // Already released.
ResourceLoadSchedulerClient* client = found->value;
pending_request_map_.erase(found);
Run(id, client);
}
}
void ResourceLoadScheduler::Run(ResourceLoadScheduler::ClientId id,
ResourceLoadSchedulerClient* client) {
running_requests_.insert(id);
if (running_requests_.size() > maximum_running_requests_seen_) {
maximum_running_requests_seen_ = running_requests_.size();
}
client->Run();
}
void ResourceLoadScheduler::SetOutstandingLimitAndMaybeRun(size_t limit) {
outstanding_limit_ = limit;
MaybeRun();
}
} // namespace blink