1 /* 2 Copyright (C) 1998 Lars Knoll (knoll (at) mpi-hd.mpg.de) 3 Copyright (C) 2001 Dirk Mueller (mueller (at) kde.org) 4 Copyright (C) 2002 Waldo Bastian (bastian (at) kde.org) 5 Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 6 7 This library is free software; you can redistribute it and/or 8 modify it under the terms of the GNU Library General Public 9 License as published by the Free Software Foundation; either 10 version 2 of the License, or (at your option) any later version. 11 12 This library is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 Library General Public License for more details. 16 17 You should have received a copy of the GNU Library General Public License 18 along with this library; see the file COPYING.LIB. If not, write to 19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 Boston, MA 02110-1301, USA. 21 */ 22 23 #include "config.h" 24 #include "core/fetch/MemoryCache.h" 25 26 #include "core/dom/CrossThreadTask.h" 27 #include "core/dom/Document.h" 28 #include "core/fetch/ResourcePtr.h" 29 #include "core/frame/FrameView.h" 30 #include "core/workers/WorkerGlobalScope.h" 31 #include "core/workers/WorkerLoaderProxy.h" 32 #include "core/workers/WorkerThread.h" 33 #include "platform/Logging.h" 34 #include "platform/TraceEvent.h" 35 #include "platform/weborigin/SecurityOrigin.h" 36 #include "platform/weborigin/SecurityOriginHash.h" 37 #include "public/platform/Platform.h" 38 #include "wtf/Assertions.h" 39 #include "wtf/CurrentTime.h" 40 #include "wtf/MathExtras.h" 41 #include "wtf/TemporaryChange.h" 42 #include "wtf/text/CString.h" 43 44 using namespace std; 45 46 namespace WebCore { 47 48 static MemoryCache* gMemoryCache; 49 50 static const unsigned cDefaultCacheCapacity = 8192 * 1024; 51 static const unsigned cDeferredPruneDeadCapacityFactor = 2; 52 static const int cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. 53 static const double cMaxPruneDeferralDelay = 0.5; // Seconds. 54 static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again. 55 56 MemoryCache* memoryCache() 57 { 58 ASSERT(WTF::isMainThread()); 59 if (!gMemoryCache) 60 gMemoryCache = new MemoryCache(); 61 return gMemoryCache; 62 } 63 64 void setMemoryCacheForTesting(MemoryCache* memoryCache) 65 { 66 gMemoryCache = memoryCache; 67 } 68 69 MemoryCache::MemoryCache() 70 : m_inPruneResources(false) 71 , m_prunePending(false) 72 , m_maxPruneDeferralDelay(cMaxPruneDeferralDelay) 73 , m_capacity(cDefaultCacheCapacity) 74 , m_minDeadCapacity(0) 75 , m_maxDeadCapacity(cDefaultCacheCapacity) 76 , m_maxDeferredPruneDeadCapacity(cDeferredPruneDeadCapacityFactor * cDefaultCacheCapacity) 77 , m_delayBeforeLiveDecodedPrune(cMinDelayBeforeLiveDecodedPrune) 78 , m_liveSize(0) 79 , m_deadSize(0) 80 #ifdef MEMORY_CACHE_STATS 81 , m_statsTimer(this, &MemoryCache::dumpStats) 82 #endif 83 { 84 #ifdef MEMORY_CACHE_STATS 85 const double statsIntervalInSeconds = 15; 86 m_statsTimer.startRepeating(statsIntervalInSeconds); 87 #endif 88 m_pruneTimeStamp = m_pruneFrameTimeStamp = FrameView::currentFrameTimeStamp(); 89 } 90 91 MemoryCache::~MemoryCache() 92 { 93 if (m_prunePending) 94 blink::Platform::current()->currentThread()->removeTaskObserver(this); 95 } 96 97 KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL) 98 { 99 if (!originalURL.hasFragmentIdentifier()) 100 return originalURL; 101 // Strip away fragment identifier from HTTP URLs. 102 // Data URLs must be unmodified. For file and custom URLs clients may expect resources 103 // to be unique even when they differ by the fragment identifier only. 104 if (!originalURL.protocolIsInHTTPFamily()) 105 return originalURL; 106 KURL url = originalURL; 107 url.removeFragmentIdentifier(); 108 return url; 109 } 110 111 void MemoryCache::add(Resource* resource) 112 { 113 ASSERT(WTF::isMainThread()); 114 m_resources.set(resource->url(), resource); 115 resource->setInCache(true); 116 resource->updateForAccess(); 117 118 WTF_LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().string().latin1().data(), resource); 119 } 120 121 void MemoryCache::replace(Resource* newResource, Resource* oldResource) 122 { 123 evict(oldResource); 124 ASSERT(!m_resources.get(newResource->url())); 125 m_resources.set(newResource->url(), newResource); 126 newResource->setInCache(true); 127 insertInLRUList(newResource); 128 int delta = newResource->size(); 129 if (newResource->decodedSize() && newResource->hasClients()) 130 insertInLiveDecodedResourcesList(newResource); 131 if (delta) 132 adjustSize(newResource->hasClients(), delta); 133 } 134 135 Resource* MemoryCache::resourceForURL(const KURL& resourceURL) 136 { 137 ASSERT(WTF::isMainThread()); 138 KURL url = removeFragmentIdentifierIfNeeded(resourceURL); 139 Resource* resource = m_resources.get(url); 140 if (resource && !resource->makePurgeable(false)) { 141 ASSERT(!resource->hasClients()); 142 evict(resource); 143 return 0; 144 } 145 return resource; 146 } 147 148 size_t MemoryCache::deadCapacity() const 149 { 150 // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum. 151 size_t capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity. 152 capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum. 153 capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum. 154 return capacity; 155 } 156 157 size_t MemoryCache::liveCapacity() const 158 { 159 // Live resource capacity is whatever is left over after calculating dead resource capacity. 160 return m_capacity - deadCapacity(); 161 } 162 163 void MemoryCache::pruneLiveResources() 164 { 165 ASSERT(!m_prunePending); 166 size_t capacity = liveCapacity(); 167 if (!m_liveSize || (capacity && m_liveSize <= capacity)) 168 return; 169 170 size_t targetSize = static_cast<size_t>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. 171 172 // Destroy any decoded data in live objects that we can. 173 // Start from the tail, since this is the lowest priority 174 // and least recently accessed of the objects. 175 176 // The list might not be sorted by the m_lastDecodedFrameTimeStamp. The impact 177 // of this weaker invariant is minor as the below if statement to check the 178 // elapsedTime will evaluate to false as the current time will be a lot 179 // greater than the current->m_lastDecodedFrameTimeStamp. 180 // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209 181 182 // Start pruning from the lowest priority list. 183 for (int priority = Resource::CacheLiveResourcePriorityLow; priority <= Resource::CacheLiveResourcePriorityHigh; ++priority) { 184 Resource* current = m_liveDecodedResources[priority].m_tail; 185 while (current) { 186 Resource* prev = current->m_prevInLiveResourcesList; 187 ASSERT(current->hasClients()); 188 if (current->isLoaded() && current->decodedSize()) { 189 // Check to see if the remaining resources are too new to prune. 190 double elapsedTime = m_pruneFrameTimeStamp - current->m_lastDecodedAccessTime; 191 if (elapsedTime < m_delayBeforeLiveDecodedPrune) 192 return; 193 194 // Destroy our decoded data. This will remove us from 195 // m_liveDecodedResources, and possibly move us to a different LRU 196 // list in m_allResources. 197 current->destroyDecodedData(); 198 199 if (targetSize && m_liveSize <= targetSize) 200 return; 201 } 202 current = prev; 203 } 204 } 205 } 206 207 void MemoryCache::pruneDeadResources() 208 { 209 size_t capacity = deadCapacity(); 210 if (!m_deadSize || (capacity && m_deadSize <= capacity)) 211 return; 212 213 size_t targetSize = static_cast<size_t>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. 214 215 int size = m_allResources.size(); 216 217 // See if we have any purged resources we can evict. 218 for (int i = 0; i < size; i++) { 219 Resource* current = m_allResources[i].m_tail; 220 while (current) { 221 Resource* prev = current->m_prevInAllResourcesList; 222 if (current->wasPurged()) { 223 ASSERT(!current->hasClients()); 224 ASSERT(!current->isPreloaded()); 225 evict(current); 226 } 227 current = prev; 228 } 229 } 230 if (targetSize && m_deadSize <= targetSize) 231 return; 232 233 bool canShrinkLRULists = true; 234 for (int i = size - 1; i >= 0; i--) { 235 // Remove from the tail, since this is the least frequently accessed of the objects. 236 Resource* current = m_allResources[i].m_tail; 237 238 // First flush all the decoded data in this queue. 239 while (current) { 240 // Protect 'previous' so it can't get deleted during destroyDecodedData(). 241 ResourcePtr<Resource> previous = current->m_prevInAllResourcesList; 242 ASSERT(!previous || previous->inCache()); 243 if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) { 244 // Destroy our decoded data. This will remove us from 245 // m_liveDecodedResources, and possibly move us to a different 246 // LRU list in m_allResources. 247 current->destroyDecodedData(); 248 249 if (targetSize && m_deadSize <= targetSize) 250 return; 251 } 252 // Decoded data may reference other resources. Stop iterating if 'previous' somehow got 253 // kicked out of cache during destroyDecodedData(). 254 if (previous && !previous->inCache()) 255 break; 256 current = previous.get(); 257 } 258 259 // Now evict objects from this queue. 260 current = m_allResources[i].m_tail; 261 while (current) { 262 ResourcePtr<Resource> previous = current->m_prevInAllResourcesList; 263 ASSERT(!previous || previous->inCache()); 264 if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) { 265 evict(current); 266 if (targetSize && m_deadSize <= targetSize) 267 return; 268 } 269 if (previous && !previous->inCache()) 270 break; 271 current = previous.get(); 272 } 273 274 // Shrink the vector back down so we don't waste time inspecting 275 // empty LRU lists on future prunes. 276 if (m_allResources[i].m_head) 277 canShrinkLRULists = false; 278 else if (canShrinkLRULists) 279 m_allResources.resize(i); 280 } 281 } 282 283 void MemoryCache::setCapacities(size_t minDeadBytes, size_t maxDeadBytes, size_t totalBytes) 284 { 285 ASSERT(minDeadBytes <= maxDeadBytes); 286 ASSERT(maxDeadBytes <= totalBytes); 287 m_minDeadCapacity = minDeadBytes; 288 m_maxDeadCapacity = maxDeadBytes; 289 m_maxDeferredPruneDeadCapacity = cDeferredPruneDeadCapacityFactor * maxDeadBytes; 290 m_capacity = totalBytes; 291 prune(); 292 } 293 294 void MemoryCache::evict(Resource* resource) 295 { 296 ASSERT(WTF::isMainThread()); 297 WTF_LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().string().latin1().data()); 298 // The resource may have already been removed by someone other than our caller, 299 // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>. 300 if (resource->inCache()) { 301 // Remove from the resource map. 302 m_resources.remove(resource->url()); 303 resource->setInCache(false); 304 305 // Remove from the appropriate LRU list. 306 removeFromLRUList(resource); 307 removeFromLiveDecodedResourcesList(resource); 308 adjustSize(resource->hasClients(), -static_cast<ptrdiff_t>(resource->size())); 309 } else { 310 ASSERT(m_resources.get(resource->url()) != resource); 311 } 312 313 resource->deleteIfPossible(); 314 } 315 316 MemoryCache::LRUList* MemoryCache::lruListFor(Resource* resource) 317 { 318 unsigned accessCount = max(resource->accessCount(), 1U); 319 unsigned queueIndex = WTF::fastLog2(resource->size() / accessCount); 320 #ifndef NDEBUG 321 resource->m_lruIndex = queueIndex; 322 #endif 323 if (m_allResources.size() <= queueIndex) 324 m_allResources.grow(queueIndex + 1); 325 return &m_allResources[queueIndex]; 326 } 327 328 void MemoryCache::removeFromLRUList(Resource* resource) 329 { 330 // If we've never been accessed, then we're brand new and not in any list. 331 if (!resource->accessCount()) 332 return; 333 334 #if !ASSERT_DISABLED 335 unsigned oldListIndex = resource->m_lruIndex; 336 #endif 337 338 LRUList* list = lruListFor(resource); 339 340 #if !ASSERT_DISABLED 341 // Verify that the list we got is the list we want. 342 ASSERT(resource->m_lruIndex == oldListIndex); 343 344 // Verify that we are in fact in this list. 345 bool found = false; 346 for (Resource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { 347 if (current == resource) { 348 found = true; 349 break; 350 } 351 } 352 ASSERT(found); 353 #endif 354 355 Resource* next = resource->m_nextInAllResourcesList; 356 Resource* prev = resource->m_prevInAllResourcesList; 357 358 if (!next && !prev && list->m_head != resource) 359 return; 360 361 resource->m_nextInAllResourcesList = 0; 362 resource->m_prevInAllResourcesList = 0; 363 364 if (next) 365 next->m_prevInAllResourcesList = prev; 366 else if (list->m_tail == resource) 367 list->m_tail = prev; 368 369 if (prev) 370 prev->m_nextInAllResourcesList = next; 371 else if (list->m_head == resource) 372 list->m_head = next; 373 } 374 375 void MemoryCache::insertInLRUList(Resource* resource) 376 { 377 // Make sure we aren't in some list already. 378 ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList); 379 ASSERT(resource->inCache()); 380 ASSERT(resource->accessCount() > 0); 381 382 LRUList* list = lruListFor(resource); 383 384 resource->m_nextInAllResourcesList = list->m_head; 385 if (list->m_head) 386 list->m_head->m_prevInAllResourcesList = resource; 387 list->m_head = resource; 388 389 if (!resource->m_nextInAllResourcesList) 390 list->m_tail = resource; 391 392 #if !ASSERT_DISABLED 393 // Verify that we are in now in the list like we should be. 394 list = lruListFor(resource); 395 bool found = false; 396 for (Resource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { 397 if (current == resource) { 398 found = true; 399 break; 400 } 401 } 402 ASSERT(found); 403 #endif 404 405 } 406 407 void MemoryCache::removeFromLiveDecodedResourcesList(Resource* resource) 408 { 409 // If we've never been accessed, then we're brand new and not in any list. 410 if (!resource->m_inLiveDecodedResourcesList) 411 return; 412 resource->m_inLiveDecodedResourcesList = false; 413 414 LRUList* list = &m_liveDecodedResources[resource->cacheLiveResourcePriority()]; 415 416 #if !ASSERT_DISABLED 417 // Verify that we are in fact in this list. 418 bool found = false; 419 for (Resource* current = list->m_head; current; current = current->m_nextInLiveResourcesList) { 420 if (current == resource) { 421 found = true; 422 break; 423 } 424 } 425 ASSERT(found); 426 #endif 427 428 Resource* next = resource->m_nextInLiveResourcesList; 429 Resource* prev = resource->m_prevInLiveResourcesList; 430 431 if (!next && !prev && list->m_head != resource) 432 return; 433 434 resource->m_nextInLiveResourcesList = 0; 435 resource->m_prevInLiveResourcesList = 0; 436 437 if (next) 438 next->m_prevInLiveResourcesList = prev; 439 else if (list->m_tail == resource) 440 list->m_tail = prev; 441 442 if (prev) 443 prev->m_nextInLiveResourcesList = next; 444 else if (list->m_head == resource) 445 list->m_head = next; 446 } 447 448 void MemoryCache::insertInLiveDecodedResourcesList(Resource* resource) 449 { 450 // Make sure we aren't in the list already. 451 ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList); 452 resource->m_inLiveDecodedResourcesList = true; 453 454 LRUList* list = &m_liveDecodedResources[resource->cacheLiveResourcePriority()]; 455 resource->m_nextInLiveResourcesList = list->m_head; 456 if (list->m_head) 457 list->m_head->m_prevInLiveResourcesList = resource; 458 list->m_head = resource; 459 460 if (!resource->m_nextInLiveResourcesList) 461 list->m_tail = resource; 462 463 #if !ASSERT_DISABLED 464 // Verify that we are in now in the list like we should be. 465 bool found = false; 466 for (Resource* current = list->m_head; current; current = current->m_nextInLiveResourcesList) { 467 if (current == resource) { 468 found = true; 469 break; 470 } 471 } 472 ASSERT(found); 473 #endif 474 475 } 476 477 void MemoryCache::addToLiveResourcesSize(Resource* resource) 478 { 479 ASSERT(m_deadSize >= resource->size()); 480 m_liveSize += resource->size(); 481 m_deadSize -= resource->size(); 482 } 483 484 void MemoryCache::removeFromLiveResourcesSize(Resource* resource) 485 { 486 ASSERT(m_liveSize >= resource->size()); 487 m_liveSize -= resource->size(); 488 m_deadSize += resource->size(); 489 } 490 491 void MemoryCache::adjustSize(bool live, ptrdiff_t delta) 492 { 493 if (live) { 494 ASSERT(delta >= 0 || m_liveSize >= static_cast<size_t>(-delta) ); 495 m_liveSize += delta; 496 } else { 497 ASSERT(delta >= 0 || m_deadSize >= static_cast<size_t>(-delta) ); 498 m_deadSize += delta; 499 } 500 } 501 502 void MemoryCache::removeURLFromCache(ExecutionContext* context, const KURL& url) 503 { 504 if (context->isWorkerGlobalScope()) { 505 WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context); 506 workerGlobalScope->thread()->workerLoaderProxy().postTaskToLoader(createCallbackTask(&removeURLFromCacheInternal, url)); 507 return; 508 } 509 removeURLFromCacheInternal(context, url); 510 } 511 512 void MemoryCache::removeURLFromCacheInternal(ExecutionContext*, const KURL& url) 513 { 514 if (Resource* resource = memoryCache()->resourceForURL(url)) 515 memoryCache()->remove(resource); 516 } 517 518 void MemoryCache::TypeStatistic::addResource(Resource* o) 519 { 520 bool purged = o->wasPurged(); 521 bool purgeable = o->isPurgeable() && !purged; 522 size_t pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095; 523 count++; 524 size += purged ? 0 : o->size(); 525 liveSize += o->hasClients() ? o->size() : 0; 526 decodedSize += o->decodedSize(); 527 encodedSize += o->encodedSize(); 528 encodedSizeDuplicatedInDataURLs += o->url().protocolIsData() ? o->encodedSize() : 0; 529 purgeableSize += purgeable ? pageSize : 0; 530 purgedSize += purged ? pageSize : 0; 531 } 532 533 MemoryCache::Statistics MemoryCache::getStatistics() 534 { 535 Statistics stats; 536 ResourceMap::iterator e = m_resources.end(); 537 for (ResourceMap::iterator i = m_resources.begin(); i != e; ++i) { 538 Resource* resource = i->value; 539 switch (resource->type()) { 540 case Resource::Image: 541 stats.images.addResource(resource); 542 break; 543 case Resource::CSSStyleSheet: 544 stats.cssStyleSheets.addResource(resource); 545 break; 546 case Resource::Script: 547 stats.scripts.addResource(resource); 548 break; 549 case Resource::XSLStyleSheet: 550 stats.xslStyleSheets.addResource(resource); 551 break; 552 case Resource::Font: 553 stats.fonts.addResource(resource); 554 break; 555 default: 556 stats.other.addResource(resource); 557 break; 558 } 559 } 560 return stats; 561 } 562 563 void MemoryCache::evictResources() 564 { 565 for (;;) { 566 ResourceMap::iterator i = m_resources.begin(); 567 if (i == m_resources.end()) 568 break; 569 evict(i->value); 570 } 571 } 572 573 void MemoryCache::prune(Resource* justReleasedResource) 574 { 575 TRACE_EVENT0("renderer", "MemoryCache::prune()"); 576 577 if (m_inPruneResources) 578 return; 579 if (m_liveSize + m_deadSize <= m_capacity && m_maxDeadCapacity && m_deadSize <= m_maxDeadCapacity) // Fast path. 580 return; 581 582 // To avoid burdening the current thread with repetitive pruning jobs, 583 // pruning is postponed until the end of the current task. If it has 584 // been more that m_maxPruneDeferralDelay since the last prune, 585 // then we prune immediately. 586 // If the current thread's run loop is not active, then pruning will happen 587 // immediately only if it has been over m_maxPruneDeferralDelay 588 // since the last prune. 589 double currentTime = WTF::currentTime(); 590 if (m_prunePending) { 591 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { 592 pruneNow(currentTime); 593 } 594 } else { 595 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { 596 pruneNow(currentTime); // Delay exceeded, prune now. 597 } else { 598 // Defer. 599 blink::Platform::current()->currentThread()->addTaskObserver(this); 600 m_prunePending = true; 601 } 602 } 603 604 if (m_prunePending && m_deadSize > m_maxDeferredPruneDeadCapacity && justReleasedResource) { 605 // The following eviction does not respect LRU order, but it can be done 606 // immediately in constant time, as opposed to pruneDeadResources, which 607 // we would rather defer because it is O(N), which would make tear-down of N 608 // objects O(N^2) if we pruned immediately. This immediate eviction is a 609 // safeguard against runaway memory consumption by dead resources 610 // while a prune is pending. 611 evict(justReleasedResource); 612 613 // As a last resort, prune immediately 614 if (m_deadSize > m_maxDeferredPruneDeadCapacity) 615 pruneNow(currentTime); 616 } 617 } 618 619 void MemoryCache::willProcessTask() 620 { 621 } 622 623 void MemoryCache::didProcessTask() 624 { 625 // Perform deferred pruning 626 ASSERT(m_prunePending); 627 pruneNow(WTF::currentTime()); 628 } 629 630 void MemoryCache::pruneNow(double currentTime) 631 { 632 if (m_prunePending) { 633 m_prunePending = false; 634 blink::Platform::current()->currentThread()->removeTaskObserver(this); 635 } 636 637 TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true); 638 pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live. 639 pruneLiveResources(); 640 m_pruneFrameTimeStamp = FrameView::currentFrameTimeStamp(); 641 m_pruneTimeStamp = currentTime; 642 } 643 644 #ifdef MEMORY_CACHE_STATS 645 646 void MemoryCache::dumpStats(Timer<MemoryCache>*) 647 { 648 Statistics s = getStatistics(); 649 printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize"); 650 printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); 651 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize); 652 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize); 653 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize); 654 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize); 655 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize); 656 printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Other", s.other.count, s.other.size, s.other.liveSize, s.other.decodedSize, s.other.purgeableSize, s.other.purgedSize); 657 printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); 658 659 printf("Duplication of encoded data from data URLs\n"); 660 printf("%-13s %13d of %13d\n", "Images", s.images.encodedSizeDuplicatedInDataURLs, s.images.encodedSize); 661 printf("%-13s %13d of %13d\n", "CSS", s.cssStyleSheets.encodedSizeDuplicatedInDataURLs, s.cssStyleSheets.encodedSize); 662 printf("%-13s %13d of %13d\n", "XSL", s.xslStyleSheets.encodedSizeDuplicatedInDataURLs, s.xslStyleSheets.encodedSize); 663 printf("%-13s %13d of %13d\n", "JavaScript", s.scripts.encodedSizeDuplicatedInDataURLs, s.scripts.encodedSize); 664 printf("%-13s %13d of %13d\n", "Fonts", s.fonts.encodedSizeDuplicatedInDataURLs, s.fonts.encodedSize); 665 printf("%-13s %13d of %13d\n", "Other", s.other.encodedSizeDuplicatedInDataURLs, s.other.encodedSize); 666 } 667 668 void MemoryCache::dumpLRULists(bool includeLive) const 669 { 670 printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n"); 671 672 int size = m_allResources.size(); 673 for (int i = size - 1; i >= 0; i--) { 674 printf("\n\nList %d: ", i); 675 Resource* current = m_allResources[i].m_tail; 676 while (current) { 677 Resource* prev = current->m_prevInAllResourcesList; 678 if (includeLive || !current->hasClients()) 679 printf("(%.1fK, %.1fK, %uA, %dR, %d, %d); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients(), current->isPurgeable(), current->wasPurged()); 680 681 current = prev; 682 } 683 } 684 } 685 686 #endif // MEMORY_CACHE_STATS 687 688 } // namespace WebCore 689