• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
ResourceState(DomainReliabilityContext * context,const DomainReliabilityConfig::Resource * config)36   ResourceState(DomainReliabilityContext* context,
37                 const DomainReliabilityConfig::Resource* config)
38       : context(context),
39         config(config),
40         successful_requests(0),
41         failed_requests(0) {}
~ResourceState()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.
ToValue(base::TimeTicks upload_time) const47   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.
MarkUpload()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.
CommitUpload()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.
GetOldestBeaconStart(base::TimeTicks * oldest_start_out) const83   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.
RemoveOldestBeacon()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 
DomainReliabilityContext(MockableTime * time,const DomainReliabilityScheduler::Params & scheduler_params,const std::string & upload_reporter_string,DomainReliabilityDispatcher * dispatcher,DomainReliabilityUploader * uploader,scoped_ptr<const DomainReliabilityConfig> config)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 
~DomainReliabilityContext()143 DomainReliabilityContext::~DomainReliabilityContext() {}
144 
OnBeacon(const GURL & url,const DomainReliabilityBeacon & beacon)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 
ClearBeacons()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 
GetQueuedDataForTesting(size_t resource_index,std::vector<DomainReliabilityBeacon> * beacons_out,uint32 * successful_requests_out,uint32 * failed_requests_out) const195 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 
InitializeResourceStates()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 
ScheduleUpload(base::TimeDelta min_delay,base::TimeDelta max_delay)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 
StartUpload()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 
OnUploadComplete(bool success)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 
CreateReport(base::TimeTicks upload_time) const267 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 
MarkUpload()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 
CommitUpload()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 
RemoveOldestBeacon()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