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