Implement a caching permuted entropy provider on mobile platforms.
Introduces a CachingPermutedEntropyProvider class that adds a cache
in local state for storing previous permuted entropy provider results.
This avoids (expensively) recomputing them each time, which is on
the startup path.
BUG=236972
TEST=New unit tests and verifying performance in iOS build using Instruments.
Review URL: https://chromiumcodereview.appspot.com/14373025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198680 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/common/metrics/entropy_provider.cc b/chrome/common/metrics/entropy_provider.cc
index 558e0f9e..1c19cd2 100644
--- a/chrome/common/metrics/entropy_provider.cc
+++ b/chrome/common/metrics/entropy_provider.cc
@@ -8,11 +8,15 @@
#include <limits>
#include <vector>
+#include "base/base64.h"
#include "base/logging.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/sha1.h"
#include "base/sys_byteorder.h"
#include "chrome/common/metrics/metrics_util.h"
+#include "chrome/common/pref_names.h"
namespace metrics {
@@ -102,13 +106,118 @@
double PermutedEntropyProvider::GetEntropyForTrial(
const std::string& trial_name,
uint32 randomization_seed) const {
- std::vector<uint16> mapping(low_entropy_source_max_);
if (randomization_seed == 0)
randomization_seed = HashName(trial_name);
+
+ std::vector<uint16> mapping(low_entropy_source_max_);
internal::PermuteMappingUsingRandomizationSeed(randomization_seed, &mapping);
return mapping[low_entropy_source_] /
static_cast<double>(low_entropy_source_max_);
}
+CachingPermutedEntropyProvider::CachingPermutedEntropyProvider(
+ PrefService* local_state,
+ uint16 low_entropy_source,
+ size_t low_entropy_source_max)
+ : local_state_(local_state),
+ low_entropy_source_(low_entropy_source),
+ low_entropy_source_max_(low_entropy_source_max) {
+ DCHECK_LT(low_entropy_source, low_entropy_source_max);
+ DCHECK_LE(low_entropy_source_max, std::numeric_limits<uint16>::max());
+ ReadFromLocalState();
+}
+
+CachingPermutedEntropyProvider::~CachingPermutedEntropyProvider() {
+}
+
+// static
+void CachingPermutedEntropyProvider::RegisterPrefs(
+ PrefRegistrySimple* registry) {
+ registry->RegisterStringPref(prefs::kMetricsPermutedEntropyCache,
+ std::string());
+}
+
+// static
+void CachingPermutedEntropyProvider::ClearCache(PrefService* local_state) {
+ local_state->ClearPref(prefs::kMetricsPermutedEntropyCache);
+}
+
+double CachingPermutedEntropyProvider::GetEntropyForTrial(
+ const std::string& trial_name,
+ uint32 randomization_seed) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (randomization_seed == 0)
+ randomization_seed = HashName(trial_name);
+
+ uint16 value = 0;
+ if (!FindValue(randomization_seed, &value)) {
+ std::vector<uint16> mapping(low_entropy_source_max_);
+ internal::PermuteMappingUsingRandomizationSeed(randomization_seed,
+ &mapping);
+ value = mapping[low_entropy_source_];
+ AddToCache(randomization_seed, value);
+ }
+
+ return value / static_cast<double>(low_entropy_source_max_);
+}
+
+void CachingPermutedEntropyProvider::ReadFromLocalState() const {
+ const std::string base64_cache_data =
+ local_state_->GetString(prefs::kMetricsPermutedEntropyCache);
+ std::string cache_data;
+ if (!base::Base64Decode(base64_cache_data, &cache_data) ||
+ !cache_.ParseFromString(cache_data)) {
+ local_state_->ClearPref(prefs::kMetricsPermutedEntropyCache);
+ NOTREACHED();
+ }
+}
+
+void CachingPermutedEntropyProvider::UpdateLocalState() const {
+ std::string serialized;
+ cache_.SerializeToString(&serialized);
+
+ std::string base64_encoded;
+ if (!base::Base64Encode(serialized, &base64_encoded)) {
+ NOTREACHED();
+ return;
+ }
+ local_state_->SetString(prefs::kMetricsPermutedEntropyCache, base64_encoded);
+}
+
+void CachingPermutedEntropyProvider::AddToCache(uint32 randomization_seed,
+ uint16 value) const {
+ PermutedEntropyCache::Entry* entry;
+ const int kMaxSize = 25;
+ if (cache_.entry_size() >= kMaxSize) {
+ // If the cache is full, evict the first entry, swapping later entries in
+ // to take its place. This effectively creates a FIFO cache, which is good
+ // enough here because the expectation is that there shouldn't be more than
+ // |kMaxSize| field trials at any given time, so eviction should happen very
+ // rarely, only as new trials are introduced, evicting old expired trials.
+ for (int i = 1; i < kMaxSize; ++i)
+ cache_.mutable_entry()->SwapElements(i - 1, i);
+ entry = cache_.mutable_entry(kMaxSize - 1);
+ } else {
+ entry = cache_.add_entry();
+ }
+
+ entry->set_randomization_seed(randomization_seed);
+ entry->set_value(value);
+
+ UpdateLocalState();
+}
+
+bool CachingPermutedEntropyProvider::FindValue(uint32 randomization_seed,
+ uint16* value) const {
+ for (int i = 0; i < cache_.entry_size(); ++i) {
+ if (cache_.entry(i).randomization_seed() == randomization_seed) {
+ *value = cache_.entry(i).value();
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace metrics