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
22using base::DictionaryValue;
23using base::ListValue;
24using base::Value;
25
26namespace domain_reliability {
27
28namespace {
29typedef std::deque<DomainReliabilityBeacon> BeaconDeque;
30typedef BeaconDeque::iterator BeaconIterator;
31typedef BeaconDeque::const_iterator BeaconConstIterator;
32}  // namespace
33
34class 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
119const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150;
120
121DomainReliabilityContext::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
143DomainReliabilityContext::~DomainReliabilityContext() {}
144
145void 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
180void 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
195void 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
211void 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
217void 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
228void 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
255void 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
267scoped_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
284void DomainReliabilityContext::MarkUpload() {
285  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
286    (*it)->MarkUpload();
287  uploading_beacon_count_ = beacon_count_;
288}
289
290void DomainReliabilityContext::CommitUpload() {
291  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
292    (*it)->CommitUpload();
293  beacon_count_ -= uploading_beacon_count_;
294}
295
296void 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