blob: c04ed55b32d721d3cd49f04c411ec715593e48db [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 "src/developer/memory/metrics/capture_strategy.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
#include <algorithm>
#include <iterator>
#include <optional>
#include <tuple>
#include "src/developer/memory/metrics/capture.h"
namespace memory {
namespace {
// GetInfoVector executes an OS::GetInfo call that outputs a list of element inside |buffer|,
// ensuring that |buffer| is big enough to receive the list of elements. |GetInfoVector| returns
// the status of the call and the number of elements effectively returned.
template <typename T>
zx::result<size_t> GetInfoVector(OS& os, zx_handle_t handle, uint32_t topic,
std::vector<T>& buffer) {
size_t num_entries = 0, available_entries = 0;
zx_status_t s = os.GetInfo(handle, topic, buffer.data(), buffer.size() * sizeof(T), &num_entries,
&available_entries);
if (s != ZX_OK) {
return zx::error(s);
}
if (num_entries == available_entries) {
return zx::ok(num_entries);
}
buffer.resize(available_entries);
s = os.GetInfo(handle, topic, buffer.data(), buffer.size() * sizeof(T), &num_entries,
&available_entries);
if (s != ZX_OK) {
return zx::error(s);
}
return zx::ok(num_entries);
}
} // namespace
zx_status_t BaseCaptureStrategy::OnNewProcess(OS& os, Process process, zx::handle process_handle) {
TRACE_DURATION_BEGIN("memory_metrics", "BaseCaptureStrategy::OnNewProcess::GetVMOs");
auto result = GetInfoVector<zx_info_vmo_t>(os, process_handle.get(), ZX_INFO_PROCESS_VMOS, vmos_);
// We don't want to show processes for which we don't have data (e.g. because they exited).
if (result.is_error()) {
return result.error_value();
}
size_t num_vmos = result.value();
TRACE_DURATION_END("memory_metrics", "BaseCaptureStrategy::OnNewProcess::GetVMOs");
TRACE_DURATION_BEGIN("memory_metrics", "BaseCaptureStrategy::OnNewProcess::UniqueProcessVMOs");
std::unordered_map<zx_koid_t, const zx_info_vmo_t&> unique_vmos;
unique_vmos.reserve(num_vmos);
for (size_t i = 0; i < num_vmos; i++) {
const auto& vmo_info = vmos_[i];
unique_vmos.try_emplace(vmo_info.koid, vmo_info);
}
TRACE_DURATION_END("memory_metrics", "BaseCaptureStrategy::OnNewProcess::UniqueProcessVMOs");
TRACE_DURATION_BEGIN("memory_metrics", "BaseCaptureStrategy::OnNewProcess::UniqueVMOs");
process.vmos.reserve(unique_vmos.size());
for (const auto& [vmo_koid, vmo] : unique_vmos) {
koid_to_vmo_.try_emplace(vmo_koid, vmo);
process.vmos.push_back(vmo_koid);
}
TRACE_DURATION_END("memory_metrics", "BaseCaptureStrategy::OnNewProcess::UniqueVMOs");
zx_koid_t process_koid = process.koid;
koid_to_process_[process_koid] = std::move(process);
return ZX_OK;
}
std::pair<std::unordered_map<zx_koid_t, Process>, std::unordered_map<zx_koid_t, Vmo>>
BaseCaptureStrategy::Finalize(OS& os, BaseCaptureStrategy&& base_capture_strategy) {
return {std::move(base_capture_strategy.koid_to_process_),
std::move(base_capture_strategy.koid_to_vmo_)};
}
StarnixCaptureStrategy::StarnixCaptureStrategy(std::string process_name)
: process_name_(std::move(process_name)) {}
zx_status_t StarnixCaptureStrategy::OnNewProcess(OS& os, Process process,
zx::handle process_handle) {
if (process_name_ == process.name) {
starnix_jobs_[process.job].kernel_koid = process.koid;
}
zx_koid_t process_koid = process.koid;
process_handles_[process_koid] = std::move(process_handle);
koid_to_process_[process_koid] = std::move(process);
return ZX_OK;
}
zx::result<std::tuple<std::unordered_map<zx_koid_t, Process>, std::unordered_map<zx_koid_t, Vmo>>>
StarnixCaptureStrategy::Finalize(OS& os, StarnixCaptureStrategy&& starnix_capture_strategy) {
TRACE_DURATION("memory_metrics", "StarnixCaptureStrategy::Finalize");
BaseCaptureStrategy base;
// The cutoff address between the restricted space and the Starnix kernel depends on the
// architecture. We rely on the fact that shared processes, as used by Starnix, have two root
// VMARs, one for the restricted space and one for the Starnix kernel, to determine this cutoff at
// runtime, instead of having it hardcoded.
std::optional<zx_vaddr_t> starnix_kernel_cutoff = std::nullopt;
// Capture the data for each process.
for (auto& [_, process] : starnix_capture_strategy.koid_to_process_) {
auto starnix_proc = starnix_capture_strategy.starnix_jobs_.find(process.job);
if (starnix_proc == starnix_capture_strategy.starnix_jobs_.end()) {
zx_status_t s =
base.OnNewProcess(os, std::move(process),
std::move(starnix_capture_strategy.process_handles_[process.koid]));
// No error or a process-specific error (e.g.: the process exited), we continue.
if (s != ZX_OK && s != ZX_ERR_BAD_STATE) {
return zx::error(s);
}
continue;
}
// This is the first process in this Starnix job. We get the list of all VMOs in that job only
// once as we assume this list is shared by all processes in that job.
if (!starnix_proc->second.vmos_retrieved) {
TRACE_DURATION_BEGIN("memory_metrics", "StarnixCaptureStrategy::Finalize::StarnixVMOs");
std::vector<zx_info_vmo_t> vmos;
auto result = GetInfoVector(os, starnix_capture_strategy.process_handles_[process.koid].get(),
ZX_INFO_PROCESS_VMOS, vmos);
if (result.status_value() == ZX_ERR_BAD_STATE) {
continue;
}
if (result.is_error()) {
return result.take_error();
}
starnix_proc->second.vmos_retrieved = true;
// We fill |unmapped_vmos| with all the known VMOs of this process group. Mapped VMOs will be
// removed from |unmapped_vmos| later, as we learn of the mappings.
size_t num_vmos = result.value();
for (size_t i = 0; i < num_vmos; i++) {
auto [it, is_new] = starnix_proc->second.unmapped_vmos.insert(vmos[i].koid);
// If we have already seen the VMO in this process, then we have seen it globally and we
// don't need to insert it again.
if (is_new) {
starnix_capture_strategy.koid_to_vmo_.try_emplace(vmos[i].koid, vmos[i]);
}
}
TRACE_DURATION_END("memory_metrics", "StarnixCaptureStrategy::Finalize::StarnixVMOs");
}
TRACE_DURATION_BEGIN("memory_metrics", "StarnixCaptureStrategy::Finalize::StarnixMappings");
auto result = GetInfoVector(os, starnix_capture_strategy.process_handles_[process.koid].get(),
ZX_INFO_PROCESS_MAPS, starnix_capture_strategy.mappings_);
if (result.status_value() == ZX_ERR_BAD_STATE) {
continue;
}
if (result.is_error()) {
return result.take_error();
}
size_t num_mappings = result.value();
for (size_t i = 0; i < num_mappings; i++) {
const auto& mapping = starnix_capture_strategy.mappings_[i];
if (!starnix_kernel_cutoff.has_value() && mapping.type == ZX_INFO_MAPS_TYPE_VMAR &&
mapping.depth == 1) {
starnix_kernel_cutoff = mapping.base + mapping.size;
}
if (mapping.type == ZX_INFO_MAPS_TYPE_MAPPING) {
if (!starnix_capture_strategy.koid_to_vmo_.contains(mapping.u.mapping.vmo_koid)) {
// It is a new VMO that we haven't captured. This can happen if the list of VMOs change
// while we do the data collection.
continue;
}
FX_DCHECK(starnix_kernel_cutoff.has_value())
<< "starnix_kernel_cutoff should have been set";
if (mapping.base >= starnix_kernel_cutoff) {
// This is a Starnix kernel mapping.
starnix_proc->second.kernel_mapped_vmos.insert(mapping.u.mapping.vmo_koid);
} else {
// This is a restricted space mapping.
starnix_proc->second.process_mapped_vmos[process.koid].insert(mapping.u.mapping.vmo_koid);
}
starnix_proc->second.unmapped_vmos.erase(mapping.u.mapping.vmo_koid);
}
}
TRACE_DURATION_END("memory_metrics", "StarnixCaptureStrategy::Finalize::StarnixMappings");
}
auto [base_koid_to_process, base_koid_to_vmo] =
BaseCaptureStrategy::Finalize(os, std::move(base));
// Both |koid_to_process_| and |base_koid_to_process| will contain process entries for
// non-Starnix processes. However, only the |base_koid_to_process| entries will be filled with
// the non-Starnix process VMOs. This calls adds the entries for Starnix processes to
// |base_koid_to_process|, ready to be filled by the loop below..
base_koid_to_process.merge(starnix_capture_strategy.koid_to_process_);
base_koid_to_vmo.merge(starnix_capture_strategy.koid_to_vmo_);
for (auto& [_, starnix_job] : starnix_capture_strategy.starnix_jobs_) {
for (auto& [process_koid, vmos] : starnix_job.process_mapped_vmos) {
std::ranges::move(vmos, std::back_inserter(base_koid_to_process[process_koid].vmos));
}
std::ranges::move(starnix_job.kernel_mapped_vmos,
std::back_inserter(base_koid_to_process[starnix_job.kernel_koid].vmos));
std::ranges::move(starnix_job.unmapped_vmos,
std::back_inserter(base_koid_to_process[starnix_job.kernel_koid].vmos));
}
return zx::ok(std::make_tuple(std::move(base_koid_to_process), std::move(base_koid_to_vmo)));
}
} // namespace memory