Home | History | Annotate | Download | only in url_request
      1 // Copyright (c) 2006-2009 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 "net/url_request/url_request_job.h"
      6 
      7 #include "base/message_loop.h"
      8 #include "base/string_util.h"
      9 #include "net/base/auth.h"
     10 #include "net/base/io_buffer.h"
     11 #include "net/base/load_flags.h"
     12 #include "net/base/net_errors.h"
     13 #include "net/http/http_response_headers.h"
     14 #include "net/url_request/url_request.h"
     15 #include "net/url_request/url_request_job_metrics.h"
     16 #include "net/url_request/url_request_job_tracker.h"
     17 
     18 using base::Time;
     19 using base::TimeTicks;
     20 
     21 // Buffer size allocated when de-compressing data.
     22 // static
     23 const int URLRequestJob::kFilterBufSize = 32 * 1024;
     24 
     25 URLRequestJob::URLRequestJob(URLRequest* request)
     26     : request_(request),
     27       done_(false),
     28       filter_needs_more_output_space_(false),
     29       read_buffer_(NULL),
     30       read_buffer_len_(0),
     31       has_handled_response_(false),
     32       expected_content_size_(-1),
     33       deferred_redirect_status_code_(-1),
     34       packet_timing_enabled_(false),
     35       filter_input_byte_count_(0),
     36       bytes_observed_in_packets_(0),
     37       max_packets_timed_(0),
     38       observed_packet_count_(0) {
     39   load_flags_ = request_->load_flags();
     40   is_profiling_ = request->enable_profiling();
     41   if (is_profiling()) {
     42     metrics_.reset(new URLRequestJobMetrics());
     43     metrics_->start_time_ = TimeTicks::Now();
     44   }
     45   g_url_request_job_tracker.AddNewJob(this);
     46 }
     47 
     48 URLRequestJob::~URLRequestJob() {
     49   g_url_request_job_tracker.RemoveJob(this);
     50 }
     51 
     52 void URLRequestJob::Kill() {
     53   // Make sure the request is notified that we are done.  We assume that the
     54   // request took care of setting its error status before calling Kill.
     55   if (request_)
     56     NotifyCanceled();
     57 }
     58 
     59 void URLRequestJob::DetachRequest() {
     60   request_ = NULL;
     61 }
     62 
     63 bool URLRequestJob::IsDownload() const {
     64   return (load_flags_ & net::LOAD_IS_DOWNLOAD) != 0;
     65 }
     66 
     67 void URLRequestJob::SetupFilter() {
     68   std::vector<Filter::FilterType> encoding_types;
     69   if (GetContentEncodings(&encoding_types)) {
     70     filter_.reset(Filter::Factory(encoding_types, *this));
     71   }
     72 }
     73 
     74 bool URLRequestJob::IsRedirectResponse(GURL* location,
     75                                        int* http_status_code) {
     76   // For non-HTTP jobs, headers will be null.
     77   net::HttpResponseHeaders* headers = request_->response_headers();
     78   if (!headers)
     79     return false;
     80 
     81   std::string value;
     82   if (!headers->IsRedirect(&value))
     83     return false;
     84 
     85   *location = request_->url().Resolve(value);
     86   *http_status_code = headers->response_code();
     87   return true;
     88 }
     89 
     90 void URLRequestJob::GetAuthChallengeInfo(
     91     scoped_refptr<net::AuthChallengeInfo>* auth_info) {
     92   // This will only be called if NeedsAuth() returns true, in which
     93   // case the derived class should implement this!
     94   NOTREACHED();
     95 }
     96 
     97 void URLRequestJob::SetAuth(const std::wstring& username,
     98                             const std::wstring& password) {
     99   // This will only be called if NeedsAuth() returns true, in which
    100   // case the derived class should implement this!
    101   NOTREACHED();
    102 }
    103 
    104 void URLRequestJob::CancelAuth() {
    105   // This will only be called if NeedsAuth() returns true, in which
    106   // case the derived class should implement this!
    107   NOTREACHED();
    108 }
    109 
    110 void URLRequestJob::ContinueWithCertificate(
    111     net::X509Certificate* client_cert) {
    112   // The derived class should implement this!
    113   NOTREACHED();
    114 }
    115 
    116 void URLRequestJob::ContinueDespiteLastError() {
    117   // Implementations should know how to recover from errors they generate.
    118   // If this code was reached, we are trying to recover from an error that
    119   // we don't know how to recover from.
    120   NOTREACHED();
    121 }
    122 
    123 void URLRequestJob::FollowDeferredRedirect() {
    124   DCHECK(deferred_redirect_status_code_ != -1);
    125 
    126   // NOTE: deferred_redirect_url_ may be invalid, and attempting to redirect to
    127   // such an URL will fail inside FollowRedirect.  The DCHECK above asserts
    128   // that we called OnReceivedRedirect.
    129 
    130   // It is also possible that FollowRedirect will drop the last reference to
    131   // this job, so we need to reset our members before calling it.
    132 
    133   GURL redirect_url = deferred_redirect_url_;
    134   int redirect_status_code = deferred_redirect_status_code_;
    135 
    136   deferred_redirect_url_ = GURL();
    137   deferred_redirect_status_code_ = -1;
    138 
    139   FollowRedirect(redirect_url, redirect_status_code);
    140 }
    141 
    142 int64 URLRequestJob::GetByteReadCount() const {
    143   return filter_input_byte_count_;
    144 }
    145 
    146 bool URLRequestJob::GetURL(GURL* gurl) const {
    147   if (!request_)
    148     return false;
    149   *gurl = request_->url();
    150   return true;
    151 }
    152 
    153 base::Time URLRequestJob::GetRequestTime() const {
    154   if (!request_)
    155     return base::Time();
    156   return request_->request_time();
    157 };
    158 
    159 // This function calls ReadData to get stream data. If a filter exists, passes
    160 // the data to the attached filter. Then returns the output from filter back to
    161 // the caller.
    162 bool URLRequestJob::Read(net::IOBuffer* buf, int buf_size, int *bytes_read) {
    163   bool rv = false;
    164 
    165   DCHECK_LT(buf_size, 1000000);  // sanity check
    166   DCHECK(buf);
    167   DCHECK(bytes_read);
    168 
    169   *bytes_read = 0;
    170 
    171   // Skip Filter if not present
    172   if (!filter_.get()) {
    173     rv = ReadRawData(buf, buf_size, bytes_read);
    174     if (rv && *bytes_read > 0)
    175       RecordBytesRead(*bytes_read);
    176   } else {
    177     // Save the caller's buffers while we do IO
    178     // in the filter's buffers.
    179     read_buffer_ = buf;
    180     read_buffer_len_ = buf_size;
    181 
    182     if (ReadFilteredData(bytes_read)) {
    183       rv = true;   // we have data to return
    184     } else {
    185       rv = false;  // error, or a new IO is pending
    186     }
    187   }
    188   if (rv && *bytes_read == 0)
    189     NotifyDone(URLRequestStatus());
    190   return rv;
    191 }
    192 
    193 bool URLRequestJob::ReadRawDataForFilter(int *bytes_read) {
    194   bool rv = false;
    195 
    196   DCHECK(bytes_read);
    197   DCHECK(filter_.get());
    198 
    199   *bytes_read = 0;
    200 
    201   // Get more pre-filtered data if needed.
    202   // TODO(mbelshe): is it possible that the filter needs *MORE* data
    203   //    when there is some data already in the buffer?
    204   if (!filter_->stream_data_len() && !is_done()) {
    205     net::IOBuffer* stream_buffer = filter_->stream_buffer();
    206     int stream_buffer_size = filter_->stream_buffer_size();
    207     rv = ReadRawData(stream_buffer, stream_buffer_size, bytes_read);
    208     if (rv && *bytes_read > 0)
    209       RecordBytesRead(*bytes_read);
    210   }
    211   return rv;
    212 }
    213 
    214 void URLRequestJob::FollowRedirect(const GURL& location, int http_status_code) {
    215   g_url_request_job_tracker.OnJobRedirect(this, location, http_status_code);
    216 
    217   int rv = request_->Redirect(location, http_status_code);
    218   if (rv != net::OK)
    219     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
    220 }
    221 
    222 void URLRequestJob::FilteredDataRead(int bytes_read) {
    223   DCHECK(filter_.get());  // don't add data if there is no filter
    224   filter_->FlushStreamBuffer(bytes_read);
    225 }
    226 
    227 bool URLRequestJob::ReadFilteredData(int *bytes_read) {
    228   DCHECK(filter_.get());  // don't add data if there is no filter
    229   DCHECK(read_buffer_ != NULL);  // we need to have a buffer to fill
    230   DCHECK_GT(read_buffer_len_, 0);  // sanity check
    231   DCHECK_LT(read_buffer_len_, 1000000);  // sanity check
    232 
    233   bool rv = false;
    234   *bytes_read = 0;
    235 
    236   if (is_done())
    237     return true;
    238 
    239   if (!filter_needs_more_output_space_ && !filter_->stream_data_len()) {
    240     // We don't have any raw data to work with, so
    241     // read from the socket.
    242     int filtered_data_read;
    243     if (ReadRawDataForFilter(&filtered_data_read)) {
    244       if (filtered_data_read > 0) {
    245         filter_->FlushStreamBuffer(filtered_data_read);  // Give data to filter.
    246       } else {
    247         return true;  // EOF
    248       }
    249     } else {
    250       return false;  // IO Pending (or error)
    251     }
    252   }
    253 
    254   if ((filter_->stream_data_len() || filter_needs_more_output_space_)
    255       && !is_done()) {
    256     // Get filtered data.
    257     int filtered_data_len = read_buffer_len_;
    258     Filter::FilterStatus status;
    259     int output_buffer_size = filtered_data_len;
    260     status = filter_->ReadData(read_buffer_->data(), &filtered_data_len);
    261 
    262     if (filter_needs_more_output_space_ && 0 == filtered_data_len) {
    263       // filter_needs_more_output_space_ was mistaken... there are no more bytes
    264       // and we should have at least tried to fill up the filter's input buffer.
    265       // Correct the state, and try again.
    266       filter_needs_more_output_space_ = false;
    267       return ReadFilteredData(bytes_read);
    268     }
    269 
    270     switch (status) {
    271       case Filter::FILTER_DONE: {
    272         filter_needs_more_output_space_ = false;
    273         *bytes_read = filtered_data_len;
    274         rv = true;
    275         break;
    276       }
    277       case Filter::FILTER_NEED_MORE_DATA: {
    278         filter_needs_more_output_space_ =
    279             (filtered_data_len == output_buffer_size);
    280         // We have finished filtering all data currently in the buffer.
    281         // There might be some space left in the output buffer. One can
    282         // consider reading more data from the stream to feed the filter
    283         // and filling up the output buffer. This leads to more complicated
    284         // buffer management and data notification mechanisms.
    285         // We can revisit this issue if there is a real perf need.
    286         if (filtered_data_len > 0) {
    287           *bytes_read = filtered_data_len;
    288           rv = true;
    289         } else {
    290           // Read again since we haven't received enough data yet (e.g., we may
    291           // not have a complete gzip header yet)
    292           rv = ReadFilteredData(bytes_read);
    293         }
    294         break;
    295       }
    296       case Filter::FILTER_OK: {
    297         filter_needs_more_output_space_ =
    298             (filtered_data_len == output_buffer_size);
    299         *bytes_read = filtered_data_len;
    300         rv = true;
    301         break;
    302       }
    303       case Filter::FILTER_ERROR: {
    304         filter_needs_more_output_space_ = false;
    305         NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
    306                    net::ERR_CONTENT_DECODING_FAILED));
    307         rv = false;
    308         break;
    309       }
    310       default: {
    311         NOTREACHED();
    312         filter_needs_more_output_space_ = false;
    313         rv = false;
    314         break;
    315       }
    316     }
    317   } else {
    318     // we are done, or there is no data left.
    319     rv = true;
    320   }
    321 
    322   if (rv) {
    323     // When we successfully finished a read, we no longer need to
    324     // save the caller's buffers.  For debugging purposes, we clear
    325     // them out.
    326     read_buffer_ = NULL;
    327     read_buffer_len_ = 0;
    328   }
    329   return rv;
    330 }
    331 
    332 bool URLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
    333                                 int *bytes_read) {
    334   DCHECK(bytes_read);
    335   *bytes_read = 0;
    336   NotifyDone(URLRequestStatus());
    337   return false;
    338 }
    339 
    340 URLRequestJobMetrics* URLRequestJob::RetrieveMetrics() {
    341   if (is_profiling())
    342     return metrics_.release();
    343   else
    344     return NULL;
    345 }
    346 
    347 void URLRequestJob::NotifyHeadersComplete() {
    348   if (!request_ || !request_->delegate())
    349     return;  // The request was destroyed, so there is no more work to do.
    350 
    351   if (has_handled_response_)
    352     return;
    353 
    354   DCHECK(!request_->status().is_io_pending());
    355 
    356   // Initialize to the current time, and let the subclass optionally override
    357   // the time stamps if it has that information.  The default request_time is
    358   // set by URLRequest before it calls our Start method.
    359   request_->response_info_.response_time = Time::Now();
    360   GetResponseInfo(&request_->response_info_);
    361 
    362   // When notifying the delegate, the delegate can release the request
    363   // (and thus release 'this').  After calling to the delgate, we must
    364   // check the request pointer to see if it still exists, and return
    365   // immediately if it has been destroyed.  self_preservation ensures our
    366   // survival until we can get out of this method.
    367   scoped_refptr<URLRequestJob> self_preservation = this;
    368 
    369   GURL new_location;
    370   int http_status_code;
    371   if (IsRedirectResponse(&new_location, &http_status_code)) {
    372     const GURL& url = request_->url();
    373 
    374     // Move the reference fragment of the old location to the new one if the
    375     // new one has none. This duplicates mozilla's behavior.
    376     if (url.is_valid() && url.has_ref() && !new_location.has_ref()) {
    377       GURL::Replacements replacements;
    378       // Reference the |ref| directly out of the original URL to avoid a
    379       // malloc.
    380       replacements.SetRef(url.spec().data(),
    381                           url.parsed_for_possibly_invalid_spec().ref);
    382       new_location = new_location.ReplaceComponents(replacements);
    383     }
    384 
    385     bool defer_redirect = false;
    386     request_->ReceivedRedirect(new_location, &defer_redirect);
    387 
    388     // Ensure that the request wasn't detached or destroyed in ReceivedRedirect
    389     if (!request_ || !request_->delegate())
    390       return;
    391 
    392     // If we were not cancelled, then maybe follow the redirect.
    393     if (request_->status().is_success()) {
    394       if (defer_redirect) {
    395         deferred_redirect_url_ = new_location;
    396         deferred_redirect_status_code_ = http_status_code;
    397       } else {
    398         FollowRedirect(new_location, http_status_code);
    399       }
    400       return;
    401     }
    402   } else if (NeedsAuth()) {
    403     scoped_refptr<net::AuthChallengeInfo> auth_info;
    404     GetAuthChallengeInfo(&auth_info);
    405     // Need to check for a NULL auth_info because the server may have failed
    406     // to send a challenge with the 401 response.
    407     if (auth_info) {
    408       request_->delegate()->OnAuthRequired(request_, auth_info);
    409       // Wait for SetAuth or CancelAuth to be called.
    410       return;
    411     }
    412   }
    413 
    414   has_handled_response_ = true;
    415   if (request_->status().is_success())
    416     SetupFilter();
    417 
    418   if (!filter_.get()) {
    419     std::string content_length;
    420     request_->GetResponseHeaderByName("content-length", &content_length);
    421     if (!content_length.empty())
    422       expected_content_size_ = StringToInt64(content_length);
    423   }
    424 
    425   request_->ResponseStarted();
    426 }
    427 
    428 void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
    429   DCHECK(!has_handled_response_);
    430   has_handled_response_ = true;
    431   if (request_) {
    432     request_->set_status(status);
    433     request_->ResponseStarted();
    434   }
    435 }
    436 
    437 void URLRequestJob::NotifyReadComplete(int bytes_read) {
    438   if (!request_ || !request_->delegate())
    439     return;  // The request was destroyed, so there is no more work to do.
    440 
    441   // TODO(darin): Bug 1004233. Re-enable this test once all of the chrome
    442   // unit_tests have been fixed to not trip this.
    443   //DCHECK(!request_->status().is_io_pending());
    444 
    445   // The headers should be complete before reads complete
    446   DCHECK(has_handled_response_);
    447 
    448   if (bytes_read > 0)
    449     RecordBytesRead(bytes_read);
    450 
    451   // Don't notify if we had an error.
    452   if (!request_->status().is_success())
    453     return;
    454 
    455   // When notifying the delegate, the delegate can release the request
    456   // (and thus release 'this').  After calling to the delgate, we must
    457   // check the request pointer to see if it still exists, and return
    458   // immediately if it has been destroyed.  self_preservation ensures our
    459   // survival until we can get out of this method.
    460   scoped_refptr<URLRequestJob> self_preservation = this;
    461 
    462   if (filter_.get()) {
    463     // Tell the filter that it has more data
    464     FilteredDataRead(bytes_read);
    465 
    466     // Filter the data.
    467     int filter_bytes_read = 0;
    468     if (ReadFilteredData(&filter_bytes_read))
    469       request_->delegate()->OnReadCompleted(request_, filter_bytes_read);
    470   } else {
    471     request_->delegate()->OnReadCompleted(request_, bytes_read);
    472   }
    473 }
    474 
    475 void URLRequestJob::NotifyDone(const URLRequestStatus &status) {
    476   DCHECK(!done_) << "Job sending done notification twice";
    477   if (done_)
    478     return;
    479   done_ = true;
    480 
    481   if (is_profiling() && metrics_->total_bytes_read_ > 0) {
    482     // There are valid IO statistics. Fill in other fields of metrics for
    483     // profiling consumers to retrieve information.
    484     metrics_->original_url_.reset(new GURL(request_->original_url()));
    485     metrics_->end_time_ = TimeTicks::Now();
    486     metrics_->success_ = status.is_success();
    487 
    488     if (!(request_->original_url() == request_->url())) {
    489       metrics_->url_.reset(new GURL(request_->url()));
    490     }
    491   } else {
    492     metrics_.reset();
    493   }
    494 
    495 
    496   // Unless there was an error, we should have at least tried to handle
    497   // the response before getting here.
    498   DCHECK(has_handled_response_ || !status.is_success());
    499 
    500   // As with NotifyReadComplete, we need to take care to notice if we were
    501   // destroyed during a delegate callback.
    502   if (request_) {
    503     request_->set_is_pending(false);
    504     // With async IO, it's quite possible to have a few outstanding
    505     // requests.  We could receive a request to Cancel, followed shortly
    506     // by a successful IO.  For tracking the status(), once there is
    507     // an error, we do not change the status back to success.  To
    508     // enforce this, only set the status if the job is so far
    509     // successful.
    510     if (request_->status().is_success())
    511       request_->set_status(status);
    512   }
    513 
    514   g_url_request_job_tracker.OnJobDone(this, status);
    515 
    516   // Complete this notification later.  This prevents us from re-entering the
    517   // delegate if we're done because of a synchronous call.
    518   MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
    519       this, &URLRequestJob::CompleteNotifyDone));
    520 }
    521 
    522 void URLRequestJob::CompleteNotifyDone() {
    523   // Check if we should notify the delegate that we're done because of an error.
    524   if (request_ &&
    525       !request_->status().is_success() &&
    526       request_->delegate()) {
    527     // We report the error differently depending on whether we've called
    528     // OnResponseStarted yet.
    529     if (has_handled_response_) {
    530       // We signal the error by calling OnReadComplete with a bytes_read of -1.
    531       request_->delegate()->OnReadCompleted(request_, -1);
    532     } else {
    533       has_handled_response_ = true;
    534       request_->ResponseStarted();
    535     }
    536   }
    537 }
    538 
    539 void URLRequestJob::NotifyCanceled() {
    540   if (!done_) {
    541     NotifyDone(URLRequestStatus(URLRequestStatus::CANCELED,
    542                                 net::ERR_ABORTED));
    543   }
    544 }
    545 
    546 void URLRequestJob::NotifyRestartRequired() {
    547   DCHECK(!has_handled_response_);
    548   if (GetStatus().status() != URLRequestStatus::CANCELED)
    549     request_->Restart();
    550 }
    551 
    552 bool URLRequestJob::FilterHasData() {
    553     return filter_.get() && filter_->stream_data_len();
    554 }
    555 
    556 void URLRequestJob::RecordBytesRead(int bytes_read) {
    557   if (is_profiling()) {
    558     ++(metrics_->number_of_read_IO_);
    559     metrics_->total_bytes_read_ += bytes_read;
    560   }
    561   filter_input_byte_count_ += bytes_read;
    562   UpdatePacketReadTimes();  // Facilitate stats recording if it is active.
    563   g_url_request_job_tracker.OnBytesRead(this, bytes_read);
    564 }
    565 
    566 const URLRequestStatus URLRequestJob::GetStatus() {
    567   if (request_)
    568     return request_->status();
    569   // If the request is gone, we must be cancelled.
    570   return URLRequestStatus(URLRequestStatus::CANCELED,
    571                           net::ERR_ABORTED);
    572 }
    573 
    574 void URLRequestJob::SetStatus(const URLRequestStatus &status) {
    575   if (request_)
    576     request_->set_status(status);
    577 }
    578 
    579 void URLRequestJob::UpdatePacketReadTimes() {
    580   if (!packet_timing_enabled_)
    581     return;
    582 
    583   if (filter_input_byte_count_ <= bytes_observed_in_packets_) {
    584     DCHECK(filter_input_byte_count_ == bytes_observed_in_packets_);
    585     return;  // No new bytes have arrived.
    586   }
    587 
    588   if (!bytes_observed_in_packets_)
    589     request_time_snapshot_ = GetRequestTime();
    590 
    591   final_packet_time_ = base::Time::Now();
    592   const size_t kTypicalPacketSize = 1430;
    593   while (filter_input_byte_count_ > bytes_observed_in_packets_) {
    594     ++observed_packet_count_;
    595     if (max_packets_timed_ > packet_times_.size()) {
    596       packet_times_.push_back(final_packet_time_);
    597       DCHECK(static_cast<size_t>(observed_packet_count_) ==
    598              packet_times_.size());
    599     }
    600     bytes_observed_in_packets_ += kTypicalPacketSize;
    601   }
    602   // Since packets may not be full, we'll remember the number of bytes we've
    603   // accounted for in packets thus far.
    604   bytes_observed_in_packets_ = filter_input_byte_count_;
    605 }
    606 
    607 void URLRequestJob::EnablePacketCounting(size_t max_packets_timed) {
    608   if (max_packets_timed_ < max_packets_timed)
    609     max_packets_timed_ = max_packets_timed;
    610   packet_timing_enabled_ = true;
    611 }
    612 
    613 void URLRequestJob::RecordPacketStats(StatisticSelector statistic) const {
    614   if (!packet_timing_enabled_ || (final_packet_time_ == base::Time()))
    615     return;
    616 
    617   // Caller should verify that we're not cached content, but we can't always
    618   // really check for it here because we may (at destruction time) call our own
    619   // class method and get a bogus const answer of false. This DCHECK only helps
    620   // when this method has a valid overridden definition.
    621   DCHECK(!IsCachedContent());
    622 
    623   base::TimeDelta duration = final_packet_time_ - request_time_snapshot_;
    624   switch (statistic) {
    625     case SDCH_DECODE: {
    626       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_Latency_F_a", duration,
    627                                   base::TimeDelta::FromMilliseconds(20),
    628                                   base::TimeDelta::FromMinutes(10), 100);
    629       UMA_HISTOGRAM_COUNTS_100("Sdch3.Network_Decode_Packets_b",
    630                                static_cast<int>(observed_packet_count_));
    631       UMA_HISTOGRAM_CUSTOM_COUNTS("Sdch3.Network_Decode_Bytes_Processed_b",
    632           static_cast<int>(bytes_observed_in_packets_), 500, 100000, 100);
    633       if (packet_times_.empty())
    634         return;
    635       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_1st_To_Last_a",
    636                                   final_packet_time_ - packet_times_[0],
    637                                   base::TimeDelta::FromMilliseconds(20),
    638                                   base::TimeDelta::FromMinutes(10), 100);
    639 
    640       DCHECK(max_packets_timed_ >= kSdchPacketHistogramCount);
    641       DCHECK(kSdchPacketHistogramCount > 4);
    642       if (packet_times_.size() <= 4)
    643         return;
    644       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_1st_To_2nd_c",
    645                                   packet_times_[1] - packet_times_[0],
    646                                   base::TimeDelta::FromMilliseconds(1),
    647                                   base::TimeDelta::FromSeconds(10), 100);
    648       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_2nd_To_3rd_c",
    649                                   packet_times_[2] - packet_times_[1],
    650                                   base::TimeDelta::FromMilliseconds(1),
    651                                   base::TimeDelta::FromSeconds(10), 100);
    652       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_3rd_To_4th_c",
    653                                   packet_times_[3] - packet_times_[2],
    654                                   base::TimeDelta::FromMilliseconds(1),
    655                                   base::TimeDelta::FromSeconds(10), 100);
    656       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Decode_4th_To_5th_c",
    657                                   packet_times_[4] - packet_times_[3],
    658                                   base::TimeDelta::FromMilliseconds(1),
    659                                   base::TimeDelta::FromSeconds(10), 100);
    660       return;
    661     }
    662     case SDCH_PASSTHROUGH: {
    663       // Despite advertising a dictionary, we handled non-sdch compressed
    664       // content.
    665       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_Latency_F_a",
    666                                   duration,
    667                                   base::TimeDelta::FromMilliseconds(20),
    668                                   base::TimeDelta::FromMinutes(10), 100);
    669       UMA_HISTOGRAM_COUNTS_100("Sdch3.Network_Pass-through_Packets_b",
    670                                observed_packet_count_);
    671       if (packet_times_.empty())
    672         return;
    673       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_1st_To_Last_a",
    674                                   final_packet_time_ - packet_times_[0],
    675                                   base::TimeDelta::FromMilliseconds(20),
    676                                   base::TimeDelta::FromMinutes(10), 100);
    677       DCHECK(max_packets_timed_ >= kSdchPacketHistogramCount);
    678       DCHECK(kSdchPacketHistogramCount > 4);
    679       if (packet_times_.size() <= 4)
    680         return;
    681       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_1st_To_2nd_c",
    682                                   packet_times_[1] - packet_times_[0],
    683                                   base::TimeDelta::FromMilliseconds(1),
    684                                   base::TimeDelta::FromSeconds(10), 100);
    685       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_2nd_To_3rd_c",
    686                                   packet_times_[2] - packet_times_[1],
    687                                   base::TimeDelta::FromMilliseconds(1),
    688                                   base::TimeDelta::FromSeconds(10), 100);
    689       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_3rd_To_4th_c",
    690                                   packet_times_[3] - packet_times_[2],
    691                                   base::TimeDelta::FromMilliseconds(1),
    692                                   base::TimeDelta::FromSeconds(10), 100);
    693       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Network_Pass-through_4th_To_5th_c",
    694                                   packet_times_[4] - packet_times_[3],
    695                                   base::TimeDelta::FromMilliseconds(1),
    696                                   base::TimeDelta::FromSeconds(10), 100);
    697       return;
    698     }
    699 
    700     case SDCH_EXPERIMENT_DECODE: {
    701       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Decode",
    702                                   duration,
    703                                   base::TimeDelta::FromMilliseconds(20),
    704                                   base::TimeDelta::FromMinutes(10), 100);
    705       // We already provided interpacket histograms above in the SDCH_DECODE
    706       // case, so we don't need them here.
    707       return;
    708     }
    709     case SDCH_EXPERIMENT_HOLDBACK: {
    710       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback",
    711                                   duration,
    712                                   base::TimeDelta::FromMilliseconds(20),
    713                                   base::TimeDelta::FromMinutes(10), 100);
    714       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback_1st_To_Last_a",
    715                                   final_packet_time_ - packet_times_[0],
    716                                   base::TimeDelta::FromMilliseconds(20),
    717                                   base::TimeDelta::FromMinutes(10), 100);
    718 
    719       DCHECK(max_packets_timed_ >= kSdchPacketHistogramCount);
    720       DCHECK(kSdchPacketHistogramCount > 4);
    721       if (packet_times_.size() <= 4)
    722         return;
    723       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback_1st_To_2nd_c",
    724                                   packet_times_[1] - packet_times_[0],
    725                                   base::TimeDelta::FromMilliseconds(1),
    726                                   base::TimeDelta::FromSeconds(10), 100);
    727       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback_2nd_To_3rd_c",
    728                                   packet_times_[2] - packet_times_[1],
    729                                   base::TimeDelta::FromMilliseconds(1),
    730                                   base::TimeDelta::FromSeconds(10), 100);
    731       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback_3rd_To_4th_c",
    732                                   packet_times_[3] - packet_times_[2],
    733                                   base::TimeDelta::FromMilliseconds(1),
    734                                   base::TimeDelta::FromSeconds(10), 100);
    735       UMA_HISTOGRAM_CLIPPED_TIMES("Sdch3.Experiment_Holdback_4th_To_5th_c",
    736                                   packet_times_[4] - packet_times_[3],
    737                                   base::TimeDelta::FromMilliseconds(1),
    738                                   base::TimeDelta::FromSeconds(10), 100);
    739       return;
    740     }
    741     default:
    742       NOTREACHED();
    743       return;
    744   }
    745 }
    746