Commit 6f53eab6 authored by Douglas Creager's avatar Douglas Creager Committed by Commit Bot

Network Error Logging: Add success and failure sampling rates

There are separate sampling rates for reports about successful and
failed requests.  Even though we're not currently reporting anything for
successful requests, this plumbs through both sampling rates from the
NEL header into the OriginPolicy instance.

(A future change will add reports for successful requests; doing the
patches in this order ensures that we never upload reports for 100% of
successful requests unless specifically instructed to do so by the
origin.)

Bug: 748549
Change-Id: I66d9d0c0544372e642d3a146deb0a06f7973452d
Reviewed-on: https://chromium-review.googlesource.com/891741
Commit-Queue: Julia Tuttle <juliatuttle@chromium.org>
Reviewed-by: default avatarJulia Tuttle <juliatuttle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#532911}
parent 53b1b7da
......@@ -11,6 +11,7 @@
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
......@@ -30,6 +31,8 @@ namespace {
const char kReportToKey[] = "report-to";
const char kMaxAgeKey[] = "max-age";
const char kIncludeSubdomainsKey[] = "includeSubdomains";
const char kSuccessFractionKey[] = "success-fraction";
const char kFailureFractionKey[] = "failure-fraction";
// Returns the superdomain of a given domain, or the empty string if the given
// domain is just a single label. Note that this does not take into account
......@@ -110,6 +113,8 @@ bool GetTypeFromNetError(Error error, std::string* type_out) {
const char NetworkErrorLoggingService::kReportType[] = "network-error";
const char NetworkErrorLoggingService::kUriKey[] = "uri";
const char NetworkErrorLoggingService::kReferrerKey[] = "referrer";
const char NetworkErrorLoggingService::kSamplingFractionKey[] =
"sampling-fraction";
const char NetworkErrorLoggingService::kServerIpKey[] = "server-ip";
const char NetworkErrorLoggingService::kProtocolKey[] = "protocol";
const char NetworkErrorLoggingService::kStatusCodeKey[] = "status-code";
......@@ -182,8 +187,14 @@ void NetworkErrorLoggingService::OnNetworkError(const ErrorDetails& details) {
if (!GetTypeFromNetError(details.type, &type_string))
return;
reporting_service_->QueueReport(details.uri, policy->report_to, kReportType,
CreateReportBody(type_string, details));
// TODO(dcreager): Report successful requests, too.
double sampling_fraction = policy->failure_fraction;
if (base::RandDouble() >= sampling_fraction)
return;
reporting_service_->QueueReport(
details.uri, policy->report_to, kReportType,
CreateReportBody(type_string, sampling_fraction, details));
}
void NetworkErrorLoggingService::RemoveBrowsingData(
......@@ -243,6 +254,16 @@ bool NetworkErrorLoggingService::ParseHeader(const std::string& json_value,
// GetBoolean fails.
dict->GetBoolean(kIncludeSubdomainsKey, &include_subdomains);
double success_fraction = 0.0;
// success-fraction is optional and defaults to 0.0, so it's okay if
// GetDouble fails.
dict->GetDouble(kSuccessFractionKey, &success_fraction);
double failure_fraction = 1.0;
// failure-fraction is optional and defaults to 1.0, so it's okay if
// GetDouble fails.
dict->GetDouble(kFailureFractionKey, &failure_fraction);
policy_out->report_to = report_to;
if (max_age_sec > 0) {
policy_out->expires =
......@@ -251,6 +272,8 @@ bool NetworkErrorLoggingService::ParseHeader(const std::string& json_value,
policy_out->expires = base::TimeTicks();
}
policy_out->include_subdomains = include_subdomains;
policy_out->success_fraction = success_fraction;
policy_out->failure_fraction = failure_fraction;
return true;
}
......@@ -334,11 +357,13 @@ void NetworkErrorLoggingService::MaybeRemoveWildcardPolicy(
std::unique_ptr<const base::Value> NetworkErrorLoggingService::CreateReportBody(
const std::string& type,
double sampling_fraction,
const NetworkErrorLoggingDelegate::ErrorDetails& details) const {
auto body = std::make_unique<base::DictionaryValue>();
body->SetString(kUriKey, details.uri.spec());
body->SetString(kReferrerKey, details.referrer.spec());
body->SetDouble(kSamplingFractionKey, sampling_fraction);
body->SetString(kServerIpKey, details.server_ip.ToString());
std::string protocol = NextProtoToString(details.protocol);
if (protocol == "unknown")
......
......@@ -45,6 +45,7 @@ class NET_EXPORT NetworkErrorLoggingService
static const char kUriKey[];
static const char kReferrerKey[];
static const char kSamplingFractionKey[];
static const char kServerIpKey[];
static const char kProtocolKey[];
static const char kStatusCodeKey[];
......@@ -80,6 +81,8 @@ class NET_EXPORT NetworkErrorLoggingService
base::TimeTicks expires;
double success_fraction;
double failure_fraction;
bool include_subdomains;
};
......@@ -114,6 +117,7 @@ class NET_EXPORT NetworkErrorLoggingService
const OriginPolicy* policy);
std::unique_ptr<const base::Value> CreateReportBody(
const std::string& type,
double sampling_fraction,
const ErrorDetails& details) const;
base::TickClock* tick_clock_;
......
......@@ -153,6 +153,10 @@ class NetworkErrorLoggingServiceTest : public ::testing::Test {
const std::string kHeader_ = "{\"report-to\":\"group\",\"max-age\":86400}";
const std::string kHeaderIncludeSubdomains_ =
"{\"report-to\":\"group\",\"max-age\":86400,\"includeSubdomains\":true}";
const std::string kHeaderFailureFraction0_ =
"{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":0.0}";
const std::string kHeaderFailureFractionHalf_ =
"{\"report-to\":\"group\",\"max-age\":86400,\"failure-fraction\":0.5}";
const std::string kHeaderMaxAge0_ = "{\"max-age\":0}";
const std::string kGroup_ = "group";
......@@ -166,6 +170,14 @@ class NetworkErrorLoggingServiceTest : public ::testing::Test {
std::unique_ptr<TestReportingService> reporting_service_;
};
void ExpectDictDoubleValue(double expected_value,
const base::DictionaryValue& value,
const std::string& key) {
double double_value = 0.0;
EXPECT_TRUE(value.GetDouble(key, &double_value)) << key;
EXPECT_DOUBLE_EQ(expected_value, double_value) << key;
}
TEST_F(NetworkErrorLoggingServiceTest, CreateService) {
// Service is created by default in the test fixture..
EXPECT_TRUE(service());
......@@ -214,6 +226,8 @@ TEST_F(NetworkErrorLoggingServiceTest, ReportQueued) {
base::ExpectDictStringValue(kReferrer_.spec(), *body,
NetworkErrorLoggingService::kReferrerKey);
// TODO(juliatuttle): Extract these constants.
ExpectDictDoubleValue(1.0, *body,
NetworkErrorLoggingService::kSamplingFractionKey);
base::ExpectDictStringValue("0.0.0.0", *body,
NetworkErrorLoggingService::kServerIpKey);
base::ExpectDictStringValue("", *body,
......@@ -236,6 +250,45 @@ TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, FailureFraction0) {
service()->OnHeader(kOrigin_, kHeaderFailureFraction0_);
// Each network error has a 0% chance of being reported. Fire off several and
// verify that no reports are produced.
constexpr size_t kReportCount = 100;
for (size_t i = 0; i < kReportCount; ++i)
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, FailureFractionHalf) {
service()->OnHeader(kOrigin_, kHeaderFailureFractionHalf_);
// Each network error has a 50% chance of being reported. Fire off several
// and verify that some requests were reported and some weren't. (We can't
// verify exact counts because each decision is made randomly.)
constexpr size_t kReportCount = 100;
for (size_t i = 0; i < kReportCount; ++i)
service()->OnNetworkError(MakeErrorDetails(kUrl_, ERR_CONNECTION_REFUSED));
// If our random selection logic is correct, there is a 2^-100 chance that
// every single report above was skipped. If this check fails, it's much more
// likely that our code is wrong.
EXPECT_FALSE(reports().empty());
// There's also a 2^-100 chance that every single report was logged. Same as
// above, that's much more likely to be a code error.
EXPECT_GT(kReportCount, reports().size());
for (const auto& report : reports()) {
const base::DictionaryValue* body;
ASSERT_TRUE(report.body->GetAsDictionary(&body));
ExpectDictDoubleValue(0.5, *body,
NetworkErrorLoggingService::kSamplingFractionKey);
}
}
TEST_F(NetworkErrorLoggingServiceTest,
ExcludeSubdomainsDoesntMatchDifferentPort) {
service()->OnHeader(kOrigin_, kHeader_);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment