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: 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 98 const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150; 99 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 122 DomainReliabilityContext::~DomainReliabilityContext() {} 123 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 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 172 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 184 void DomainReliabilityContext::GetQueuedBeaconsForTesting( 185 std::vector<DomainReliabilityBeacon>* beacons_out) const { 186 beacons_out->assign(beacons_.begin(), beacons_.end()); 187 } 188 189 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 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 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 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 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 259 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 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 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 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 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