Commit 9cd252e6 authored by Tom McKee's avatar Tom McKee Committed by Commit Bot

Adding Memory.TabFootprint UKM events.

Memory.TabFootprint events give a high-level sample of how much memory
is used to render a given URL. To keep these events easy to reason about
and analyze, some memory isn't accounted for. Namely, when processes
host parts of other tabs, we don't record their contributions. We do,
however, make sure that an individual record has all the information
nescessary to classify it as a full or partial record.

Change-Id: If7a19031502e357be9567ebed760de234f2974ac
Reviewed-on: https://chromium-review.googlesource.com/c/1191817Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarSteven Holte <holte@chromium.org>
Commit-Queue: Tom McKee <tommckee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601136}
parent 729d147d
......@@ -790,6 +790,8 @@ jumbo_split_static_library("browser") {
"metrics/sampling_metrics_provider.h",
"metrics/subprocess_metrics_provider.cc",
"metrics/subprocess_metrics_provider.h",
"metrics/tab_footprint_aggregator.cc",
"metrics/tab_footprint_aggregator.h",
"metrics/testing/metrics_reporting_pref_helper.cc",
"metrics/testing/metrics_reporting_pref_helper.h",
"metrics/thread_watcher.cc",
......
......@@ -10,6 +10,7 @@
#include "base/trace_event/memory_dump_request_args.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/tab_footprint_aggregator.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/audio_service_info.h"
#include "content/public/browser/render_process_host.h"
......@@ -546,9 +547,12 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
uint32_t shared_footprint_total_kb = 0;
bool emit_metrics_for_all_processes = pid_scope_ == base::kNullProcessId;
TabFootprintAggregator per_tab_metrics;
base::Time now = base::Time::Now();
for (const auto& pmd : global_dump_->process_dumps()) {
private_footprint_total_kb += pmd.os_dump().private_footprint_kb;
uint32_t process_pmf_kb = pmd.os_dump().private_footprint_kb;
private_footprint_total_kb += process_pmf_kb;
shared_footprint_total_kb += pmd.os_dump().shared_footprint_kb;
if (!emit_metrics_for_all_processes && pid_scope_ != pmd.pid())
......@@ -562,23 +566,42 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
break;
}
case memory_instrumentation::mojom::ProcessType::RENDERER: {
renderer_private_footprint_total_kb +=
pmd.os_dump().private_footprint_kb;
resource_coordinator::mojom::PageInfoPtr page_info;
// If there is more than one frame being hosted in a renderer, don't
// emit any URLs. This is not ideal, but UKM does not support
// multiple-URLs per entry, and we must have one entry per process.
if (process_infos_.find(pmd.pid()) != process_infos_.end()) {
renderer_private_footprint_total_kb += process_pmf_kb;
resource_coordinator::mojom::PageInfoPtr single_page_info;
auto iter = process_infos_.find(pmd.pid());
if (iter != process_infos_.end()) {
const resource_coordinator::mojom::ProcessInfoPtr& process_info =
process_infos_[pmd.pid()];
iter->second;
if (emit_metrics_for_all_processes) {
// Renderer metrics-by-tab only make sense if we're visiting all
// render processes.
for (const resource_coordinator::mojom::PageInfoPtr& page_info :
process_info->page_infos) {
if (page_info->hosts_main_frame) {
per_tab_metrics.AssociateMainFrame(page_info->ukm_source_id,
pmd.pid(), page_info->tab_id,
process_pmf_kb);
} else {
per_tab_metrics.AssociateSubFrame(page_info->ukm_source_id,
pmd.pid(), page_info->tab_id,
process_pmf_kb);
}
}
}
// If there is more than one frame being hosted in a renderer, don't
// emit any per-renderer URLs. This is not ideal, but UKM does not
// support multiple-URLs per entry, and we must have one entry per
// process.
if (process_info->page_infos.size() == 1) {
page_info = std::move(process_info->page_infos[0]);
single_page_info = std::move(process_info->page_infos[0]);
}
}
int number_of_extensions = GetNumberOfExtensions(pmd.pid());
EmitRendererMemoryMetrics(
pmd, page_info, GetUkmRecorder(), number_of_extensions,
pmd, single_page_info, GetUkmRecorder(), number_of_extensions,
GetProcessUptime(now, pmd.pid()), emit_metrics_for_all_processes);
break;
}
......@@ -622,6 +645,9 @@ void ProcessMemoryMetricsEmitter::CollateResults() {
.SetTotal2_PrivateMemoryFootprint(private_footprint_total_kb / 1024)
.SetTotal2_SharedMemoryFootprint(shared_footprint_total_kb / 1024)
.Record(GetUkmRecorder());
}
// Renderer metrics-by-tab only make sense if we're visiting all render
// processes.
per_tab_metrics.RecordPmfs(GetUkmRecorder());
}
}
......@@ -476,6 +476,8 @@ ProcessInfoVector GetProcessInfo(ukm::TestUkmRecorder& ukm_recorder) {
PageInfoPtr page_info(resource_coordinator::mojom::PageInfo::New());
page_info->ukm_source_id = first_source_id;
page_info->tab_id = 201;
page_info->hosts_main_frame = true;
page_info->is_visible = true;
page_info->time_since_last_visibility_change =
base::TimeDelta::FromSeconds(15);
......@@ -497,11 +499,15 @@ ProcessInfoVector GetProcessInfo(ukm::TestUkmRecorder& ukm_recorder) {
GURL("http://www.url2022.com/"));
PageInfoPtr page_info1(resource_coordinator::mojom::PageInfo::New());
page_info1->ukm_source_id = first_source_id;
page_info1->tab_id = 2021;
page_info1->hosts_main_frame = true;
page_info1->time_since_last_visibility_change =
base::TimeDelta::FromSeconds(11);
page_info1->time_since_last_navigation = base::TimeDelta::FromSeconds(21);
PageInfoPtr page_info2(resource_coordinator::mojom::PageInfo::New());
page_info2->ukm_source_id = second_source_id;
page_info2->tab_id = 2022;
page_info2->hosts_main_frame = true;
page_info2->time_since_last_visibility_change =
base::TimeDelta::FromSeconds(12);
page_info2->time_since_last_navigation = base::TimeDelta::FromSeconds(22);
......@@ -784,3 +790,29 @@ TEST_F(ProcessMemoryMetricsEmitterTest,
"Memory.Total.RendererPrivateMemoryFootprint");
EXPECT_EQ(1, samples->TotalCount());
}
TEST_F(ProcessMemoryMetricsEmitterTest, MainFramePMFEmitted) {
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedRendererMetrics();
AddPageMetrics(expected_metrics);
PopulateRendererMetrics(global_dump, expected_metrics, 201);
auto entries = test_ukm_recorder_.GetEntriesByName(
ukm::builders::Memory_TabFootprint::kEntryName);
ASSERT_EQ(entries.size(), 0u);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedMemoryDump(
true, GlobalMemoryDump::MoveFrom(std::move(global_dump)));
emitter->ReceivedProcessInfos(GetProcessInfo(test_ukm_recorder_));
entries = test_ukm_recorder_.GetEntriesByName(
ukm::builders::Memory_TabFootprint::kEntryName);
ASSERT_EQ(entries.size(), 1u);
const auto* entry = entries.front();
ASSERT_TRUE(test_ukm_recorder_.EntryHasMetric(
entry, ukm::builders::Memory_TabFootprint::kMainFrameProcessPMFName));
}
// Copyright 2018 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 "chrome/browser/metrics/tab_footprint_aggregator.h"
#include <limits>
#include <numeric>
#include <utility>
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
using PageId = TabFootprintAggregator::PageId;
using ukm::builders::Memory_TabFootprint;
namespace {
static const uint64_t kInvalidAmount = std::numeric_limits<uint64_t>::max();
// Unfortunately, there's no way to read attributes from |UkmEntryBuilderBase|
// instances so we can't build up counts in-place. We'll use |TabStats| for
// building the counts and copy the results to a |Memory_TabFootprint| to
// report them. See the |Memory_TabFootprint| event definition in ukm.xml for a
// description of each metric; they correspond to the getters of this class.
// Note that this class expects and reports values in terms of kilobytes while
// the ukm event uses megabytes.
class TabStats {
public:
uint64_t GetMainFramePmf() const { return main_frame_pmf_; }
void SetMainFramePmf(uint64_t pmf_kb) { main_frame_pmf_ = pmf_kb; }
uint64_t GetSubFramePmf() const { return sub_frame_pmf_; }
void AddSubFramePmf(uint64_t pmf_kb) {
sub_frame_pmf_ += pmf_kb;
++sub_frames_included_;
}
void IgnoreSubFrame() { ++sub_frames_excluded_; }
uint64_t GetSubFramesIncluded() const { return sub_frames_included_; }
uint64_t GetSubFramesExcluded() const { return sub_frames_excluded_; }
uint64_t GetTabPmf() const {
if (sub_frames_excluded_ != 0) {
return kInvalidAmount;
}
if (main_frame_pmf_ == kInvalidAmount) {
return kInvalidAmount;
}
return main_frame_pmf_ + sub_frame_pmf_;
}
private:
uint64_t main_frame_pmf_ = kInvalidAmount;
uint64_t sub_frame_pmf_ = 0u;
uint64_t sub_frames_included_ = 0u;
uint64_t sub_frames_excluded_ = 0u;
};
} // namespace
TabFootprintAggregator::TabFootprintAggregator() = default;
TabFootprintAggregator::~TabFootprintAggregator() = default;
void TabFootprintAggregator::AssociateMainFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb) {
bool did_insert =
page_to_main_frame_process_.insert(std::make_pair(page_id, pid)).second;
DCHECK(did_insert) << "there shouldn't be more than one main frame per page.";
AssociateFrame(sid, pid, page_id, pmf_kb);
}
void TabFootprintAggregator::AssociateSubFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb) {
AssociateFrame(sid, pid, page_id, pmf_kb);
}
void TabFootprintAggregator::AssociateFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb) {
std::map<PageId, ukm::SourceId>::iterator insert_position;
bool did_insert;
std::tie(insert_position, did_insert) =
page_to_source_id_.insert(std::make_pair(page_id, sid));
// If there was already a |SourceId| associated to the |PageId|, make sure
// it's the same |SourceId| as |sid|. This guards against attempts to
// associate more than one top-level-navigation to a single tab.
DCHECK(did_insert || insert_position->second == sid)
<< "Can't associate multiple SourceIds to a single PageId.";
std::vector<PageId>& pages = process_to_pages_[pid];
DCHECK(!std::count(pages.begin(), pages.end(), page_id))
<< "Can't duplicate associations between a process and a page.";
pages.push_back(page_id);
std::vector<base::ProcessId>& processes = page_to_processes_[page_id];
DCHECK(!std::count(processes.begin(), processes.end(), pid))
<< "Can't duplicate associations between a page and a process.";
processes.push_back(pid);
process_to_pmf_.insert(std::make_pair(pid, pmf_kb));
}
void TabFootprintAggregator::RecordPmfs(ukm::UkmRecorder* ukm_recorder) const {
// A map from page identifier (1:1 with tab) to a collection of stats for
// that page's memory usage. Note that, if a component of a particular
// TabStats::tab_pmf is invalid, the whole tab_pmf is invalid.
std::map<PageId, TabStats> page_stats;
for (const auto& page_procs : page_to_processes_) {
PageId page_id = page_procs.first;
TabStats& sink = page_stats[page_id];
// Set |main_frame_process| to the id of the process that hosts the main
// frame for |page_id|.
base::ProcessId main_frame_process = base::kNullProcessId;
auto page_to_main_frame_process_iterator =
page_to_main_frame_process_.find(page_id);
if (page_to_main_frame_process_iterator !=
page_to_main_frame_process_.end()) {
main_frame_process = page_to_main_frame_process_iterator->second;
}
for (const auto& proc : page_procs.second) {
if (proc == main_frame_process) {
// Determine if the process hosting the main frame for |page_id| is only
// concerned with frames in that tab. If the process hosts frames from
// any other tab, we can't use the MainFrameProcessPMF.
const auto& all_tabs_for_proc = process_to_pages_.at(proc);
if (all_tabs_for_proc.size() == 1) {
DCHECK_EQ(sink.GetMainFramePmf(), kInvalidAmount)
<< "there can't be more than one process hosting a particular "
"frame.";
sink.SetMainFramePmf(process_to_pmf_.at(proc));
}
} else {
// The SubFrameProcessPMF is viable iff |proc| is associated to
// |page_id| only.
const auto& pages = process_to_pages_.at(proc);
if (pages.size() == 1) {
DCHECK_EQ(pages.front(), page_id);
sink.AddSubFramePmf(process_to_pmf_.at(proc));
} else {
sink.IgnoreSubFrame();
}
}
}
}
for (const auto& page_stat : page_stats) {
// Note: fields in |Memory_TabFootprint| use MB while our accumulators use
// KB.
Memory_TabFootprint sink(page_to_source_id_.at(page_stat.first));
// If MainFrameProcessPMF has been marked invalid it should be skipped.
uint64_t main_frame_pmf = page_stat.second.GetMainFramePmf();
if (main_frame_pmf != kInvalidAmount) {
sink.SetMainFrameProcessPMF(main_frame_pmf / 1024);
}
sink.SetSubFrameProcessPMF_Total(page_stat.second.GetSubFramePmf() / 1024);
sink.SetSubFrameProcessPMF_Included(
page_stat.second.GetSubFramesIncluded());
sink.SetSubFrameProcessPMF_Excluded(
page_stat.second.GetSubFramesExcluded());
// If TabPMF has been marked invalid it should be skipped.
uint64_t tab_pmf = page_stat.second.GetTabPmf();
if (tab_pmf != kInvalidAmount) {
sink.SetTabPMF(tab_pmf / 1024);
}
sink.Record(ukm_recorder);
}
}
// Copyright 2018 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.
#ifndef CHROME_BROWSER_METRICS_TAB_FOOTPRINT_AGGREGATOR_H_
#define CHROME_BROWSER_METRICS_TAB_FOOTPRINT_AGGREGATOR_H_
#include <map>
#include <vector>
#include "base/process/process_handle.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/resource_coordinator/public/cpp/coordination_unit_id.h"
namespace ukm {
class UkmRecorder;
}
// Given information about which render processes are responsible for hosting
// the main- and sub-frames of a page instance, this class produces
// |Memory_TabFootprint| UKM records. |Memory_TabFootprint| records can be used
// to analyze and monitor the effective memory footprints that real world sites
// impose.
class TabFootprintAggregator {
public:
TabFootprintAggregator();
~TabFootprintAggregator();
typedef resource_coordinator::CoordinationUnitID::CoordinationUnitTypeId
PageId;
// Tracks the process identified by |pid| as the host of the main-frame for
// the tab identified by |page_id|. |pmf_kb| should be the private memory
// footprint of the process. |sid| should be the source id of the tab's
// top-level navigation.
void AssociateMainFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb);
// Tracks the process identified by |pid| as the host of one or more
// sub-frames for the tab identified by |page_id|. |pmf_kb| should be the
// private memory footprint of the process. |sid| should be the source id of
// the tab's top-level navigation.
void AssociateSubFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb);
// Serializes this aggregator's current state as a collection of
// |Memory_TabFootprint| events which get written to the given recorder.
void RecordPmfs(ukm::UkmRecorder* ukm_recorder) const;
private:
void AssociateFrame(ukm::SourceId sid,
base::ProcessId pid,
PageId page_id,
uint64_t pmf_kb);
private:
// For a given tab, this tracks what renderer process hosts the main frame.
std::map<PageId, base::ProcessId> page_to_main_frame_process_;
// This tracks the tabs who have a frame hosted by a given process.
std::map<base::ProcessId, std::vector<PageId>> process_to_pages_;
// For a given tab, this tracks which processes host some frame in the tab.
std::map<PageId, std::vector<base::ProcessId>> page_to_processes_;
// Tracks the pmf (in kilobytes) of a given process.
std::map<base::ProcessId, uint64_t> process_to_pmf_;
// Tracks the main frame's |ukm::SourceId| for a given tab. Conceptually,
// distinct |ukm::SourceId|s correspond to distinct URLs. Note that, although
// multiple tabs can navigate to the same top-level URL, an individual tab
// can only be at a single URL at a time.
std::map<PageId, ukm::SourceId> page_to_source_id_;
};
#endif // CHROME_BROWSER_METRICS_TAB_FOOTPRINT_AGGREGATOR_H_
This diff is collapsed.
......@@ -2497,6 +2497,7 @@ test("unit_tests") {
"../browser/metrics/perf/perf_provider_chromeos_unittest.cc",
"../browser/metrics/process_memory_metrics_emitter_unittest.cc",
"../browser/metrics/subprocess_metrics_provider_unittest.cc",
"../browser/metrics/tab_footprint_aggregator_unittest.cc",
"../browser/metrics/thread_watcher_android_unittest.cc",
"../browser/metrics/thread_watcher_unittest.cc",
"../browser/navigation_predictor/navigation_predictor_unittest.cc",
......
......@@ -4,6 +4,8 @@
#include "services/resource_coordinator/coordination_unit/coordination_unit_introspector_impl.h"
#include <set>
#include <utility>
#include <vector>
#include "base/process/process_handle.h"
......@@ -14,6 +16,27 @@
#include "services/resource_coordinator/coordination_unit/process_coordination_unit_impl.h"
#include "services/service_manager/public/cpp/bind_source_info.h"
namespace {
using resource_coordinator::ProcessCoordinationUnitImpl;
using resource_coordinator::PageCoordinationUnitImpl;
using resource_coordinator::FrameCoordinationUnitImpl;
// Returns true iff the given |process| is responsible for hosting the
// main-frame of the given |page|.
bool HostsMainFrame(ProcessCoordinationUnitImpl* process,
PageCoordinationUnitImpl* page) {
FrameCoordinationUnitImpl* main_frame = page->GetMainFrameCoordinationUnit();
if (main_frame == nullptr) {
// |process| can't host a frame that doesn't exist.
return false;
}
return main_frame->GetProcessCoordinationUnit() == process;
}
} // namespace
namespace resource_coordinator {
CoordinationUnitIntrospectorImpl::CoordinationUnitIntrospectorImpl(
......@@ -39,7 +62,6 @@ void CoordinationUnitIntrospectorImpl::GetProcessToURLMap(
std::set<PageCoordinationUnitImpl*> page_cus =
process_cu->GetAssociatedPageCoordinationUnits();
std::vector<resource_coordinator::mojom::PageInfoPtr> page_infos;
for (PageCoordinationUnitImpl* page_cu : page_cus) {
int64_t ukm_source_id;
if (page_cu->GetProperty(
......@@ -47,6 +69,8 @@ void CoordinationUnitIntrospectorImpl::GetProcessToURLMap(
&ukm_source_id)) {
mojom::PageInfoPtr page_info(mojom::PageInfo::New());
page_info->ukm_source_id = ukm_source_id;
page_info->tab_id = page_cu->id().id;
page_info->hosts_main_frame = HostsMainFrame(process_cu, page_cu);
page_info->is_visible = page_cu->IsVisible();
page_info->time_since_last_visibility_change =
page_cu->TimeSinceLastVisibilityChange();
......
......@@ -8,8 +8,15 @@ import "services/resource_coordinator/public/mojom/coordination_unit.mojom";
import "mojo/public/mojom/base/process_id.mojom";
import "mojo/public/mojom/base/time.mojom";
// A |PageInfo| describes some metrics about a particular page with respect to
// a given process.
struct PageInfo {
// Identifier to distinguish which URL this |PageInfo| corresponds to.
int64 ukm_source_id;
// Identifier to distinguish which tab this |PageInfo| corresponds to.
uint64 tab_id;
// True iff the process for this |PageInfo| hosts the main frame of the page.
bool hosts_main_frame;
bool is_visible;
mojo_base.mojom.TimeDelta time_since_last_navigation;
mojo_base.mojom.TimeDelta time_since_last_visibility_change;
......
......@@ -2776,6 +2776,52 @@ be describing additional metrics about the same event.
</metric>
</event>
<event name="Memory.TabFootprint">
<owner>tommckee@chromium.org</owner>
<summary>
Measure of memory used by the processes that host an instance of a tab.
Intended as a high-level metric for analyzing effective memory use of
individual sites.
</summary>
<metric name="MainFrameProcessPMF">
<summary>
Measure of private memory, in MB, consumed by the render process dedicated
to hosting the main frame. Undefined if the main frame is hosted by a
render process used by other tabs. If this is undefined, TabPMF will be
undefined.
</summary>
</metric>
<metric name="SubFrameProcessPMF.Excluded">
<summary>
Number of render processes that were blocked from contributing to
SubFrameProcessPMF.Total. A process can have its contribution blocked if
the process has responsiblities outside the scope of the relevant tab. If
this is non-zero, TabPMF will be undefined.
</summary>
</metric>
<metric name="SubFrameProcessPMF.Included">
<summary>
Number of render processes that contributed to SubFrameProcessPMF.Total.
</summary>
</metric>
<metric name="SubFrameProcessPMF.Total">
<summary>
Measure of total private memory, in MB, consumed by the render processes
dedicated to hosting sub-frames of the tab. Note that, if a render process
hosts frames of other tabs, it isn't considered to be 'dedicated' and so
it doesn't contribute to this total.
</summary>
</metric>
<metric name="TabPMF">
<summary>
Measure of private memory, in MB, consumed by all render processes
dedicated to hosting some part of a tab. Undefined if there are processes
responsible for hosting parts of this tab while hosting parts of other
tabs.
</summary>
</metric>
</event>
<event name="Notification">
<owner>peter@chromium.org</owner>
<summary>
......
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