Home | History | Annotate | Download | only in domain_reliability
      1 // Copyright 2014 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 "components/domain_reliability/context.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/json/json_writer.h"
     11 #include "base/logging.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/metrics/sparse_histogram.h"
     14 #include "base/values.h"
     15 #include "components/domain_reliability/beacon.h"
     16 #include "components/domain_reliability/dispatcher.h"
     17 #include "components/domain_reliability/uploader.h"
     18 #include "components/domain_reliability/util.h"
     19 #include "net/base/net_errors.h"
     20 #include "net/url_request/url_request_context_getter.h"
     21 
     22 using base::DictionaryValue;
     23 using base::ListValue;
     24 using base::Value;
     25 
     26 namespace domain_reliability {
     27 
     28 namespace {
     29 typedef std::deque<DomainReliabilityBeacon> BeaconDeque;
     30 typedef BeaconDeque::iterator BeaconIterator;
     31 typedef BeaconDeque::const_iterator BeaconConstIterator;
     32 }  // namespace
     33 
     34 class DomainReliabilityContext::ResourceState {
     35  public:
     36   ResourceState(DomainReliabilityContext* context,
     37                 const DomainReliabilityConfig::Resource* config)
     38       : context(context),
     39         config(config),
     40         successful_requests(0),
     41         failed_requests(0) {}
     42   ~ResourceState() {}
     43 
     44   // Serializes the resource state into a Value to be included in an upload.
     45   // If there is nothing to report (no beacons and all request counters are 0),
     46   // returns a scoped_ptr to NULL instead so the resource can be omitted.
     47   scoped_ptr<base::Value> ToValue(base::TimeTicks upload_time) const {
     48     if (beacons.empty() && successful_requests == 0 && failed_requests == 0)
     49       return scoped_ptr<base::Value>();
     50 
     51     ListValue* beacons_value = new ListValue();
     52     for (BeaconConstIterator it = beacons.begin(); it != beacons.end(); ++it)
     53       beacons_value->Append(it->ToValue(upload_time));
     54 
     55     DictionaryValue* resource_value = new DictionaryValue();
     56     resource_value->SetString("resource_name", config->name);
     57     resource_value->SetInteger("successful_requests", successful_requests);
     58     resource_value->SetInteger("failed_requests", failed_requests);
     59     resource_value->Set("beacons", beacons_value);
     60 
     61     return scoped_ptr<Value>(resource_value);
     62   }
     63 
     64   // Remembers the current state of the resource data when an upload starts.
     65   void MarkUpload() {
     66     uploading_beacons_size = beacons.size();
     67     uploading_successful_requests = successful_requests;
     68     uploading_failed_requests = failed_requests;
     69   }
     70 
     71   // Uses the state remembered by |MarkUpload| to remove successfully uploaded
     72   // data but keep beacons and request counts added after the upload started.
     73   void CommitUpload() {
     74     BeaconIterator begin = beacons.begin();
     75     BeaconIterator end = begin + uploading_beacons_size;
     76     beacons.erase(begin, end);
     77     successful_requests -= uploading_successful_requests;
     78     failed_requests -= uploading_failed_requests;
     79   }
     80 
     81   // Gets the start time of the oldest beacon, if there are any. Returns true
     82   // and sets |oldest_start_out| if so; otherwise, returns false.
     83   bool GetOldestBeaconStart(base::TimeTicks* oldest_start_out) const {
     84     if (beacons.empty())
     85       return false;
     86     *oldest_start_out = beacons[0].start_time;
     87     return true;
     88   }
     89 
     90   // Removes the oldest beacon. DCHECKs if there isn't one.
     91   void RemoveOldestBeacon() {
     92     DCHECK(!beacons.empty());
     93     beacons.erase(beacons.begin());
     94     // If that just removed a beacon counted in uploading_beacons_size,
     95     // decrement
     96     // that.
     97     if (uploading_beacons_size > 0)
     98       --uploading_beacons_size;
     99   }
    100 
    101   DomainReliabilityContext* context;
    102   const DomainReliabilityConfig::Resource* config;
    103 
    104   std::deque<DomainReliabilityBeacon> beacons;
    105   uint32 successful_requests;
    106   uint32 failed_requests;
    107 
    108   // State saved during uploads; if an upload succeeds, these are used to
    109   // remove uploaded data from the beacon list and request counters.
    110   size_t uploading_beacons_size;
    111   uint32 uploading_successful_requests;
    112   uint32 uploading_failed_requests;
    113 
    114  private:
    115   DISALLOW_COPY_AND_ASSIGN(ResourceState);
    116 };
    117 
    118 // static
    119 const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150;
    120 
    121 DomainReliabilityContext::DomainReliabilityContext(
    122     MockableTime* time,
    123     const DomainReliabilityScheduler::Params& scheduler_params,
    124     const std::string& upload_reporter_string,
    125     DomainReliabilityDispatcher* dispatcher,
    126     DomainReliabilityUploader* uploader,
    127     scoped_ptr<const DomainReliabilityConfig> config)
    128     : config_(config.Pass()),
    129       time_(time),
    130       upload_reporter_string_(upload_reporter_string),
    131       scheduler_(time,
    132                  config_->collectors.size(),
    133                  scheduler_params,
    134                  base::Bind(&DomainReliabilityContext::ScheduleUpload,
    135                             base::Unretained(this))),
    136       dispatcher_(dispatcher),
    137       uploader_(uploader),
    138       beacon_count_(0),
    139       weak_factory_(this) {
    140   InitializeResourceStates();
    141 }
    142 
    143 DomainReliabilityContext::~DomainReliabilityContext() {}
    144 
    145 void DomainReliabilityContext::OnBeacon(const GURL& url,
    146                                         const DomainReliabilityBeacon& beacon) {
    147   size_t index = config_->GetResourceIndexForUrl(url);
    148   if (index == DomainReliabilityConfig::kInvalidResourceIndex)
    149     return;
    150   DCHECK_GT(states_.size(), index);
    151 
    152   bool success = (beacon.status == "ok");
    153 
    154   ResourceState* state = states_[index];
    155   if (success)
    156     ++state->successful_requests;
    157   else
    158     ++state->failed_requests;
    159 
    160   bool reported = false;
    161   bool evicted = false;
    162   if (state->config->DecideIfShouldReportRequest(success)) {
    163     state->beacons.push_back(beacon);
    164     ++beacon_count_;
    165     if (beacon_count_ > kMaxQueuedBeacons) {
    166       RemoveOldestBeacon();
    167       evicted = true;
    168     }
    169     scheduler_.OnBeaconAdded();
    170     reported = true;
    171     UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.ReportedBeaconError",
    172                                 -beacon.chrome_error);
    173     // TODO(ttuttle): Histogram HTTP response code?
    174   }
    175 
    176   UMA_HISTOGRAM_BOOLEAN("DomainReliability.BeaconReported", reported);
    177   UMA_HISTOGRAM_BOOLEAN("DomainReliability.OnBeaconDidEvict", evicted);
    178 }
    179 
    180 void DomainReliabilityContext::ClearBeacons() {
    181   ResourceStateVector::iterator it;
    182   for (it = states_.begin(); it != states_.end(); ++it) {
    183     ResourceState* state = *it;
    184     state->beacons.clear();
    185     state->successful_requests = 0;
    186     state->failed_requests = 0;
    187     state->uploading_beacons_size = 0;
    188     state->uploading_successful_requests = 0;
    189     state->uploading_failed_requests = 0;
    190   }
    191   beacon_count_ = 0;
    192   uploading_beacon_count_ = 0;
    193 }
    194 
    195 void DomainReliabilityContext::GetQueuedDataForTesting(
    196     size_t resource_index,
    197     std::vector<DomainReliabilityBeacon>* beacons_out,
    198     uint32* successful_requests_out,
    199     uint32* failed_requests_out) const {
    200   DCHECK_NE(DomainReliabilityConfig::kInvalidResourceIndex, resource_index);
    201   DCHECK_GT(states_.size(), resource_index);
    202   const ResourceState& state = *states_[resource_index];
    203   if (beacons_out)
    204     beacons_out->assign(state.beacons.begin(), state.beacons.end());
    205   if (successful_requests_out)
    206     *successful_requests_out = state.successful_requests;
    207   if (failed_requests_out)
    208     *failed_requests_out = state.failed_requests;
    209 }
    210 
    211 void DomainReliabilityContext::InitializeResourceStates() {
    212   ScopedVector<DomainReliabilityConfig::Resource>::const_iterator it;
    213   for (it = config_->resources.begin(); it != config_->resources.end(); ++it)
    214     states_.push_back(new ResourceState(this, *it));
    215 }
    216 
    217 void DomainReliabilityContext::ScheduleUpload(
    218     base::TimeDelta min_delay,
    219     base::TimeDelta max_delay) {
    220   dispatcher_->ScheduleTask(
    221       base::Bind(
    222           &DomainReliabilityContext::StartUpload,
    223           weak_factory_.GetWeakPtr()),
    224       min_delay,
    225       max_delay);
    226 }
    227 
    228 void DomainReliabilityContext::StartUpload() {
    229   MarkUpload();
    230 
    231   DCHECK(upload_time_.is_null());
    232   upload_time_ = time_->NowTicks();
    233   std::string report_json;
    234   scoped_ptr<const Value> report_value(CreateReport(upload_time_));
    235   base::JSONWriter::Write(report_value.get(), &report_json);
    236   report_value.reset();
    237 
    238   size_t collector_index = scheduler_.OnUploadStart();
    239 
    240   uploader_->UploadReport(
    241       report_json,
    242       config_->collectors[collector_index]->upload_url,
    243       base::Bind(
    244           &DomainReliabilityContext::OnUploadComplete,
    245           weak_factory_.GetWeakPtr()));
    246 
    247   UMA_HISTOGRAM_BOOLEAN("DomainReliability.UploadFailover",
    248                         collector_index > 0);
    249   if (!last_upload_time_.is_null()) {
    250     UMA_HISTOGRAM_LONG_TIMES("DomainReliability.UploadInterval",
    251                              upload_time_ - last_upload_time_);
    252   }
    253 }
    254 
    255 void DomainReliabilityContext::OnUploadComplete(bool success) {
    256   if (success)
    257     CommitUpload();
    258   scheduler_.OnUploadComplete(success);
    259   UMA_HISTOGRAM_BOOLEAN("DomainReliability.UploadSuccess", success);
    260   DCHECK(!upload_time_.is_null());
    261   UMA_HISTOGRAM_MEDIUM_TIMES("DomainReliability.UploadDuration",
    262                              time_->NowTicks() - upload_time_);
    263   last_upload_time_ = upload_time_;
    264   upload_time_ = base::TimeTicks();
    265 }
    266 
    267 scoped_ptr<const Value> DomainReliabilityContext::CreateReport(
    268     base::TimeTicks upload_time) const {
    269   ListValue* resources_value = new ListValue();
    270   for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it) {
    271     scoped_ptr<Value> resource_report = (*it)->ToValue(upload_time);
    272     if (resource_report)
    273       resources_value->Append(resource_report.release());
    274   }
    275 
    276   DictionaryValue* report_value = new DictionaryValue();
    277   report_value->SetString("config_version", config().version);
    278   report_value->SetString("reporter", upload_reporter_string_);
    279   report_value->Set("resource_reports", resources_value);
    280 
    281   return scoped_ptr<const Value>(report_value);
    282 }
    283 
    284 void DomainReliabilityContext::MarkUpload() {
    285   for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
    286     (*it)->MarkUpload();
    287   uploading_beacon_count_ = beacon_count_;
    288 }
    289 
    290 void DomainReliabilityContext::CommitUpload() {
    291   for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
    292     (*it)->CommitUpload();
    293   beacon_count_ -= uploading_beacon_count_;
    294 }
    295 
    296 void DomainReliabilityContext::RemoveOldestBeacon() {
    297   DCHECK_LT(0u, beacon_count_);
    298 
    299   base::TimeTicks min_time;
    300   ResourceState* min_resource = NULL;
    301   for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it) {
    302     base::TimeTicks oldest;
    303     if ((*it)->GetOldestBeaconStart(&oldest)) {
    304       if (!min_resource || oldest < min_time) {
    305         min_time = oldest;
    306         min_resource = *it;
    307       }
    308     }
    309   }
    310   DCHECK(min_resource);
    311 
    312   VLOG(1) << "Beacon queue for " << config().domain << " full; "
    313           << "removing oldest beacon from " << min_resource->config->name;
    314 
    315   min_resource->RemoveOldestBeacon();
    316   --beacon_count_;
    317   // If that just removed a beacon counted in uploading_beacon_count_, decrement
    318   // that.
    319   if (uploading_beacon_count_ > 0)
    320     --uploading_beacon_count_;
    321 }
    322 
    323 }  // namespace domain_reliability
    324