Skip to content

Commit afd5cf3

Browse files
Cherry pick PR #9128: cobalt/metrics: Port chrome memory metrics emitter to Cobalt (#9209)
Refer to the original PR: #9128 This refactor moves the core logic of ProcessMemoryMetricsEmitter from //chrome to //content to enable cross-platform reuse of granular memory reporting. This allows Cobalt and other content embedders to benefit from detailed breakdowns of memory usage by region (Malloc, V8, Skia, etc.) without duplicating the fetching and emission logic. Key changes: - Created content::ProcessMemoryMetricsEmitter as a generic base class. - Moved the header to content/public/browser for accessibility. - Updated chrome::ProcessMemoryMetricsEmitter to inherit from the base class, maintaining UKM and performance_manager integrations. - Integrated the new emitter into CobaltMetricsServiceClient, replacing manual collection with automated, region-specific reporting. - Standardized UMA emission using base::UmaHistogram functions. - Updated cobalt_unittests and cobalt_browsertests to verify the new granular histograms (e.g., Memory.Experimental.Browser2.Malloc). Test: `$OUT_DIR/bin/run_cobalt_browsertests --gtest_filter="*CobaltMetricsBrowserTest.*"` Test: `$OUT_DIR/bin/run_cobalt_unittests --gtest_filter="CobaltMetricsServiceClientTest.*:CobaltMetricsLogUploaderTest.*:CobaltEnabledStateProviderTest.*"` Bug: 483715113 --------- Co-authored-by: Adam Walls <avvall@google.com>
1 parent 97ca4ee commit afd5cf3

File tree

7 files changed

+726
-67
lines changed

7 files changed

+726
-67
lines changed

cobalt/browser/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ source_set("metrics") {
143143
sources = [
144144
"metrics/cobalt_enabled_state_provider.cc",
145145
"metrics/cobalt_enabled_state_provider.h",
146+
"metrics/cobalt_memory_metrics_emitter.cc",
147+
"metrics/cobalt_memory_metrics_emitter.h",
146148
"metrics/cobalt_metrics_log_uploader.cc",
147149
"metrics/cobalt_metrics_log_uploader.h",
148150
"metrics/cobalt_metrics_service_client.cc",
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
// Copyright 2026 The Cobalt Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "cobalt/browser/metrics/cobalt_memory_metrics_emitter.h"
16+
17+
#include <string>
18+
#include <string_view>
19+
#include <utility>
20+
21+
#include "base/functional/bind.h"
22+
#include "base/logging.h"
23+
#include "base/metrics/histogram_functions.h"
24+
#include "base/metrics/histogram_macros.h"
25+
#include "base/strings/strcat.h"
26+
#include "base/task/sequenced_task_runner.h"
27+
#include "base/trace_event/memory_dump_request_args.h"
28+
#include "build/build_config.h"
29+
#include "services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.h"
30+
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
31+
32+
#if BUILDFLAG(IS_ANDROID)
33+
#include "base/android/meminfo_dump_provider.h"
34+
#endif
35+
36+
using base::trace_event::MemoryAllocatorDump;
37+
using memory_instrumentation::GetPrivateFootprintHistogramName;
38+
using memory_instrumentation::GlobalMemoryDump;
39+
using memory_instrumentation::HistogramProcessType;
40+
using memory_instrumentation::HistogramProcessTypeToString;
41+
using memory_instrumentation::kMemoryHistogramPrefix;
42+
43+
namespace cobalt {
44+
45+
namespace {
46+
47+
const char kEffectiveSize[] = "effective_size";
48+
const char kAllocatedObjectsSize[] = "allocated_objects_size";
49+
50+
constexpr int kKiB = 1024;
51+
constexpr int kMiB = 1024 * 1024;
52+
53+
const CobaltMemoryMetricsEmitter::Metric kAllocatorDumpNamesForMetrics[] = {
54+
{"blink_gc",
55+
"BlinkGC",
56+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
57+
kEffectiveSize,
58+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
59+
{}},
60+
{"blink_gc",
61+
"BlinkGC.AllocatedObjects",
62+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
63+
kAllocatedObjectsSize,
64+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
65+
{}},
66+
{"blink_objects/Document",
67+
"NumberOfDocuments",
68+
CobaltMemoryMetricsEmitter::MetricSize::kTiny,
69+
MemoryAllocatorDump::kNameObjectCount,
70+
CobaltMemoryMetricsEmitter::EmitTo::kCountsInUkmAndSizeInUma,
71+
{}},
72+
{"blink_objects/Frame",
73+
"NumberOfFrames",
74+
CobaltMemoryMetricsEmitter::MetricSize::kTiny,
75+
MemoryAllocatorDump::kNameObjectCount,
76+
CobaltMemoryMetricsEmitter::EmitTo::kCountsInUkmAndSizeInUma,
77+
{}},
78+
{"blink_objects/LayoutObject",
79+
"NumberOfLayoutObjects",
80+
CobaltMemoryMetricsEmitter::MetricSize::kTiny,
81+
MemoryAllocatorDump::kNameObjectCount,
82+
CobaltMemoryMetricsEmitter::EmitTo::kCountsInUkmAndSizeInUma,
83+
{}},
84+
{"blink_objects/Node",
85+
"NumberOfNodes",
86+
CobaltMemoryMetricsEmitter::MetricSize::kSmall,
87+
MemoryAllocatorDump::kNameObjectCount,
88+
CobaltMemoryMetricsEmitter::EmitTo::kCountsInUkmAndSizeInUma,
89+
{}},
90+
{"font_caches",
91+
"FontCaches",
92+
CobaltMemoryMetricsEmitter::MetricSize::kSmall,
93+
kEffectiveSize,
94+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
95+
{}},
96+
{"java_heap",
97+
"JavaHeap",
98+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
99+
kEffectiveSize,
100+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
101+
{}},
102+
{"leveldatabase",
103+
"LevelDatabase",
104+
CobaltMemoryMetricsEmitter::MetricSize::kSmall,
105+
kEffectiveSize,
106+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
107+
{}},
108+
{"malloc",
109+
"Malloc",
110+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
111+
kEffectiveSize,
112+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
113+
{}},
114+
{"malloc",
115+
"Malloc.AllocatedObjects",
116+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
117+
kAllocatedObjectsSize,
118+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
119+
{}},
120+
{"partition_alloc",
121+
"PartitionAlloc",
122+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
123+
kEffectiveSize,
124+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
125+
{}},
126+
{"partition_alloc/allocated_objects",
127+
"PartitionAlloc.AllocatedObjects",
128+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
129+
kEffectiveSize,
130+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
131+
{}},
132+
{"skia",
133+
"Skia",
134+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
135+
kEffectiveSize,
136+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
137+
{}},
138+
{"sqlite",
139+
"Sqlite",
140+
CobaltMemoryMetricsEmitter::MetricSize::kSmall,
141+
kEffectiveSize,
142+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
143+
{}},
144+
{"ui",
145+
"UI",
146+
CobaltMemoryMetricsEmitter::MetricSize::kSmall,
147+
kEffectiveSize,
148+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
149+
{}},
150+
{"v8",
151+
"V8",
152+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
153+
kEffectiveSize,
154+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
155+
{}},
156+
{"v8",
157+
"V8.AllocatedObjects",
158+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
159+
kAllocatedObjectsSize,
160+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUkmAndUma,
161+
{}},
162+
#if BUILDFLAG(IS_ANDROID)
163+
{base::android::MeminfoDumpProvider::kDumpName,
164+
"AndroidOtherPss",
165+
CobaltMemoryMetricsEmitter::MetricSize::kLarge,
166+
base::android::MeminfoDumpProvider::kPssMetricName,
167+
CobaltMemoryMetricsEmitter::EmitTo::kSizeInUmaOnly,
168+
{}},
169+
#endif
170+
};
171+
172+
constexpr char kExperimentalUmaPrefix[] = "Memory.Experimental.";
173+
constexpr char kVersionSuffixNormal[] = "2.";
174+
constexpr char kVersionSuffixSmall[] = "2.Small.";
175+
constexpr char kVersionSuffixTiny[] = "2.Tiny.";
176+
177+
static const char* MetricSizeToVersionSuffix(
178+
CobaltMemoryMetricsEmitter::MetricSize size) {
179+
switch (size) {
180+
case CobaltMemoryMetricsEmitter::MetricSize::kPercentage:
181+
return kVersionSuffixNormal;
182+
case CobaltMemoryMetricsEmitter::MetricSize::kLarge:
183+
return kVersionSuffixNormal;
184+
case CobaltMemoryMetricsEmitter::MetricSize::kSmall:
185+
return kVersionSuffixSmall;
186+
case CobaltMemoryMetricsEmitter::MetricSize::kTiny:
187+
return kVersionSuffixTiny;
188+
case CobaltMemoryMetricsEmitter::MetricSize::kCustom:
189+
return kVersionSuffixNormal;
190+
}
191+
}
192+
193+
} // namespace
194+
195+
CobaltMemoryMetricsEmitter::CobaltMemoryMetricsEmitter() = default;
196+
197+
void CobaltMemoryMetricsEmitter::FetchAndEmitProcessMemoryMetrics() {
198+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
199+
200+
memory_dump_in_progress_ = true;
201+
202+
auto* instrumentation =
203+
memory_instrumentation::MemoryInstrumentation::GetInstance();
204+
if (instrumentation) {
205+
auto callback =
206+
base::BindOnce(&CobaltMemoryMetricsEmitter::ReceivedMemoryDump, this);
207+
std::vector<std::string> mad_list;
208+
for (const auto& metric : kAllocatorDumpNamesForMetrics) {
209+
mad_list.push_back(metric.dump_name);
210+
}
211+
instrumentation->RequestGlobalDump(mad_list, std::move(callback));
212+
} else {
213+
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
214+
FROM_HERE,
215+
base::BindOnce(&CobaltMemoryMetricsEmitter::ReceivedMemoryDump, this,
216+
false, nullptr));
217+
}
218+
}
219+
220+
CobaltMemoryMetricsEmitter::~CobaltMemoryMetricsEmitter() = default;
221+
222+
void CobaltMemoryMetricsEmitter::ReceivedMemoryDump(
223+
bool success,
224+
std::unique_ptr<GlobalMemoryDump> dump) {
225+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
226+
227+
memory_dump_in_progress_ = false;
228+
if (!success || !dump) {
229+
if (callback_for_testing_) {
230+
std::move(callback_for_testing_).Run();
231+
}
232+
return;
233+
}
234+
global_dump_ = std::move(dump);
235+
CollateResults();
236+
}
237+
238+
// static
239+
void CobaltMemoryMetricsEmitter::EmitProcessUma(
240+
HistogramProcessType process_type,
241+
const Metric& item,
242+
uint64_t value) {
243+
std::string uma_name = base::StrCat(
244+
{kExperimentalUmaPrefix, HistogramProcessTypeToString(process_type),
245+
MetricSizeToVersionSuffix(item.metric_size), item.uma_name});
246+
247+
switch (item.metric_size) {
248+
case MetricSize::kPercentage:
249+
base::UmaHistogramPercentage(uma_name, static_cast<int>(value));
250+
break;
251+
case MetricSize::kLarge:
252+
base::UmaHistogramMemoryLargeMB(uma_name, static_cast<int>(value / kMiB));
253+
break;
254+
case MetricSize::kSmall:
255+
base::UmaHistogramMemoryKB(uma_name, static_cast<int>(value / kKiB));
256+
break;
257+
case MetricSize::kTiny:
258+
base::UmaHistogramCustomCounts(uma_name, static_cast<int>(value), 1,
259+
500000, 100);
260+
break;
261+
case MetricSize::kCustom:
262+
base::UmaHistogramCustomCounts(uma_name, static_cast<int>(value),
263+
item.range.min, item.range.max, 100);
264+
break;
265+
}
266+
}
267+
268+
void CobaltMemoryMetricsEmitter::CollateResults() {
269+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
270+
271+
if (memory_dump_in_progress_) {
272+
return;
273+
}
274+
if (!global_dump_) {
275+
if (callback_for_testing_) {
276+
std::move(callback_for_testing_).Run();
277+
}
278+
return;
279+
}
280+
281+
uint64_t private_footprint_total_kb = 0;
282+
uint64_t shared_footprint_total_kb = 0;
283+
uint64_t resident_set_total_kb = 0;
284+
285+
for (const auto& pmd : global_dump_->process_dumps()) {
286+
HistogramProcessType ptype;
287+
switch (pmd.process_type()) {
288+
case memory_instrumentation::mojom::ProcessType::BROWSER:
289+
ptype = HistogramProcessType::kBrowser;
290+
break;
291+
case memory_instrumentation::mojom::ProcessType::RENDERER:
292+
ptype = HistogramProcessType::kRenderer;
293+
break;
294+
case memory_instrumentation::mojom::ProcessType::GPU:
295+
ptype = HistogramProcessType::kGpu;
296+
break;
297+
case memory_instrumentation::mojom::ProcessType::UTILITY:
298+
ptype = HistogramProcessType::kUtility;
299+
break;
300+
default:
301+
ptype = HistogramProcessType::kUtility;
302+
break;
303+
}
304+
305+
private_footprint_total_kb += pmd.os_dump().private_footprint_kb;
306+
shared_footprint_total_kb += pmd.os_dump().shared_footprint_kb;
307+
resident_set_total_kb += pmd.os_dump().resident_set_kb;
308+
309+
for (const auto& item : kAllocatorDumpNamesForMetrics) {
310+
absl::optional<uint64_t> value =
311+
pmd.GetMetric(item.dump_name, item.metric);
312+
if (value) {
313+
EmitProcessUma(ptype, item, value.value());
314+
}
315+
}
316+
317+
const char* process_name = HistogramProcessTypeToString(ptype);
318+
base::UmaHistogramMemoryLargeMB(
319+
std::string(kMemoryHistogramPrefix) + process_name + ".ResidentSet",
320+
static_cast<int>(pmd.os_dump().resident_set_kb / kKiB));
321+
base::UmaHistogramMemoryLargeMB(
322+
GetPrivateFootprintHistogramName(ptype),
323+
static_cast<int>(pmd.os_dump().private_footprint_kb / kKiB));
324+
base::UmaHistogramMemoryLargeMB(
325+
std::string(kMemoryHistogramPrefix) + process_name +
326+
".SharedMemoryFootprint",
327+
static_cast<int>(pmd.os_dump().shared_footprint_kb / kKiB));
328+
}
329+
330+
base::UmaHistogramMemoryLargeMB(
331+
"Memory.Total.ResidentSet",
332+
static_cast<int>(resident_set_total_kb / kKiB));
333+
base::UmaHistogramMemoryLargeMB(
334+
"Memory.Total.PrivateMemoryFootprint",
335+
static_cast<int>(private_footprint_total_kb / kKiB));
336+
base::UmaHistogramMemoryLargeMB(
337+
"Memory.Total.SharedMemoryFootprint",
338+
static_cast<int>(shared_footprint_total_kb / kKiB));
339+
340+
global_dump_ = nullptr;
341+
342+
if (callback_for_testing_) {
343+
std::move(callback_for_testing_).Run();
344+
}
345+
}
346+
347+
} // namespace cobalt

0 commit comments

Comments
 (0)