blob: f452bbffc0884944c0ab23b0bb4351b162135ea8 [file] [log] [blame]
// Copyright 2019 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.h"
#include <fcntl.h>
#include <fuchsia/boot/c/fidl.h>
#include <fuchsia/kernel/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/job.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <memory>
#include <task-utils/walker.h>
#include <trace/event.h>
#include "src/lib/fxl/logging.h"
namespace memory {
class OSImpl : public OS, public TaskEnumerator {
private:
zx_status_t GetKernelStats(
std::unique_ptr<llcpp::fuchsia::kernel::Stats::SyncClient>* stats) override {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
const char* kernel_stats_svc = "/svc/fuchsia.kernel.Stats";
status = fdio_service_connect(kernel_stats_svc, remote.release());
if (status != ZX_OK) {
return status;
}
*stats = std::make_unique<llcpp::fuchsia::kernel::Stats::SyncClient>(std::move(local));
return ZX_OK;
}
zx_handle_t ProcessSelf() override { return zx_process_self(); }
zx_time_t GetMonotonic() override { return zx_clock_get_monotonic(); }
zx_status_t GetProcesses(
fit::function<zx_status_t(int, zx_handle_t, zx_koid_t, zx_koid_t)> cb) override {
cb_ = std::move(cb);
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
const char* root_job_svc = "/svc/fuchsia.boot.RootJobForInspect";
status = fdio_service_connect(root_job_svc, remote.release());
if (status != ZX_OK) {
return status;
}
zx::job root_job;
status = fuchsia_boot_RootJobForInspectGet(local.get(), root_job.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
return WalkJobTree(root_job.get());
}
zx_status_t OnProcess(int depth, zx_handle_t handle, zx_koid_t koid,
zx_koid_t parent_koid) override {
return cb_(depth, handle, koid, parent_koid);
}
zx_status_t GetProperty(zx_handle_t handle, uint32_t property, void* value,
size_t name_len) override {
return zx_object_get_property(handle, property, value, name_len);
}
zx_status_t GetInfo(zx_handle_t handle, uint32_t topic, void* buffer, size_t buffer_size,
size_t* actual, size_t* avail) override {
TRACE_DURATION("memory_metrics", "OSImpl::GetInfo", "topic", topic, "buffer_size", buffer_size);
return zx_object_get_info(handle, topic, buffer, buffer_size, actual, avail);
}
zx_status_t GetKernelMemoryStats(llcpp::fuchsia::kernel::Stats::SyncClient* stats_client,
zx_info_kmem_stats_t* kmem) override {
if (stats_client == nullptr) {
return ZX_ERR_BAD_STATE;
}
auto result = stats_client->GetMemoryStats();
if (result.status() != ZX_OK) {
return result.status();
}
const auto& stats = result.Unwrap()->stats;
kmem->total_bytes = stats.total_bytes();
kmem->free_bytes = stats.free_bytes();
kmem->wired_bytes = stats.wired_bytes();
kmem->total_heap_bytes = stats.total_heap_bytes();
kmem->free_heap_bytes = stats.free_heap_bytes();
kmem->vmo_bytes = stats.vmo_bytes();
kmem->mmu_overhead_bytes = stats.mmu_overhead_bytes();
kmem->ipc_bytes = stats.ipc_bytes();
kmem->other_bytes = stats.other_bytes();
return ZX_OK;
}
bool has_on_process() const final { return true; }
fit::function<zx_status_t(int /* depth */, zx_handle_t /* handle */, zx_koid_t /* koid */,
zx_koid_t /* parent_koid */)>
cb_;
};
const std::vector<std::string> Capture::kDefaultRootedVmoNames = {"SysmemContiguousPool",
"SysmemAmlogicProtectedPool"};
// static.
zx_status_t Capture::GetCaptureState(CaptureState* state) {
OSImpl osImpl;
return GetCaptureState(state, &osImpl);
}
zx_status_t Capture::GetCaptureState(CaptureState* state, OS* os) {
zx_status_t err = os->GetKernelStats(&state->stats_client);
if (err != ZX_OK) {
return err;
}
zx_info_handle_basic_t info;
err = os->GetInfo(os->ProcessSelf(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (err != ZX_OK) {
return err;
}
state->self_koid = info.koid;
return ZX_OK;
}
// static.
zx_status_t Capture::GetCapture(Capture* capture, const CaptureState& state, CaptureLevel level,
const std::vector<std::string>& rooted_vmo_names) {
OSImpl osImpl;
return GetCapture(capture, state, level, &osImpl, rooted_vmo_names);
}
zx_status_t Capture::GetCapture(Capture* capture, const CaptureState& state, CaptureLevel level,
OS* os, const std::vector<std::string>& rooted_vmo_names) {
TRACE_DURATION("memory_metrics", "Capture::GetCapture");
capture->time_ = os->GetMonotonic();
zx_status_t err = os->GetKernelMemoryStats(state.stats_client.get(), &capture->kmem_);
if (err != ZX_OK) {
return err;
}
if (level == KMEM) {
return ZX_OK;
}
err = os->GetProcesses(
[&state, capture, &os](int depth, zx_handle_t handle, zx_koid_t koid, zx_koid_t parent_koid) {
if (koid == state.self_koid) {
return ZX_OK;
}
Process process = {.koid = koid};
zx_status_t s = os->GetProperty(handle, ZX_PROP_NAME, process.name, ZX_MAX_NAME_LEN);
if (s != ZX_OK) {
return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
}
size_t num_vmos;
s = os->GetInfo(handle, ZX_INFO_PROCESS_VMOS, nullptr, 0, nullptr, &num_vmos);
if (s != ZX_OK) {
return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
}
auto vmos = std::make_unique<zx_info_vmo_t[]>(num_vmos);
{
s = os->GetInfo(handle, ZX_INFO_PROCESS_VMOS, vmos.get(),
num_vmos * sizeof(zx_info_vmo_t), &num_vmos, nullptr);
if (s != ZX_OK) {
return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
}
}
process.vmos.reserve(num_vmos);
for (size_t i = 0; i < num_vmos; i++) {
const auto& vmo = vmos[i];
capture->koid_to_vmo_.try_emplace(vmo.koid, vmo);
process.vmos.push_back(vmo.koid);
}
capture->koid_to_process_.emplace(koid, process);
return ZX_OK;
});
capture->ReallocateDescendents(rooted_vmo_names);
return err;
}
// Descendents of this vmo will have their allocated_bytes treated as an allocation of their
// immediate parent. This supports a usage pattern where a potentially large allocation is done
// and then slices are given to read / write children. In this case the children have no
// committed_bytes of their own. For accounting purposes it gives more clarity to push the
// committed bytes to the lowest points in the tree, where the vmo names give more specific
// meanings.
void Capture::ReallocateDescendents(zx_koid_t parent_koid) {
Vmo& parent = koid_to_vmo_.at(parent_koid);
for (auto& pair : koid_to_vmo_) {
Vmo& child = pair.second;
if (child.parent_koid == parent_koid) {
parent.committed_bytes -= child.allocated_bytes;
child.committed_bytes = child.allocated_bytes;
ReallocateDescendents(child.koid);
}
}
}
// See the above description of ReallocateDescendents(zx_koid_t) for the specific behavior for each
// vmo that has a name listed in rooted_vmo_names.
void Capture::ReallocateDescendents(const std::vector<std::string>& rooted_vmo_names) {
TRACE_DURATION("memory_metrics", "Capture::ReallocateDescendents");
for (const auto& vmo_name : rooted_vmo_names) {
for (const auto& pair : koid_to_vmo_) {
if (pair.second.name == vmo_name) {
ReallocateDescendents(pair.first);
}
}
}
}
} // namespace memory