| // 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 "targets.h" |
| |
| #include <lib/fit/function.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/job.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/thread.h> |
| #include <zircon/errors.h> |
| #include <zircon/rights.h> |
| #include <zircon/system/ulib/elf-search/include/elf-search.h> |
| #include <zircon/types.h> |
| |
| #include <cstddef> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include <src/lib/unwinder/module.h> |
| |
| zx::result<> profiler::JobTarget::ForEachProcess( |
| const fit::function<zx::result<>(cpp20::span<const zx_koid_t> job_path, |
| const ProcessTarget& target)>& f) const { |
| std::vector<zx_koid_t> job_path{ancestry.begin(), ancestry.end()}; |
| job_path.push_back(job_id); |
| for (const auto& [_, process] : processes) { |
| zx::result<> res = f(job_path, process); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| for (const auto& [_, job] : child_jobs) { |
| zx::result<> res = job.ForEachProcess(f); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> profiler::JobTarget::ForEachJob( |
| const fit::function<zx::result<>(const JobTarget& target)>& f) const { |
| zx::result res = f(*this); |
| if (res.is_error()) { |
| return res; |
| } |
| |
| for (const auto& [_, job] : child_jobs) { |
| zx::result res = job.ForEachJob(f); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> profiler::JobTarget::AddJob(cpp20::span<const zx_koid_t> ancestry, JobTarget&& job) { |
| if (ancestry.empty()) { |
| zx_koid_t job_id = job.job_id; |
| auto [_, emplaced] = child_jobs.try_emplace(job_id, std::move(job)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| auto it = child_jobs.find(ancestry[0]); |
| if (it == child_jobs.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return it->second.AddJob(ancestry.subspan(1), std::move(job)); |
| } |
| |
| zx::result<> profiler::JobTarget::AddProcess(cpp20::span<const zx_koid_t> job_path, |
| ProcessTarget&& process) { |
| if (job_path.empty()) { |
| zx_koid_t pid = process.pid; |
| auto [_, emplaced] = processes.try_emplace(pid, std::move(process)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| auto next_child = child_jobs.find(job_path[0]); |
| if (next_child == child_jobs.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return next_child->second.AddProcess(job_path.subspan(1), std::move(process)); |
| } |
| |
| zx::result<> profiler::JobTarget::AddThread(cpp20::span<const zx_koid_t> job_path, zx_koid_t pid, |
| ThreadTarget&& thread) { |
| if (job_path.empty()) { |
| auto process = processes.find(pid); |
| if (process == processes.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| zx_koid_t tid = thread.tid; |
| auto [_, emplaced] = process->second.threads.try_emplace(tid, std::move(thread)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| |
| auto next_child = child_jobs.find(job_path[0]); |
| if (next_child == child_jobs.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return next_child->second.AddThread(job_path.subspan(1), pid, std::move(thread)); |
| } |
| |
| zx::result<> profiler::JobTarget::RemoveThread(cpp20::span<const zx_koid_t> job_path, zx_koid_t pid, |
| zx_koid_t tid) { |
| if (job_path.empty()) { |
| auto process = processes.find(pid); |
| if (process == processes.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| size_t num_removed = process->second.threads.erase(tid); |
| return zx::make_result(num_removed == 1 ? ZX_OK : ZX_ERR_NOT_FOUND); |
| } |
| |
| auto next_child = child_jobs.find(job_path[0]); |
| if (next_child == child_jobs.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return next_child->second.RemoveThread(job_path.subspan(1), pid, tid); |
| } |
| |
| zx::result<std::vector<zx_koid_t>> GetChildrenTids(const zx::process& process) { |
| size_t num_threads; |
| zx_status_t status = process.get_info(ZX_INFO_PROCESS_THREADS, nullptr, 0, nullptr, &num_threads); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get process thread info (#threads)"; |
| return zx::error(status); |
| } |
| if (num_threads < 1) { |
| // A job or process in early initialization may not have threads yet. |
| // That's okay, we'll attach to them when they are created. |
| return zx::ok(std::vector<zx_koid_t>{}); |
| } |
| |
| zx_koid_t threads[num_threads]; |
| size_t records_read; |
| status = process.get_info(ZX_INFO_PROCESS_THREADS, threads, num_threads * sizeof(threads[0]), |
| &records_read, nullptr); |
| |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get process thread info"; |
| return zx::error(status); |
| } |
| |
| if (records_read != num_threads) { |
| FX_LOGS(ERROR) << "records_read != num_threads"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| std::vector<zx_koid_t> children{threads, threads + num_threads}; |
| return zx::ok(children); |
| } |
| |
| zx::result<profiler::ProcessTarget> profiler::MakeProcessTarget(zx::process process) { |
| zx::result<std::vector<zx_koid_t>> children = GetChildrenTids(process); |
| if (children.is_error()) { |
| return children.take_error(); |
| } |
| zx_info_handle_basic_t handle_info; |
| zx_status_t res = |
| process.get_info(ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(handle_info), nullptr, nullptr); |
| if (res != ZX_OK) { |
| return zx::error(res); |
| } |
| std::unordered_map<zx_koid_t, profiler::ThreadTarget> threads; |
| for (auto child_tid : *children) { |
| zx::thread child_thread; |
| zx_status_t res = process.get_child(child_tid, ZX_DEFAULT_THREAD_RIGHTS, &child_thread); |
| if (res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Failed to get handle for child (tid: " << child_tid << ")"; |
| continue; |
| } |
| threads.try_emplace(child_tid, profiler::ThreadTarget{std::move(child_thread), child_tid}); |
| } |
| profiler::ProcessTarget process_target{std::move(process), handle_info.koid, std::move(threads)}; |
| |
| elf_search::ForEachModule(*zx::unowned_process{process_target.handle}, |
| [&process_target](const elf_search::ModuleInfo& info) { |
| process_target.unwinder_data->modules.emplace_back( |
| info.vaddr, &process_target.unwinder_data->memory, |
| unwinder::Module::AddressMode::kProcess); |
| }); |
| return zx::ok(std::move(process_target)); |
| } |
| |
| zx::result<profiler::JobTarget> profiler::MakeJobTarget(zx::job job, |
| cpp20::span<const zx_koid_t> ancestry) { |
| size_t num_child_jobs; |
| if (zx_status_t status = job.get_info(ZX_INFO_JOB_CHILDREN, nullptr, 0, nullptr, &num_child_jobs); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to query number of job children"; |
| return zx::error(status); |
| } |
| |
| zx_info_handle_basic_t info; |
| if (zx_status_t status = |
| job.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to make process_target"; |
| return zx::error(status); |
| } |
| zx_koid_t job_id = info.koid; |
| |
| // Provide each of this job's children their ancestry, which is this job's ancestry, prepended to |
| // this job's job id. |
| std::vector<const zx_koid_t> child_job_ancestry{ancestry.begin(), ancestry.end()}; |
| child_job_ancestry.push_back(job_id); |
| |
| std::unordered_map<zx_koid_t, profiler::JobTarget> child_job_targets; |
| if (num_child_jobs > 0) { |
| zx_koid_t child_jobs[num_child_jobs]; |
| if (zx_status_t status = |
| job.get_info(ZX_INFO_JOB_CHILDREN, child_jobs, sizeof(child_jobs), nullptr, nullptr); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get job children"; |
| return zx::error(status); |
| } |
| |
| for (zx_koid_t child_koid : child_jobs) { |
| zx::job child_job; |
| if (zx_status_t status = job.get_child(child_koid, ZX_DEFAULT_JOB_RIGHTS, &child_job); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get job: " << child_koid; |
| return zx::error(status); |
| } |
| zx::result<profiler::JobTarget> child_job_target = |
| MakeJobTarget(std::move(child_job), child_job_ancestry); |
| if (child_job_target.is_error()) { |
| FX_PLOGS(ERROR, child_job_target.status_value()) << "failed to make job_target"; |
| return child_job_target.take_error(); |
| } |
| child_job_target->ancestry = std::move(child_job_ancestry); |
| child_job_targets.try_emplace(child_koid, std::move(*child_job_target)); |
| } |
| } |
| |
| size_t num_processes; |
| if (zx_status_t status = job.get_info(ZX_INFO_JOB_PROCESSES, nullptr, 0, nullptr, &num_processes); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to query number of job processes"; |
| return zx::error(status); |
| } |
| std::unordered_map<zx_koid_t, profiler::ProcessTarget> process_targets; |
| if (num_processes > 0) { |
| zx_koid_t processes[num_processes]; |
| if (zx_status_t status = |
| job.get_info(ZX_INFO_JOB_PROCESSES, processes, sizeof(processes), nullptr, nullptr); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get job processes"; |
| return zx::error(status); |
| } |
| |
| for (zx_koid_t process_koid : processes) { |
| zx::process process; |
| if (zx_status_t status = job.get_child(process_koid, ZX_DEFAULT_PROCESS_RIGHTS, &process); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "failed to get process: " << process_koid; |
| return zx::error(status); |
| } |
| zx::result<profiler::ProcessTarget> process_target = |
| profiler::MakeProcessTarget(std::move(process)); |
| |
| if (process_target.is_error()) { |
| FX_PLOGS(ERROR, process_target.status_value()) << "failed to make process_target"; |
| return process_target.take_error(); |
| } |
| process_targets.try_emplace(process_koid, std::move(*process_target)); |
| } |
| } |
| return zx::ok(profiler::JobTarget{std::move(job), job_id, std::move(process_targets), |
| std::move(child_job_targets), ancestry}); |
| } |
| |
| zx::result<profiler::JobTarget> profiler::MakeJobTarget(zx::job job) { |
| return MakeJobTarget(std::move(job), cpp20::span<const zx_koid_t>{}); |
| } |
| |
| zx::result<> profiler::TargetTree::AddJob(JobTarget&& job) { |
| return AddJob(cpp20::span<const zx_koid_t>{}, std::move(job)); |
| } |
| |
| zx::result<> profiler::TargetTree::AddJob(cpp20::span<const zx_koid_t> ancestry, JobTarget&& job) { |
| if (ancestry.empty()) { |
| zx_koid_t job_id = job.job_id; |
| auto [it, emplaced] = jobs_.try_emplace(job_id, std::move(job)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| |
| zx_koid_t next_child_koid = ancestry[0]; |
| auto it = jobs_.find(next_child_koid); |
| return it == jobs_.end() ? zx::error(ZX_ERR_NOT_FOUND) |
| : it->second.AddJob(ancestry.subspan(1), std::move(job)); |
| } |
| |
| zx::result<> profiler::TargetTree::AddProcess(ProcessTarget&& process) { |
| return AddProcess(cpp20::span<const zx_koid_t>{}, std::move(process)); |
| } |
| |
| zx::result<> profiler::TargetTree::AddProcess(cpp20::span<const zx_koid_t> job_path, |
| ProcessTarget&& process) { |
| if (job_path.empty()) { |
| zx_koid_t pid = process.pid; |
| auto [it, emplaced] = processes_.try_emplace(pid, std::move(process)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| |
| zx_koid_t next_child_koid = job_path[0]; |
| auto it = jobs_.find(next_child_koid); |
| return it == jobs_.end() ? zx::error(ZX_ERR_NOT_FOUND) |
| : it->second.AddProcess(job_path.subspan(1), std::move(process)); |
| } |
| |
| zx::result<> profiler::TargetTree::AddThread(zx_koid_t pid, ThreadTarget&& thread) { |
| return AddThread(cpp20::span<const zx_koid_t>{}, pid, std::move(thread)); |
| } |
| |
| zx::result<> profiler::TargetTree::RemoveThread(zx_koid_t pid, zx_koid_t tid) { |
| return RemoveThread(cpp20::span<const zx_koid_t>{}, pid, tid); |
| } |
| |
| zx::result<> profiler::TargetTree::AddThread(cpp20::span<const zx_koid_t> job_path, zx_koid_t pid, |
| ThreadTarget&& thread) { |
| if (job_path.empty()) { |
| auto it = processes_.find(pid); |
| if (it == processes_.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| zx_koid_t tid = thread.tid; |
| auto [_, emplaced] = it->second.threads.try_emplace(tid, std::move(thread)); |
| return zx::make_result(emplaced ? ZX_OK : ZX_ERR_ALREADY_EXISTS); |
| } |
| |
| zx_koid_t next_child_koid = job_path[0]; |
| auto it = jobs_.find(next_child_koid); |
| return it == jobs_.end() ? zx::error(ZX_ERR_NOT_FOUND) |
| : it->second.AddThread(job_path.subspan(1), pid, std::move(thread)); |
| } |
| |
| zx::result<> profiler::TargetTree::RemoveThread(cpp20::span<const zx_koid_t> job_path, |
| zx_koid_t pid, zx_koid_t tid) { |
| if (job_path.empty()) { |
| auto it = processes_.find(pid); |
| if (it == processes_.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| size_t num_erased = it->second.threads.erase(tid); |
| return zx::make_result(num_erased == 1 ? ZX_OK : ZX_ERR_NOT_FOUND); |
| } |
| |
| zx_koid_t next_child_koid = job_path[0]; |
| auto it = jobs_.find(next_child_koid); |
| return it == jobs_.end() ? zx::error(ZX_ERR_NOT_FOUND) |
| : it->second.RemoveThread(job_path.subspan(1), pid, tid); |
| } |
| |
| void profiler::TargetTree::Clear() { |
| jobs_.clear(); |
| processes_.clear(); |
| } |
| |
| zx::result<> profiler::TargetTree::ForEachJob( |
| const fit::function<zx::result<>(const JobTarget& target)>& f) { |
| for (const auto& [_, job] : jobs_) { |
| zx::result<> res = job.ForEachJob(f); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> profiler::TargetTree::ForEachProcess( |
| const fit::function<zx::result<>(cpp20::span<const zx_koid_t> job_path, |
| const ProcessTarget& target)>& f) { |
| for (const auto& [_, process] : processes_) { |
| zx::result res = f(cpp20::span<const zx_koid_t>{}, process); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| for (const auto& [_, job] : jobs_) { |
| zx::result<> res = job.ForEachProcess(f); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| return zx::ok(); |
| } |