1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
fuchsia_web / webengine / browser / web_engine_memory_inspector.cc [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 "fuchsia_web/webengine/browser/web_engine_memory_inspector.h"
#include <lib/fpromise/promise.h>
#include <lib/inspect/cpp/inspector.h>
#include <sstream>
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "components/fuchsia_component_support/config_reader.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
namespace {
std::vector<std::string> GetAllocatorDumpNamesFromConfig() {
const std::optional<base::Value::Dict>& config =
fuchsia_component_support::LoadPackageConfig();
if (!config)
return {};
const base::Value::List* names_list =
config->FindList("allocator-dump-names");
if (!names_list)
return {};
std::vector<std::string> names;
names.reserve(names_list->size());
for (auto& name : *names_list) {
names.push_back(name.GetString());
}
return names;
}
// List of allocator dump names to include.
const std::vector<std::string>& AllocatorDumpNames() {
static base::NoDestructor<std::vector<std::string>> allocator_dump_names(
GetAllocatorDumpNamesFromConfig());
return *allocator_dump_names;
}
// Returns true if every field in the supplied |dump|, and those of its
// children, are zero. Generally parent nodes summarize the total usage across
// all of their children, such that if the parent is all-zero then the children
// must also be all-zero. This implementation is optimized for the case in
// which that property holds, but also copes gracefully when it does not.
bool AreAllDumpEntriesZero(
const memory_instrumentation::mojom::AllocatorMemDump* dump) {
for (auto& it : dump->numeric_entries) {
if (it.second != 0u)
return false;
}
for (auto& it : dump->children) {
if (!AreAllDumpEntriesZero(it.second.get()))
return false;
}
return true;
}
// Creates a node |name|, under |parent|, populated recursively with the
// contents of |dump|. The returned tree of Nodes are emplace()d to be owned by
// the specified |owner|.
inspect::Node NodeFromAllocatorMemDump(
inspect::Inspector* owner,
inspect::Node* parent,
const std::string& name,
const memory_instrumentation::mojom::AllocatorMemDump* dump) {
auto node = parent->CreateChild(name);
// Add subordinate nodes for any children.
std::vector<const memory_instrumentation::mojom::AllocatorMemDump*> children;
children.reserve(dump->children.size());
for (auto& it : dump->children) {
// If a child contains no information then omit it.
if (AreAllDumpEntriesZero(it.second.get()))
continue;
children.push_back(it.second.get());
owner->emplace(
NodeFromAllocatorMemDump(owner, &node, it.first, it.second.get()));
}
// Publish the allocator-provided fields into the node. Entries are not
// published if there is a single child, with identical entries, to avoid
// redundancy in the emitted output.
bool same_as_child =
(children.size() == 1u &&
(*children.begin())->numeric_entries == dump->numeric_entries);
if (!same_as_child) {
for (auto& it : dump->numeric_entries) {
node.CreateUint(it.first, it.second, owner);
}
}
return node;
}
} // namespace
WebEngineMemoryInspector::WebEngineMemoryInspector(inspect::Node& parent_node) {
// Loading the allocator dump names from the config involves blocking I/O so
// trigger it to be done during construction, before the prohibition on
// blocking the main thread is applied.
AllocatorDumpNames();
node_ = parent_node.CreateLazyNode("memory", [this]() {
return fpromise::make_promise(fit::bind_member(
this, &WebEngineMemoryInspector::ResolveMemoryDumpPromise));
});
}
WebEngineMemoryInspector::~WebEngineMemoryInspector() = default;
fpromise::result<inspect::Inspector>
WebEngineMemoryInspector::ResolveMemoryDumpPromise(fpromise::context& context) {
// If there is a |dump_results_| then resolve the promise with it.
if (dump_results_) {
auto memory_dump = std::move(dump_results_);
return fpromise::ok(*memory_dump);
}
// If MemoryInstrumentation is not initialized then resolve an error.
auto* memory_instrumentation =
memory_instrumentation::MemoryInstrumentation::GetInstance();
if (!memory_instrumentation)
return fpromise::error();
// Request memory usage summaries for all processes, including details for
// any configured allocator dumps.
auto* coordinator = memory_instrumentation->GetCoordinator();
DCHECK(coordinator);
coordinator->RequestGlobalMemoryDump(
base::trace_event::MemoryDumpType::kSummaryOnly,
base::trace_event::MemoryDumpLevelOfDetail::kBackground,
base::trace_event::MemoryDumpDeterminism::kNone, AllocatorDumpNames(),
base::BindOnce(&WebEngineMemoryInspector::OnMemoryDumpComplete,
weak_this_.GetMutableWeakPtr(), base::TimeTicks::Now(),
context.suspend_task()));
return fpromise::pending();
}
void WebEngineMemoryInspector::OnMemoryDumpComplete(
base::TimeTicks requested_at,
fpromise::suspended_task task,
bool success,
memory_instrumentation::mojom::GlobalMemoryDumpPtr raw_dump) {
DCHECK(!dump_results_);
dump_results_ = std::make_unique<inspect::Inspector>();
// If capture failed then there is no data to report.
if (!success || !raw_dump) {
task.resume_task();
return;
}
// Note the delay between requesting the dump, and it being started.
dump_results_->GetRoot().CreateDouble(
"dump_queued_duration_ms",
(raw_dump->start_time - requested_at).InMillisecondsF(),
dump_results_.get());
// Note the delay between starting the dump, and it completing.
dump_results_->GetRoot().CreateDouble(
"dump_duration_ms",
(base::TimeTicks::Now() - raw_dump->start_time).InMillisecondsF(),
dump_results_.get());
for (const auto& process_dump : raw_dump->process_dumps) {
auto node = dump_results_->GetRoot().CreateChild(
base::NumberToString(process_dump->pid));
// Include details of each process' role in the web instance.
std::ostringstream type;
type << process_dump->process_type;
static const inspect::StringReference kTypeNodeName("type");
node.CreateString(kTypeNodeName, type.str(), dump_results_.get());
const auto service_name = process_dump->service_name;
if (service_name) {
static const inspect::StringReference kServiceNodeName("service");
node.CreateString(kServiceNodeName, *service_name, dump_results_.get());
}
// Include the summary of the process' memory usage.
const auto& os_dump = process_dump->os_dump;
static const inspect::StringReference kResidentKbNodeName("resident_kb");
node.CreateUint(kResidentKbNodeName, os_dump->resident_set_kb,
dump_results_.get());
static const inspect::StringReference kPrivateKbNodeName("private_kb");
node.CreateUint(kPrivateKbNodeName, os_dump->private_footprint_kb,
dump_results_.get());
static const inspect::StringReference kSharedKbNodeName("shared_kb");
node.CreateUint(kSharedKbNodeName, os_dump->shared_footprint_kb,
dump_results_.get());
// If provided, include detail from individual allocators.
if (!process_dump->chrome_allocator_dumps.empty()) {
static const inspect::StringReference kAllocatorDumpNodeName(
"allocator_dump");
auto detail_node = node.CreateChild(kAllocatorDumpNodeName);
for (auto& it : process_dump->chrome_allocator_dumps) {
dump_results_->emplace(NodeFromAllocatorMemDump(
dump_results_.get(), &detail_node, it.first, it.second.get()));
}
dump_results_->emplace(std::move(detail_node));
}
dump_results_->emplace(std::move(node));
}
task.resume_task();
}