Chromium Code Reviews| Index: webkit/glue/weburlloader_impl.cc |
| diff --git a/webkit/glue/weburlloader_impl.cc b/webkit/glue/weburlloader_impl.cc |
| index 7b4ec9b79f564571687f4697015ead4f85ba40e7..e1400d7407e9372f2ce2a320f9e6bd60651084b7 100644 |
| --- a/webkit/glue/weburlloader_impl.cc |
| +++ b/webkit/glue/weburlloader_impl.cc |
| @@ -34,6 +34,7 @@ |
| using base::Time; |
| using base::TimeDelta; |
| +using base::TimeTicks; |
| using WebKit::WebData; |
| using WebKit::WebHTTPBody; |
| using WebKit::WebHTTPHeaderVisitor; |
| @@ -112,7 +113,8 @@ class HeaderFlattener : public WebHTTPHeaderVisitor { |
| bool GetInfoFromDataURL(const GURL& url, |
| ResourceResponseInfo* info, |
| std::string* data, |
| - net::URLRequestStatus* status) { |
| + net::URLRequestStatus* status, |
| + TimeTicks* response_time) { |
| std::string mime_type; |
| std::string charset; |
| if (net::DataURL::Parse(url, &mime_type, &charset, data)) { |
| @@ -120,6 +122,7 @@ bool GetInfoFromDataURL(const GURL& url, |
| // Assure same time for all time fields of data: URLs. |
| Time now = Time::Now(); |
| info->load_timing.base_time = now; |
| + info->load_timing.base_ticks = *response_time = TimeTicks::Now(); |
| info->request_time = now; |
| info->response_time = now; |
| info->headers = NULL; |
| @@ -142,6 +145,10 @@ typedef ResourceDevToolsInfo::HeadersVector HeadersVector; |
| void PopulateURLResponse( |
| const GURL& url, |
| const ResourceResponseInfo& info, |
| + const TimeTicks& ipc_request_time, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time, |
| + const TimeTicks& ipc_response_time, |
| WebURLResponse* response) { |
| response->setURL(url); |
| response->setResponseTime(info.response_time.ToDoubleT()); |
| @@ -167,20 +174,9 @@ void PopulateURLResponse( |
| const ResourceLoadTimingInfo& timing_info = info.load_timing; |
| if (!timing_info.base_time.is_null()) { |
| - WebURLLoadTiming timing; |
| - timing.initialize(); |
| - timing.setRequestTime(timing_info.base_time.ToDoubleT()); |
| - timing.setProxyStart(timing_info.proxy_start); |
| - timing.setProxyEnd(timing_info.proxy_end); |
| - timing.setDNSStart(timing_info.dns_start); |
| - timing.setDNSEnd(timing_info.dns_end); |
| - timing.setConnectStart(timing_info.connect_start); |
| - timing.setConnectEnd(timing_info.connect_end); |
| - timing.setSSLStart(timing_info.ssl_start); |
| - timing.setSSLEnd(timing_info.ssl_end); |
| - timing.setSendStart(timing_info.send_start); |
| - timing.setSendEnd(timing_info.send_end); |
| - timing.setReceiveHeadersEnd(timing_info.receive_headers_end); |
| + WebURLLoadTiming timing = PopulateWebURLLoadTiming( |
| + timing_info, ipc_request_time, net_start_time, net_end_time, |
| + ipc_response_time); |
| response->setLoadTiming(timing); |
| } |
| @@ -269,9 +265,13 @@ class WebURLLoaderImpl::Context : public base::RefCounted<Context>, |
| virtual bool OnReceivedRedirect( |
| const GURL& new_url, |
| const ResourceResponseInfo& info, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time, |
| bool* has_new_first_party_for_cookies, |
| GURL* new_first_party_for_cookies); |
| - virtual void OnReceivedResponse(const ResourceResponseInfo& info); |
| + virtual void OnReceivedResponse(const ResourceResponseInfo& info, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time); |
| virtual void OnDownloadedData(int len); |
| virtual void OnReceivedData(const char* data, |
| int data_length, |
| @@ -279,7 +279,7 @@ class WebURLLoaderImpl::Context : public base::RefCounted<Context>, |
| virtual void OnReceivedCachedMetadata(const char* data, int len); |
| virtual void OnCompletedRequest(const net::URLRequestStatus& status, |
| const std::string& security_info, |
| - const base::Time& completion_time); |
| + const TimeTicks& completion_time); |
| private: |
| friend class base::RefCounted<Context>; |
| @@ -292,6 +292,7 @@ class WebURLLoaderImpl::Context : public base::RefCounted<Context>, |
| WebURLLoaderImpl* loader_; |
| WebURLRequest request_; |
| WebURLLoaderClient* client_; |
| + TimeTicks initiation_time_; |
| scoped_ptr<ResourceLoaderBridge> bridge_; |
| scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_; |
| scoped_ptr<MultipartResponseDelegate> multipart_delegate_; |
| @@ -342,9 +343,12 @@ void WebURLLoaderImpl::Context::Start( |
| // This is a sync load. Do the work now. |
| sync_load_response->url = url; |
| std::string data; |
| + TimeTicks response_time; |
| GetInfoFromDataURL(sync_load_response->url, sync_load_response, |
| &sync_load_response->data, |
| - &sync_load_response->status); |
| + &sync_load_response->status, &response_time); |
| + sync_load_response->net_start_time = response_time; |
| + sync_load_response->net_end_time = response_time; |
|
jar (doing other things)
2011/11/19 03:42:32
Is this meant to be the default, and hence end_tim
James Simonsen
2011/11/23 07:06:15
This is the default. start_time == end_time and th
|
| } else { |
| AddRef(); // Balanced in OnCompletedRequest |
| MessageLoop::current()->PostTask(FROM_HERE, |
| @@ -452,6 +456,7 @@ void WebURLLoaderImpl::Context::Start( |
| bridge_->SetUploadIdentifier(request.httpBody().identifier()); |
| } |
| + initiation_time_ = TimeTicks::Now(); |
| if (sync_load_response) { |
| bridge_->SyncLoad(sync_load_response); |
| return; |
| @@ -472,14 +477,18 @@ void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) { |
| bool WebURLLoaderImpl::Context::OnReceivedRedirect( |
| const GURL& new_url, |
| const ResourceResponseInfo& info, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time, |
| bool* has_new_first_party_for_cookies, |
| GURL* new_first_party_for_cookies) { |
| if (!client_) |
| return false; |
| + TimeTicks ipc_response_time = TimeTicks::Now(); |
| WebURLResponse response; |
| response.initialize(); |
| - PopulateURLResponse(request_.url(), info, &response); |
| + PopulateURLResponse(request_.url(), info, initiation_time_, net_start_time, |
| + net_end_time, ipc_response_time, &response); |
| // TODO(darin): We lack sufficient information to construct the actual |
| // request that resulted from the redirect. |
| @@ -511,13 +520,17 @@ bool WebURLLoaderImpl::Context::OnReceivedRedirect( |
| } |
| void WebURLLoaderImpl::Context::OnReceivedResponse( |
| - const ResourceResponseInfo& info) { |
| + const ResourceResponseInfo& info, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time) { |
| if (!client_) |
| return; |
| + TimeTicks ipc_response_time = TimeTicks::Now(); |
| WebURLResponse response; |
| response.initialize(); |
| - PopulateURLResponse(request_.url(), info, &response); |
| + PopulateURLResponse(request_.url(), info, initiation_time_, net_start_time, |
| + net_end_time, ipc_response_time, &response); |
| bool show_raw_listing = (GURL(request_.url()).query() == "raw"); |
| @@ -594,7 +607,9 @@ void WebURLLoaderImpl::Context::OnReceivedCachedMetadata( |
| void WebURLLoaderImpl::Context::OnCompletedRequest( |
| const net::URLRequestStatus& status, |
| const std::string& security_info, |
| - const base::Time& completion_time) { |
| + const TimeTicks& completion_time) { |
| + TimeTicks monotonic_completion_time = std::min(completion_time, |
| + TimeTicks::Now()); |
| if (ftp_listing_delegate_.get()) { |
| ftp_listing_delegate_->OnCompletedRequest(); |
| ftp_listing_delegate_.reset(NULL); |
| @@ -626,7 +641,9 @@ void WebURLLoaderImpl::Context::OnCompletedRequest( |
| error.unreachableURL = request_.url(); |
| client_->didFail(loader_, error); |
| } else { |
| - client_->didFinishLoading(loader_, completion_time.ToDoubleT()); |
| + client_->didFinishLoading( |
| + loader_, completion_time.ToInternalValue() / |
|
jar (doing other things)
2011/11/19 03:42:32
Given you divide by MicrosecondsPerSecond, shouldn
James Simonsen
2011/11/23 07:06:15
Done.
|
| + static_cast<double>(base::Time::kMicrosecondsPerSecond)); |
| } |
| } |
| @@ -664,13 +681,15 @@ void WebURLLoaderImpl::Context::HandleDataURL() { |
| net::URLRequestStatus status; |
| std::string data; |
| - if (GetInfoFromDataURL(request_.url(), &info, &data, &status)) { |
| - OnReceivedResponse(info); |
| + TimeTicks response_time; |
| + if (GetInfoFromDataURL(request_.url(), &info, &data, &status, |
| + &response_time)) { |
| + OnReceivedResponse(info, response_time, response_time); |
|
jar (doing other things)
2011/11/19 03:42:32
Here again I'm confused about both start_time and
James Simonsen
2011/11/23 07:06:15
It's a data: url. There's no downloading.
Theoret
|
| if (!data.empty()) |
| OnReceivedData(data.data(), data.size(), 0); |
| } |
| - OnCompletedRequest(status, info.security_info, base::Time::Now()); |
| + OnCompletedRequest(status, info.security_info, TimeTicks::Now()); |
| } |
| // WebURLLoaderImpl ----------------------------------------------------------- |
| @@ -687,8 +706,10 @@ void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request, |
| WebURLResponse& response, |
| WebURLError& error, |
| WebData& data) { |
| + TimeTicks ipc_request_time = TimeTicks::Now(); |
| ResourceLoaderBridge::SyncLoadResponse sync_load_response; |
| context_->Start(request, &sync_load_response); |
| + TimeTicks completion_time = TimeTicks::Now(); |
| const GURL& final_url = sync_load_response.url; |
| @@ -705,7 +726,10 @@ void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request, |
| return; |
| } |
| - PopulateURLResponse(final_url, sync_load_response, &response); |
| + PopulateURLResponse(final_url, sync_load_response, ipc_request_time, |
| + sync_load_response.net_start_time, |
| + sync_load_response.net_end_time, completion_time, |
| + &response); |
| data.assign(sync_load_response.data.data(), |
| sync_load_response.data.size()); |
| @@ -731,4 +755,206 @@ void WebURLLoaderImpl::UpdateRoutingId(int new_routing_id) { |
| context_->UpdateRoutingId(new_routing_id); |
| } |
| +// |timing_info| originates in the network layer, which may live in another |
| +// (browser) process. These values are recorded and passed as TimeTicks. On |
| +// Windows, TimeTicks do not tick consistently across processes. So one process |
| +// may be slightly ahead of the other and the delta between them may drift. In |
| +// practice, they usually differ by a few ms or less, and never more than 10 ms. |
| +// |
| +// Our goal is to present monotonically increasing timestamps to WebKit. So we |
| +// must fit the network layer's timestamps into WebKit's timeline. If we don't |
| +// correct for this skew, then we may report anomolies, such as negative page |
| +// load times, to developer tools and analytics scripts. |
| +// |
| +// To correct for this skew, we record 4 times: |
| +// - |ipc_request_time|: when this process passed the request on to the network |
| +// layer. |
| +// - |net_start_time|: when the network layer (other process) started handling |
| +// the request. |
| +// - |net_end_time|: when the network layer finished the request. |
| +// - |ipc_response_time|: when this process received the response from the |
| +// network layer. |
| +// |
| +// Because of the monotonic clock, all members of |timing_info| will occur |
| +// between |net_start_time| and |net_end_time|. If |net_start_time| and/or |
| +// |net_end_time| are out of order wrt |ipc_request_time| and |
| +// |ipc_response_time|, then we adjust the bounds to fit within |
| +// |ipc_request_time| and |ipc_response_time|. When the bounds are adjusted, we |
| +// scale all of the members of |timing_info| so that they are relatively the |
| +// same time delta from |net_start_time| and |net_end_time| as they were before |
| +// adjustment. |
|
jar (doing other things)
2011/11/19 03:42:32
Given that "drift" is the most likely (and underst
James Simonsen
2011/11/23 07:06:15
Agreed. This is much better.
|
| +namespace { |
| + |
| +class WebKitDelta { |
| + public: |
| + int ToWebKitTime() { return value_; } |
| + |
| + private: |
| + friend class TimeConverter; |
| + friend class WebKitMilliseconds; |
| + |
| + WebKitDelta(int value) : value_(value) {} |
| + |
| + int value_; |
| +}; |
| + |
| +class WebKitMilliseconds { |
| + public: |
| + static WebKitMilliseconds PickLowerBound(const TimeTicks& webkit_value, |
| + const TimeTicks& net_value) { |
| + int64 lower_bound = net_value.ToInternalValue(); |
| + if (net_value < webkit_value) { |
| + lower_bound = webkit_value.ToInternalValue(); |
| + } |
| + return WebKitMilliseconds(lower_bound); |
| + } |
| + |
| + static WebKitMilliseconds PickUpperBound(const TimeTicks& webkit_value, |
| + const TimeTicks& net_value) { |
| + int64 upper_bound = net_value.ToInternalValue(); |
| + if (net_value > webkit_value) { |
| + upper_bound = webkit_value.ToInternalValue(); |
| + } |
| + return WebKitMilliseconds(upper_bound); |
|
jar (doing other things)
2011/11/19 03:42:32
I'm really wary that this is moving one samples in
James Simonsen
2011/11/23 07:06:15
Agreed. The data backs that up too. It's typically
|
| + } |
| + |
| + void EnsureLessOrEqual(const WebKitMilliseconds& limit) { |
| + if (value_ > limit.value_) { |
| + value_ = limit.value_; |
| + } |
| + } |
| + |
| + WebKitMilliseconds operator+(const WebKitDelta& delta) { |
| + return WebKitMilliseconds(value_ + delta.value_); |
| + } |
| + |
| + double ToWebKitTime() { |
| + return value_ / static_cast<double>(base::Time::kMicrosecondsPerSecond); |
| + } |
| + |
| + private: |
| + friend class TimeConverter; |
| + |
| + WebKitMilliseconds(int64 value) : value_(value) {} |
| + |
| + int64 value_; |
| +}; |
| + |
| +class NetDelta { |
| + public: |
| + static NetDelta FromRawDelta(int delta) { |
| + return NetDelta(delta); |
| + } |
| + |
| + private: |
| + friend class NetMilliseconds; |
| + friend class TimeConverter; |
| + |
| + NetDelta(int value) : value_(value) {} |
| + |
| + int value_; |
| +}; |
| + |
| +class NetMilliseconds { |
| + public: |
| + static NetMilliseconds FromTimeTicks(const TimeTicks& ticks) { |
| + return NetMilliseconds(ticks.ToInternalValue()); |
| + } |
| + |
| + NetDelta operator-(const NetMilliseconds& rhs) { |
| + return NetDelta(value_ - rhs.value_); |
| + } |
| + |
| + private: |
| + friend class TimeConverter; |
| + |
| + NetMilliseconds(int64 value) : value_(value) {} |
| + |
| + int64 value_; |
| +}; |
| + |
| +class TimeConverter { |
| + public: |
| + TimeConverter(const WebKitMilliseconds& lower_bound, |
| + const WebKitMilliseconds& upper_bound, |
| + const NetMilliseconds& net_start_time, |
| + const NetMilliseconds& net_end_time) { |
| + numerator_ = upper_bound.value_ - lower_bound.value_; |
| + denominator_ = net_end_time.value_ - net_start_time.value_; |
| + if (denominator_ == 0) { |
| + numerator_ = 0; |
| + denominator_ = 1; |
| + } |
|
jar (doing other things)
2011/11/19 03:42:32
I think you should use a nice affine map, includin
James Simonsen
2011/11/23 07:06:15
Done with a few minor tweaks:
- Offset should not
jar (doing other things)
2011/11/23 17:25:04
re: Why did I mention a +1.
This integer math stu
|
| + } |
| + |
| + WebKitDelta ConvertDelta(const NetDelta& net_delta) { |
| + return WebKitDelta(Convert(net_delta.value_)); |
| + } |
| + |
| + private: |
| + int64 Convert(int64 value) { |
| + if (value <= 0) { |
| + return value; |
| + } |
| + return numerator_ * value / denominator_; |
| + } |
| + |
| + int64 numerator_; |
| + int64 denominator_; |
| +}; |
| + |
| +} // anonymous namespace |
| + |
| +WebURLLoadTiming PopulateWebURLLoadTiming( |
| + const ResourceLoadTimingInfo& net_timing_info, |
| + const TimeTicks& ipc_request_time, |
| + const TimeTicks& net_start_time, |
| + const TimeTicks& net_end_time, |
| + const TimeTicks& ipc_response_time) { |
| + // We only adjust the bounds if we have an inconsistency. Otherwise, if the |
| + // |net_*| times are already within the |ipc_*| range, we leave the bounds as |
| + // is. That means the times won't be adjusted. By doing so, we give the |
| + // |net_*| times the benefit of the doubt and assume there was no skew. |
| + WebKitMilliseconds lower_bound = WebKitMilliseconds::PickLowerBound( |
| + ipc_request_time, net_start_time); |
| + WebKitMilliseconds upper_bound = WebKitMilliseconds::PickUpperBound( |
| + ipc_response_time, net_end_time); |
|
jar (doing other things)
2011/11/19 03:42:32
This appears to broaden the interval between lower
James Simonsen
2011/11/23 07:06:15
Done.
|
| + lower_bound.EnsureLessOrEqual(upper_bound); |
| + |
| + NetMilliseconds net_start_ms = NetMilliseconds::FromTimeTicks(net_start_time); |
| + NetMilliseconds net_end_ms = NetMilliseconds::FromTimeTicks(net_end_time); |
| + NetMilliseconds base_ms = NetMilliseconds::FromTimeTicks( |
| + net_timing_info.base_ticks); |
| + |
| + TimeConverter time_converter(lower_bound, upper_bound, |
| + net_start_ms, net_end_ms); |
| + |
| + WebURLLoadTiming timing; |
| + timing.initialize(); |
| + |
| + WebKitMilliseconds adjusted_base = lower_bound + |
| + time_converter.ConvertDelta(base_ms - net_start_ms); |
| + timing.setRequestTime(adjusted_base.ToWebKitTime()); |
| + |
| +#define SET_TIME(setter, value) \ |
| + setter(time_converter.ConvertDelta(NetDelta::FromRawDelta(value)) \ |
| + .ToWebKitTime()) |
| + |
| + SET_TIME(timing.setProxyStart, net_timing_info.proxy_start); |
| + SET_TIME(timing.setProxyEnd, net_timing_info.proxy_end); |
| + SET_TIME(timing.setDNSStart, net_timing_info.dns_start); |
| + SET_TIME(timing.setDNSEnd, net_timing_info.dns_end); |
| + SET_TIME(timing.setConnectStart, net_timing_info.connect_start); |
| + SET_TIME(timing.setConnectEnd, net_timing_info.connect_end); |
| + SET_TIME(timing.setSSLStart, net_timing_info.ssl_start); |
| + SET_TIME(timing.setSSLEnd, net_timing_info.ssl_end); |
| + SET_TIME(timing.setSendStart, net_timing_info.send_start); |
| + SET_TIME(timing.setSendEnd, net_timing_info.send_end); |
| + SET_TIME(timing.setReceiveHeadersEnd, net_timing_info.receive_headers_end); |
| + |
| +#undef SET_TIME |
| + |
| + return timing; |
| +} |
| + |
| } // namespace webkit_glue |