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
30 using base::Time;
31 using base::TimeDelta;
32 using blink::WebCache;
33
34 static const int kReviseAllocationDelayMS = 200;
35
36 // The default size limit of the in-memory cache is 8 MB
37 static const int kDefaultMemoryCacheSize = 8 * 1024 * 1024;
38
39 namespace {
40
GetDefaultCacheSize()41 int 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
RegisterPrefs(PrefRegistrySimple * registry)61 void WebCacheManager::RegisterPrefs(PrefRegistrySimple* registry) {
62 registry->RegisterIntegerPref(prefs::kMemoryCacheSize, GetDefaultCacheSize());
63 }
64
65 // static
GetInstance()66 WebCacheManager* WebCacheManager::GetInstance() {
67 return Singleton<WebCacheManager>::get();
68 }
69
WebCacheManager()70 WebCacheManager::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
~WebCacheManager()79 WebCacheManager::~WebCacheManager() {
80 }
81
Add(int renderer_id)82 void 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
Remove(int renderer_id)104 void 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
ObserveActivity(int renderer_id)114 void 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
ObserveStats(int renderer_id,const WebCache::UsageStats & stats)134 void 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
SetGlobalSizeLimit(size_t bytes)148 void WebCacheManager::SetGlobalSizeLimit(size_t bytes) {
149 global_size_limit_ = bytes;
150 ReviseAllocationStrategyLater();
151 }
152
ClearCache()153 void WebCacheManager::ClearCache() {
154 // Tell each renderer process to clear the cache.
155 ClearRendererCache(active_renderers_, INSTANTLY);
156 ClearRendererCache(inactive_renderers_, INSTANTLY);
157 }
158
ClearCacheOnNavigation()159 void 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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)166 void 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
GetDefaultGlobalSizeLimit()189 size_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
GatherStats(const std::set<int> & renderers,WebCache::UsageStats * stats)197 void 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
GetSize(AllocationTactic tactic,const WebCache::UsageStats & stats)218 size_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
AttemptTactic(AllocationTactic active_tactic,const WebCache::UsageStats & active_stats,AllocationTactic inactive_tactic,const WebCache::UsageStats & inactive_stats,AllocationStrategy * strategy)242 bool 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
AddToStrategy(const std::set<int> & renderers,AllocationTactic tactic,size_t extra_bytes_to_allocate,AllocationStrategy * strategy)283 void 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
EnactStrategy(const AllocationStrategy & strategy)312 void 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
ClearRendererCache(const std::set<int> & renderers,WebCacheManager::ClearCacheOccasion occasion)343 void 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
ReviseAllocationStrategy()355 void 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
ReviseAllocationStrategyLater()425 void 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
FindInactiveRenderers()435 void 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