#include "content/browser/loader/resource_scheduler.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
#include "base/stl_util.h"
+#include "base/time/time.h"
#include "content/common/resource_messages.h"
#include "content/browser/loader/resource_message_delegate.h"
#include "content/public/browser/resource_controller.h"
namespace content {
+namespace {
+
+void PostHistogram(const char* base_name,
+ const char* suffix,
+ base::TimeDelta time) {
+ std::string histogram_name =
+ base::StringPrintf("ResourceScheduler.%s.%s", base_name, suffix);
+ base::HistogramBase* histogram_counter = base::Histogram::FactoryTimeGet(
+ histogram_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(5),
+ 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_counter->AddTime(time);
+}
+
+} // namespace
+
static const size_t kCoalescedTimerPeriod = 5000;
static const size_t kMaxNumDelayableRequestsPerClient = 10;
static const size_t kMaxNumDelayableRequestsPerHost = 6;
const RequestPriorityParams& priority)
: ResourceMessageDelegate(request),
client_id_(client_id),
+ client_state_on_creation_(scheduler->GetClientState(client_id_)),
request_(request),
ready_(false),
deferred_(false),
"url", request->url().spec());
}
- virtual ~ScheduledResourceRequest() {
- scheduler_->RemoveRequest(this);
- }
+ ~ScheduledResourceRequest() override { scheduler_->RemoveRequest(this); }
void Start() {
TRACE_EVENT_ASYNC_STEP_PAST0("net", "URLRequest", request_, "Queued");
ready_ = true;
- if (deferred_ && request_->status().is_success()) {
+ if (!request_->status().is_success())
+ return;
+ base::TimeTicks time = base::TimeTicks::Now();
+ ClientState current_state = scheduler_->GetClientState(client_id_);
+ // Note: the client state isn't perfectly accurate since it won't capture
+ // tabs which have switched between active and background multiple times.
+ // Ex: A tab with the following transitions Active -> Background -> Active
+ // will be recorded as Active.
+ const char* client_state = "Other";
+ if (current_state == client_state_on_creation_ && current_state == ACTIVE) {
+ client_state = "Active";
+ } else if (current_state == client_state_on_creation_ &&
+ current_state == BACKGROUND) {
+ client_state = "Background";
+ }
+
+ base::TimeDelta time_was_deferred = base::TimeDelta::FromMicroseconds(0);
+ if (deferred_) {
deferred_ = false;
controller()->Resume();
+ time_was_deferred = time - time_deferred_;
}
+ PostHistogram("RequestTimeDeferred", client_state, time_was_deferred);
+ PostHistogram(
+ "RequestTimeThrottled", client_state, time - request_->creation_time());
+ // TODO(aiolos): Remove one of the above histograms after gaining an
+ // understanding of the difference between them and which one is more
+ // interesting.
}
void set_request_priority_params(const RequestPriorityParams& priority) {
private:
// ResourceMessageDelegate interface:
- virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ bool OnMessageReceived(const IPC::Message& message) override {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ScheduledResourceRequest, message)
IPC_MESSAGE_HANDLER(ResourceHostMsg_DidChangePriority, DidChangePriority)
}
// ResourceThrottle interface:
- virtual void WillStartRequest(bool* defer) OVERRIDE {
+ void WillStartRequest(bool* defer) override {
deferred_ = *defer = !ready_;
+ time_deferred_ = base::TimeTicks::Now();
}
- virtual const char* GetNameForLogging() const OVERRIDE {
- return "ResourceScheduler";
- }
+ const char* GetNameForLogging() const override { return "ResourceScheduler"; }
void DidChangePriority(int request_id, net::RequestPriority new_priority,
int intra_priority_value) {
scheduler_->ReprioritizeRequest(this, new_priority, intra_priority_value);
}
- ClientId client_id_;
+ const ClientId client_id_;
+ const ResourceScheduler::ClientState client_state_on_creation_;
net::URLRequest* request_;
bool ready_;
bool deferred_;
ResourceScheduler* scheduler_;
RequestPriorityParams priority_;
uint32 fifo_ordering_;
+ base::TimeTicks time_deferred_;
DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
};
// Each client represents a tab.
class ResourceScheduler::Client {
public:
- explicit Client(ResourceScheduler* scheduler, bool is_visible)
- : is_audible_(false),
+ explicit Client(ResourceScheduler* scheduler,
+ bool is_visible,
+ bool is_audible)
+ : is_audible_(is_audible),
is_visible_(is_visible),
is_loaded_(false),
is_paused_(false),
// If a request is already marked as layout-blocking make sure to keep the
// classification across redirects unless the priority was lowered.
if (request->classification() == LAYOUT_BLOCKING_REQUEST &&
- request->url_request()->priority() >= net::LOW) {
+ request->url_request()->priority() > net::LOW) {
return LAYOUT_BLOCKING_REQUEST;
}
- if (!has_body_ && request->url_request()->priority() >= net::LOW)
+ if (!has_body_ && request->url_request()->priority() > net::LOW)
return LAYOUT_BLOCKING_REQUEST;
if (request->url_request()->priority() < net::LOW) {
// * Higher priority requests (>= net::LOW).
//
// 4. Layout-blocking requests:
- // * High-priority requests initiated before the renderer has a <body>.
+ // * High-priority requests (> net::LOW) initiated before the renderer has
+ // a <body>.
//
// 5. Low priority requests
//
// immediately.
// * Low priority requests are delayable.
// * Allow one delayable request to load at a time while layout-blocking
- // requests are loading.
+ // requests are loading or the body tag has not yet been parsed.
// * If no high priority or layout-blocking requests are in flight, start
// loading delayable requests.
// * Never exceed 10 delayable requests in flight per client.
bool have_immediate_requests_in_flight =
in_flight_requests_.size() > in_flight_delayable_count_;
if (have_immediate_requests_in_flight &&
- total_layout_blocking_count_ != 0 &&
+ (!has_body_ || total_layout_blocking_count_ != 0) &&
in_flight_delayable_count_ != 0) {
return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
}
coalesced_clients_(0),
coalescing_timer_(new base::Timer(true /* retain_user_task */,
true /* is_repeating */)) {
+ std::string throttling_trial_group =
+ base::FieldTrialList::FindFullName("RequestThrottlingAndCoalescing");
+ if (throttling_trial_group == "Throttle") {
+ should_throttle_ = true;
+ } else if (throttling_trial_group == "Coalesce") {
+ should_coalesce_ = true;
+ should_throttle_ = true;
+ }
}
ResourceScheduler::~ResourceScheduler() {
net::URLRequest* url_request) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
- scoped_ptr<ScheduledResourceRequest> request(
- new ScheduledResourceRequest(client_id, url_request, this,
- RequestPriorityParams(url_request->priority(), 0)));
+ scoped_ptr<ScheduledResourceRequest> request(new ScheduledResourceRequest(
+ client_id,
+ url_request,
+ this,
+ RequestPriorityParams(url_request->priority(), 0)));
ClientMap::iterator it = client_map_.find(client_id);
if (it == client_map_.end()) {
// 3. The tab is closed while a RequestResource IPC is in flight.
unowned_requests_.insert(request.get());
request->Start();
- return request.PassAs<ResourceThrottle>();
+ return request.Pass();
}
Client* client = it->second;
client->ScheduleRequest(url_request, request.get());
- return request.PassAs<ResourceThrottle>();
+ return request.Pass();
}
void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
void ResourceScheduler::OnClientCreated(int child_id,
int route_id,
- bool is_visible) {
+ bool is_visible,
+ bool is_audible) {
DCHECK(CalledOnValidThread());
ClientId client_id = MakeClientId(child_id, route_id);
DCHECK(!ContainsKey(client_map_, client_id));
- Client* client = new Client(this, is_visible);
+ Client* client = new Client(this, is_visible, is_audible);
client_map_[client_id] = client;
// TODO(aiolos): set Client visibility/audibility when signals are added
client_map_.erase(it);
}
+void ResourceScheduler::OnLoadingStateChanged(int child_id,
+ int route_id,
+ bool is_loaded) {
+ Client* client = GetClient(child_id, route_id);
+ DCHECK(client);
+ client->OnLoadingStateChanged(is_loaded);
+}
+
void ResourceScheduler::OnVisibilityChanged(int child_id,
int route_id,
bool is_visible) {
client->OnVisibilityChanged(is_visible);
}
-void ResourceScheduler::OnLoadingStateChanged(int child_id,
- int route_id,
- bool is_loaded) {
+void ResourceScheduler::OnAudibilityChanged(int child_id,
+ int route_id,
+ bool is_audible) {
Client* client = GetClient(child_id, route_id);
- DCHECK(client);
- client->OnLoadingStateChanged(is_loaded);
+ // We might get this call after the client has been deleted.
+ if (client)
+ client->OnAudibilityChanged(is_audible);
}
void ResourceScheduler::OnNavigate(int child_id, int route_id) {
client->OnReceivedSpdyProxiedHttpResponse();
}
-void ResourceScheduler::OnAudibilityChanged(int child_id,
- int route_id,
- bool is_audible) {
- Client* client = GetClient(child_id, route_id);
- DCHECK(client);
- client->OnAudibilityChanged(is_audible);
-}
-
bool ResourceScheduler::IsClientVisibleForTesting(int child_id, int route_id) {
Client* client = GetClient(child_id, route_id);
DCHECK(client);
}
}
+ResourceScheduler::ClientState ResourceScheduler::GetClientState(
+ ClientId client_id) const {
+ ClientMap::const_iterator client_it = client_map_.find(client_id);
+ if (client_it == client_map_.end())
+ return UNKNOWN;
+ return client_it->second->is_active() ? ACTIVE : BACKGROUND;
+}
+
void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
net::RequestPriority new_priority,
int new_intra_priority_value) {