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 |