blob: acde65ca9040c38982103691c6bc4d25ce8f06f6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/parkable_image_manager.h"
#include "base/metrics/histogram_functions.h"
#include "base/single_thread_task_runner.h"
#include "base/trace_event/process_memory_dump.h"
#include "third_party/blink/renderer/platform/graphics/parkable_image.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
const base::Feature kParkableImagesToDisk{"ParkableImagesToDisk",
base::FEATURE_DISABLED_BY_DEFAULT};
struct ParkableImageManager::Statistics {
size_t unparked_size = 0;
size_t on_disk_size = 0;
size_t total_size = 0;
};
constexpr const char* ParkableImageManager::kAllocatorDumpName;
constexpr base::TimeDelta ParkableImageManager::kDelayedParkingInterval;
// static
ParkableImageManager& ParkableImageManager::Instance() {
static base::NoDestructor<ParkableImageManager> instance;
return *instance;
}
bool ParkableImageManager::OnMemoryDump(
const base::trace_event::MemoryDumpArgs&,
base::trace_event::ProcessMemoryDump* pmd) {
auto* dump = pmd->CreateAllocatorDump(kAllocatorDumpName);
MutexLocker lock(lock_);
Statistics stats = ComputeStatistics();
dump->AddScalar("total_size", "bytes", stats.total_size);
dump->AddScalar("unparked_size", "bytes", stats.unparked_size);
dump->AddScalar("on_disk_size", "bytes", stats.on_disk_size);
return true;
}
ParkableImageManager::Statistics ParkableImageManager::ComputeStatistics()
const {
Statistics stats;
for (auto* unparked : unparked_images_)
stats.unparked_size += unparked->size();
for (auto* on_disk : on_disk_images_)
stats.on_disk_size += on_disk->size();
stats.total_size = stats.on_disk_size + stats.unparked_size;
return stats;
}
size_t ParkableImageManager::Size() const {
MutexLocker lock(lock_);
return on_disk_images_.size() + unparked_images_.size();
}
DiskDataAllocator& ParkableImageManager::data_allocator() const {
if (allocator_for_testing_)
return *allocator_for_testing_;
return DiskDataAllocator::Instance();
}
void ParkableImageManager::ResetForTesting() {
MutexLocker lock(lock_);
has_pending_parking_task_ = false;
has_posted_accounting_task_ = false;
unparked_images_.clear();
on_disk_images_.clear();
allocator_for_testing_ = nullptr;
total_disk_read_time_ = base::TimeDelta();
total_disk_write_time_ = base::TimeDelta();
}
void ParkableImageManager::Add(ParkableImage* image) {
DCHECK(IsMainThread());
MutexLocker lock(lock_);
ScheduleDelayedParkingTaskIfNeeded();
if (!has_posted_accounting_task_) {
auto task_runner = Thread::Current()->GetTaskRunner();
DCHECK(task_runner);
// |base::Unretained(this)| is fine because |this| is a NoDestructor
// singleton.
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ParkableImageManager::RecordStatisticsAfter5Minutes,
base::Unretained(this)),
base::TimeDelta::FromMinutes(5));
has_posted_accounting_task_ = true;
}
unparked_images_.insert(image);
}
void ParkableImageManager::RecordStatisticsAfter5Minutes() const {
DCHECK(IsMainThread());
MutexLocker lock(lock_);
Statistics stats = ComputeStatistics();
base::UmaHistogramCounts100000("Memory.ParkableImage.TotalSize.5min",
stats.total_size / 1024); // in KiB
base::UmaHistogramCounts100000("Memory.ParkableImage.OnDiskSize.5min",
stats.on_disk_size / 1024); // in KiB
base::UmaHistogramCounts100000("Memory.ParkableImage.UnparkedSize.5min",
stats.unparked_size / 1024); // in KiB
// Metrics related to parking only should be recorded if the feature is
// enabled.
if (IsParkableImagesToDiskEnabled()) {
base::UmaHistogramBoolean("Memory.ParkableImage.DiskIsUsable.5min",
data_allocator().may_write());
// These metrics only make sense if the disk allocator is used.
if (data_allocator().may_write()) {
base::UmaHistogramTimes("Memory.ParkableImage.TotalWriteTime.5min",
total_disk_write_time_);
base::UmaHistogramTimes("Memory.ParkableImage.TotalReadTime.5min",
total_disk_read_time_);
}
}
}
void ParkableImageManager::Remove(ParkableImage* image) {
DCHECK(IsMainThread());
MutexLocker lock(lock_);
// Image could be on disk or unparked. Remove it in either case.
unparked_images_.erase(image);
on_disk_images_.erase(image);
}
void ParkableImageManager::MoveImage(ParkableImage* image,
WTF::HashSet<ParkableImage*>* from,
WTF::HashSet<ParkableImage*>* to) {
auto it = from->find(image);
CHECK(it != from->end());
CHECK(!to->Contains(image));
from->erase(it);
to->insert(image);
}
void ParkableImageManager::OnWrittenToDisk(ParkableImage* image) {
MutexLocker lock(lock_);
MoveImage(image, &unparked_images_, &on_disk_images_);
}
void ParkableImageManager::OnReadFromDisk(ParkableImage* image) {
MutexLocker lock(lock_);
MoveImage(image, &on_disk_images_, &unparked_images_);
ScheduleDelayedParkingTaskIfNeeded();
}
void ParkableImageManager::ScheduleDelayedParkingTaskIfNeeded() {
if (!ParkableImageManager::IsParkableImagesToDiskEnabled())
return;
if (has_pending_parking_task_)
return;
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
Thread::Current()->GetTaskRunner();
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ParkableImageManager::MaybeParkImages,
base::Unretained(this)),
ParkableImageManager::kDelayedParkingInterval);
has_pending_parking_task_ = true;
}
void ParkableImageManager::MaybeParkImages() {
DCHECK(ParkableImageManager::IsParkableImagesToDiskEnabled());
MutexLocker lock(lock_);
// This makes a copy of the pointers stored in |unparked_images_|. We iterate
// over this copy in |MaybeParkImages|, instead of |unparked_images_|
// directly, for two reasons:
// (1) Avoiding a deadlock when we need to park synchronously (i.e. if we have
// already written to disk and don't need to post a background task), as
// synchronous parking calls |ParkableImageManager::OnWrittenToDisk()|;
// (2) Keeping the images alive until we are done iterating, without locking
// (through use of scoped_refptr instead of a raw pointer).
WTF::Vector<scoped_refptr<ParkableImage>> unparked_images;
for (auto* image : unparked_images_)
unparked_images.push_back(scoped_refptr<ParkableImage>(image));
// We unlock here so that we can avoid a deadlock, since if the data for the
// image is already written to disk, we can discard our copy of the data
// synchronously, which calls back into the manager.
lock_.unlock();
for (auto image : unparked_images)
image->MaybePark();
lock_.lock();
has_pending_parking_task_ = false;
if (unparked_images_.size() > 0)
ScheduleDelayedParkingTaskIfNeeded();
}
} // namespace blink