Home | History | Annotate | Download | only in net
      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.
     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 
     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 
     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 
     59 LoadTimingObserver::HTTPStreamJobRecord::HTTPStreamJobRecord()
     60     : socket_log_id(net::NetLog::Source::kInvalidId),
     61       socket_reused(false) {
     62 }
     63 
     64 LoadTimingObserver::LoadTimingObserver()
     65     : ThreadSafeObserver(net::NetLog::LOG_BASIC),
     66       last_connect_job_id_(net::NetLog::Source::kInvalidId) {
     67 }
     68 
     69 LoadTimingObserver::~LoadTimingObserver() {
     70 }
     71 
     72 LoadTimingObserver::URLRequestRecord*
     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 
     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
    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 
    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 
    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 
    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 
    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