| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "chrome/browser/renderer_host/web_cache_manager.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/compiler_specific.h" | |
| 11 #include "base/memory/singleton.h" | |
| 12 #include "base/message_loop/message_loop.h" | |
| 13 #include "base/metrics/histogram.h" | |
| 14 #include "base/prefs/pref_registry_simple.h" | |
| 15 #include "base/prefs/pref_service.h" | |
| 16 #include "base/sys_info.h" | |
| 17 #include "base/time/time.h" | |
| 18 #include "chrome/browser/browser_process.h" | |
| 19 #include "chrome/browser/chrome_notification_types.h" | |
| 20 #include "chrome/common/chrome_constants.h" | |
| 21 #include "chrome/common/pref_names.h" | |
| 22 #include "chrome/common/render_messages.h" | |
| 23 #include "content/public/browser/notification_service.h" | |
| 24 #include "content/public/browser/render_process_host.h" | |
| 25 | |
| 26 using base::Time; | |
| 27 using base::TimeDelta; | |
| 28 using blink::WebCache; | |
| 29 | |
| 30 static const int kReviseAllocationDelayMS = 200; | |
| 31 | |
| 32 // The default size limit of the in-memory cache is 8 MB | |
| 33 static const int kDefaultMemoryCacheSize = 8 * 1024 * 1024; | |
| 34 | |
| 35 namespace { | |
| 36 | |
| 37 int GetDefaultCacheSize() { | |
| 38 // Start off with a modest default | |
| 39 int default_cache_size = kDefaultMemoryCacheSize; | |
| 40 | |
| 41 // Check how much physical memory the OS has | |
| 42 int mem_size_mb = base::SysInfo::AmountOfPhysicalMemoryMB(); | |
| 43 if (mem_size_mb >= 1000) // If we have a GB of memory, set a larger default. | |
| 44 default_cache_size *= 4; | |
| 45 else if (mem_size_mb >= 512) // With 512 MB, set a slightly larger default. | |
| 46 default_cache_size *= 2; | |
| 47 | |
| 48 UMA_HISTOGRAM_MEMORY_MB("Cache.MaxCacheSizeMB", | |
| 49 default_cache_size / 1024 / 1024); | |
| 50 | |
| 51 return default_cache_size; | |
| 52 } | |
| 53 | |
| 54 } // anonymous namespace | |
| 55 | |
| 56 // static | |
| 57 void WebCacheManager::RegisterPrefs(PrefRegistrySimple* registry) { | |
| 58 registry->RegisterIntegerPref(prefs::kMemoryCacheSize, GetDefaultCacheSize()); | |
| 59 } | |
| 60 | |
| 61 // static | |
| 62 WebCacheManager* WebCacheManager::GetInstance() { | |
| 63 return Singleton<WebCacheManager>::get(); | |
| 64 } | |
| 65 | |
| 66 WebCacheManager::WebCacheManager() | |
| 67 : global_size_limit_(GetDefaultGlobalSizeLimit()), | |
| 68 weak_factory_(this) { | |
| 69 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, | |
| 70 content::NotificationService::AllBrowserContextsAndSources()); | |
| 71 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, | |
| 72 content::NotificationService::AllBrowserContextsAndSources()); | |
| 73 } | |
| 74 | |
| 75 WebCacheManager::~WebCacheManager() { | |
| 76 } | |
| 77 | |
| 78 void WebCacheManager::Add(int renderer_id) { | |
| 79 DCHECK(inactive_renderers_.count(renderer_id) == 0); | |
| 80 | |
| 81 // It is tempting to make the following DCHECK here, but it fails when a new | |
| 82 // tab is created as we observe activity from that tab because the | |
| 83 // RenderProcessHost is recreated and adds itself. | |
| 84 // | |
| 85 // DCHECK(active_renderers_.count(renderer_id) == 0); | |
| 86 // | |
| 87 // However, there doesn't seem to be much harm in receiving the calls in this | |
| 88 // order. | |
| 89 | |
| 90 active_renderers_.insert(renderer_id); | |
| 91 | |
| 92 RendererInfo* stats = &(stats_[renderer_id]); | |
| 93 memset(stats, 0, sizeof(*stats)); | |
| 94 stats->access = Time::Now(); | |
| 95 | |
| 96 // Revise our allocation strategy to account for this new renderer. | |
| 97 ReviseAllocationStrategyLater(); | |
| 98 } | |
| 99 | |
| 100 void WebCacheManager::Remove(int renderer_id) { | |
| 101 // Erase all knowledge of this renderer | |
| 102 active_renderers_.erase(renderer_id); | |
| 103 inactive_renderers_.erase(renderer_id); | |
| 104 stats_.erase(renderer_id); | |
| 105 | |
| 106 // Reallocate the resources used by this renderer | |
| 107 ReviseAllocationStrategyLater(); | |
| 108 } | |
| 109 | |
| 110 void WebCacheManager::ObserveActivity(int renderer_id) { | |
| 111 StatsMap::iterator item = stats_.find(renderer_id); | |
| 112 if (item == stats_.end()) | |
| 113 return; // We might see stats for a renderer that has been destroyed. | |
| 114 | |
| 115 // Record activity. | |
| 116 active_renderers_.insert(renderer_id); | |
| 117 item->second.access = Time::Now(); | |
| 118 | |
| 119 std::set<int>::iterator elmt = inactive_renderers_.find(renderer_id); | |
| 120 if (elmt != inactive_renderers_.end()) { | |
| 121 inactive_renderers_.erase(elmt); | |
| 122 | |
| 123 // A renderer that was inactive, just became active. We should make sure | |
| 124 // it is given a fair cache allocation, but we defer this for a bit in | |
| 125 // order to make this function call cheap. | |
| 126 ReviseAllocationStrategyLater(); | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 void WebCacheManager::ObserveStats(int renderer_id, | |
| 131 const WebCache::UsageStats& stats) { | |
| 132 StatsMap::iterator entry = stats_.find(renderer_id); | |
| 133 if (entry == stats_.end()) | |
| 134 return; // We might see stats for a renderer that has been destroyed. | |
| 135 | |
| 136 // Record the updated stats. | |
| 137 entry->second.capacity = stats.capacity; | |
| 138 entry->second.deadSize = stats.deadSize; | |
| 139 entry->second.liveSize = stats.liveSize; | |
| 140 entry->second.maxDeadCapacity = stats.maxDeadCapacity; | |
| 141 entry->second.minDeadCapacity = stats.minDeadCapacity; | |
| 142 } | |
| 143 | |
| 144 void WebCacheManager::SetGlobalSizeLimit(size_t bytes) { | |
| 145 global_size_limit_ = bytes; | |
| 146 ReviseAllocationStrategyLater(); | |
| 147 } | |
| 148 | |
| 149 void WebCacheManager::ClearCache() { | |
| 150 // Tell each renderer process to clear the cache. | |
| 151 ClearRendererCache(active_renderers_, INSTANTLY); | |
| 152 ClearRendererCache(inactive_renderers_, INSTANTLY); | |
| 153 } | |
| 154 | |
| 155 void WebCacheManager::ClearCacheOnNavigation() { | |
| 156 // Tell each renderer process to clear the cache when a tab is reloaded or | |
| 157 // the user navigates to a new website. | |
| 158 ClearRendererCache(active_renderers_, ON_NAVIGATION); | |
| 159 ClearRendererCache(inactive_renderers_, ON_NAVIGATION); | |
| 160 } | |
| 161 | |
| 162 void WebCacheManager::Observe(int type, | |
| 163 const content::NotificationSource& source, | |
| 164 const content::NotificationDetails& details) { | |
| 165 switch (type) { | |
| 166 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { | |
| 167 content::RenderProcessHost* process = | |
| 168 content::Source<content::RenderProcessHost>(source).ptr(); | |
| 169 Add(process->GetID()); | |
| 170 break; | |
| 171 } | |
| 172 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: { | |
| 173 content::RenderProcessHost* process = | |
| 174 content::Source<content::RenderProcessHost>(source).ptr(); | |
| 175 Remove(process->GetID()); | |
| 176 break; | |
| 177 } | |
| 178 default: | |
| 179 NOTREACHED(); | |
| 180 break; | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 // static | |
| 185 size_t WebCacheManager::GetDefaultGlobalSizeLimit() { | |
| 186 PrefService* perf_service = g_browser_process->local_state(); | |
| 187 if (perf_service) | |
| 188 return perf_service->GetInteger(prefs::kMemoryCacheSize); | |
| 189 | |
| 190 return GetDefaultCacheSize(); | |
| 191 } | |
| 192 | |
| 193 void WebCacheManager::GatherStats(const std::set<int>& renderers, | |
| 194 WebCache::UsageStats* stats) { | |
| 195 DCHECK(stats); | |
| 196 | |
| 197 memset(stats, 0, sizeof(WebCache::UsageStats)); | |
| 198 | |
| 199 std::set<int>::const_iterator iter = renderers.begin(); | |
| 200 while (iter != renderers.end()) { | |
| 201 StatsMap::iterator elmt = stats_.find(*iter); | |
| 202 if (elmt != stats_.end()) { | |
| 203 stats->minDeadCapacity += elmt->second.minDeadCapacity; | |
| 204 stats->maxDeadCapacity += elmt->second.maxDeadCapacity; | |
| 205 stats->capacity += elmt->second.capacity; | |
| 206 stats->liveSize += elmt->second.liveSize; | |
| 207 stats->deadSize += elmt->second.deadSize; | |
| 208 } | |
| 209 ++iter; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 // static | |
| 214 size_t WebCacheManager::GetSize(AllocationTactic tactic, | |
| 215 const WebCache::UsageStats& stats) { | |
| 216 switch (tactic) { | |
| 217 case DIVIDE_EVENLY: | |
| 218 // We aren't going to reserve any space for existing objects. | |
| 219 return 0; | |
| 220 case KEEP_CURRENT_WITH_HEADROOM: | |
| 221 // We need enough space for our current objects, plus some headroom. | |
| 222 return 3 * GetSize(KEEP_CURRENT, stats) / 2; | |
| 223 case KEEP_CURRENT: | |
| 224 // We need enough space to keep our current objects. | |
| 225 return stats.liveSize + stats.deadSize; | |
| 226 case KEEP_LIVE_WITH_HEADROOM: | |
| 227 // We need enough space to keep out live resources, plus some headroom. | |
| 228 return 3 * GetSize(KEEP_LIVE, stats) / 2; | |
| 229 case KEEP_LIVE: | |
| 230 // We need enough space to keep our live resources. | |
| 231 return stats.liveSize; | |
| 232 default: | |
| 233 NOTREACHED() << "Unknown cache allocation tactic"; | |
| 234 return 0; | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 bool WebCacheManager::AttemptTactic( | |
| 239 AllocationTactic active_tactic, | |
| 240 const WebCache::UsageStats& active_stats, | |
| 241 AllocationTactic inactive_tactic, | |
| 242 const WebCache::UsageStats& inactive_stats, | |
| 243 AllocationStrategy* strategy) { | |
| 244 DCHECK(strategy); | |
| 245 | |
| 246 size_t active_size = GetSize(active_tactic, active_stats); | |
| 247 size_t inactive_size = GetSize(inactive_tactic, inactive_stats); | |
| 248 | |
| 249 // Give up if we don't have enough space to use this tactic. | |
| 250 if (global_size_limit_ < active_size + inactive_size) | |
| 251 return false; | |
| 252 | |
| 253 // Compute the unreserved space available. | |
| 254 size_t total_extra = global_size_limit_ - (active_size + inactive_size); | |
| 255 | |
| 256 // The plan for the extra space is to divide it evenly amoung the active | |
| 257 // renderers. | |
| 258 size_t shares = active_renderers_.size(); | |
| 259 | |
| 260 // The inactive renderers get one share of the extra memory to be divided | |
| 261 // among themselves. | |
| 262 size_t inactive_extra = 0; | |
| 263 if (!inactive_renderers_.empty()) { | |
| 264 ++shares; | |
| 265 inactive_extra = total_extra / shares; | |
| 266 } | |
| 267 | |
| 268 // The remaining memory is allocated to the active renderers. | |
| 269 size_t active_extra = total_extra - inactive_extra; | |
| 270 | |
| 271 // Actually compute the allocations for each renderer. | |
| 272 AddToStrategy(active_renderers_, active_tactic, active_extra, strategy); | |
| 273 AddToStrategy(inactive_renderers_, inactive_tactic, inactive_extra, strategy); | |
| 274 | |
| 275 // We succeeded in computing an allocation strategy. | |
| 276 return true; | |
| 277 } | |
| 278 | |
| 279 void WebCacheManager::AddToStrategy(const std::set<int>& renderers, | |
| 280 AllocationTactic tactic, | |
| 281 size_t extra_bytes_to_allocate, | |
| 282 AllocationStrategy* strategy) { | |
| 283 DCHECK(strategy); | |
| 284 | |
| 285 // Nothing to do if there are no renderers. It is common for there to be no | |
| 286 // inactive renderers if there is a single active tab. | |
| 287 if (renderers.empty()) | |
| 288 return; | |
| 289 | |
| 290 // Divide the extra memory evenly among the renderers. | |
| 291 size_t extra_each = extra_bytes_to_allocate / renderers.size(); | |
| 292 | |
| 293 std::set<int>::const_iterator iter = renderers.begin(); | |
| 294 while (iter != renderers.end()) { | |
| 295 size_t cache_size = extra_each; | |
| 296 | |
| 297 // Add in the space required to implement |tactic|. | |
| 298 StatsMap::iterator elmt = stats_.find(*iter); | |
| 299 if (elmt != stats_.end()) | |
| 300 cache_size += GetSize(tactic, elmt->second); | |
| 301 | |
| 302 // Record the allocation in our strategy. | |
| 303 strategy->push_back(Allocation(*iter, cache_size)); | |
| 304 ++iter; | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 void WebCacheManager::EnactStrategy(const AllocationStrategy& strategy) { | |
| 309 // Inform each render process of its cache allocation. | |
| 310 AllocationStrategy::const_iterator allocation = strategy.begin(); | |
| 311 while (allocation != strategy.end()) { | |
| 312 content::RenderProcessHost* host = | |
| 313 content::RenderProcessHost::FromID(allocation->first); | |
| 314 if (host) { | |
| 315 // This is the capacity this renderer has been allocated. | |
| 316 size_t capacity = allocation->second; | |
| 317 | |
| 318 // We don't reserve any space for dead objects in the cache. Instead, we | |
| 319 // prefer to keep live objects around. There is probably some performance | |
| 320 // tuning to be done here. | |
| 321 size_t min_dead_capacity = 0; | |
| 322 | |
| 323 // We allow the dead objects to consume up to half of the cache capacity. | |
| 324 size_t max_dead_capacity = capacity / 2; | |
| 325 if (base::SysInfo::IsLowEndDevice()) { | |
| 326 max_dead_capacity = std::min(static_cast<size_t>(512 * 1024), | |
| 327 max_dead_capacity); | |
| 328 } | |
| 329 host->Send(new ChromeViewMsg_SetCacheCapacities(min_dead_capacity, | |
| 330 max_dead_capacity, | |
| 331 capacity)); | |
| 332 } | |
| 333 ++allocation; | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 void WebCacheManager::ClearRendererCache( | |
| 338 const std::set<int>& renderers, | |
| 339 WebCacheManager::ClearCacheOccasion occasion) { | |
| 340 std::set<int>::const_iterator iter = renderers.begin(); | |
| 341 for (; iter != renderers.end(); ++iter) { | |
| 342 content::RenderProcessHost* host = | |
| 343 content::RenderProcessHost::FromID(*iter); | |
| 344 if (host) | |
| 345 host->Send(new ChromeViewMsg_ClearCache(occasion == ON_NAVIGATION)); | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 void WebCacheManager::ReviseAllocationStrategy() { | |
| 350 DCHECK(stats_.size() <= | |
| 351 active_renderers_.size() + inactive_renderers_.size()); | |
| 352 | |
| 353 // Check if renderers have gone inactive. | |
| 354 FindInactiveRenderers(); | |
| 355 | |
| 356 // Gather statistics | |
| 357 WebCache::UsageStats active; | |
| 358 WebCache::UsageStats inactive; | |
| 359 GatherStats(active_renderers_, &active); | |
| 360 GatherStats(inactive_renderers_, &inactive); | |
| 361 | |
| 362 UMA_HISTOGRAM_COUNTS_100("Cache.ActiveTabs", active_renderers_.size()); | |
| 363 UMA_HISTOGRAM_COUNTS_100("Cache.InactiveTabs", inactive_renderers_.size()); | |
| 364 UMA_HISTOGRAM_MEMORY_MB("Cache.ActiveCapacityMB", | |
| 365 active.capacity / 1024 / 1024); | |
| 366 UMA_HISTOGRAM_MEMORY_MB("Cache.ActiveDeadSizeMB", | |
| 367 active.deadSize / 1024 / 1024); | |
| 368 UMA_HISTOGRAM_MEMORY_MB("Cache.ActiveLiveSizeMB", | |
| 369 active.liveSize / 1024 / 1024); | |
| 370 UMA_HISTOGRAM_MEMORY_MB("Cache.InactiveCapacityMB", | |
| 371 inactive.capacity / 1024 / 1024); | |
| 372 UMA_HISTOGRAM_MEMORY_MB("Cache.InactiveDeadSizeMB", | |
| 373 inactive.deadSize / 1024 / 1024); | |
| 374 UMA_HISTOGRAM_MEMORY_MB("Cache.InactiveLiveSizeMB", | |
| 375 inactive.liveSize / 1024 / 1024); | |
| 376 | |
| 377 // Compute an allocation strategy. | |
| 378 // | |
| 379 // We attempt various tactics in order of preference. Our first preference | |
| 380 // is not to evict any objects. If we don't have enough resources, we'll | |
| 381 // first try to evict dead data only. If that fails, we'll just divide the | |
| 382 // resources we have evenly. | |
| 383 // | |
| 384 // We always try to give the active renderers some head room in their | |
| 385 // allocations so they can take memory away from an inactive renderer with | |
| 386 // a large cache allocation. | |
| 387 // | |
| 388 // Notice the early exit will prevent attempting less desirable tactics once | |
| 389 // we've found a workable strategy. | |
| 390 AllocationStrategy strategy; | |
| 391 if ( // Ideally, we'd like to give the active renderers some headroom and | |
| 392 // keep all our current objects. | |
| 393 AttemptTactic(KEEP_CURRENT_WITH_HEADROOM, active, | |
| 394 KEEP_CURRENT, inactive, &strategy) || | |
| 395 // If we can't have that, then we first try to evict the dead objects in | |
| 396 // the caches of inactive renderers. | |
| 397 AttemptTactic(KEEP_CURRENT_WITH_HEADROOM, active, | |
| 398 KEEP_LIVE, inactive, &strategy) || | |
| 399 // Next, we try to keep the live objects in the active renders (with some | |
| 400 // room for new objects) and give whatever is left to the inactive | |
| 401 // renderers. | |
| 402 AttemptTactic(KEEP_LIVE_WITH_HEADROOM, active, | |
| 403 DIVIDE_EVENLY, inactive, &strategy) || | |
| 404 // If we've gotten this far, then we are very tight on memory. Let's try | |
| 405 // to at least keep around the live objects for the active renderers. | |
| 406 AttemptTactic(KEEP_LIVE, active, DIVIDE_EVENLY, inactive, &strategy) || | |
| 407 // We're basically out of memory. The best we can do is just divide up | |
| 408 // what we have and soldier on. | |
| 409 AttemptTactic(DIVIDE_EVENLY, active, DIVIDE_EVENLY, inactive, | |
| 410 &strategy)) { | |
| 411 // Having found a workable strategy, we enact it. | |
| 412 EnactStrategy(strategy); | |
| 413 } else { | |
| 414 // DIVIDE_EVENLY / DIVIDE_EVENLY should always succeed. | |
| 415 NOTREACHED() << "Unable to find a cache allocation"; | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 void WebCacheManager::ReviseAllocationStrategyLater() { | |
| 420 // Ask to be called back in a few milliseconds to actually recompute our | |
| 421 // allocation. | |
| 422 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
| 423 base::Bind( | |
| 424 &WebCacheManager::ReviseAllocationStrategy, | |
| 425 weak_factory_.GetWeakPtr()), | |
| 426 base::TimeDelta::FromMilliseconds(kReviseAllocationDelayMS)); | |
| 427 } | |
| 428 | |
| 429 void WebCacheManager::FindInactiveRenderers() { | |
| 430 std::set<int>::const_iterator iter = active_renderers_.begin(); | |
| 431 while (iter != active_renderers_.end()) { | |
| 432 StatsMap::iterator elmt = stats_.find(*iter); | |
| 433 DCHECK(elmt != stats_.end()); | |
| 434 TimeDelta idle = Time::Now() - elmt->second.access; | |
| 435 if (idle >= TimeDelta::FromMinutes(kRendererInactiveThresholdMinutes)) { | |
| 436 // Moved to inactive status. This invalidates our iterator. | |
| 437 inactive_renderers_.insert(*iter); | |
| 438 active_renderers_.erase(*iter); | |
| 439 iter = active_renderers_.begin(); | |
| 440 continue; | |
| 441 } | |
| 442 ++iter; | |
| 443 } | |
| 444 } | |
| OLD | NEW |