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
21using base::DictionaryValue;
22using base::ListValue;
23using base::Value;
24
25namespace domain_reliability {
26
27namespace {
28typedef std::deque<DomainReliabilityBeacon> BeaconDeque;
29typedef BeaconDeque::iterator BeaconIterator;
30typedef BeaconDeque::const_iterator BeaconConstIterator;
31}  // namespace
32
33class 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
98const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150;
99
100DomainReliabilityContext::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
122DomainReliabilityContext::~DomainReliabilityContext() {}
123
124void 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
159void 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
172scoped_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
184void DomainReliabilityContext::GetQueuedBeaconsForTesting(
185    std::vector<DomainReliabilityBeacon>* beacons_out) const {
186  beacons_out->assign(beacons_.begin(), beacons_.end());
187}
188
189void 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
201void 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
207void 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
218void 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
245void 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
259scoped_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
283void 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
291void 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
301void 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
308void 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