load_timing_observer.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
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/compiler_specific.h"
8#include "base/time.h"
9#include "chrome/browser/net/chrome_net_log.h"
10#include "chrome/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
15using base::Time;
16using base::TimeTicks;
17using webkit_glue::ResourceLoaderBridge;
18
19const size_t kMaxNumEntries = 1000;
20
21namespace {
22
23// We know that this conversion is not solid and suffers from world clock
24// changes, but it should be good enough for the load timing info.
25static Time TimeTicksToTime(const TimeTicks& time_ticks) {
26  static int64 tick_to_time_offset;
27  static bool tick_to_time_offset_available = false;
28  if (!tick_to_time_offset_available) {
29    int64 cur_time = (Time::Now() - Time()).InMicroseconds();
30    int64 cur_time_ticks = (TimeTicks::Now() - TimeTicks()).InMicroseconds();
31    // If we add this number to a time tick value, it gives the timestamp.
32    tick_to_time_offset = cur_time - cur_time_ticks;
33    tick_to_time_offset_available = true;
34  }
35  return Time::FromInternalValue(time_ticks.ToInternalValue() +
36                                 tick_to_time_offset);
37}
38
39static int32 TimeTicksToOffset(
40    const TimeTicks& time_ticks,
41    LoadTimingObserver::URLRequestRecord* record) {
42  return static_cast<int32>(
43      (time_ticks - record->base_ticks).InMillisecondsRoundedUp());
44}
45
46}
47
48LoadTimingObserver::URLRequestRecord::URLRequestRecord()
49    : connect_job_id(net::NetLog::Source::kInvalidId),
50      socket_log_id(net::NetLog::Source::kInvalidId),
51      socket_reused(false) {
52}
53
54LoadTimingObserver::LoadTimingObserver()
55    : Observer(net::NetLog::LOG_BASIC),
56      last_connect_job_id_(net::NetLog::Source::kInvalidId) {
57}
58
59LoadTimingObserver::~LoadTimingObserver() {
60}
61
62LoadTimingObserver::URLRequestRecord*
63LoadTimingObserver::GetURLRequestRecord(uint32 source_id) {
64  URLRequestToRecordMap::iterator it = url_request_to_record_.find(source_id);
65  if (it != url_request_to_record_.end())
66    return &it->second;
67  return NULL;
68}
69
70void LoadTimingObserver::OnAddEntry(net::NetLog::EventType type,
71                                    const base::TimeTicks& time,
72                                    const net::NetLog::Source& source,
73                                    net::NetLog::EventPhase phase,
74                                    net::NetLog::EventParameters* params) {
75  if (source.type == net::NetLog::SOURCE_URL_REQUEST)
76    OnAddURLRequestEntry(type, time, source, phase, params);
77  else if (source.type == net::NetLog::SOURCE_CONNECT_JOB)
78    OnAddConnectJobEntry(type, time, source, phase, params);
79  else if (source.type == net::NetLog::SOURCE_SOCKET)
80    OnAddSocketEntry(type, time, source, phase, params);
81}
82
83// static
84void LoadTimingObserver::PopulateTimingInfo(URLRequest* request,
85                                            ResourceResponse* response) {
86  if (!(request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING))
87    return;
88
89  ChromeNetLog* chrome_net_log = static_cast<ChromeNetLog*>(
90      request->net_log().net_log());
91  if (chrome_net_log == NULL)
92    return;
93
94  uint32 source_id = request->net_log().source().id;
95  LoadTimingObserver* observer = chrome_net_log->load_timing_observer();
96  LoadTimingObserver::URLRequestRecord* record =
97      observer->GetURLRequestRecord(source_id);
98  if (record) {
99    response->response_head.connection_id = record->socket_log_id;
100    response->response_head.connection_reused = record->socket_reused;
101    response->response_head.load_timing = record->timing;
102  }
103}
104
105void LoadTimingObserver::OnAddURLRequestEntry(
106    net::NetLog::EventType type,
107    const base::TimeTicks& time,
108    const net::NetLog::Source& source,
109    net::NetLog::EventPhase phase,
110    net::NetLog::EventParameters* params) {
111  bool is_begin = phase == net::NetLog::PHASE_BEGIN;
112  bool is_end = phase == net::NetLog::PHASE_END;
113
114  if (type == net::NetLog::TYPE_URL_REQUEST_START_JOB) {
115    if (is_begin) {
116      // Only record timing for entries with corresponding flag.
117      int load_flags = static_cast<URLRequestStartEventParameters*>(params)->
118          load_flags();
119      if (!(load_flags & net::LOAD_ENABLE_LOAD_TIMING))
120        return;
121
122      // Prevents us from passively growing the memory memory unbounded in case
123      // something went wrong. Should not happen.
124      if (url_request_to_record_.size() > kMaxNumEntries) {
125        LOG(WARNING) << "The load timing observer url request count has grown "
126                        "larger than expected, resetting";
127        url_request_to_record_.clear();
128      }
129
130      URLRequestRecord& record = url_request_to_record_[source.id];
131      record.base_ticks = time;
132      record.timing.base_time = TimeTicksToTime(time);
133    }
134    return;
135  } else if (type == net::NetLog::TYPE_REQUEST_ALIVE) {
136    // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
137    if (is_end)
138      url_request_to_record_.erase(source.id);
139    return;
140  }
141
142  URLRequestRecord* record = GetURLRequestRecord(source.id);
143  if (!record)
144    return;
145
146  ResourceLoaderBridge::LoadTimingInfo& timing = record->timing;
147
148  switch (type) {
149    case net::NetLog::TYPE_PROXY_SERVICE:
150      if (is_begin)
151        timing.proxy_start = TimeTicksToOffset(time, record);
152      else if (is_end)
153        timing.proxy_end = TimeTicksToOffset(time, record);
154      break;
155    case net::NetLog::TYPE_SOCKET_POOL:
156      if (is_begin)
157        timing.connect_start = TimeTicksToOffset(time, record);
158      else if (is_end)
159        timing.connect_end = TimeTicksToOffset(time, record);
160      break;
161    case net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB:
162      {
163        uint32 connect_job_id = static_cast<net::NetLogSourceParameter*>(
164            params)->value().id;
165        if (last_connect_job_id_ == connect_job_id &&
166            !last_connect_job_record_.dns_start.is_null()) {
167          timing.dns_start =
168              TimeTicksToOffset(last_connect_job_record_.dns_start, record);
169          timing.dns_end =
170              TimeTicksToOffset(last_connect_job_record_.dns_end, record);
171        }
172      }
173      break;
174    case net::NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET:
175      record->socket_reused = true;
176      break;
177    case net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET:
178      record->socket_log_id = static_cast<net::NetLogSourceParameter*>(
179          params)->value().id;
180      if (!record->socket_reused) {
181        SocketToRecordMap::iterator it =
182            socket_to_record_.find(record->socket_log_id);
183        if (it != socket_to_record_.end() && !it->second.ssl_start.is_null()) {
184          timing.ssl_start = TimeTicksToOffset(it->second.ssl_start, record);
185          timing.ssl_end = TimeTicksToOffset(it->second.ssl_end, record);
186        }
187      }
188      break;
189    case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST:
190      if (is_begin)
191        timing.send_start = TimeTicksToOffset(time, record);
192      else if (is_end)
193        timing.send_end = TimeTicksToOffset(time, record);
194      break;
195    case net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS:
196      if (is_begin)
197        timing.receive_headers_start =  TimeTicksToOffset(time, record);
198      else if (is_end)
199        timing.receive_headers_end =  TimeTicksToOffset(time, record);
200      break;
201    default:
202      break;
203  }
204}
205
206void LoadTimingObserver::OnAddConnectJobEntry(
207    net::NetLog::EventType type,
208    const base::TimeTicks& time,
209    const net::NetLog::Source& source,
210    net::NetLog::EventPhase phase,
211    net::NetLog::EventParameters* params) {
212  bool is_begin = phase == net::NetLog::PHASE_BEGIN;
213  bool is_end = phase == net::NetLog::PHASE_END;
214
215  // Manage record lifetime based on the SOCKET_POOL_CONNECT_JOB entry.
216  if (type == net::NetLog::TYPE_SOCKET_POOL_CONNECT_JOB) {
217    if (is_begin) {
218      // Prevents us from passively growing the memory memory unbounded in case
219      // something went wrong. Should not happen.
220      if (connect_job_to_record_.size() > kMaxNumEntries) {
221        LOG(WARNING) << "The load timing observer connect job count has grown "
222                        "larger than expected, resetting";
223        connect_job_to_record_.clear();
224      }
225
226      connect_job_to_record_.insert(
227          std::make_pair(source.id, ConnectJobRecord()));
228    } else if (is_end) {
229      ConnectJobToRecordMap::iterator it =
230          connect_job_to_record_.find(source.id);
231      if (it != connect_job_to_record_.end()) {
232        last_connect_job_id_ = it->first;
233        last_connect_job_record_ = it->second;
234        connect_job_to_record_.erase(it);
235      }
236    }
237  } else if (type == net::NetLog::TYPE_HOST_RESOLVER_IMPL) {
238    ConnectJobToRecordMap::iterator it =
239        connect_job_to_record_.find(source.id);
240    if (it != connect_job_to_record_.end()) {
241      if (is_begin)
242        it->second.dns_start = time;
243      else if (is_end)
244        it->second.dns_end = time;
245    }
246  }
247}
248
249void LoadTimingObserver::OnAddSocketEntry(
250    net::NetLog::EventType type,
251    const base::TimeTicks& time,
252    const net::NetLog::Source& source,
253    net::NetLog::EventPhase phase,
254    net::NetLog::EventParameters* params) {
255  bool is_begin = phase == net::NetLog::PHASE_BEGIN;
256  bool is_end = phase == net::NetLog::PHASE_END;
257
258  // Manage record lifetime based on the SOCKET_ALIVE entry.
259  if (type == net::NetLog::TYPE_SOCKET_ALIVE) {
260    if (is_begin) {
261      // Prevents us from passively growing the memory memory unbounded in case
262      // something went wrong. Should not happen.
263      if (socket_to_record_.size() > kMaxNumEntries) {
264        LOG(WARNING) << "The load timing observer socket count has grown "
265                        "larger than expected, resetting";
266        socket_to_record_.clear();
267      }
268
269      socket_to_record_.insert(
270          std::make_pair(source.id, SocketRecord()));
271    } else if (is_end) {
272      socket_to_record_.erase(source.id);
273    }
274    return;
275  }
276  SocketToRecordMap::iterator it = socket_to_record_.find(source.id);
277  if (it == socket_to_record_.end())
278    return;
279
280  if (type == net::NetLog::TYPE_SSL_CONNECT) {
281    if (is_begin)
282      it->second.ssl_start = time;
283    else if (is_end)
284      it->second.ssl_end = time;
285  }
286}
287