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