Commit 91616862 authored by Jesse McKenna's avatar Jesse McKenna Committed by Chromium LUCI CQ

Add metric for potential iframe process reuse

This change adds a metric to measure how often a navigation occurs
where the target URL matches a RenderProcessHost that was destroyed in
the past few seconds.

The new metric, "SiteIsolation.ReusePendingOrCommittedSite.
TimeSinceReusableProcessDestroyed", emits on every navigation. It
records the time (in ms) since a RenderProcessHost with a matching
process lock was destroyed, up to 7 seconds. If no matching host was
destroyed within the past 7 seconds, it records a sentinel value of 10
seconds.

Knowing the most common times since reusable processes were destroyed
will inform a broader experiment to potentially delay subframe process
shutdown by a few seconds to reduce process churn. Including the
sentinel value adds information around how frequently reusable
processes are destroyed, i.e., how much opportunity for process reuse
exists.

Test plan (see "Metrics" section for details on this metric):
https://docs.google.com/document/d/11XruVZzYLSkt4se6IV2O4jFOkKs-QbkRB5NMf4mH_hE

Bug: 894253
Change-Id: Ic15089d1e3b086480942b6afe32525b3c6461895
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575945Reviewed-by: default avatarOliver Li <olivierli@chromium.org>
Reviewed-by: default avatarBrian White <bcwhite@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Commit-Queue: Jesse McKenna <jessemckenna@google.com>
Cr-Commit-Position: refs/heads/master@{#846167}
parent 07d53283
......@@ -5,6 +5,7 @@
#include "content/browser/child_process_security_policy_impl.h"
#include <algorithm>
#include <tuple>
#include <utility>
#include "base/bind.h"
......@@ -236,6 +237,19 @@ bool ProcessLock::operator!=(const ProcessLock& rhs) const {
return !(*this == rhs);
}
bool ProcessLock::operator<(const ProcessLock& rhs) const {
const auto this_is_origin_keyed = is_origin_keyed();
const auto this_coop_coep_cross_origin_isolated_info =
coop_coep_cross_origin_isolated_info();
const auto rhs_is_origin_keyed = is_origin_keyed();
const auto rhs_coop_coep_cross_origin_isolated_info =
coop_coep_cross_origin_isolated_info();
return std::tie(lock_url(), this_is_origin_keyed,
this_coop_coep_cross_origin_isolated_info) <
std::tie(rhs.lock_url(), rhs_is_origin_keyed,
rhs_coop_coep_cross_origin_isolated_info);
}
std::string ProcessLock::ToString() const {
std::string ret = "{ ";
......
......@@ -161,6 +161,8 @@ class CONTENT_EXPORT ProcessLock {
bool operator==(const ProcessLock& rhs) const;
bool operator!=(const ProcessLock& rhs) const;
// Defined to allow this object to act as a key for std::map.
bool operator<(const ProcessLock& rhs) const;
std::string ToString() const;
......
......@@ -916,6 +916,118 @@ class SiteProcessCountTracker : public base::SupportsUserData::Data,
CountPerProcessPerSiteMap map_;
};
// Maintains a list of recently destroyed processes to gather metrics on the
// potential for process reuse (crbug.com/894253).
const void* const kRecentlyDestroyedHostTrackerKey =
"RecentlyDestroyedHostTrackerKey";
// Information about recently destroyed processes is stored for 7 seconds, about
// a second more than the longest time from process destruction to recreation
// observed in local tests.
static constexpr base::TimeDelta kRecentlyDestroyedTimeout =
base::TimeDelta::FromSeconds(7);
// Sentinel value indicating that no recently destroyed process matches the
// host currently seeking a process. Changing this invalidates the histogram.
static constexpr base::TimeDelta kRecentlyDestroyedNotFoundSentinel =
base::TimeDelta::FromSeconds(20);
class RecentlyDestroyedHosts : public base::SupportsUserData::Data {
public:
static base::WeakPtr<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->GetWeakPtr();
// Using WrapUnique() to access private constructor.
auto owned = base::WrapUnique(new RecentlyDestroyedHosts);
auto weak_ptr = owned->GetWeakPtr();
browser_context->SetUserData(kRecentlyDestroyedHostTrackerKey,
std::move(owned));
return weak_ptr;
}
RecentlyDestroyedHosts(const RecentlyDestroyedHosts& other) = delete;
RecentlyDestroyedHosts& operator=(const RecentlyDestroyedHosts& other) =
delete;
// If a host matching |process_lock| was recently destroyed, records the time
// between its destruction and |reusable_host_lookup_time|. If not, records a
// sentinel value.
void RecordMetricIfReusableHostRecentlyDestroyed(
const base::TimeTicks& reusable_host_lookup_time,
const ProcessLock& process_lock) {
if (map_.count(process_lock) == 1) {
RecordMetric(reusable_host_lookup_time - map_[process_lock]);
return;
}
RecordMetric(kRecentlyDestroyedNotFoundSentinel);
}
// Adds |host|'s process lock to the list of recently destroyed hosts, or
// updates its time if it's already present. Posts a task to remove it after
// |kRecentlyDestroyedTimeout|.
void Add(RenderProcessHost* host,
const base::TimeDelta& time_spent_in_delayed_shutdown) {
if (time_spent_in_delayed_shutdown > kRecentlyDestroyedTimeout)
return;
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
ProcessLock process_lock = policy->GetProcessLock(host->GetID());
// 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_in_delayed_shutdown| to exclude time
// spent running unload handlers from the metric. This makes it consistent
// across processes that were delayed by DelayProcessShutdownForUnload(),
// and those that weren't.
map_[process_lock] =
base::TimeTicks::Now() - time_spent_in_delayed_shutdown;
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RecentlyDestroyedHosts::Remove, GetWeakPtr(),
process_lock),
kRecentlyDestroyedTimeout - time_spent_in_delayed_shutdown);
}
base::WeakPtr<RecentlyDestroyedHosts> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
// Private constructor to ensure this class is only created via GetInstance().
RecentlyDestroyedHosts() = default;
void RecordMetric(base::TimeDelta value) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"SiteIsolation.ReusePendingOrCommittedSite."
"TimeSinceReusableProcessDestroyed",
value, base::TimeDelta::FromMilliseconds(1),
kRecentlyDestroyedNotFoundSentinel, 50);
}
// Removes |process_lock| from the list of recently destroyed hosts if
// |kRecentlyDestroyedTimeout| has elapsed since a matching host was last
// destroyed. If not, the same process lock was re-added to the list since
// this task was posted, so a later task is waiting to remove it.
void Remove(const ProcessLock& process_lock) {
if (base::TimeTicks::Now() - map_[process_lock] >=
kRecentlyDestroyedTimeout) {
map_.erase(process_lock);
}
}
std::map<ProcessLock, base::TimeTicks> map_;
base::WeakPtrFactory<RecentlyDestroyedHosts> weak_ptr_factory_{this};
};
bool ShouldUseSiteProcessTracking(BrowserContext* browser_context,
StoragePartition* dest_partition) {
// TODO(alexmos): Sites should be tracked separately for each
......@@ -2129,6 +2241,8 @@ void RenderProcessHostImpl::DelayProcessShutdownForUnload(
&RenderProcessHostImpl::CancelProcessShutdownDelayForUnload,
weak_factory_.GetWeakPtr()),
timeout);
time_spent_in_delayed_shutdown_ = timeout;
}
// static
......@@ -3722,6 +3836,9 @@ void RenderProcessHostImpl::Cleanup() {
NOTIFICATION_RENDERER_PROCESS_TERMINATED, Source<RenderProcessHost>(this),
NotificationService::NoDetails());
RecentlyDestroyedHosts::GetInstance(browser_context_)
->Add(this, time_spent_in_delayed_shutdown_);
#ifndef NDEBUG
is_self_deleted_ = true;
#endif
......@@ -4241,21 +4358,31 @@ RenderProcessHost* RenderProcessHostImpl::GetProcessHostForSiteInstance(
// First, attempt to reuse an existing RenderProcessHost if necessary.
switch (process_reuse_policy) {
case SiteInstanceImpl::ProcessReusePolicy::PROCESS_PER_SITE:
case SiteInstanceImpl::ProcessReusePolicy::PROCESS_PER_SITE: {
render_process_host = GetSoleProcessHostForSite(
site_instance->GetIsolationContext(), site_info);
break;
case SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE:
}
case SiteInstanceImpl::ProcessReusePolicy::
REUSE_PENDING_OR_COMMITTED_SITE: {
render_process_host =
FindReusableProcessHostForSiteInstance(site_instance);
const base::TimeTicks reusable_host_lookup_time = base::TimeTicks::Now();
UMA_HISTOGRAM_BOOLEAN(
"SiteIsolation.ReusePendingOrCommittedSite.CouldReuse",
render_process_host != nullptr);
if (!render_process_host) {
RecentlyDestroyedHosts::GetInstance(site_instance->GetBrowserContext())
->RecordMetricIfReusableHostRecentlyDestroyed(
reusable_host_lookup_time, site_instance->GetProcessLock());
}
if (render_process_host)
is_unmatched_service_worker = false;
break;
default:
}
default: {
break;
}
}
// If not, attempt to reuse an existing process with an unmatched service
......
......@@ -1160,6 +1160,11 @@ class CONTENT_EXPORT RenderProcessHostImpl
// Fields for recording MediaStream UMA.
bool has_recorded_media_stream_frame_depth_metric_ = false;
// Stores the amount of time that this RenderProcessHost's shutdown has been
// delayed to run unload handlers, or zero if the process shutdown was not
// delayed due to unload handlers.
base::TimeDelta time_spent_in_delayed_shutdown_;
// If the RenderProcessHost is being shutdown via Shutdown(), this records the
// exit code.
int shutdown_exit_code_;
......
......@@ -14558,6 +14558,20 @@ should be kept until we remove incident reporting. -->
</summary>
</histogram>
<histogram
name="SiteIsolation.ReusePendingOrCommittedSite.TimeSinceReusableProcessDestroyed"
units="ms" expires_after="2021-07-01">
<owner>jessemckenna@google.com</owner>
<owner>olivierli@chromium.org</owner>
<summary>
Recorded on navigations with a ProcessReusePolicy of
REUSE_PENDING_OR_COMMITTED_SITE (mostly subframe navigations). Measures the
time since a RenderProcessHost hosting the destination URL was last
destroyed, up to 7 seconds. If no host matching the destination was recently
destroyed, a sentinel value of 20 seconds is used.
</summary>
</histogram>
<histogram name="SiteIsolation.SavedUserTriggeredIsolatedOrigins.Size"
units="origins" expires_after="2021-09-30">
<owner>alexmos@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment