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