// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/domain_reliability/context.h" #include #include #include #include "base/bind.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/sparse_histogram.h" #include "base/rand_util.h" #include "base/values.h" #include "components/domain_reliability/dispatcher.h" #include "components/domain_reliability/uploader.h" #include "components/domain_reliability/util.h" #include "net/base/net_errors.h" namespace domain_reliability { // static const int DomainReliabilityContext::kMaxUploadDepthToSchedule = 1; // static const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150; DomainReliabilityContext::DomainReliabilityContext( const MockableTime* time, const DomainReliabilityScheduler::Params& scheduler_params, const std::string& upload_reporter_string, const base::TimeTicks* last_network_change_time, const UploadAllowedCallback& upload_allowed_callback, DomainReliabilityDispatcher* dispatcher, DomainReliabilityUploader* uploader, std::unique_ptr config) : config_(std::move(config)), time_(time), upload_reporter_string_(upload_reporter_string), scheduler_(time, config_->collectors.size(), scheduler_params, base::BindRepeating(&DomainReliabilityContext::ScheduleUpload, base::Unretained(this))), dispatcher_(dispatcher), uploader_(uploader), uploading_beacons_size_(0), last_network_change_time_(last_network_change_time), upload_allowed_callback_(upload_allowed_callback) {} DomainReliabilityContext::~DomainReliabilityContext() { for (auto& beacon_ptr : beacons_) { beacon_ptr->outcome = DomainReliabilityBeacon::Outcome::kContextShutDown; } } void DomainReliabilityContext::OnBeacon( std::unique_ptr beacon) { bool success = (beacon->status == "ok"); double sample_rate = beacon->details.quic_port_migration_detected ? 1.0 : config().GetSampleRate(success); if (base::RandDouble() >= sample_rate) return; beacon->sample_rate = sample_rate; // Allow beacons about reports, but don't schedule an upload for more than // one layer of recursion, to avoid infinite report loops. if (beacon->upload_depth <= kMaxUploadDepthToSchedule) scheduler_.OnBeaconAdded(); beacons_.push_back(std::move(beacon)); bool should_evict = beacons_.size() > kMaxQueuedBeacons; if (should_evict) RemoveOldestBeacon(); } void DomainReliabilityContext::ClearBeacons() { for (auto& beacon_ptr : beacons_) { beacon_ptr->outcome = DomainReliabilityBeacon::Outcome::kCleared; } beacons_.clear(); uploading_beacons_size_ = 0; } void DomainReliabilityContext::GetQueuedBeaconsForTesting( std::vector* beacons_out) const { DCHECK(beacons_out); beacons_out->clear(); for (const auto& beacon : beacons_) beacons_out->push_back(beacon.get()); } void DomainReliabilityContext::ScheduleUpload( base::TimeDelta min_delay, base::TimeDelta max_delay) { dispatcher_->ScheduleTask( base::BindOnce(&DomainReliabilityContext::CallUploadAllowedCallback, weak_factory_.GetWeakPtr()), min_delay, max_delay); } void DomainReliabilityContext::CallUploadAllowedCallback() { RemoveExpiredBeacons(); if (beacons_.empty()) return; upload_allowed_callback_.Run( config().origin, base::BindOnce(&DomainReliabilityContext::OnUploadAllowedCallbackComplete, weak_factory_.GetWeakPtr())); } void DomainReliabilityContext::OnUploadAllowedCallbackComplete(bool allowed) { if (allowed) StartUpload(); } void DomainReliabilityContext::StartUpload() { RemoveExpiredBeacons(); if (beacons_.empty()) return; // Find the first beacon with an `upload_depth` of at most // kMaxUploadDepthToSchedule, in preparation to create a report containing all // beacons with matching NetworkIsolationKeys. bool found_beacon_to_upload = false; for (const auto& beacon : beacons_) { if (beacon->upload_depth <= kMaxUploadDepthToSchedule) { uploading_beacons_network_isolation_key_ = beacon->network_isolation_key; found_beacon_to_upload = true; break; } } if (!found_beacon_to_upload) return; size_t collector_index = scheduler_.OnUploadStart(); const GURL& collector_url = *config().collectors[collector_index]; DCHECK(upload_time_.is_null()); upload_time_ = time_->NowTicks(); std::string report_json = "{}"; int max_upload_depth = -1; bool wrote = base::JSONWriter::Write( CreateReport(upload_time_, collector_url, &max_upload_depth), &report_json); DCHECK(wrote); DCHECK_NE(-1, max_upload_depth); uploader_->UploadReport( report_json, max_upload_depth, collector_url, uploading_beacons_network_isolation_key_, base::BindOnce(&DomainReliabilityContext::OnUploadComplete, weak_factory_.GetWeakPtr())); } void DomainReliabilityContext::OnUploadComplete( const DomainReliabilityUploader::UploadResult& result) { if (result.is_success()) CommitUpload(); else RollbackUpload(); scheduler_.OnUploadComplete(result); DCHECK(!upload_time_.is_null()); last_upload_time_ = upload_time_; upload_time_ = base::TimeTicks(); // If there are pending beacons with a low enough depth, inform the scheduler // - it's possible only some beacons were added because of NetworkIsolationKey // mismatches, rather than due to new beacons being created. if (GetMinBeaconUploadDepth() <= kMaxUploadDepthToSchedule) scheduler_.OnBeaconAdded(); } base::Value DomainReliabilityContext::CreateReport(base::TimeTicks upload_time, const GURL& collector_url, int* max_upload_depth_out) { DCHECK_GT(beacons_.size(), 0u); DCHECK_EQ(0u, uploading_beacons_size_); int max_upload_depth = 0; base::Value beacons_value(base::Value::Type::LIST); for (const auto& beacon : beacons_) { // Only include beacons with a matching NetworkIsolationKey in the report. if (beacon->network_isolation_key != uploading_beacons_network_isolation_key_) { continue; } beacons_value.Append( beacon->ToValue(upload_time, *last_network_change_time_, collector_url, config().path_prefixes)); if (beacon->upload_depth > max_upload_depth) max_upload_depth = beacon->upload_depth; ++uploading_beacons_size_; } DCHECK_GT(uploading_beacons_size_, 0u); base::Value report_value(base::Value::Type::DICTIONARY); report_value.SetStringKey("reporter", upload_reporter_string_); report_value.SetKey("entries", std::move(beacons_value)); *max_upload_depth_out = max_upload_depth; return report_value; } void DomainReliabilityContext::CommitUpload() { auto current = beacons_.begin(); while (uploading_beacons_size_ > 0) { DCHECK(current != beacons_.end()); auto last = current; ++current; if ((*last)->network_isolation_key == uploading_beacons_network_isolation_key_) { (*last)->outcome = DomainReliabilityBeacon::Outcome::kUploaded; beacons_.erase(last); --uploading_beacons_size_; } } } void DomainReliabilityContext::RollbackUpload() { uploading_beacons_size_ = 0; } void DomainReliabilityContext::RemoveOldestBeacon() { DCHECK(!beacons_.empty()); DVLOG(1) << "Beacon queue for " << config().origin << " full; " << "removing oldest beacon"; // If the beacon being removed has a NetworkIsolationKey that matches that of // the current upload, decrement |uploading_beacons_size_|. if (uploading_beacons_size_ > 0 && beacons_.front()->network_isolation_key == uploading_beacons_network_isolation_key_) { --uploading_beacons_size_; } beacons_.front()->outcome = DomainReliabilityBeacon::Outcome::kEvicted; beacons_.pop_front(); } void DomainReliabilityContext::RemoveExpiredBeacons() { base::TimeTicks now = time_->NowTicks(); const base::TimeDelta kMaxAge = base::Hours(1); while (!beacons_.empty() && now - beacons_.front()->start_time >= kMaxAge) { beacons_.front()->outcome = DomainReliabilityBeacon::Outcome::kExpired; beacons_.pop_front(); } } // Gets the minimum depth of all entries in |beacons_|. int DomainReliabilityContext::GetMinBeaconUploadDepth() const { int min = std::numeric_limits::max(); for (const auto& beacon : beacons_) { if (beacon->upload_depth < min) min = beacon->upload_depth; } return min; } } // namespace domain_reliability