blob: fdc9c8143cd299fc52c553b53004c1b4975859f5 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/recently_destroyed_hosts.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/process_lock.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
namespace content {
namespace {
// Maintains a list of recently destroyed processes to gather metrics on the
// potential for process reuse (crbug.com/894253).
const void* const kRecentlyDestroyedHostTrackerKey =
"RecentlyDestroyedHostTrackerKey";
// Sentinel value indicating that no recently destroyed process matches the
// host currently seeking a process. Changing this invalidates the histogram.
constexpr base::TimeDelta kRecentlyDestroyedNotFoundSentinel =
base::Seconds(20);
void RecordMetric(base::TimeDelta value) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"SiteIsolation.ReusePendingOrCommittedSite."
"TimeSinceReusableProcessDestroyed",
value, base::Milliseconds(1), kRecentlyDestroyedNotFoundSentinel, 50);
}
} // namespace
constexpr base::TimeDelta
RecentlyDestroyedHosts::kRecentlyDestroyedStorageTimeout;
RecentlyDestroyedHosts::~RecentlyDestroyedHosts() = default;
// static
void RecentlyDestroyedHosts::RecordMetricIfReusableHostRecentlyDestroyed(
const base::TimeTicks& reusable_host_lookup_time,
const ProcessLock& process_lock,
BrowserContext* browser_context) {
auto* instance = GetInstance(browser_context);
instance->RemoveExpiredEntries();
const auto iter = instance->recently_destroyed_hosts_.find(process_lock);
if (iter != instance->recently_destroyed_hosts_.end()) {
// A host was recently destroyed that matched |process_lock|. Record the
// interval between when the host was destroyed and when |process_lock|
// looked for a reusable host.
const base::TimeDelta reuse_interval =
reusable_host_lookup_time - iter->second;
RecordMetric(reuse_interval);
instance->AddReuseInterval(reuse_interval);
return;
}
RecordMetric(kRecentlyDestroyedNotFoundSentinel);
// Add zero to the list of recent reuse intervals to reduce the calculated
// delay when no reuse has been possible for a while.
instance->AddReuseInterval(base::TimeDelta());
}
// static
void RecentlyDestroyedHosts::Add(
RenderProcessHost* host,
const base::TimeDelta& time_spent_running_unload_handlers,
BrowserContext* browser_context) {
if (time_spent_running_unload_handlers > kRecentlyDestroyedStorageTimeout)
return;
ProcessLock process_lock = host->GetProcessLock();
// Don't record sites with an empty process lock. This includes sites on
// Android that are not isolated, and some special cases on desktop (e.g.,
// chrome-extension://). These sites would not be affected by increased
// process reuse, so are irrelevant for the metric being recorded.
if (!process_lock.is_locked_to_site())
return;
// Record the time before |time_spent_running_unload_handlers| to exclude time
// spent in delayed-shutdown state from the metric. This makes it consistent
// across processes that were delayed by DelayProcessShutdown(), and those
// that weren't.
auto* instance = GetInstance(browser_context);
instance->recently_destroyed_hosts_[process_lock] =
base::TimeTicks::Now() - time_spent_running_unload_handlers;
// Clean up list of recently destroyed hosts if it's getting large. This is
// a fallback in case a subframe process hasn't been created in a long time
// (which would clean up |recently_destroyed_hosts_|), e.g., on low-memory
// Android where site isolation is not used.
if (instance->recently_destroyed_hosts_.size() > 20)
instance->RemoveExpiredEntries();
}
// static
base::TimeDelta RecentlyDestroyedHosts::GetPercentileReuseInterval(
int percentile,
BrowserContext* browser_context) {
DCHECK_GE(percentile, 0);
DCHECK_LE(percentile, 100);
auto* instance = GetInstance(browser_context);
if (instance->reuse_intervals_.empty())
return base::TimeDelta();
auto iter = instance->reuse_intervals_.begin();
if (percentile == 0)
return iter->interval;
const size_t percentile_index =
std::ceil(instance->reuse_intervals_.size() * (percentile / 100.0)) - 1;
DCHECK_GE(percentile_index, 0u);
DCHECK_LT(percentile_index, instance->reuse_intervals_.size());
std::advance(iter, percentile_index);
return iter->interval;
}
RecentlyDestroyedHosts::RecentlyDestroyedHosts() = default;
RecentlyDestroyedHosts* RecentlyDestroyedHosts::GetInstance(
BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RecentlyDestroyedHosts* recently_destroyed_hosts =
static_cast<RecentlyDestroyedHosts*>(
browser_context->GetUserData(kRecentlyDestroyedHostTrackerKey));
if (recently_destroyed_hosts)
return recently_destroyed_hosts;
recently_destroyed_hosts = new RecentlyDestroyedHosts;
browser_context->SetUserData(kRecentlyDestroyedHostTrackerKey,
base::WrapUnique(recently_destroyed_hosts));
return recently_destroyed_hosts;
}
void RecentlyDestroyedHosts::RemoveExpiredEntries() {
const auto expired_cutoff_time =
base::TimeTicks::Now() - kRecentlyDestroyedStorageTimeout;
for (auto iter = recently_destroyed_hosts_.begin();
iter != recently_destroyed_hosts_.end();) {
if (iter->second < expired_cutoff_time) {
iter = recently_destroyed_hosts_.erase(iter);
} else {
++iter;
}
}
}
void RecentlyDestroyedHosts::AddReuseInterval(const base::TimeDelta& interval) {
// The maximum size of |reuse_intervals_|. Kept small to ensure that delay
// calculations that use |reuse_intervals_| are based on recent patterns.
static constexpr size_t kReuseIntervalsMaxSize = 5;
reuse_intervals_.insert({interval, base::TimeTicks::Now()});
if (reuse_intervals_.size() > kReuseIntervalsMaxSize) {
auto oldest_entry =
std::min_element(reuse_intervals_.begin() + 1, reuse_intervals_.end(),
[](ReuseInterval a, ReuseInterval b) {
return a.time_added < b.time_added;
});
reuse_intervals_.erase(oldest_entry);
}
DCHECK_LE(reuse_intervals_.size(), kReuseIntervalsMaxSize);
}
} // namespace content