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