• 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/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:
ResourceState(DomainReliabilityContext * context,const DomainReliabilityConfig::Resource * config)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) {}
~ResourceState()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.
ToValue(base::TimeTicks upload_time) const48   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.
MarkUpload()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.
CommitUpload()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 
RollbackUpload()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 
DomainReliabilityContext(MockableTime * time,const DomainReliabilityScheduler::Params & scheduler_params,const std::string & upload_reporter_string,DomainReliabilityDispatcher * dispatcher,DomainReliabilityUploader * uploader,scoped_ptr<const DomainReliabilityConfig> config)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 
~DomainReliabilityContext()122 DomainReliabilityContext::~DomainReliabilityContext() {}
123 
OnBeacon(const GURL & url,const DomainReliabilityBeacon & beacon)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 
ClearBeacons()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 
GetWebUIData() const172 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 
GetQueuedBeaconsForTesting(std::vector<DomainReliabilityBeacon> * beacons_out) const184 void DomainReliabilityContext::GetQueuedBeaconsForTesting(
185     std::vector<DomainReliabilityBeacon>* beacons_out) const {
186   beacons_out->assign(beacons_.begin(), beacons_.end());
187 }
188 
GetRequestCountsForTesting(size_t resource_index,uint32_t * successful_requests_out,uint32_t * failed_requests_out) const189 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 
InitializeResourceStates()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 
ScheduleUpload(base::TimeDelta min_delay,base::TimeDelta max_delay)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 
StartUpload()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 
OnUploadComplete(bool success)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 
CreateReport(base::TimeTicks upload_time) const259 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 
MarkUpload()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 
CommitUpload()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 
RollbackUpload()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 
RemoveOldestBeacon()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