blob: e6d15d61267abfebf9f37bb4f52451adc70970eb [file] [log] [blame]
// 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