Commit ee8e7062 authored by holte's avatar holte Committed by Commit bot

Create a mechanism for reporting rappor metrics from non-UMA

users.

BUG=418242

Review URL: https://codereview.chromium.org/509203002

Cr-Commit-Position: refs/heads/master@{#302682}
parent 3fba1a6a
......@@ -51,8 +51,9 @@ class TestRapporService : public rappor::RapporService {
TestRapporService::TestRapporService()
: rappor::RapporService(&prefs_) {
// Initialize the RapporService for testing.
SetCohortForTesting(0);
SetSecretForTesting(rappor::HmacByteVectorGenerator::GenerateEntropyInput());
Initialize(0,
rappor::HmacByteVectorGenerator::GenerateEntropyInput(),
rappor::FINE_LEVEL);
}
TestRapporService::~TestRapporService() {}
......
......@@ -21,6 +21,9 @@ RapporMetric::RapporMetric(const std::string& metric_name,
parameters.bloom_filter_hash_function_count) {
DCHECK_GE(cohort_seed, 0);
DCHECK_LT(cohort_seed, RapporParameters::kMaxCohorts);
// Since cohort_seed is in the range [0, kMaxCohorts), num_cohorts should
// divide kMaxCohorts for each cohort to have equal weight.
DCHECK_EQ(0, RapporParameters::kMaxCohorts % parameters.num_cohorts);
}
RapporMetric::~RapporMetric() {}
......
......@@ -19,7 +19,8 @@ const RapporParameters kTestRapporParameters = {
PROBABILITY_75 /* Fake data probability */,
PROBABILITY_50 /* Fake one probability */,
PROBABILITY_75 /* One coin probability */,
PROBABILITY_50 /* Zero coin probability */};
PROBABILITY_50 /* Zero coin probability */,
FINE_LEVEL /* Reporting level (not used) */};
const RapporParameters kTestStatsRapporParameters = {
1 /* Num cohorts */,
......@@ -28,7 +29,8 @@ const RapporParameters kTestStatsRapporParameters = {
PROBABILITY_75 /* Fake data probability */,
PROBABILITY_50 /* Fake one probability */,
PROBABILITY_75 /* One coin probability */,
PROBABILITY_50 /* Zero coin probability */};
PROBABILITY_50 /* Zero coin probability */,
FINE_LEVEL /* Reporting level (not used) */};
// Check for basic syntax and use.
TEST(RapporMetricTest, BasicMetric) {
......
......@@ -10,14 +10,15 @@
namespace rappor {
std::string RapporParameters::ToString() const {
return base::StringPrintf("{ %d, %d, %d, %d, %d, %d, %d }",
return base::StringPrintf("{ %d, %d, %d, %d, %d, %d, %d, %d }",
num_cohorts,
bloom_filter_size_bytes,
bloom_filter_hash_function_count,
fake_prob,
fake_one_prob,
one_coin_prob,
zero_coin_prob);
zero_coin_prob,
reporting_level);
}
const int RapporParameters::kMaxCohorts = 512;
......
......@@ -15,6 +15,18 @@ enum Probability {
PROBABILITY_25, // 25%
};
// A metric is reported when it's reporting level is >= the reporting level
// passed in to RapporService::Start()
enum ReportingLevel {
// No metrics are reported at this level.
REPORTING_DISABLED = 0,
// Metrics suitable for broader populations.
COARSE_LEVEL,
// Metrics suitable for UMA opt-in users.
FINE_LEVEL,
};
// An object describing a rappor metric and the parameters used to generate it.
//
// For a full description of the rappor metrics, see
......@@ -27,6 +39,8 @@ struct RapporParameters {
static const int kMaxCohorts;
// The number of cohorts to divide the reports for this metric into.
// This should divide kMaxCohorts evenly so that each cohort has an equal
// probability of being assigned users.
int num_cohorts;
// The number of bytes stored in the Bloom filter.
......@@ -43,6 +57,9 @@ struct RapporParameters {
Probability one_coin_prob;
// The probability that a zero bit in the redacted data reports as one.
Probability zero_coin_prob;
// The reporting level this metric is reported at.
ReportingLevel reporting_level;
};
} // namespace rappor
......
......@@ -37,18 +37,10 @@ const char kRapporRolloutFieldTrialName[] = "RapporRollout";
// Constant for the finch parameter name for the server URL
const char kRapporRolloutServerUrlParam[] = "ServerUrl";
// Constant for the finch parameter name for the server URL
const char kRapporRolloutRequireUmaParam[] = "RequireUma";
// The rappor server's URL.
const char kDefaultServerUrl[] = "https://clients4.google.com/rappor";
GURL GetServerUrl(bool metrics_enabled) {
bool require_uma = variations::GetVariationParamValue(
kRapporRolloutFieldTrialName,
kRapporRolloutRequireUmaParam) != "False";
if (!metrics_enabled && require_uma)
return GURL(); // Invalid URL disables Rappor.
GURL GetServerUrl() {
std::string server_url = variations::GetVariationParamValue(
kRapporRolloutFieldTrialName,
kRapporRolloutServerUrlParam);
......@@ -66,7 +58,17 @@ const RapporParameters kRapporParametersForType[NUM_RAPPOR_TYPES] = {
rappor::PROBABILITY_50 /* Fake data probability */,
rappor::PROBABILITY_50 /* Fake one probability */,
rappor::PROBABILITY_75 /* One coin probability */,
rappor::PROBABILITY_25 /* Zero coin probability */},
rappor::PROBABILITY_25 /* Zero coin probability */,
FINE_LEVEL /* Reporting level */},
// COARSE_RAPPOR_TYPE
{128 /* Num cohorts */,
1 /* Bloom filter size bytes */,
2 /* Bloom filter hash count */,
rappor::PROBABILITY_50 /* Fake data probability */,
rappor::PROBABILITY_50 /* Fake one probability */,
rappor::PROBABILITY_75 /* One coin probability */,
rappor::PROBABILITY_25 /* Zero coin probability */,
COARSE_LEVEL /* Reporting level */},
};
} // namespace
......@@ -75,8 +77,9 @@ RapporService::RapporService(PrefService* pref_service)
: pref_service_(pref_service),
cohort_(-1),
daily_event_(pref_service,
prefs::kRapporLastDailySample,
kRapporDailyEventHistogram) {
prefs::kRapporLastDailySample,
kRapporDailyEventHistogram),
reporting_level_(REPORTING_DISABLED) {
}
RapporService::~RapporService() {
......@@ -90,16 +93,22 @@ void RapporService::AddDailyObserver(
void RapporService::Start(net::URLRequestContextGetter* request_context,
bool metrics_enabled) {
const GURL server_url = GetServerUrl(metrics_enabled);
const GURL server_url = GetServerUrl();
if (!server_url.is_valid()) {
DVLOG(1) << server_url.spec() << " is invalid. "
<< "RapporService not started.";
return;
}
// TODO(holte): Consider moving this logic once we've determined the
// conditions for COARSE metrics.
ReportingLevel reporting_level = metrics_enabled ?
FINE_LEVEL : REPORTING_DISABLED;
DVLOG(1) << "RapporService reporting_level_? " << reporting_level;
if (reporting_level <= REPORTING_DISABLED)
return;
DVLOG(1) << "RapporService started. Reporting to " << server_url.spec();
DCHECK(!uploader_);
LoadSecret();
LoadCohort();
Initialize(LoadCohort(), LoadSecret(), reporting_level_);
uploader_.reset(new LogUploader(server_url, kMimeType, request_context));
log_rotation_timer_.Start(
FROM_HERE,
......@@ -108,6 +117,16 @@ void RapporService::Start(net::URLRequestContextGetter* request_context,
&RapporService::OnLogInterval);
}
void RapporService::Initialize(int32_t cohort,
const std::string& secret,
const ReportingLevel& reporting_level) {
DCHECK(!IsInitialized());
DCHECK(secret_.empty());
cohort_ = cohort;
secret_ = secret;
reporting_level_ = reporting_level;
}
void RapporService::OnLogInterval() {
DCHECK(uploader_);
DVLOG(2) << "RapporService::OnLogInterval";
......@@ -136,39 +155,40 @@ void RapporService::RegisterPrefs(PrefRegistrySimple* registry) {
prefs::kRapporLastDailySample);
}
void RapporService::LoadCohort() {
DCHECK(!IsInitialized());
int32_t RapporService::LoadCohort() {
// Ignore and delete old cohort parameter.
pref_service_->ClearPref(prefs::kRapporCohortDeprecated);
cohort_ = pref_service_->GetInteger(prefs::kRapporCohortSeed);
int32_t cohort = pref_service_->GetInteger(prefs::kRapporCohortSeed);
// If the user is already assigned to a valid cohort, we're done.
if (cohort_ >= 0 && cohort_ < RapporParameters::kMaxCohorts)
return;
if (cohort >= 0 && cohort < RapporParameters::kMaxCohorts)
return cohort;
// This is the first time the client has started the service (or their
// preferences were corrupted). Randomly assign them to a cohort.
cohort_ = base::RandGenerator(RapporParameters::kMaxCohorts);
DVLOG(2) << "Selected a new Rappor cohort: " << cohort_;
pref_service_->SetInteger(prefs::kRapporCohortSeed, cohort_);
cohort = base::RandGenerator(RapporParameters::kMaxCohorts);
DVLOG(2) << "Selected a new Rappor cohort: " << cohort;
pref_service_->SetInteger(prefs::kRapporCohortSeed, cohort);
return cohort;
}
void RapporService::LoadSecret() {
DCHECK(secret_.empty());
std::string RapporService::LoadSecret() {
std::string secret;
std::string secret_base64 = pref_service_->GetString(prefs::kRapporSecret);
if (!secret_base64.empty()) {
bool decoded = base::Base64Decode(secret_base64, &secret_);
bool decoded = base::Base64Decode(secret_base64, &secret);
if (decoded && secret_.size() == HmacByteVectorGenerator::kEntropyInputSize)
return;
return secret;
// If the preference fails to decode, or is the wrong size, it must be
// corrupt, so continue as though it didn't exist yet and generate a new
// one.
}
DVLOG(2) << "Generated a new Rappor secret.";
secret_ = HmacByteVectorGenerator::GenerateEntropyInput();
base::Base64Encode(secret_, &secret_base64);
secret = HmacByteVectorGenerator::GenerateEntropyInput();
base::Base64Encode(secret, &secret_base64);
pref_service_->SetString(prefs::kRapporSecret, secret_base64);
return secret;
}
bool RapporService::ExportMetrics(RapporReports* reports) {
......@@ -203,16 +223,21 @@ void RapporService::RecordSample(const std::string& metric_name,
if (!IsInitialized())
return;
DCHECK_LT(type, NUM_RAPPOR_TYPES);
const RapporParameters& parameters = kRapporParametersForType[type];
DVLOG(2) << "Recording sample \"" << sample
<< "\" for metric \"" << metric_name
<< "\" of type: " << type;
RecordSampleInternal(metric_name, kRapporParametersForType[type], sample);
RecordSampleInternal(metric_name, parameters, sample);
}
void RapporService::RecordSampleInternal(const std::string& metric_name,
const RapporParameters& parameters,
const std::string& sample) {
DCHECK(IsInitialized());
// Skip this metric if it's reporting level is less than the enabled
// reporting level.
if (reporting_level_ < parameters.reporting_level)
return;
RapporMetric* metric = LookUpMetric(metric_name, parameters);
metric->AddSample(sample);
}
......
......@@ -13,6 +13,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/timer/timer.h"
#include "components/metrics/daily_event.h"
#include "components/rappor/rappor_parameters.h"
class PrefRegistrySimple;
class PrefService;
......@@ -26,12 +27,12 @@ namespace rappor {
class LogUploader;
class RapporMetric;
class RapporReports;
struct RapporParameters;
// The type of data stored in a metric.
enum RapporType {
// For sampling the eTLD+1 of a URL.
ETLD_PLUS_ONE_RAPPOR_TYPE = 0,
COARSE_RAPPOR_TYPE,
NUM_RAPPOR_TYPES
};
......@@ -49,6 +50,7 @@ class RapporService {
void AddDailyObserver(scoped_ptr<metrics::DailyEvent::Observer> observer);
// Starts the periodic generation of reports and upload attempts.
// |metrics enabled| should be true if UMA users have opted in.
void Start(net::URLRequestContextGetter* context,
bool metrics_enabled);
......@@ -58,25 +60,24 @@ class RapporService {
RapporType type,
const std::string& sample);
// Sets the cohort value. For use by tests only.
void SetCohortForTesting(uint32_t cohort) { cohort_ = cohort; }
// Sets the secret value. For use by tests only.
void SetSecretForTesting(const std::string& secret) { secret_ = secret; }
// Registers the names of all of the preferences used by RapporService in the
// provided PrefRegistry. This should be called before calling Start().
static void RegisterPrefs(PrefRegistrySimple* registry);
protected:
// Initializes the state of the RapporService.
void Initialize(int32_t cohort,
const std::string& secret,
const ReportingLevel& reporting_level);
// Retrieves the cohort number this client was assigned to, generating it if
// doesn't already exist. The cohort should be persistent.
void LoadCohort();
int32_t LoadCohort();
// Retrieves the value for secret_ from preferences, generating it if doesn't
// already exist. The secret should be persistent, so that additional bits
// from the client do not get exposed over time.
void LoadSecret();
std::string LoadSecret();
// Logs all of the collected metrics to the reports proto message and clears
// the internal map. Exposed for tests. Returns true if any metrics were
......@@ -121,6 +122,9 @@ class RapporService {
// A private LogUploader instance for sending reports to the server.
scoped_ptr<LogUploader> uploader_;
// What reporting level of metrics are being reported.
ReportingLevel reporting_level_;
// We keep all registered metrics in a map, from name to metric.
// The map owns the metrics it contains.
std::map<std::string, RapporMetric*> metrics_map_;
......
......@@ -16,34 +16,53 @@ namespace rappor {
class TestRapporService : public RapporService {
public:
TestRapporService() : RapporService(&prefs_) {
RegisterPrefs(prefs_.registry());
prefs_.SetInteger(prefs::kRapporCohortSeed, 0);
std::string secret = HmacByteVectorGenerator::GenerateEntropyInput();
std::string secret_base64;
base::Base64Encode(secret, &secret_base64);
prefs_.SetString(prefs::kRapporSecret, secret_base64);
LoadCohort();
LoadSecret();
TestRapporService(ReportingLevel reporting_level) : RapporService(&prefs) {
RegisterPrefs(prefs.registry());
Initialize(0,
HmacByteVectorGenerator::GenerateEntropyInput(),
reporting_level);
}
void GetReports(RapporReports* reports) {
ExportMetrics(reports);
}
int32_t TestLoadCohort() {
return LoadCohort();
}
std::string TestLoadSecret() {
return LoadSecret();
}
void TestRecordSample(const std::string& metric_name,
const RapporParameters& parameters,
const std::string& sample) {
RecordSampleInternal(metric_name, parameters, sample);
}
protected:
TestingPrefServiceSimple prefs_;
TestingPrefServiceSimple prefs;
private:
DISALLOW_COPY_AND_ASSIGN(TestRapporService);
};
TEST(RapporServiceTest, LoadCohort) {
TestRapporService rappor_service(REPORTING_DISABLED);
rappor_service.prefs.SetInteger(prefs::kRapporCohortSeed, 1);
EXPECT_EQ(1, rappor_service.TestLoadCohort());
}
TEST(RapporServiceTest, LoadSecret) {
TestRapporService rappor_service(REPORTING_DISABLED);
std::string secret = HmacByteVectorGenerator::GenerateEntropyInput();
std::string secret_base64;
base::Base64Encode(secret, &secret_base64);
rappor_service.prefs.SetString(prefs::kRapporSecret, secret_base64);
EXPECT_EQ(secret, rappor_service.TestLoadSecret());
}
// Check that samples can be recorded and exported.
TEST(RapporServiceTest, RecordAndExportMetrics) {
const RapporParameters kTestRapporParameters = {
1 /* Num cohorts */,
......@@ -52,10 +71,12 @@ TEST(RapporServiceTest, RecordAndExportMetrics) {
PROBABILITY_75 /* Fake data probability */,
PROBABILITY_50 /* Fake one probability */,
PROBABILITY_75 /* One coin probability */,
PROBABILITY_50 /* Zero coin probability */};
PROBABILITY_50 /* Zero coin probability */,
COARSE_LEVEL};
TestRapporService rappor_service;
TestRapporService rappor_service(COARSE_LEVEL);
// Multiple samples for the same metric should only generate one report.
rappor_service.TestRecordSample("MyMetric", kTestRapporParameters, "foo");
rappor_service.TestRecordSample("MyMetric", kTestRapporParameters, "bar");
......@@ -68,4 +89,25 @@ TEST(RapporServiceTest, RecordAndExportMetrics) {
EXPECT_EQ(16u, report.bits().size());
}
// Check that the reporting level is respected.
TEST(RapporServiceTest, ReportingLevel) {
const RapporParameters kFineRapporParameters = {
1 /* Num cohorts */,
16 /* Bloom filter size bytes */,
4 /* Bloom filter hash count */,
PROBABILITY_75 /* Fake data probability */,
PROBABILITY_50 /* Fake one probability */,
PROBABILITY_75 /* One coin probability */,
PROBABILITY_50 /* Zero coin probability */,
FINE_LEVEL};
TestRapporService rappor_service(COARSE_LEVEL);
rappor_service.TestRecordSample("FineMetric", kFineRapporParameters, "foo");
RapporReports reports;
rappor_service.GetReports(&reports);
EXPECT_EQ(0, reports.report_size());
}
} // namespace rappor
......@@ -15,6 +15,46 @@ TODO(holte): Add validation and pretty printing scripts.
<rappor-configuration>
<!--
Parameters that rappor metrics can be collected with. This list should be
kept in sync with parameter type definitions in
components/rappor/rappor_service.cc.
-->
<rappor-parameter-types>
<rappor-parameters name="ETLD_PLUS_ONE">
<summary>
Parameters suitable for collecting the domain and registry of a URL from
UMA opt-in users.
</summary>
<parameters num-cohorts="128"
bytes="16"
hash-functions="2"
fake-prob=".5"
fake-one-prob=".5"
one-coin-prob=".75"
zero-coin-prob=".25"
reporting-level="FINE">
</parameters>
</rappor-parameters>
<rappor-parameters name="COARSE_RAPPOR_TYPE">
<summary>
Stricter parameters for metrics collected from a broader population.
</summary>
<parameters num-cohorts="128"
bytes="1"
hash-functions="2"
fake-prob=".5"
fake-one-prob=".5"
one-coin-prob=".75"
zero-coin-prob=".25"
reporting-level="COARSE">
</rappor-parameters>
</rappor-parameter-types>
<!-- Rappor metric definitions -->
<rappor-metrics>
......
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