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