| // Copyright 2020 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 "system_diagnostics_directory.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <algorithm> |
| |
| #include <fs/pseudo_file.h> |
| |
| #include "debug_info_retriever.h" |
| #include "lib/fit/promise.h" |
| #include "lib/inspect/cpp/inspector.h" |
| #include "lib/inspect/cpp/value_list.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| using fxl::StringPrintf; |
| |
| namespace { |
| struct ThreadInfo { |
| zx_koid_t koid; |
| fbl::String name; |
| zx::thread thread; |
| int64_t runtime; |
| }; |
| |
| static constexpr size_t kMaxThreads = 2048; |
| |
| const char* obj_type_get_name(zx_obj_type_t type) { |
| switch (type) { |
| case ZX_OBJ_TYPE_NONE: |
| return "none"; |
| case ZX_OBJ_TYPE_PROCESS: |
| return "process"; |
| case ZX_OBJ_TYPE_THREAD: |
| return "thread"; |
| case ZX_OBJ_TYPE_VMO: |
| return "vmo"; |
| case ZX_OBJ_TYPE_CHANNEL: |
| return "channel"; |
| case ZX_OBJ_TYPE_EVENT: |
| return "event"; |
| case ZX_OBJ_TYPE_PORT: |
| return "port"; |
| case ZX_OBJ_TYPE_INTERRUPT: |
| return "interrupt"; |
| case ZX_OBJ_TYPE_PCI_DEVICE: |
| return "pci_device"; |
| case ZX_OBJ_TYPE_LOG: |
| return "log"; |
| case ZX_OBJ_TYPE_SOCKET: |
| return "socket"; |
| case ZX_OBJ_TYPE_RESOURCE: |
| return "resource"; |
| case ZX_OBJ_TYPE_EVENTPAIR: |
| return "eventpair"; |
| case ZX_OBJ_TYPE_JOB: |
| return "job"; |
| case ZX_OBJ_TYPE_VMAR: |
| return "vmar"; |
| case ZX_OBJ_TYPE_FIFO: |
| return "fifo"; |
| case ZX_OBJ_TYPE_GUEST: |
| return "guest"; |
| case ZX_OBJ_TYPE_VCPU: |
| return "vcpu"; |
| case ZX_OBJ_TYPE_TIMER: |
| return "timer"; |
| case ZX_OBJ_TYPE_IOMMU: |
| return "iommu"; |
| case ZX_OBJ_TYPE_BTI: |
| return "bti"; |
| case ZX_OBJ_TYPE_PROFILE: |
| return "profile"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| zx_status_t GetProcessHandleStats(const zx::process* process, |
| zx_info_process_handle_stats_t* process_handle_stats) { |
| zx_status_t status = process->get_info(ZX_INFO_PROCESS_HANDLE_STATS, process_handle_stats, |
| sizeof(zx_info_process_handle_stats), nullptr, nullptr); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "zx_object_get_info failed, status: " << status; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t GetTaskStats(const zx::process* process, zx_info_task_stats_t* task_stats) { |
| zx_status_t status = process->get_info(ZX_INFO_TASK_STATS, task_stats, |
| sizeof(zx_info_task_stats_t), nullptr, nullptr); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "zx_object_get_info failed, status: " << status; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t GetThreadStats(zx_handle_t thread, zx_info_thread_stats_t* thread_stats) { |
| zx_status_t status = zx_object_get_info(thread, ZX_INFO_THREAD_STATS, thread_stats, |
| sizeof(zx_info_thread_stats_t), nullptr, nullptr); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "zx_object_get_info failed, status: " << status << " thread: " << thread; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void GetThreads(const zx::process* process, std::vector<ThreadInfo>* out) { |
| zx_koid_t thread_ids[kMaxThreads]; |
| size_t num_ids; |
| if (process->get_info(ZX_INFO_PROCESS_THREADS, thread_ids, sizeof(zx_koid_t) * kMaxThreads, |
| &num_ids, nullptr) != ZX_OK) { |
| return; |
| } |
| |
| for (size_t i = 0; i < num_ids; i++) { |
| zx::thread t; |
| char name[ZX_MAX_NAME_LEN]; |
| zx_info_thread_stats_t stats = {}; |
| if (process->get_child(thread_ids[i], ZX_RIGHT_SAME_RIGHTS, &t) != ZX_OK) { |
| return; |
| } |
| if (t.get_property(ZX_PROP_NAME, &name, ZX_MAX_NAME_LEN) != ZX_OK) { |
| return; |
| } |
| if (GetThreadStats(t.get(), &stats) != ZX_OK) { |
| return; |
| } |
| out->push_back(ThreadInfo{thread_ids[i], name, std::move(t), stats.total_runtime}); |
| } |
| } |
| |
| fit::promise<inspect::Inspector> PopulateThreadInspect(zx::process* process) { |
| // Make a 1mb buffer. |
| inspect::Inspector inspector(inspect::InspectSettings{.maximum_size = 1024 * 1024}); |
| |
| std::vector<ThreadInfo> threads; |
| threads.reserve(kMaxThreads); |
| GetThreads(process, &threads); |
| |
| for (const auto& thread : threads) { |
| inspect::ValueList values; |
| auto koid_string = StringPrintf("%lu", thread.koid); |
| auto thread_obj = inspector.GetRoot().CreateChild(koid_string); |
| thread_obj.CreateString("name", thread.name.data(), &values); |
| thread_obj.CreateInt("total_runtime", thread.runtime, &values); |
| auto stack_obj = thread_obj.CreateChild("stack"); |
| zx_koid_t koids[] = {thread.koid}; |
| stack_obj.CreateString( |
| "dump", |
| StringPrintf("\n%s", component::DebugInfoRetriever::GetInfo(process, koids, 1).data()), |
| &values); |
| |
| values.emplace(std::move(thread_obj)); |
| values.emplace(std::move(stack_obj)); |
| inspector.emplace(std::move(values)); |
| } |
| |
| return fit::make_ok_promise(std::move(inspector)); |
| } |
| |
| fit::promise<inspect::Inspector> PopulateMemoryInspect(zx::process* process) { |
| inspect::Inspector inspector; |
| |
| zx_info_task_stats_t task_stats = {}; |
| GetTaskStats(process, &task_stats); |
| inspector.GetRoot().CreateUint("mapped_bytes", task_stats.mem_mapped_bytes, &inspector); |
| inspector.GetRoot().CreateUint("private_bytes", task_stats.mem_private_bytes, &inspector); |
| inspector.GetRoot().CreateUint("shared_bytes", task_stats.mem_shared_bytes, &inspector); |
| inspector.GetRoot().CreateUint("scaled_shared_bytes", task_stats.mem_scaled_shared_bytes, |
| &inspector); |
| |
| return fit::make_ok_promise(std::move(inspector)); |
| } |
| } // namespace |
| |
| namespace component { |
| |
| SystemDiagnosticsDirectory::SystemDiagnosticsDirectory(zx::process process) |
| : process_(std::move(process)), inspector_() { |
| inspector_.GetRoot().CreateLazyNode( |
| "handle_count", |
| [this]() -> fit::promise<inspect::Inspector> { |
| inspect::Inspector inspector; |
| zx_info_process_handle_stats_t process_handle_stats; |
| if (GetProcessHandleStats(&process_, &process_handle_stats) != ZX_OK) { |
| return fit::make_result_promise<inspect::Inspector>(fit::error()); |
| } |
| |
| for (zx_obj_type_t obj_type = ZX_OBJ_TYPE_NONE; obj_type < ZX_OBJ_TYPE_UPPER_BOUND; |
| ++obj_type) { |
| inspector.GetRoot().CreateUint(obj_type_get_name(obj_type), |
| process_handle_stats.handle_count[obj_type], &inspector); |
| } |
| return fit::make_ok_promise(std::move(inspector)); |
| }, |
| &inspector_); |
| inspector_.GetRoot().CreateLazyNode( |
| "threads", [this] { return PopulateThreadInspect(&process_); }, &inspector_); |
| inspector_.GetRoot().CreateLazyNode( |
| "memory", [this] { return PopulateMemoryInspect(&process_); }, &inspector_); |
| } |
| |
| } // namespace component |