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 |