• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/net/load_timing_observer.h"
6 
7 #include "base/time.h"
8 #include "chrome/browser/net/chrome_net_log.h"
9 #include "content/browser/browser_thread.h"
10 #include "content/common/resource_response.h"
11 #include "net/base/load_flags.h"
12 #include "net/url_request/url_request.h"
13 #include "net/url_request/url_request_netlog_params.h"
14 
15 using base::Time;
16 using base::TimeTicks;
17 using webkit_glue::ResourceLoaderBridge;
18 using webkit_glue::ResourceLoadTimingInfo;
19 
20 const size_t kMaxNumEntries = 1000;
21 
22 namespace {
23 
24 const int64 kSyncPeriodMicroseconds = 1000 * 1000 * 10;
25 
26 // We know that this conversion is not solid and suffers from world clock
27 // changes, but given that we sync clock every 10 seconds, it should be good
28 // enough for the load timing info.
TimeTicksToTime(const TimeTicks & time_ticks)29 static Time TimeTicksToTime(const TimeTicks& time_ticks) {
30   static int64 tick_to_time_offset;
31   static int64 last_sync_ticks = 0;
32   if (time_ticks.ToInternalValue() - last_sync_ticks >
33           kSyncPeriodMicroseconds) {
34     int64 cur_time = (Time::Now() - Time()).InMicroseconds();
35     int64 cur_time_ticks = (TimeTicks::Now() - TimeTicks()).InMicroseconds();
36     // If we add this number to a time tick value, it gives the timestamp.
37     tick_to_time_offset = cur_time - cur_time_ticks;
38     last_sync_ticks = time_ticks.ToInternalValue();
39   }
40   return Time::FromInternalValue(time_ticks.ToInternalValue() +
41                                  tick_to_time_offset);
42 }
43 
TimeTicksToOffset(const TimeTicks & time_ticks,LoadTimingObserver::URLRequestRecord * record)44 static int32 TimeTicksToOffset(
45     const TimeTicks& time_ticks,
46     LoadTimingObserver::URLRequestRecord* record) {
47   return static_cast<int32>(
48       (time_ticks - record->base_ticks).InMillisecondsRoundedUp());
49 }
50 
51 }  // namespace
52 
URLRequestRecord()53 LoadTimingObserver::URLRequestRecord::URLRequestRecord()
54     : connect_job_id(net::NetLog::Source::kInvalidId),
55       socket_log_id(net::NetLog::Source::kInvalidId),
56       socket_reused(false) {
57 }
58 
HTTPStreamJobRecord()59 LoadTimingObserver::HTTPStreamJobRecord::HTTPStreamJobRecord()
60     : socket_log_id(net::NetLog::Source::kInvalidId),
61       socket_reused(false) {
62 }
63 
LoadTimingObserver()64 LoadTimingObserver::LoadTimingObserver()
65     : ThreadSafeObserver(net::NetLog::LOG_BASIC),
66       last_connect_job_id_(net::NetLog::Source::kInvalidId) {
67 }
68 
~LoadTimingObserver()69 LoadTimingObserver::~LoadTimingObserver() {
70 }
71 
72 LoadTimingObserver::URLRequestRecord*
GetURLRequestRecord(uint32 source_id)73 LoadTimingObserver::GetURLRequestRecord(uint32 source_id) {
74   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
75 
76   URLRequestToRecordMap::iterator it = url_request_to_record_.find(source_id);
77   if (it != url_request_to_record_.end())
78     return &it->second;
79   return NULL;
80 }
81 
OnAddEntry(net::NetLog::EventType type,const base::TimeTicks & time,const net::NetLog::Source & source,net::NetLog::EventPhase phase,net::NetLog::EventParameters * params)82 void LoadTimingObserver::OnAddEntry(net::NetLog::EventType type,
83                                     const base::TimeTicks& time,
84                                     const net::NetLog::Source& source,
85                                     net::NetLog::EventPhase phase,
86                                     net::NetLog::EventParameters* params) {
87   // The events that the Observer is interested in only occur on the IO thread.
88   if (!BrowserThread::CurrentlyOn(BrowserThread::IO))
89     return;
90   if (source.type == net::NetLog::SOURCE_URL_REQUEST)
91     OnAddURLRequestEntry(type, time, source, phase, params);
92   else if (source.type == net::NetLog::SOURCE_HTTP_STREAM_JOB)
93     OnAddHTTPStreamJobEntry(type, time, source, phase, params);
94   else if (source.type == net::NetLog::SOURCE_CONNECT_JOB)
95     OnAddConnectJobEntry(type, time, source, phase, params);
96   else if (source.type == net::NetLog::SOURCE_SOCKET)
97     OnAddSocketEntry(type, time, source, phase, params);
98 }
99 
100 // static
PopulateTimingInfo(net::URLRequest * request,ResourceResponse * response)101 void LoadTimingObserver::PopulateTimingInfo(net::URLRequest* request,
102                                             ResourceResponse* response) {
103   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
104   if (!(request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING))
105     return;
106 
107   ChromeNetLog* chrome_net_log = static_cast<ChromeNetLog*>(
108       request->net_log().net_log());
109   if (chrome_net_log == NULL)
110     return;
111 
112   uint32 source_id = request->net_log().source().id;
113   LoadTimingObserver* observer = chrome_net_log->load_timing_observer();
114   LoadTimingObserver::URLRequestRecord* record =
115       observer->GetURLRequestRecord(source_id);
116   if (record) {
117     response->response_head.connection_id = record->socket_log_id;
118     response->response_head.connection_reused = record->socket_reused;
119     response->response_head.load_timing = record->timing;
120   }
121 }
122 
OnAddURLRequestEntry(net::NetLog::EventType type,const base::TimeTicks & time,const net::NetLog::Source & source,net::NetLog::EventPhase phase,net::NetLog::EventParameters * params)123 void LoadTimingObserver::OnAddURLRequestEntry(
124     net::NetLog::EventType type,
125     const base::TimeTicks& time,
126     const net::NetLog::Source& source,
127     net::NetLog::EventPhase phase,
128     net::NetLog::EventParameters* params) {
129   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
130 
131   bool is_begin = phase == net::NetLog::PHASE_BEGIN;
132   bool is_end = phase == net::NetLog::PHASE_END;
133 
134   if (type == net::NetLog::TYPE_URL_REQUEST_START_JOB) {
135     if (is_begin) {
136       // Only record timing for entries with corresponding flag.
137       int load_flags =
138           static_cast<net::URLRequestStartEventParameters*>(params)->
139               load_flags();
140       if (!(load_flags & net::LOAD_ENABLE_LOAD_TIMING))
141         return;
142 
143       // Prevents us from passively growing the memory unbounded in case
144       // something went wrong. Should not happen.
145       if (url_request_to_record_.size() > kMaxNumEntries) {
146         LOG(WARNING) << "The load timing observer url request count has grown "
147                         "larger than expected, resetting";
148         url_request_to_record_.clear();
149       }
150 
151       URLRequestRecord& record = url_request_to_record_[source.id];
152       record.base_ticks = time;
153       record.timing.base_time = TimeTicksToTime(time);
154     }
155     return;
156   } else if (type == net::NetLog::TYPE_REQUEST_ALIVE) {
157     // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
158     if (is_end)
159       url_request_to_record_.erase(source.id);
160     return;
161   }
162 
163   URLRequestRecord* record = GetURLRequestRecord(source.id);
164   if (!record)
165     return;
166 
167   ResourceLoadTimingInfo& timing = record->timing;
168 
169   switch (type) {
170     case net::NetLog::TYPE_PROXY_SERVICE:
171       if (is_begin)
172         timing.proxy_start = TimeTicksToOffset(time, record);
173       else if (is_end)
174         timing.proxy_end = TimeTicksToOffset(time, record);
175       break;
176     case net::NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB: {
177       uint32 http_stream_job_id = static_cast<net::NetLogSourceParameter*>(
178           params)->value().id;
179       HTTPStreamJobToRecordMap::iterator it =
180           http_stream_job_to_record_.find(http_stream_job_id);
181       if (it == http_stream_job_to_record_.end())
182         return;
183       if (!it->second.connect_start.is_null()) {
184         timing.connect_start = TimeTicksToOffset(it->second.connect_start,
185                                                  record);
186       }
187       if (!it->second.connect_end.is_null())
188         timing.connect_end = TimeTicksToOffset(it->second.connect_end, record);
189       if (!it->second.dns_start.is_null())
190         timing.dns_start = TimeTicksToOffset(it->second.dns_start, record);
191       if (!it->second.dns_end.is_null())
192         timing.dns_end = TimeTicksToOffset(it->second.dns_end, record);
193       if (!it->second.ssl_start.is_null())
194         timing.ssl_start = TimeTicksToOffset(it->second.ssl_start, record);
195       if (!it->second.ssl_end.is_null())
196         timing.ssl_end = TimeTicksToOffset(it->second.ssl_end, record);
197       record->socket_reused = it->second.socket_reused;
198       record->socket_log_id = it->second.socket_log_id;
199       break;
200     }
201     case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST:
202       if (is_begin)
203         timing.send_start = TimeTicksToOffset(time, record);
204       else if (is_end)
205         timing.send_end = TimeTicksToOffset(time, record);
206       break;
207     case net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS:
208       if (is_begin)
209         timing.receive_headers_start =  TimeTicksToOffset(time, record);
210       else if (is_end)
211         timing.receive_headers_end =  TimeTicksToOffset(time, record);
212       break;
213     default:
214       break;
215   }
216 }
217 
OnAddHTTPStreamJobEntry(net::NetLog::EventType type,const base::TimeTicks & time,const net::NetLog::Source & source,net::NetLog::EventPhase phase,net::NetLog::EventParameters * params)218 void LoadTimingObserver::OnAddHTTPStreamJobEntry(
219     net::NetLog::EventType type,
220     const base::TimeTicks& time,
221     const net::NetLog::Source& source,
222     net::NetLog::EventPhase phase,
223     net::NetLog::EventParameters* params) {
224   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
225 
226   bool is_begin = phase == net::NetLog::PHASE_BEGIN;
227   bool is_end = phase == net::NetLog::PHASE_END;
228 
229   if (type == net::NetLog::TYPE_HTTP_STREAM_JOB) {
230     if (is_begin) {
231       // Prevents us from passively growing the memory unbounded in
232       // case something went wrong. Should not happen.
233       if (http_stream_job_to_record_.size() > kMaxNumEntries) {
234         LOG(WARNING) << "The load timing observer http stream job count "
235                         "has grown larger than expected, resetting";
236         http_stream_job_to_record_.clear();
237       }
238 
239       http_stream_job_to_record_.insert(
240           std::make_pair(source.id, HTTPStreamJobRecord()));
241     } else if (is_end) {
242       http_stream_job_to_record_.erase(source.id);
243     }
244     return;
245   }
246 
247   HTTPStreamJobToRecordMap::iterator it =
248       http_stream_job_to_record_.find(source.id);
249   if (it == http_stream_job_to_record_.end())
250     return;
251 
252   switch (type) {
253     case net::NetLog::TYPE_SOCKET_POOL:
254       if (is_begin)
255         it->second.connect_start = time;
256       else if (is_end)
257         it->second.connect_end = time;
258       break;
259     case net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB: {
260       uint32 connect_job_id = static_cast<net::NetLogSourceParameter*>(
261           params)->value().id;
262       if (last_connect_job_id_ == connect_job_id &&
263           !last_connect_job_record_.dns_start.is_null()) {
264         it->second.dns_start = last_connect_job_record_.dns_start;
265         it->second.dns_end = last_connect_job_record_.dns_end;
266       }
267       break;
268     }
269     case net::NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET:
270       it->second.socket_reused = true;
271       break;
272     case net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET:
273       it->second.socket_log_id = static_cast<net::NetLogSourceParameter*>(
274         params)->value().id;
275       if (!it->second.socket_reused) {
276         SocketToRecordMap::iterator socket_it =
277             socket_to_record_.find(it->second.socket_log_id);
278         if (socket_it != socket_to_record_.end() &&
279             !socket_it->second.ssl_start.is_null()) {
280           it->second.ssl_start = socket_it->second.ssl_start;
281           it->second.ssl_end = socket_it->second.ssl_end;
282         }
283       }
284       break;
285     default:
286       break;
287   }
288 }
289 
OnAddConnectJobEntry(net::NetLog::EventType type,const base::TimeTicks & time,const net::NetLog::Source & source,net::NetLog::EventPhase phase,net::NetLog::EventParameters * params)290 void LoadTimingObserver::OnAddConnectJobEntry(
291     net::NetLog::EventType type,
292     const base::TimeTicks& time,
293     const net::NetLog::Source& source,
294     net::NetLog::EventPhase phase,
295     net::NetLog::EventParameters* params) {
296   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
297 
298   bool is_begin = phase == net::NetLog::PHASE_BEGIN;
299   bool is_end = phase == net::NetLog::PHASE_END;
300 
301   // Manage record lifetime based on the SOCKET_POOL_CONNECT_JOB entry.
302   if (type == net::NetLog::TYPE_SOCKET_POOL_CONNECT_JOB) {
303     if (is_begin) {
304       // Prevents us from passively growing the memory unbounded in case
305       // something went wrong. Should not happen.
306       if (connect_job_to_record_.size() > kMaxNumEntries) {
307         LOG(WARNING) << "The load timing observer connect job count has grown "
308                         "larger than expected, resetting";
309         connect_job_to_record_.clear();
310       }
311 
312       connect_job_to_record_.insert(
313           std::make_pair(source.id, ConnectJobRecord()));
314     } else if (is_end) {
315       ConnectJobToRecordMap::iterator it =
316           connect_job_to_record_.find(source.id);
317       if (it != connect_job_to_record_.end()) {
318         last_connect_job_id_ = it->first;
319         last_connect_job_record_ = it->second;
320         connect_job_to_record_.erase(it);
321       }
322     }
323   } else if (type == net::NetLog::TYPE_HOST_RESOLVER_IMPL) {
324     ConnectJobToRecordMap::iterator it =
325         connect_job_to_record_.find(source.id);
326     if (it != connect_job_to_record_.end()) {
327       if (is_begin)
328         it->second.dns_start = time;
329       else if (is_end)
330         it->second.dns_end = time;
331     }
332   }
333 }
334 
OnAddSocketEntry(net::NetLog::EventType type,const base::TimeTicks & time,const net::NetLog::Source & source,net::NetLog::EventPhase phase,net::NetLog::EventParameters * params)335 void LoadTimingObserver::OnAddSocketEntry(
336     net::NetLog::EventType type,
337     const base::TimeTicks& time,
338     const net::NetLog::Source& source,
339     net::NetLog::EventPhase phase,
340     net::NetLog::EventParameters* params) {
341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
342 
343   bool is_begin = phase == net::NetLog::PHASE_BEGIN;
344   bool is_end = phase == net::NetLog::PHASE_END;
345 
346   // Manage record lifetime based on the SOCKET_ALIVE entry.
347   if (type == net::NetLog::TYPE_SOCKET_ALIVE) {
348     if (is_begin) {
349       // Prevents us from passively growing the memory unbounded in case
350       // something went wrong. Should not happen.
351       if (socket_to_record_.size() > kMaxNumEntries) {
352         LOG(WARNING) << "The load timing observer socket count has grown "
353                         "larger than expected, resetting";
354         socket_to_record_.clear();
355       }
356 
357       socket_to_record_.insert(
358           std::make_pair(source.id, SocketRecord()));
359     } else if (is_end) {
360       socket_to_record_.erase(source.id);
361     }
362     return;
363   }
364   SocketToRecordMap::iterator it = socket_to_record_.find(source.id);
365   if (it == socket_to_record_.end())
366     return;
367 
368   if (type == net::NetLog::TYPE_SSL_CONNECT) {
369     if (is_begin)
370       it->second.ssl_start = time;
371     else if (is_end)
372       it->second.ssl_end = time;
373   }
374 }
375