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: 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 119 const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150; 120 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 143 DomainReliabilityContext::~DomainReliabilityContext() {} 144 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 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 195 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 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 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 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 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 267 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 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 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 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