| // 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 <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #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<const DomainReliabilityConfig> 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<DomainReliabilityBeacon> 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; |
| } |
| |
| base::Value DomainReliabilityContext::GetWebUIData() const { |
| base::Value context_value(base::Value::Type::DICTIONARY); |
| |
| context_value.SetStringKey("origin", config().origin.spec()); |
| context_value.SetIntKey("beacon_count", static_cast<int>(beacons_.size())); |
| context_value.SetIntKey("uploading_beacon_count", |
| static_cast<int>(uploading_beacons_size_)); |
| context_value.SetKey("scheduler", scheduler_.GetWebUIData()); |
| |
| return context_value; |
| } |
| |
| void DomainReliabilityContext::GetQueuedBeaconsForTesting( |
| std::vector<const DomainReliabilityBeacon*>* 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::TimeDelta::FromHours(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<int>::max(); |
| for (const auto& beacon : beacons_) { |
| if (beacon->upload_depth < min) |
| min = beacon->upload_depth; |
| } |
| return min; |
| } |
| |
| } // namespace domain_reliability |