Factor out NetworkID and caching mechanism from n_q_e.{h,cc}

This keeps the caching mechanism separate from NQE. This
is first step towards enabling persistent caching of
estimates.

This CL does not record any functional changes, although
tests have been expanded.

Also, add UMA to record how frequently cached network
quality is available.

BUG=490870

Review-Url: https://codereview.chromium.org/2128793003
Cr-Commit-Position: refs/heads/master@{#407521}
diff --git a/net/net.gypi b/net/net.gypi
index f2fadb4..10d9eb5 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -747,12 +747,15 @@
       'nqe/cached_network_quality.cc',
       'nqe/cached_network_quality.h',
       'nqe/external_estimate_provider.h',
+      'nqe/network_id.h',
       'nqe/network_quality.cc',
       'nqe/network_quality.h',
       'nqe/network_quality_estimator.cc',
       'nqe/network_quality_estimator.h',
       'nqe/network_quality_observation.h',
       'nqe/network_quality_observation_source.h',
+      'nqe/network_quality_store.cc',
+      'nqe/network_quality_store.h',
       'nqe/observation_buffer.h',
       'nqe/socket_watcher.cc',
       'nqe/socket_watcher.h',
@@ -1558,6 +1561,7 @@
       'log/write_to_file_net_log_observer_unittest.cc',
       'nqe/network_quality_estimator_unittest.cc',
       'nqe/network_quality_observation_unittest.cc',
+      'nqe/network_quality_store_unittest.cc',
       'nqe/throughput_analyzer_unittest.cc',
       'proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc',
       'proxy/dhcp_proxy_script_fetcher_factory_unittest.cc',
diff --git a/net/nqe/cached_network_quality.cc b/net/nqe/cached_network_quality.cc
index 5902f75a..d4e454d 100644
--- a/net/nqe/cached_network_quality.cc
+++ b/net/nqe/cached_network_quality.cc
@@ -10,10 +10,12 @@
 
 namespace internal {
 
+CachedNetworkQuality::CachedNetworkQuality() {}
+
 CachedNetworkQuality::CachedNetworkQuality(
+    base::TimeTicks last_update_time,
     const NetworkQuality& network_quality)
-    : last_update_time_(base::TimeTicks::Now()),
-      network_quality_(network_quality) {}
+    : last_update_time_(last_update_time), network_quality_(network_quality) {}
 
 CachedNetworkQuality::CachedNetworkQuality(const CachedNetworkQuality& other)
     : last_update_time_(other.last_update_time_),
@@ -21,6 +23,13 @@
 
 CachedNetworkQuality::~CachedNetworkQuality() {}
 
+CachedNetworkQuality& CachedNetworkQuality::operator=(
+    const CachedNetworkQuality& other) {
+  last_update_time_ = other.last_update_time_;
+  network_quality_ = other.network_quality_;
+  return *this;
+}
+
 bool CachedNetworkQuality::OlderThan(
     const CachedNetworkQuality& cached_network_quality) const {
   return last_update_time_ < cached_network_quality.last_update_time_;
diff --git a/net/nqe/cached_network_quality.h b/net/nqe/cached_network_quality.h
index cc0af2f..a8fae2f7 100644
--- a/net/nqe/cached_network_quality.h
+++ b/net/nqe/cached_network_quality.h
@@ -19,25 +19,33 @@
 // CachedNetworkQuality stores the quality of a previously seen network.
 class NET_EXPORT_PRIVATE CachedNetworkQuality {
  public:
-  explicit CachedNetworkQuality(const NetworkQuality& network_quality);
+  CachedNetworkQuality();
+
+  // |last_update_time| is the time when the |network_quality| was computed.
+  CachedNetworkQuality(base::TimeTicks last_update_time,
+                       const NetworkQuality& network_quality);
   CachedNetworkQuality(const CachedNetworkQuality& other);
   ~CachedNetworkQuality();
 
   // Returns the network quality associated with this cached entry.
   const NetworkQuality& network_quality() const { return network_quality_; }
 
+  CachedNetworkQuality& operator=(const CachedNetworkQuality& other);
+
   // Returns true if this cache entry was updated before
   // |cached_network_quality|.
   bool OlderThan(const CachedNetworkQuality& cached_network_quality) const;
 
-  // Time when this cache entry was last updated.
-  const base::TimeTicks last_update_time_;
+  base::TimeTicks last_update_time() { return last_update_time_; }
 
-  // Quality of this cached network.
-  const NetworkQuality network_quality_;
+  const NetworkQuality& network_quality() { return network_quality_; }
 
  private:
-  DISALLOW_ASSIGN(CachedNetworkQuality);
+  // Time when this cache entry was last updated.
+  base::TimeTicks last_update_time_;
+
+  // Quality of this cached network.
+  NetworkQuality network_quality_;
 };
 
 }  // namespace internal
diff --git a/net/nqe/network_id.h b/net/nqe/network_id.h
new file mode 100644
index 0000000..cf8ceac9
--- /dev/null
+++ b/net/nqe/network_id.h
@@ -0,0 +1,59 @@
+// Copyright 2016 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.
+
+#ifndef NET_NQE_NETWORK_ID_H_
+#define NET_NQE_NETWORK_ID_H_
+
+#include <string>
+#include <tuple>
+
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+namespace nqe {
+namespace internal {
+
+// NetworkID is used to uniquely identify a network.
+// For the purpose of network quality estimation and caching, a network is
+// uniquely identified by a combination of |type| and
+// |id|. This approach is unable to distinguish networks with
+// same name (e.g., different Wi-Fi networks with same SSID).
+// This is a protected member to expose it to tests.
+struct NET_EXPORT_PRIVATE NetworkID {
+  NetworkID(NetworkChangeNotifier::ConnectionType type, const std::string& id)
+      : type(type), id(id) {}
+  NetworkID(const NetworkID& other) : type(other.type), id(other.id) {}
+  ~NetworkID() {}
+
+  NetworkID& operator=(const NetworkID& other) {
+    type = other.type;
+    id = other.id;
+    return *this;
+  }
+
+  // Overloaded to support ordered collections.
+  bool operator<(const NetworkID& other) const {
+    return std::tie(type, id) < std::tie(other.type, other.id);
+  }
+
+  // Connection type of the network.
+  NetworkChangeNotifier::ConnectionType type;
+
+  // Name of this network. This is set to:
+  // - Wi-Fi SSID if the device is connected to a Wi-Fi access point and the
+  //   SSID name is available, or
+  // - MCC/MNC code of the cellular carrier if the device is connected to a
+  //   cellular network, or
+  // - "Ethernet" in case the device is connected to ethernet.
+  // - An empty string in all other cases or if the network name is not
+  //   exposed by platform APIs.
+  std::string id;
+};
+
+}  // namespace internal
+}  // namespace nqe
+}  // namespace net
+
+#endif  // NET_NQE_NETWORK_QUALITY_ESTIMATOR_H_
\ No newline at end of file
diff --git a/net/nqe/network_quality.cc b/net/nqe/network_quality.cc
index 94bcc803..1132b0eb 100644
--- a/net/nqe/network_quality.cc
+++ b/net/nqe/network_quality.cc
@@ -5,9 +5,7 @@
 #include "net/nqe/network_quality.h"
 
 namespace net {
-
 namespace nqe {
-
 namespace internal {
 
 base::TimeDelta InvalidRTT() {
@@ -40,8 +38,12 @@
   return *this;
 }
 
+bool NetworkQuality::operator==(const NetworkQuality& other) const {
+  return http_rtt_ == other.http_rtt_ &&
+         transport_rtt_ == other.transport_rtt_ &&
+         downstream_throughput_kbps_ == other.downstream_throughput_kbps_;
+}
+
 }  // namespace internal
-
 }  // namespace nqe
-
 }  // namespace net
\ No newline at end of file
diff --git a/net/nqe/network_quality.h b/net/nqe/network_quality.h
index bc1ec77..dc32ef4e 100644
--- a/net/nqe/network_quality.h
+++ b/net/nqe/network_quality.h
@@ -13,9 +13,7 @@
 #include "net/base/net_export.h"
 
 namespace net {
-
 namespace nqe {
-
 namespace internal {
 
 // Returns the RTT value to be used when the valid RTT is unavailable. Readers
@@ -43,6 +41,8 @@
 
   NetworkQuality& operator=(const NetworkQuality& other);
 
+  bool operator==(const NetworkQuality& other) const;
+
   // Returns the estimate of the round trip time at the HTTP layer.
   const base::TimeDelta& http_rtt() const { return http_rtt_; }
 
@@ -77,9 +77,7 @@
 };
 
 }  // namespace internal
-
 }  // namespace nqe
-
 }  // namespace net
 
 #endif  // NET_NQE_NETWORK_QUALITY_H_
\ No newline at end of file
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc
index 8e37d46..f84455f 100644
--- a/net/nqe/network_quality_estimator.cc
+++ b/net/nqe/network_quality_estimator.cc
@@ -307,9 +307,9 @@
       effective_connection_type_recomputation_interval_(
           base::TimeDelta::FromSeconds(15)),
       last_connection_change_(tick_clock_->NowTicks()),
-      current_network_id_(
-          NetworkID(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
-                    std::string())),
+      current_network_id_(nqe::internal::NetworkID(
+          NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
+          std::string())),
       downstream_throughput_kbps_observations_(weight_multiplier_per_second_),
       rtt_observations_(weight_multiplier_per_second_),
       effective_connection_type_at_last_main_frame_(
@@ -326,12 +326,6 @@
       weak_ptr_factory_(this) {
   static_assert(kDefaultHalfLifeSeconds > 0,
                 "Default half life duration must be > 0");
-  static_assert(kMaximumNetworkQualityCacheSize > 0,
-                "Size of the network quality cache must be > 0");
-  // This limit should not be increased unless the logic for removing the
-  // oldest cache entry is rewritten to use a doubly-linked-list LRU queue.
-  static_assert(kMaximumNetworkQualityCacheSize <= 10,
-                "Size of the network quality cache must <= 10");
   // None of the algorithms can have an empty name.
   DCHECK(algorithm_name_to_enum_.end() ==
          algorithm_name_to_enum_.find(std::string()));
@@ -923,7 +917,10 @@
   RecordMetricsOnConnectionTypeChanged();
 
   // Write the estimates of the previous network to the cache.
-  CacheNetworkQualityEstimate();
+  network_quality_store_.Add(current_network_id_,
+                             nqe::internal::CachedNetworkQuality(
+                                 last_effective_connection_type_computation_,
+                                 estimated_quality_at_last_main_frame_));
 
   // Clear the local state.
   last_connection_change_ = tick_clock_->NowTicks();
@@ -1170,7 +1167,7 @@
 
   // If the device is currently offline, then return
   // EFFECTIVE_CONNECTION_TYPE_OFFLINE.
-  if (GetCurrentNetworkID().type == NetworkChangeNotifier::CONNECTION_NONE)
+  if (current_network_id_.type == NetworkChangeNotifier::CONNECTION_NONE)
     return EFFECTIVE_CONNECTION_TYPE_OFFLINE;
 
   base::TimeDelta http_rtt = nqe::internal::InvalidRTT();
@@ -1355,8 +1352,7 @@
   return kbps;
 }
 
-NetworkQualityEstimator::NetworkID
-NetworkQualityEstimator::GetCurrentNetworkID() const {
+nqe::internal::NetworkID NetworkQualityEstimator::GetCurrentNetworkID() const {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // TODO(tbansal): crbug.com/498068 Add NetworkQualityEstimatorAndroid class
@@ -1369,7 +1365,7 @@
   // capture majority of cases, and should not significantly affect estimates
   // (that are approximate to begin with).
   while (true) {
-    NetworkQualityEstimator::NetworkID network_id(
+    nqe::internal::NetworkID network_id(
         NetworkChangeNotifier::GetConnectionType(), std::string());
 
     switch (network_id.type) {
@@ -1405,42 +1401,37 @@
 bool NetworkQualityEstimator::ReadCachedNetworkQualityEstimate() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  // If the network name is unavailable, caching should not be performed.
-  if (current_network_id_.id.empty())
+  nqe::internal::CachedNetworkQuality cached_network_quality;
+
+  const bool cached_estimate_available = network_quality_store_.GetById(
+      current_network_id_, &cached_network_quality);
+  UMA_HISTOGRAM_BOOLEAN("NQE.CachedNetworkQualityAvailable",
+                        cached_estimate_available);
+
+  if (!cached_estimate_available)
     return false;
 
-  CachedNetworkQualities::const_iterator it =
-      cached_network_qualities_.find(current_network_id_);
-
-  if (it == cached_network_qualities_.end())
-    return false;
-
-  nqe::internal::NetworkQuality network_quality(it->second.network_quality());
-
   const base::TimeTicks now = tick_clock_->NowTicks();
-  bool read_cached_estimate = false;
 
-  if (network_quality.downstream_throughput_kbps() !=
+  if (cached_network_quality.network_quality().downstream_throughput_kbps() !=
       nqe::internal::kInvalidThroughput) {
-    read_cached_estimate = true;
     ThroughputObservation througphput_observation(
-        network_quality.downstream_throughput_kbps(), now,
-        NETWORK_QUALITY_OBSERVATION_SOURCE_CACHED_ESTIMATE);
+        cached_network_quality.network_quality().downstream_throughput_kbps(),
+        now, NETWORK_QUALITY_OBSERVATION_SOURCE_CACHED_ESTIMATE);
     downstream_throughput_kbps_observations_.AddObservation(
         througphput_observation);
     NotifyObserversOfThroughput(througphput_observation);
   }
 
-  if (network_quality.http_rtt() != nqe::internal::InvalidRTT()) {
-    read_cached_estimate = true;
+  if (cached_network_quality.network_quality().http_rtt() !=
+      nqe::internal::InvalidRTT()) {
     RttObservation rtt_observation(
-        network_quality.http_rtt(), now,
+        cached_network_quality.network_quality().http_rtt(), now,
         NETWORK_QUALITY_OBSERVATION_SOURCE_CACHED_ESTIMATE);
     rtt_observations_.AddObservation(rtt_observation);
     NotifyObserversOfRTT(rtt_observation);
   }
-
-  return read_cached_estimate;
+  return true;
 }
 
 void NetworkQualityEstimator::OnUpdatedEstimateAvailable(
@@ -1531,51 +1522,6 @@
   return base::RandDouble();
 }
 
-void NetworkQualityEstimator::CacheNetworkQualityEstimate() {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK_LE(cached_network_qualities_.size(),
-            static_cast<size_t>(kMaximumNetworkQualityCacheSize));
-
-  // If the network name is unavailable, caching should not be performed.
-  if (current_network_id_.id.empty())
-    return;
-
-  base::TimeDelta http_rtt = nqe::internal::InvalidRTT();
-  int32_t downlink_throughput_kbps = nqe::internal::kInvalidThroughput;
-
-  if (!GetHttpRTTEstimate(&http_rtt) ||
-      !GetDownlinkThroughputKbpsEstimate(&downlink_throughput_kbps)) {
-    return;
-  }
-
-  // |transport_rtt| is currently not cached.
-  nqe::internal::NetworkQuality network_quality = nqe::internal::NetworkQuality(
-      http_rtt, nqe::internal::InvalidRTT() /* transport_rtt */,
-      downlink_throughput_kbps);
-
-  if (cached_network_qualities_.size() == kMaximumNetworkQualityCacheSize) {
-    // Remove the oldest entry.
-    CachedNetworkQualities::iterator oldest_entry_iterator =
-        cached_network_qualities_.begin();
-
-    for (CachedNetworkQualities::iterator it =
-             cached_network_qualities_.begin();
-         it != cached_network_qualities_.end(); ++it) {
-      if ((it->second).OlderThan(oldest_entry_iterator->second))
-        oldest_entry_iterator = it;
-    }
-    cached_network_qualities_.erase(oldest_entry_iterator);
-  }
-  DCHECK_LT(cached_network_qualities_.size(),
-            static_cast<size_t>(kMaximumNetworkQualityCacheSize));
-
-  cached_network_qualities_.insert(
-      std::make_pair(current_network_id_,
-                     nqe::internal::CachedNetworkQuality(network_quality)));
-  DCHECK_LE(cached_network_qualities_.size(),
-            static_cast<size_t>(kMaximumNetworkQualityCacheSize));
-}
-
 void NetworkQualityEstimator::OnUpdatedRTTAvailable(
     SocketPerformanceWatcherFactory::Protocol protocol,
     const base::TimeDelta& rtt) {
diff --git a/net/nqe/network_quality_estimator.h b/net/nqe/network_quality_estimator.h
index 44fb303..04329ef 100644
--- a/net/nqe/network_quality_estimator.h
+++ b/net/nqe/network_quality_estimator.h
@@ -10,7 +10,6 @@
 #include <map>
 #include <memory>
 #include <string>
-#include <tuple>
 
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
@@ -24,9 +23,11 @@
 #include "net/base/network_change_notifier.h"
 #include "net/nqe/cached_network_quality.h"
 #include "net/nqe/external_estimate_provider.h"
+#include "net/nqe/network_id.h"
 #include "net/nqe/network_quality.h"
 #include "net/nqe/network_quality_observation.h"
 #include "net/nqe/network_quality_observation_source.h"
+#include "net/nqe/network_quality_store.h"
 #include "net/nqe/observation_buffer.h"
 #include "net/socket/socket_performance_watcher_factory.h"
 
@@ -243,46 +244,6 @@
       EffectiveConnectionType effective_connection_type);
 
  protected:
-  // NetworkID is used to uniquely identify a network.
-  // For the purpose of network quality estimation and caching, a network is
-  // uniquely identified by a combination of |type| and
-  // |id|. This approach is unable to distinguish networks with
-  // same name (e.g., different Wi-Fi networks with same SSID).
-  // This is a protected member to expose it to tests.
-  struct NET_EXPORT_PRIVATE NetworkID {
-    NetworkID(NetworkChangeNotifier::ConnectionType type, const std::string& id)
-        : type(type), id(id) {}
-    NetworkID(const NetworkID& other) : type(other.type), id(other.id) {}
-    ~NetworkID() {}
-
-    NetworkID& operator=(const NetworkID& other) {
-      type = other.type;
-      id = other.id;
-      return *this;
-    }
-
-    // Overloaded because NetworkID is used as key in a map.
-    bool operator<(const NetworkID& other) const {
-      return std::tie(type, id) < std::tie(other.type, other.id);
-    }
-
-    // Connection type of the network.
-    NetworkChangeNotifier::ConnectionType type;
-
-    // Name of this network. This is set to:
-    // - Wi-Fi SSID if the device is connected to a Wi-Fi access point and the
-    //   SSID name is available, or
-    // - MCC/MNC code of the cellular carrier if the device is connected to a
-    //   cellular network, or
-    // - "Ethernet" in case the device is connected to ethernet.
-    // - An empty string in all other cases or if the network name is not
-    //   exposed by platform APIs.
-    std::string id;
-  };
-
-  // Returns true if the cached network quality estimate was successfully read.
-  bool ReadCachedNetworkQualityEstimate();
-
   // NetworkChangeNotifier::ConnectionTypeObserver implementation:
   void OnConnectionTypeChanged(
       NetworkChangeNotifier::ConnectionType type) override;
@@ -360,9 +321,6 @@
                            ObtainAlgorithmToUseFromParams);
   FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, HalfLifeParam);
   FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, ComputedPercentiles);
-  FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestCaching);
-  FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest,
-                           TestLRUCacheMaximumSize);
   FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestGetMetricsSince);
   FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest,
                            TestExternalEstimateProviderMergeEstimates);
@@ -376,12 +334,6 @@
   typedef nqe::internal::Observation<int32_t> ThroughputObservation;
   typedef nqe::internal::ObservationBuffer<int32_t> ThroughputObservationBuffer;
 
-  // This does not use a unordered_map or hash_map for code simplicity (key just
-  // implements operator<, rather than hash and equality) and because the map is
-  // tiny.
-  typedef std::map<NetworkID, nqe::internal::CachedNetworkQuality>
-      CachedNetworkQualities;
-
   // Algorithms supported by network quality estimator for computing effective
   // connection type.
   enum class EffectiveConnectionTypeAlgorithm {
@@ -424,11 +376,6 @@
   // kilobits per second) values.
   static const int kMinimumThroughputVariationParameterKbps = 1;
 
-  // Maximum size of the cache that holds network quality estimates.
-  // Smaller size may reduce the cache hit rate due to frequent evictions.
-  // Larger size may affect performance.
-  static const size_t kMaximumNetworkQualityCacheSize = 10;
-
   // Returns the RTT value to be used when the valid RTT is unavailable. Readers
   // should discard RTT if it is set to the value returned by |InvalidRTT()|.
   static const base::TimeDelta InvalidRTT();
@@ -487,10 +434,7 @@
 
   // Returns the current network ID checking by calling the platform APIs.
   // Virtualized for testing.
-  virtual NetworkID GetCurrentNetworkID() const;
-
-  // Writes the estimated quality of the current network to the cache.
-  void CacheNetworkQualityEstimate();
+  virtual nqe::internal::NetworkID GetCurrentNetworkID() const;
 
   void NotifyObserversOfRTT(const RttObservation& observation);
 
@@ -546,6 +490,9 @@
   void RecordExternalEstimateProviderMetrics(
       NQEExternalEstimateProviderStatus status) const;
 
+  // Returns true if the cached network quality estimate was successfully read.
+  bool ReadCachedNetworkQualityEstimate();
+
   // Records a correlation metric that can be used for computing the correlation
   // between HTTP-layer RTT, transport-layer RTT, throughput and the time
   // taken to complete |request|.
@@ -591,7 +538,7 @@
   base::TimeTicks last_connection_change_;
 
   // ID of the current network.
-  NetworkID current_network_id_;
+  nqe::internal::NetworkID current_network_id_;
 
   // Peak network quality (fastest round-trip-time (RTT) and highest
   // downstream throughput) measured since last connectivity change. RTT is
@@ -601,9 +548,6 @@
   // 2) Includes server processing time.
   nqe::internal::NetworkQuality peak_network_quality_;
 
-  // Cache that stores quality of previously seen networks.
-  CachedNetworkQualities cached_network_qualities_;
-
   // Buffer that holds throughput observations (in kilobits per second) sorted
   // by timestamp.
   ThroughputObservationBuffer downstream_throughput_kbps_observations_;
@@ -670,6 +614,9 @@
   // if it is 1.0, then it will be recorded for all valid HTTP requests.
   const double correlation_uma_logging_probability_;
 
+  // Stores the qualities of different networks.
+  nqe::internal::NetworkQualityStore network_quality_store_;
+
   base::ThreadChecker thread_checker_;
 
   base::WeakPtrFactory<NetworkQualityEstimator> weak_ptr_factory_;
diff --git a/net/nqe/network_quality_estimator_unittest.cc b/net/nqe/network_quality_estimator_unittest.cc
index 643dff52..9b67bcc 100644
--- a/net/nqe/network_quality_estimator_unittest.cc
+++ b/net/nqe/network_quality_estimator_unittest.cc
@@ -269,16 +269,14 @@
   double RandDouble() const override { return rand_double_; }
 
   using NetworkQualityEstimator::SetTickClockForTesting;
-  using NetworkQualityEstimator::ReadCachedNetworkQualityEstimate;
   using NetworkQualityEstimator::OnConnectionTypeChanged;
 
  private:
   // NetworkQualityEstimator implementation that returns the overridden
   // network
   // id (instead of invoking platform APIs).
-  NetworkQualityEstimator::NetworkID GetCurrentNetworkID() const override {
-    return NetworkQualityEstimator::NetworkID(current_network_type_,
-                                              current_network_id_);
+  nqe::internal::NetworkID GetCurrentNetworkID() const override {
+    return nqe::internal::NetworkID(current_network_type_, current_network_id_);
   }
 
   bool effective_connection_type_set_;
@@ -398,6 +396,11 @@
   std::map<std::string, std::string> variation_params;
   TestNetworkQualityEstimator estimator(variation_params);
 
+  estimator.SimulateNetworkChangeTo(
+      NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, "test");
+  histogram_tester.ExpectUniqueSample("NQE.CachedNetworkQualityAvailable",
+                                      false, 1);
+
   base::TimeDelta rtt;
   int32_t kbps;
   EXPECT_FALSE(estimator.GetHttpRTTEstimate(&rtt));
@@ -438,6 +441,8 @@
 
   estimator.SimulateNetworkChangeTo(
       NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI, "test-1");
+  histogram_tester.ExpectUniqueSample("NQE.CachedNetworkQualityAvailable",
+                                      false, 2);
   histogram_tester.ExpectTotalCount("NQE.PeakKbps.Unknown", 1);
   histogram_tester.ExpectTotalCount("NQE.FastestRTT.Unknown", 1);
 
@@ -464,6 +469,8 @@
 
   estimator.SimulateNetworkChangeTo(
       NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI, std::string());
+  histogram_tester.ExpectUniqueSample("NQE.CachedNetworkQualityAvailable",
+                                      false, 3);
   histogram_tester.ExpectTotalCount("NQE.PeakKbps.Unknown", 1);
   histogram_tester.ExpectTotalCount("NQE.FastestRTT.Unknown", 1);
 
@@ -480,6 +487,13 @@
       NetworkQualityEstimator::EffectiveConnectionType::
           EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
       1);
+
+  estimator.SimulateNetworkChangeTo(
+      NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, "test");
+  histogram_tester.ExpectBucketCount("NQE.CachedNetworkQualityAvailable", false,
+                                     3);
+  histogram_tester.ExpectBucketCount("NQE.CachedNetworkQualityAvailable", true,
+                                     1);
 }
 
 TEST(NetworkQualityEstimatorTest, StoreObservations) {
@@ -986,164 +1000,6 @@
   }
 }
 
-// Test if the network estimates are cached when network change notification
-// is invoked.
-TEST(NetworkQualityEstimatorTest, TestCaching) {
-  std::map<std::string, std::string> variation_params;
-  TestNetworkQualityEstimator estimator(variation_params);
-  size_t expected_cache_size = 0;
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Cache entry will not be added for (NONE, "").
-  estimator.downstream_throughput_kbps_observations_.AddObservation(
-      NetworkQualityEstimator::ThroughputObservation(
-          1, base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.rtt_observations_.AddObservation(
-      NetworkQualityEstimator::RttObservation(
-          base::TimeDelta::FromMilliseconds(1000), base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test-1");
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Entry will be added for (2G, "test1").
-  // Also, set the network quality for (2G, "test1") so that it is stored in
-  // the cache.
-  estimator.downstream_throughput_kbps_observations_.AddObservation(
-      NetworkQualityEstimator::ThroughputObservation(
-          1, base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.rtt_observations_.AddObservation(
-      NetworkQualityEstimator::RttObservation(
-          base::TimeDelta::FromMilliseconds(1000), base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_3G, "test-1");
-  ++expected_cache_size;
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Entry will be added for (3G, "test1").
-  // Also, set the network quality for (3G, "test1") so that it is stored in
-  // the cache.
-  estimator.downstream_throughput_kbps_observations_.AddObservation(
-      NetworkQualityEstimator::ThroughputObservation(
-          2, base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.rtt_observations_.AddObservation(
-      NetworkQualityEstimator::RttObservation(
-          base::TimeDelta::FromMilliseconds(500), base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_3G, "test-2");
-  ++expected_cache_size;
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Entry will not be added for (3G, "test2").
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test-1");
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Read the network quality for (2G, "test-1").
-  EXPECT_TRUE(estimator.ReadCachedNetworkQualityEstimate());
-
-  base::TimeDelta rtt;
-  int32_t kbps;
-  EXPECT_TRUE(estimator.GetHttpRTTEstimate(&rtt));
-  EXPECT_TRUE(estimator.GetDownlinkThroughputKbpsEstimate(&kbps));
-  EXPECT_EQ(1, kbps);
-  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1000), rtt);
-  EXPECT_FALSE(estimator.GetTransportRTTEstimate(&rtt));
-
-  // No new entry should be added for (2G, "test-1") since it already exists
-  // in the cache.
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_3G, "test-1");
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Read the network quality for (3G, "test-1").
-  EXPECT_TRUE(estimator.ReadCachedNetworkQualityEstimate());
-  EXPECT_TRUE(estimator.GetHttpRTTEstimate(&rtt));
-  EXPECT_TRUE(estimator.GetDownlinkThroughputKbpsEstimate(&kbps));
-  EXPECT_EQ(2, kbps);
-  EXPECT_EQ(base::TimeDelta::FromMilliseconds(500), rtt);
-  // No new entry should be added for (3G, "test1") since it already exists
-  // in the cache.
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_3G, "test-2");
-  EXPECT_EQ(expected_cache_size, estimator.cached_network_qualities_.size());
-
-  // Reading quality of (3G, "test-2") should return false.
-  EXPECT_FALSE(estimator.ReadCachedNetworkQualityEstimate());
-
-  // Reading quality of (2G, "test-3") should return false.
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test-3");
-  EXPECT_FALSE(estimator.ReadCachedNetworkQualityEstimate());
-}
-
-// Tests if the cache size remains bounded. Also, ensure that the cache is
-// LRU.
-TEST(NetworkQualityEstimatorTest, TestLRUCacheMaximumSize) {
-  std::map<std::string, std::string> variation_params;
-  TestNetworkQualityEstimator estimator(variation_params);
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI, std::string());
-  EXPECT_EQ(0U, estimator.cached_network_qualities_.size());
-
-  // Add 100 more networks than the maximum size of the cache.
-  size_t network_count =
-      NetworkQualityEstimator::kMaximumNetworkQualityCacheSize + 100;
-
-  base::TimeTicks update_time_of_network_100;
-  for (size_t i = 0; i < network_count; ++i) {
-    estimator.downstream_throughput_kbps_observations_.AddObservation(
-        NetworkQualityEstimator::ThroughputObservation(
-            2, base::TimeTicks::Now(),
-            NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-    estimator.rtt_observations_.AddObservation(
-        NetworkQualityEstimator::RttObservation(
-            base::TimeDelta::FromMilliseconds(500), base::TimeTicks::Now(),
-            NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-
-    if (i == 100)
-      update_time_of_network_100 = base::TimeTicks::Now();
-
-    estimator.SimulateNetworkChangeTo(
-        NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
-        base::SizeTToString(i));
-    if (i < NetworkQualityEstimator::kMaximumNetworkQualityCacheSize)
-      EXPECT_EQ(i, estimator.cached_network_qualities_.size());
-    EXPECT_LE(estimator.cached_network_qualities_.size(),
-              static_cast<size_t>(
-                  NetworkQualityEstimator::kMaximumNetworkQualityCacheSize));
-  }
-  // One more call so that the last network is also written to cache.
-  estimator.downstream_throughput_kbps_observations_.AddObservation(
-      NetworkQualityEstimator::ThroughputObservation(
-          2, base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.rtt_observations_.AddObservation(
-      NetworkQualityEstimator::RttObservation(
-          base::TimeDelta::FromMilliseconds(500), base::TimeTicks::Now(),
-          NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
-  estimator.SimulateNetworkChangeTo(
-      NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
-      base::SizeTToString(network_count - 1));
-  EXPECT_EQ(static_cast<size_t>(
-                NetworkQualityEstimator::kMaximumNetworkQualityCacheSize),
-            estimator.cached_network_qualities_.size());
-
-  // Test that the cache is LRU by examining its contents. Networks in cache
-  // must all be newer than the 100th network.
-  for (NetworkQualityEstimator::CachedNetworkQualities::iterator it =
-           estimator.cached_network_qualities_.begin();
-       it != estimator.cached_network_qualities_.end(); ++it) {
-    EXPECT_GE((it->second).last_update_time_, update_time_of_network_100);
-  }
-}
-
 TEST(NetworkQualityEstimatorTest, TestGetMetricsSince) {
   std::map<std::string, std::string> variation_params;
 
diff --git a/net/nqe/network_quality_observation_source.h b/net/nqe/network_quality_observation_source.h
index da64c67..c1614027 100644
--- a/net/nqe/network_quality_observation_source.h
+++ b/net/nqe/network_quality_observation_source.h
@@ -32,7 +32,9 @@
   NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_FROM_PLATFORM,
 
   // The observation came from a Chromium-external source.
-  NETWORK_QUALITY_OBSERVATION_SOURCE_EXTERNAL_ESTIMATE
+  NETWORK_QUALITY_OBSERVATION_SOURCE_EXTERNAL_ESTIMATE,
+
+  NETWORK_QUALITY_OBSERVATION_SOURCE_MAX,
 };
 
 }  // namespace net
diff --git a/net/nqe/network_quality_store.cc b/net/nqe/network_quality_store.cc
new file mode 100644
index 0000000..4f74491
--- /dev/null
+++ b/net/nqe/network_quality_store.cc
@@ -0,0 +1,83 @@
+// Copyright 2016 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 "net/nqe/network_quality_store.h"
+
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+
+namespace nqe {
+
+namespace internal {
+
+NetworkQualityStore::NetworkQualityStore() {
+  static_assert(kMaximumNetworkQualityCacheSize > 0,
+                "Size of the network quality cache must be > 0");
+  // This limit should not be increased unless the logic for removing the
+  // oldest cache entry is rewritten to use a doubly-linked-list LRU queue.
+  static_assert(kMaximumNetworkQualityCacheSize <= 10,
+                "Size of the network quality cache must <= 10");
+}
+
+NetworkQualityStore::~NetworkQualityStore() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void NetworkQualityStore::Add(
+    const nqe::internal::NetworkID& network_id,
+    const nqe::internal::CachedNetworkQuality& cached_network_quality) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_LE(cached_network_qualities_.size(),
+            static_cast<size_t>(kMaximumNetworkQualityCacheSize));
+
+  // If the network name is unavailable, caching should not be performed.
+  if (network_id.type != net::NetworkChangeNotifier::CONNECTION_ETHERNET &&
+      network_id.id.empty()) {
+    return;
+  }
+
+  // Remove the entry from the map, if it is already present.
+  cached_network_qualities_.erase(network_id);
+
+  if (cached_network_qualities_.size() == kMaximumNetworkQualityCacheSize) {
+    // Remove the oldest entry.
+    CachedNetworkQualities::iterator oldest_entry_iterator =
+        cached_network_qualities_.begin();
+
+    for (CachedNetworkQualities::iterator it =
+             cached_network_qualities_.begin();
+         it != cached_network_qualities_.end(); ++it) {
+      if ((it->second).OlderThan(oldest_entry_iterator->second))
+        oldest_entry_iterator = it;
+    }
+    cached_network_qualities_.erase(oldest_entry_iterator);
+  }
+
+  cached_network_qualities_.insert(
+      std::make_pair(network_id, cached_network_quality));
+  DCHECK_LE(cached_network_qualities_.size(),
+            static_cast<size_t>(kMaximumNetworkQualityCacheSize));
+}
+
+bool NetworkQualityStore::GetById(
+    const nqe::internal::NetworkID& network_id,
+    nqe::internal::CachedNetworkQuality* cached_network_quality) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  CachedNetworkQualities::const_iterator it =
+      cached_network_qualities_.find(network_id);
+
+  if (it == cached_network_qualities_.end())
+    return false;
+
+  *cached_network_quality = it->second;
+  return true;
+}
+
+}  // namespace internal
+
+}  // namespace nqe
+
+}  // namespace net
diff --git a/net/nqe/network_quality_store.h b/net/nqe/network_quality_store.h
new file mode 100644
index 0000000..8ac78e9
--- /dev/null
+++ b/net/nqe/network_quality_store.h
@@ -0,0 +1,67 @@
+// Copyright 2016 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.
+
+#ifndef NET_NQE_NETWORK_QUALITY_STORE_H_
+#define NET_NQE_NETWORK_QUALITY_STORE_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "net/base/net_export.h"
+#include "net/nqe/cached_network_quality.h"
+#include "net/nqe/network_id.h"
+
+namespace net {
+
+namespace nqe {
+
+namespace internal {
+
+// NetworkQualityStore holds the network qualities of different networks in
+// memory. Entries are stored in LRU order, and older entries may be evicted.
+class NET_EXPORT_PRIVATE NetworkQualityStore {
+ public:
+  NetworkQualityStore();
+  ~NetworkQualityStore();
+
+  // Stores the network quality |cached_network_quality| of network with ID
+  // |network_id|.
+  void Add(const nqe::internal::NetworkID& network_id,
+           const nqe::internal::CachedNetworkQuality& cached_network_quality);
+
+  // Returns true if the network quality estimate was successfully read
+  // for a network with ID |network_id|, and sets |cached_network_quality| to
+  // the estimate read.
+  bool GetById(const nqe::internal::NetworkID& network_id,
+               nqe::internal::CachedNetworkQuality* cached_network_quality);
+
+ private:
+  // Maximum size of the store that holds network quality estimates.
+  // A smaller size may reduce the cache hit rate due to frequent evictions.
+  // A larger size may affect performance.
+  static const size_t kMaximumNetworkQualityCacheSize = 10;
+
+  // This does not use an unordered_map or hash_map for code simplicity (the key
+  // just implements operator<, rather than hash and equality) and because the
+  // map is tiny.
+  typedef std::map<nqe::internal::NetworkID,
+                   nqe::internal::CachedNetworkQuality>
+      CachedNetworkQualities;
+
+  // Data structure that stores the qualities of networks.
+  CachedNetworkQualities cached_network_qualities_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkQualityStore);
+};
+
+}  // namespace internal
+
+}  // namespace nqe
+
+}  // namespace net
+
+#endif  // NET_NQE_NETWORK_QUALITY_STORE_H_
diff --git a/net/nqe/network_quality_store_unittest.cc b/net/nqe/network_quality_store_unittest.cc
new file mode 100644
index 0000000..d880720
--- /dev/null
+++ b/net/nqe/network_quality_store_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright 2016 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 "net/nqe/network_quality_store.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "net/base/network_change_notifier.h"
+#include "net/nqe/cached_network_quality.h"
+#include "net/nqe/network_id.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(NetworkQualityStoreTest, TestCaching) {
+  nqe::internal::NetworkQualityStore network_quality_store;
+  base::SimpleTestTickClock tick_clock;
+
+  // Cached network quality for network with NetworkID (2G, "test1").
+  const nqe::internal::CachedNetworkQuality cached_network_quality_2g_test1(
+      tick_clock.NowTicks(),
+      nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(1),
+                                    base::TimeDelta::FromSeconds(1), 1));
+
+  {
+    // Entry will be added for (2G, "test1").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test1");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    network_quality_store.Add(network_id, cached_network_quality_2g_test1);
+    EXPECT_TRUE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+    EXPECT_EQ(cached_network_quality_2g_test1.network_quality(),
+              read_network_quality.network_quality());
+  }
+
+  {
+    // Entry will be added for (2G, "test2").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test2");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    nqe::internal::CachedNetworkQuality cached_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(2),
+                                      base::TimeDelta::FromSeconds(2), 2));
+    network_quality_store.Add(network_id, cached_network_quality);
+    EXPECT_TRUE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+    EXPECT_EQ(read_network_quality.network_quality(),
+              cached_network_quality.network_quality());
+  }
+
+  {
+    // Entry will be added for (3G, "test3").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_3G,
+                                        "test3");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    nqe::internal::CachedNetworkQuality cached_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(3),
+                                      base::TimeDelta::FromSeconds(3), 3));
+    network_quality_store.Add(network_id, cached_network_quality);
+    EXPECT_TRUE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+    EXPECT_EQ(read_network_quality.network_quality(),
+              cached_network_quality.network_quality());
+  }
+
+  {
+    // Entry will not be added for (Unknown, "").
+    nqe::internal::NetworkID network_id(
+        NetworkChangeNotifier::CONNECTION_UNKNOWN, "");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    nqe::internal::CachedNetworkQuality set_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(4),
+                                      base::TimeDelta::FromSeconds(4), 4));
+    network_quality_store.Add(network_id, set_network_quality);
+    EXPECT_FALSE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+  }
+
+  {
+    // Existing entry will be read for (2G, "test1").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test1");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    EXPECT_TRUE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+    EXPECT_EQ(cached_network_quality_2g_test1.network_quality(),
+              read_network_quality.network_quality());
+  }
+
+  {
+    // Existing entry will be overwritten for (2G, "test1").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test1");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    const nqe::internal::CachedNetworkQuality cached_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(5),
+                                      base::TimeDelta::FromSeconds(5), 5));
+    network_quality_store.Add(network_id, cached_network_quality);
+    EXPECT_TRUE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+    EXPECT_EQ(cached_network_quality.network_quality(),
+              read_network_quality.network_quality());
+  }
+
+  {
+    // No entry should exist for (2G, "test4").
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test4");
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    EXPECT_FALSE(
+        network_quality_store.GetById(network_id, &read_network_quality));
+  }
+}
+
+// Tests if the cache size remains bounded. Also, ensure that the cache is
+// LRU.
+TEST(NetworkQualityStoreTest, TestLRUCacheMaximumSize) {
+  nqe::internal::NetworkQualityStore network_quality_store;
+  base::SimpleTestTickClock tick_clock;
+
+  // Add more networks than the maximum size of the cache.
+  const size_t network_count = 11;
+
+  nqe::internal::CachedNetworkQuality read_network_quality(
+      tick_clock.NowTicks(),
+      nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                    base::TimeDelta::FromSeconds(0), 0));
+
+  for (size_t i = 0; i < network_count; ++i) {
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test" + base::IntToString(i));
+
+    const nqe::internal::CachedNetworkQuality network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(1),
+                                      base::TimeDelta::FromSeconds(1), 1));
+    network_quality_store.Add(network_id, network_quality);
+    tick_clock.Advance(base::TimeDelta::FromSeconds(1));
+  }
+
+  base::TimeTicks earliest_last_update_time = tick_clock.NowTicks();
+  size_t cache_match_count = 0;
+  for (size_t i = 0; i < network_count; ++i) {
+    nqe::internal::NetworkID network_id(NetworkChangeNotifier::CONNECTION_2G,
+                                        "test" + base::IntToString(i));
+
+    nqe::internal::CachedNetworkQuality read_network_quality(
+        tick_clock.NowTicks(),
+        nqe::internal::NetworkQuality(base::TimeDelta::FromSeconds(0),
+                                      base::TimeDelta::FromSeconds(0), 0));
+    if (network_quality_store.GetById(network_id, &read_network_quality)) {
+      cache_match_count++;
+      earliest_last_update_time = std::min(
+          earliest_last_update_time, read_network_quality.last_update_time());
+    }
+  }
+
+  // Ensure that the number of entries in cache are fewer than |network_count|.
+  EXPECT_LT(cache_match_count, network_count);
+  EXPECT_GT(cache_match_count, 0u);
+
+  // Ensure that only LRU entries are cached by comparing the
+  // |earliest_last_update_time|.
+  EXPECT_EQ(
+      tick_clock.NowTicks() - base::TimeDelta::FromSeconds(cache_match_count),
+      earliest_last_update_time);
+}
+
+}  // namespace
+
+}  // namespace net
\ No newline at end of file
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 35c0485..ca096ba 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -35791,6 +35791,15 @@
   </summary>
 </histogram>
 
+<histogram name="NQE.CachedNetworkQualityAvailable" enum="BooleanAvailable">
+  <owner>[email protected]</owner>
+  <owner>[email protected]</owner>
+  <summary>
+    Records if the cached network quality (from memory or from a persistent
+    source) was available. Recorded right after connection change event.
+  </summary>
+</histogram>
+
 <histogram name="NQE.CellularSignalStrengthAvailable" enum="BooleanAvailable">
   <owner>[email protected]</owner>
   <owner>[email protected]</owner>