| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/sdch/sdch_owner.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/metrics/histogram_macros.h" | |
| 9 #include "base/time/default_clock.h" | |
| 10 #include "net/base/sdch_manager.h" | |
| 11 #include "net/base/sdch_net_log_params.h" | |
| 12 | |
| 13 namespace { | |
| 14 | |
| 15 enum DictionaryFate { | |
| 16 // A Get-Dictionary header wasn't acted on. | |
| 17 DICTIONARY_FATE_GET_IGNORED = 1, | |
| 18 | |
| 19 // A fetch was attempted, but failed. | |
| 20 // TODO(rdsmith): Actually record this case. | |
| 21 DICTIONARY_FATE_FETCH_FAILED = 2, | |
| 22 | |
| 23 // A successful fetch was dropped on the floor, no space. | |
| 24 DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3, | |
| 25 | |
| 26 // A successful fetch was refused by the SdchManager. | |
| 27 DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4, | |
| 28 | |
| 29 // A dictionary was successfully added. | |
| 30 DICTIONARY_FATE_ADDED = 5, | |
| 31 | |
| 32 // A dictionary was evicted by an incoming dict. | |
| 33 DICTIONARY_FATE_EVICT_FOR_DICT = 6, | |
| 34 | |
| 35 // A dictionary was evicted by memory pressure. | |
| 36 DICTIONARY_FATE_EVICT_FOR_MEMORY = 7, | |
| 37 | |
| 38 // A dictionary was evicted on destruction. | |
| 39 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8, | |
| 40 | |
| 41 DICTIONARY_FATE_MAX = 9 | |
| 42 }; | |
| 43 | |
| 44 void RecordDictionaryFate(enum DictionaryFate fate) { | |
| 45 UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); | |
| 46 } | |
| 47 | |
| 48 void RecordDictionaryEviction(int use_count, DictionaryFate fate) { | |
| 49 DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || | |
| 50 fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || | |
| 51 fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); | |
| 52 | |
| 53 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); | |
| 54 RecordDictionaryFate(fate); | |
| 55 } | |
| 56 | |
| 57 } // namespace | |
| 58 | |
| 59 namespace net { | |
| 60 | |
| 61 // Adjust SDCH limits downwards for mobile. | |
| 62 #if defined(OS_ANDROID) || defined(OS_IOS) | |
| 63 // static | |
| 64 const size_t SdchOwner::kMaxTotalDictionarySize = 1000 * 1000; | |
| 65 #else | |
| 66 // static | |
| 67 const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; | |
| 68 #endif | |
| 69 | |
| 70 // Somewhat arbitrary, but we assume a dictionary smaller than | |
| 71 // 50K isn't going to do anyone any good. Note that this still doesn't | |
| 72 // prevent download and addition unless there is less than this | |
| 73 // amount of space available in storage. | |
| 74 const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; | |
| 75 | |
| 76 SdchOwner::SdchOwner(net::SdchManager* sdch_manager, | |
| 77 net::URLRequestContext* context) | |
| 78 : manager_(sdch_manager), | |
| 79 fetcher_(context, | |
| 80 base::Bind(&SdchOwner::OnDictionaryFetched, | |
| 81 // Because |fetcher_| is owned by SdchOwner, the | |
| 82 // SdchOwner object will be available for the lifetime | |
| 83 // of |fetcher_|. | |
| 84 base::Unretained(this))), | |
| 85 total_dictionary_bytes_(0), | |
| 86 clock_(new base::DefaultClock), | |
| 87 max_total_dictionary_size_(kMaxTotalDictionarySize), | |
| 88 min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), | |
| 89 memory_pressure_listener_( | |
| 90 base::Bind(&SdchOwner::OnMemoryPressure, | |
| 91 // Because |memory_pressure_listener_| is owned by | |
| 92 // SdchOwner, the SdchOwner object will be available | |
| 93 // for the lifetime of |memory_pressure_listener_|. | |
| 94 base::Unretained(this))) { | |
| 95 manager_->AddObserver(this); | |
| 96 } | |
| 97 | |
| 98 SdchOwner::~SdchOwner() { | |
| 99 for (auto it = local_dictionary_info_.begin(); | |
| 100 it != local_dictionary_info_.end(); ++it) { | |
| 101 RecordDictionaryEviction(it->second.use_count, | |
| 102 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); | |
| 103 } | |
| 104 manager_->RemoveObserver(this); | |
| 105 } | |
| 106 | |
| 107 void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { | |
| 108 max_total_dictionary_size_ = max_total_dictionary_size; | |
| 109 } | |
| 110 | |
| 111 void SdchOwner::SetMinSpaceForDictionaryFetch( | |
| 112 size_t min_space_for_dictionary_fetch) { | |
| 113 min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; | |
| 114 } | |
| 115 | |
| 116 void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text, | |
| 117 const GURL& dictionary_url, | |
| 118 const net::BoundNetLog& net_log) { | |
| 119 struct DictionaryItem { | |
| 120 base::Time last_used; | |
| 121 std::string server_hash; | |
| 122 int use_count; | |
| 123 size_t dictionary_size; | |
| 124 | |
| 125 DictionaryItem() : use_count(0), dictionary_size(0) {} | |
| 126 DictionaryItem(const base::Time& last_used, | |
| 127 const std::string& server_hash, | |
| 128 int use_count, | |
| 129 size_t dictionary_size) | |
| 130 : last_used(last_used), | |
| 131 server_hash(server_hash), | |
| 132 use_count(use_count), | |
| 133 dictionary_size(dictionary_size) {} | |
| 134 DictionaryItem(const DictionaryItem& rhs) = default; | |
| 135 DictionaryItem& operator=(const DictionaryItem& rhs) = default; | |
| 136 bool operator<(const DictionaryItem& rhs) const { | |
| 137 return last_used < rhs.last_used; | |
| 138 } | |
| 139 }; | |
| 140 | |
| 141 std::vector<DictionaryItem> stale_dictionary_list; | |
| 142 size_t recoverable_bytes = 0; | |
| 143 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); | |
| 144 for (auto used_it = local_dictionary_info_.begin(); | |
| 145 used_it != local_dictionary_info_.end(); ++used_it) { | |
| 146 if (used_it->second.last_used < stale_boundary) { | |
| 147 stale_dictionary_list.push_back( | |
| 148 DictionaryItem(used_it->second.last_used, used_it->first, | |
| 149 used_it->second.use_count, used_it->second.size)); | |
| 150 recoverable_bytes += used_it->second.size; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > | |
| 155 max_total_dictionary_size_) { | |
| 156 RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); | |
| 157 net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); | |
| 158 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, | |
| 159 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, | |
| 160 SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); | |
| 161 return; | |
| 162 } | |
| 163 | |
| 164 // Evict from oldest to youngest until we have space. | |
| 165 std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end()); | |
| 166 size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_; | |
| 167 auto stale_it = stale_dictionary_list.begin(); | |
| 168 while (avail_bytes < dictionary_text.size() && | |
| 169 stale_it != stale_dictionary_list.end()) { | |
| 170 manager_->RemoveSdchDictionary(stale_it->server_hash); | |
| 171 RecordDictionaryEviction(stale_it->use_count, | |
| 172 DICTIONARY_FATE_EVICT_FOR_DICT); | |
| 173 local_dictionary_info_.erase(stale_it->server_hash); | |
| 174 avail_bytes += stale_it->dictionary_size; | |
| 175 ++stale_it; | |
| 176 } | |
| 177 DCHECK(avail_bytes >= dictionary_text.size()); | |
| 178 | |
| 179 std::string server_hash; | |
| 180 net::SdchProblemCode rv = manager_->AddSdchDictionary( | |
| 181 dictionary_text, dictionary_url, &server_hash); | |
| 182 if (rv != net::SDCH_OK) { | |
| 183 RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); | |
| 184 net::SdchManager::SdchErrorRecovery(rv); | |
| 185 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, | |
| 186 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, | |
| 187 rv, dictionary_url, true)); | |
| 188 return; | |
| 189 } | |
| 190 | |
| 191 RecordDictionaryFate(DICTIONARY_FATE_ADDED); | |
| 192 | |
| 193 DCHECK(local_dictionary_info_.end() == | |
| 194 local_dictionary_info_.find(server_hash)); | |
| 195 total_dictionary_bytes_ += dictionary_text.size(); | |
| 196 local_dictionary_info_[server_hash] = DictionaryInfo( | |
| 197 // Set the time last used to something to avoid thrashing, but not recent, | |
| 198 // to avoid taking too much time/space with useless dictionaries/one-off | |
| 199 // visits to web sites. | |
| 200 clock_->Now() - base::TimeDelta::FromHours(23), dictionary_text.size()); | |
| 201 } | |
| 202 | |
| 203 void SdchOwner::OnDictionaryUsed(SdchManager* manager, | |
| 204 const std::string& server_hash) { | |
| 205 auto it = local_dictionary_info_.find(server_hash); | |
| 206 DCHECK(local_dictionary_info_.end() != it); | |
| 207 | |
| 208 it->second.last_used = clock_->Now(); | |
| 209 it->second.use_count++; | |
| 210 } | |
| 211 | |
| 212 void SdchOwner::OnGetDictionary(net::SdchManager* manager, | |
| 213 const GURL& request_url, | |
| 214 const GURL& dictionary_url) { | |
| 215 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); | |
| 216 size_t avail_bytes = 0; | |
| 217 for (auto it = local_dictionary_info_.begin(); | |
| 218 it != local_dictionary_info_.end(); ++it) { | |
| 219 if (it->second.last_used < stale_boundary) | |
| 220 avail_bytes += it->second.size; | |
| 221 } | |
| 222 | |
| 223 // Don't initiate the fetch if we wouldn't be able to store any | |
| 224 // reasonable dictionary. | |
| 225 // TODO(rdsmith): Maybe do a HEAD request to figure out how much | |
| 226 // storage we'd actually need? | |
| 227 if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + | |
| 228 min_space_for_dictionary_fetch_)) { | |
| 229 RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); | |
| 230 // TODO(rdsmith): Log a net-internals error. This requires | |
| 231 // SdchManager to forward the URLRequest that detected the | |
| 232 // Get-Dictionary header to its observers, which is tricky | |
| 233 // because SdchManager is layered underneath URLRequest. | |
| 234 return; | |
| 235 } | |
| 236 | |
| 237 fetcher_.Schedule(dictionary_url); | |
| 238 } | |
| 239 | |
| 240 void SdchOwner::OnClearDictionaries(net::SdchManager* manager) { | |
| 241 total_dictionary_bytes_ = 0; | |
| 242 local_dictionary_info_.clear(); | |
| 243 fetcher_.Cancel(); | |
| 244 } | |
| 245 | |
| 246 void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) { | |
| 247 clock_ = clock.Pass(); | |
| 248 } | |
| 249 | |
| 250 void SdchOwner::OnMemoryPressure( | |
| 251 base::MemoryPressureListener::MemoryPressureLevel level) { | |
| 252 DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level); | |
| 253 | |
| 254 for (auto it = local_dictionary_info_.begin(); | |
| 255 it != local_dictionary_info_.end(); ++it) { | |
| 256 RecordDictionaryEviction(it->second.use_count, | |
| 257 DICTIONARY_FATE_EVICT_FOR_MEMORY); | |
| 258 } | |
| 259 | |
| 260 // TODO(rdsmith): Make a distinction between moderate and critical | |
| 261 // memory pressure. | |
| 262 manager_->ClearData(); | |
| 263 } | |
| 264 | |
| 265 } // namespace net | |
| OLD | NEW |