Skip to content

Commit

Permalink
PMF metrics for Android accounting for renderer binding state
Browse files Browse the repository at this point in the history
Waived Renderers on Android have a cached process priority. This means
they will be reclaimed by the OS through LMK when memory is required
for other higher priority processes/apps - which is effectively
anything.

Not Perceptible Renderers on Android have a lower than visible process priority. They can be reclaimed any time a visible or perceptible app
needs memory.

Since these processes are more easily reclaimed under memory pressure there is an argument to be made that the memory they represent, while technically part of Total.PMF, is available for other uses should it be required.

This CL adds a set of metrics which records the Total.PMF of Chrome without these waived or not perceptible renderers which represents the "effective visible priority" memory footprint of Chrome on Android.

Tested locally on emulator and confirmed the waived renderers are
excluded from the metric.

The VisibleOrHigher case is useful for checking the impact of current BindingManager connections while the ExcludingWaived is helpful for looking at connections dropped by the BindingManager.

Bug: 1361024
Change-Id: Ic3365ccc3a9028f0996b6d20d9bf7691127e3392
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3824358
Reviewed-by: Bo Liu <boliu@chromium.org>
Commit-Queue: Avi Drissman <avi@chromium.org>
Auto-Submit: Calder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: Benoit Lize <lizeb@chromium.org>
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1046922}
  • Loading branch information
ckitagawa-work authored and Chromium LUCI CQ committed Sep 14, 2022
1 parent 8669ce5 commit bf52866
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,18 @@ public void removeNotPerceptibleBinding() {
}
}

/**
* @return the current connection binding state.
*/
public @ChildBindingState int bindingStateCurrent() {
// WARNING: this method can be called from a thread other than the launcher thread.
// Note that it returns the current waived bound only state and is racy. This not really
// preventable without changing the caller's API, short of blocking.
synchronized (mBindingStateLock) {
return mBindingState;
}
}

/**
* @return true if the connection is bound and only bound with the waived binding or if the
* connection is unbound and was only bound with the waived binding when it disconnected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ public void onConnectionLost(ChildProcessConnection connection) {}
// The IBinder interfaces provided to the created service.
private final List<IBinder> mClientInterfaces;

// The actual service connection. Set once we have connected to the service.
private ChildProcessConnection mConnection;
// The actual service connection. Set once we have connected to the service. Volatile as it is
// accessed from threads other than the Launcher thread.
private volatile ChildProcessConnection mConnection;

/**
* Constructor.
Expand Down
76 changes: 75 additions & 1 deletion chrome/browser/metrics/process_memory_metrics_emitter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/child_process_binding_types.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_map.h"
Expand Down Expand Up @@ -960,6 +964,33 @@ void EmitUtilityMemoryMetrics(HistogramProcessType ptype,
builder.Record(ukm_recorder);
}

#if BUILDFLAG(IS_ANDROID)
// Return the base::android::ChildBindingState if the process with `pid` is a
// renderer. If the `pid` is not in the list of live renderers it is assumed to
// be unbound. If the `process_type` is not for a renderer return nullopt.
absl::optional<base::android::ChildBindingState>
GetAndroidRendererProcessBindingState(
memory_instrumentation::mojom::ProcessType process_type,
base::ProcessId pid) {
if (process_type != memory_instrumentation::mojom::ProcessType::RENDERER) {
return absl::nullopt;
}
for (auto iter = content::RenderProcessHost::AllHostsIterator();
!iter.IsAtEnd(); iter.Advance()) {
if (!iter.GetCurrentValue()->GetProcess().IsValid())
continue;

if (iter.GetCurrentValue()->GetProcess().Pid() == pid) {
return iter.GetCurrentValue()->GetEffectiveChildBindingState();
}
}
// This can occur if the process no longer exists. Specifically, it is
// possible a memory dump was requested, but the process was killed before
// reaching this point so we cannot check its status. Treat as UNBOUND.
return base::android::ChildBindingState::UNBOUND;
}
#endif // BUILDFLAG(IS_ANDROID)

} // namespace

ProcessMemoryMetricsEmitter::ProcessMemoryMetricsEmitter()
Expand Down Expand Up @@ -1126,6 +1157,12 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
return;

uint32_t private_footprint_total_kb = 0;
#if BUILDFLAG(IS_ANDROID)
uint32_t private_footprint_excluding_waived_total_kb = 0;
uint32_t renderer_private_footprint_excluding_waived_total_kb = 0;
uint32_t private_footprint_visible_or_higher_total_kb = 0;
uint32_t renderer_private_footprint_visible_or_higher_total_kb = 0;
#endif // BUILDFLAG(IS_ANDROID)
uint32_t renderer_private_footprint_total_kb = 0;
uint32_t renderer_malloc_total_kb = 0;
uint32_t shared_footprint_total_kb = 0;
Expand All @@ -1139,6 +1176,24 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
for (const auto& pmd : global_dump_->process_dumps()) {
uint32_t process_pmf_kb = pmd.os_dump().private_footprint_kb;
private_footprint_total_kb += process_pmf_kb;
#if BUILDFLAG(IS_ANDROID)
bool is_waived_renderer = false;
bool is_less_than_visible_renderer = false;
auto renderer_binding_state_android =
GetAndroidRendererProcessBindingState(pmd.process_type(), pmd.pid());
if (renderer_binding_state_android) {
// Also exclude base::android::ChildBindingState::UNBOUND which can occur
// as the state change can be racy.
is_waived_renderer = *renderer_binding_state_android <=
base::android::ChildBindingState::WAIVED;
is_less_than_visible_renderer = *renderer_binding_state_android <
base::android::ChildBindingState::VISIBLE;
}
private_footprint_excluding_waived_total_kb +=
is_waived_renderer ? 0 : process_pmf_kb;
private_footprint_visible_or_higher_total_kb +=
is_less_than_visible_renderer ? 0 : process_pmf_kb;
#endif // BUILDFLAG(IS_ANDROID)
shared_footprint_total_kb += pmd.os_dump().shared_footprint_kb;
resident_set_total_kb += pmd.os_dump().resident_set_kb;

Expand All @@ -1154,6 +1209,12 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
}
case memory_instrumentation::mojom::ProcessType::RENDERER: {
renderer_private_footprint_total_kb += process_pmf_kb;
#if BUILDFLAG(IS_ANDROID)
renderer_private_footprint_excluding_waived_total_kb +=
is_waived_renderer ? 0 : process_pmf_kb;
renderer_private_footprint_visible_or_higher_total_kb +=
is_less_than_visible_renderer ? 0 : process_pmf_kb;
#endif // BUILDFLAG(IS_ANDROID)
const PageInfo* single_page_info = nullptr;
auto iter = process_infos_.find(pmd.pid());
if (iter != process_infos_.end()) {
Expand Down Expand Up @@ -1287,9 +1348,22 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
renderer_malloc_total_kb / kKiB);
UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Total.SharedMemoryFootprint",
shared_footprint_total_kb / kKiB);

UMA_HISTOGRAM_MEMORY_MEDIUM_MB("Memory.Total.TileMemory",
tiles_total_memory / kMiB);
#if BUILDFLAG(IS_ANDROID)
UMA_HISTOGRAM_MEMORY_LARGE_MB(
"Memory.Total.PrivateMemoryFootprintExcludingWaivedRenderers",
private_footprint_excluding_waived_total_kb / kKiB);
UMA_HISTOGRAM_MEMORY_LARGE_MB(
"Memory.Total.RendererPrivateMemoryFootprintExcludingWaived",
renderer_private_footprint_excluding_waived_total_kb / kKiB);
UMA_HISTOGRAM_MEMORY_LARGE_MB(
"Memory.Total.PrivateMemoryFootprintVisibleOrHigherPriorityRenderers",
private_footprint_visible_or_higher_total_kb / kKiB);
UMA_HISTOGRAM_MEMORY_LARGE_MB(
"Memory.Total.RendererPrivateMemoryFootprintVisibleOrHigherPriority",
renderer_private_footprint_visible_or_higher_total_kb / kKiB);
#endif

Memory_Experimental(ukm::UkmRecorder::GetNewSourceID())
.SetTotal2_PrivateMemoryFootprint(private_footprint_total_kb / kKiB)
Expand Down
24 changes: 24 additions & 0 deletions chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,16 @@ TEST_F(ProcessMemoryMetricsEmitterTest, RendererAndTotalHistogramsAreRecorded) {
"Memory.NativeLibrary.NotResidentOrderedCodeMemoryFootprint", 0);
histograms.ExpectTotalCount(
"Memory.NativeLibrary.ResidentNotOrderedCodeMemoryFootprint", 0);
#if BUILDFLAG(IS_ANDROID)
histograms.ExpectTotalCount(
"Memory.Total.PrivateMemoryFootprintExcludingWaivedRenderers", 0);
histograms.ExpectTotalCount(
"Memory.Total.RendererPrivateMemoryFootprintExcludingWaived", 0);
histograms.ExpectTotalCount(
"Memory.Total.PrivateMemoryFootprintVisibleOrHigherPriorityRenderers", 0);
histograms.ExpectTotalCount(
"Memory.Total.RendererPrivateMemoryFootprintVisibleOrHigherPriority", 0);
#endif

// Simulate some metrics emission.
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter =
Expand Down Expand Up @@ -996,6 +1006,20 @@ TEST_F(ProcessMemoryMetricsEmitterTest, RendererAndTotalHistogramsAreRecorded) {
histograms.ExpectUniqueSample(
"Memory.NativeLibrary.ResidentNotOrderedCodeMemoryFootprint",
kNativeLibraryResidentNotOrderedCodeFootprint, 1);
#if BUILDFLAG(IS_ANDROID)
// Expect values of 0 as the Renderer is not in the list of active processes
// and is therefore considered UNBOUND and will not emit these values.
histograms.ExpectUniqueSample(
"Memory.Total.PrivateMemoryFootprintExcludingWaivedRenderers", 0, 1);
histograms.ExpectUniqueSample(
"Memory.Total.RendererPrivateMemoryFootprintExcludingWaived", 0, 1);
histograms.ExpectUniqueSample(
"Memory.Total.PrivateMemoryFootprintVisibleOrHigherPriorityRenderers", 0,
1);
histograms.ExpectUniqueSample(
"Memory.Total.RendererPrivateMemoryFootprintVisibleOrHigherPriority", 0,
1);
#endif
}

TEST_F(ProcessMemoryMetricsEmitterTest, MainFramePMFEmitted) {
Expand Down
9 changes: 9 additions & 0 deletions content/browser/child_process_launcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include "content/public/common/content_switches.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/child_process_binding_types.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "content/browser/child_process_task_port_provider_mac.h"
#endif
Expand Down Expand Up @@ -227,6 +231,11 @@ bool ChildProcessLauncher::TerminateProcess(const base::Process& process,
}

#if BUILDFLAG(IS_ANDROID)
base::android::ChildBindingState
ChildProcessLauncher::GetEffectiveChildBindingState() {
return helper_->GetEffectiveChildBindingState();
}

void ChildProcessLauncher::DumpProcessStack() {
base::Process to_pass = process_.process.Duplicate();
GetProcessLauncherTaskRunner()->PostTask(
Expand Down
8 changes: 8 additions & 0 deletions content/browser/child_process_launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@

namespace base {
class CommandLine;
#if BUILDFLAG(IS_ANDROID)
namespace android {
enum class ChildBindingState;
}
#endif
} // namespace base

namespace perfetto {
namespace protos {
Expand Down Expand Up @@ -264,6 +269,9 @@ class CONTENT_EXPORT ChildProcessLauncher {
Client* ReplaceClientForTest(Client* client);

#if BUILDFLAG(IS_ANDROID)
// Returns the highest binding state for the ChildProcessConnection.
base::android::ChildBindingState GetEffectiveChildBindingState();

// Dumps the stack of the child process without crashing it.
void DumpProcessStack();
#endif
Expand Down
9 changes: 5 additions & 4 deletions content/browser/child_process_launcher_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ using FileMappedForLaunch = base::HandlesToInheritVector;
// process. Since ChildProcessLauncher can be deleted by its client at any time,
// this class is used to keep state as the process is started asynchronously.
// It also contains the platform specific pieces.
class ChildProcessLauncherHelper :
public base::RefCountedThreadSafe<ChildProcessLauncherHelper> {
class ChildProcessLauncherHelper
: public base::RefCountedThreadSafe<ChildProcessLauncherHelper> {
public:
// Abstraction around a process required to deal in a platform independent way
// between Linux (which can use zygotes) and the other platforms.
Expand Down Expand Up @@ -191,8 +191,9 @@ class ChildProcessLauncherHelper :
const ChildProcessLauncherPriority& priority);

#if BUILDFLAG(IS_ANDROID)
void OnChildProcessStarted(JNIEnv* env,
jint handle);
void OnChildProcessStarted(JNIEnv* env, jint handle);

base::android::ChildBindingState GetEffectiveChildBindingState();

// Dumps the stack of the child process without crashing it.
void DumpProcessStack(const base::Process& process);
Expand Down
9 changes: 9 additions & 0 deletions content/browser/child_process_launcher_helper_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ base::File OpenFileToShare(const base::FilePath& path,
return base::File(base::android::OpenApkAsset(path.value(), region));
}

base::android::ChildBindingState
ChildProcessLauncherHelper::GetEffectiveChildBindingState() {
JNIEnv* env = AttachCurrentThread();
DCHECK(env);
return static_cast<base::android::ChildBindingState>(
Java_ChildProcessLauncherHelperImpl_getEffectiveChildBindingState(
env, java_peer_));
}

void ChildProcessLauncherHelper::DumpProcessStack(
const base::Process& process) {
JNIEnv* env = AttachCurrentThread();
Expand Down
11 changes: 11 additions & 0 deletions content/browser/renderer_host/render_process_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
#include "url/origin.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/child_process_binding_types.h"
#include "content/browser/android/java_interfaces_impl.h"
#include "content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h"
#include "content/public/browser/android/java_interfaces.h"
Expand Down Expand Up @@ -2890,6 +2891,16 @@ ChildProcessImportance RenderProcessHostImpl::GetEffectiveImportance() {
return effective_importance_;
}

base::android::ChildBindingState
RenderProcessHostImpl::GetEffectiveChildBindingState() {
if (child_process_launcher_) {
return child_process_launcher_->GetEffectiveChildBindingState();
}

// If there is no ChildProcessLauncher this is the best default.
return base::android::ChildBindingState::UNBOUND;
}

void RenderProcessHostImpl::DumpProcessStack() {
if (child_process_launcher_)
child_process_launcher_->DumpProcessStack();
Expand Down
6 changes: 6 additions & 0 deletions content/browser/renderer_host/render_process_host_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
namespace base {
class CommandLine;
class PersistentMemoryAllocator;
#if BUILDFLAG(IS_ANDROID)
namespace android {
enum class ChildBindingState;
}
#endif
} // namespace base

namespace blink {
Expand Down Expand Up @@ -238,6 +243,7 @@ class CONTENT_EXPORT RenderProcessHostImpl
void ClearPriorityOverride() override;
#if BUILDFLAG(IS_ANDROID)
ChildProcessImportance GetEffectiveImportance() override;
base::android::ChildBindingState GetEffectiveChildBindingState() override;
void DumpProcessStack() override;
#endif
void SetSuddenTerminationAllowed(bool enabled) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,18 @@ static void stop(int pid) {
}
}

// Called on client (UI or IO) thread.
@CalledByNative
private @ChildBindingState int getEffectiveChildBindingState() {
ChildProcessConnection connection = mLauncher.getConnection();
// Here we are accessing the connection from a thread other than the launcher thread, but it
// does not change once it's been set. So it is safe to test whether it's null here and
// access it afterwards.
if (connection == null) return ChildBindingState.UNBOUND;

return connection.bindingStateCurrent();
}

/**
* Dumps the stack of the child process with |pid| without crashing it.
* @param pid Process id of the child process.
Expand Down
8 changes: 8 additions & 0 deletions content/public/browser/render_process_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ namespace base {
class PersistentMemoryAllocator;
class TimeDelta;
class Token;
#if BUILDFLAG(IS_ANDROID)
namespace android {
enum class ChildBindingState;
}
#endif
} // namespace base

namespace blink {
Expand Down Expand Up @@ -317,6 +322,9 @@ class CONTENT_EXPORT RenderProcessHost : public IPC::Sender,
// Return the highest importance of all widgets in this process.
virtual ChildProcessImportance GetEffectiveImportance() = 0;

// Return the highest binding this process has.
virtual base::android::ChildBindingState GetEffectiveChildBindingState() = 0;

// Dumps the stack of this render process without crashing it.
virtual void DumpProcessStack() = 0;
#endif
Expand Down
8 changes: 7 additions & 1 deletion content/public/test/mock_render_process_host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,12 @@ ChildProcessImportance MockRenderProcessHost::GetEffectiveImportance() {
return ChildProcessImportance::NORMAL;
}

base::android::ChildBindingState
MockRenderProcessHost::GetEffectiveChildBindingState() {
NOTIMPLEMENTED();
return base::android::ChildBindingState::UNBOUND;
}

void MockRenderProcessHost::DumpProcessStack() {}
#endif

Expand Down Expand Up @@ -598,7 +604,7 @@ void MockRenderProcessHost::OverrideRendererInterfaceForTesting(

MockRenderProcessHostFactory::MockRenderProcessHostFactory() = default;

MockRenderProcessHostFactory::~MockRenderProcessHostFactory() {}
MockRenderProcessHostFactory::~MockRenderProcessHostFactory() = default;

RenderProcessHost* MockRenderProcessHostFactory::CreateRenderProcessHost(
BrowserContext* browser_context,
Expand Down
2 changes: 2 additions & 0 deletions content/public/test/mock_render_process_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "net/base/network_isolation_key.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/child_process_binding_types.h"
#include "content/public/browser/android/child_process_importance.h"
#endif

Expand Down Expand Up @@ -146,6 +147,7 @@ class MockRenderProcessHost : public RenderProcessHost {
void ClearPriorityOverride() override;
#if BUILDFLAG(IS_ANDROID)
ChildProcessImportance GetEffectiveImportance() override;
base::android::ChildBindingState GetEffectiveChildBindingState() override;
void DumpProcessStack() override;
#endif
void SetSuddenTerminationAllowed(bool allowed) override;
Expand Down
Loading

0 comments on commit bf52866

Please sign in to comment.