web_cache_manager.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2009 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/metrics/histogram.h"
11#include "base/singleton.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/browser/renderer_host/render_process_host.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/common/notification_service.h"
20#include "chrome/common/render_messages.h"
21
22using base::Time;
23using base::TimeDelta;
24using WebKit::WebCache;
25
26static const unsigned int kReviseAllocationDelayMS = 200 /* milliseconds */;
27
28// The default size limit of the in-memory cache is 8 MB
29static const int kDefaultMemoryCacheSize = 8 * 1024 * 1024;
30
31namespace {
32
33int 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
53void WebCacheManager::RegisterPrefs(PrefService* prefs) {
54  prefs->RegisterIntegerPref(prefs::kMemoryCacheSize, GetDefaultCacheSize());
55}
56
57// static
58WebCacheManager* WebCacheManager::GetInstance() {
59  return Singleton<WebCacheManager>::get();
60}
61
62WebCacheManager::WebCacheManager()
63    : global_size_limit_(GetDefaultGlobalSizeLimit()),
64      ALLOW_THIS_IN_INITIALIZER_LIST(revise_allocation_factory_(this)) {
65}
66
67WebCacheManager::~WebCacheManager() {
68}
69
70void 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
92void 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
102void 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
122void 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
145void WebCacheManager::SetGlobalSizeLimit(size_t bytes) {
146  global_size_limit_ = bytes;
147  ReviseAllocationStrategyLater();
148}
149
150void WebCacheManager::ClearCache() {
151  // Tell each renderer process to clear the cache.
152  ClearRendederCache(active_renderers_);
153  ClearRendederCache(inactive_renderers_);
154}
155
156// static
157size_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
165void 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
186size_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
210bool 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_.size() > 0) {
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
251void 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.size() == 0)
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
280void 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
307void 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
316void 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
386void 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
395void 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