blob: 847e6ddbdd9f8a9f7d1226e6d30483f2a2309d74 [file] [log] [blame]
// Copyright 2021 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 "fuchsia/engine/browser/web_engine_memory_inspector.h"
#include <lib/fpromise/promise.h>
#include <lib/inspect/cpp/inspector.h>
#include <sstream>
#include "base/trace_event/memory_dump_request_args.h"
#include "fuchsia/base/config_reader.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
namespace {
std::vector<std::string> GetAllocatorDumpNamesFromConfig() {
const absl::optional<base::Value>& config = cr_fuchsia::LoadPackageConfig();
if (!config)
return {};
const base::Value* names_list = config->FindListPath("allocator-dump-names");
if (!names_list)
return {};
std::vector<std::string> names;
names.reserve(names_list->GetList().size());
for (auto& name : names_list->GetList()) {
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;
}
} // 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::SUMMARY_ONLY,
base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND,
base::trace_event::MemoryDumpDeterminism::NONE, AllocatorDumpNames(),
base::BindOnce(&WebEngineMemoryInspector::OnMemoryDumpComplete,
weak_this_.GetWeakPtr(), context.suspend_task()));
return fpromise::pending();
}
void WebEngineMemoryInspector::OnMemoryDumpComplete(
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;
}
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;
node.CreateString("type", type.str(), dump_results_.get());
const auto service_name = process_dump->service_name;
if (service_name) {
node.CreateString("service", *service_name, dump_results_.get());
}
// Include the summary of the process' memory usage.
const auto& os_dump = process_dump->os_dump;
node.CreateUint("resident_kb", os_dump->resident_set_kb,
dump_results_.get());
node.CreateUint("private_kb", os_dump->private_footprint_kb,
dump_results_.get());
node.CreateUint("shared_kb", os_dump->shared_footprint_kb,
dump_results_.get());
// If provided, include detail from individual allocators.
auto detail_node = node.CreateChild("allocator_dump");
if (!process_dump->chrome_allocator_dumps.empty()) {
for (auto& it : process_dump->chrome_allocator_dumps) {
// Create a node using the allocator dump name.
auto allocator_node = detail_node.CreateChild(it.first);
// Publish the allocator-provided fields into the node.
for (auto& field : it.second->numeric_entries) {
allocator_node.CreateUint(field.first, field.second,
dump_results_.get());
}
dump_results_->emplace(std::move(allocator_node));
}
dump_results_->emplace(std::move(detail_node));
}
dump_results_->emplace(std::move(node));
}
task.resume_task();
}