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