blob: 4659e66d3c24888c9c70acea8b58e74679f04706 [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 "component.h"
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/result.h>
#include <string>
#include <vector>
#include "component_watcher.h"
#include "sampler.h"
#include "src/lib/fxl/memory/weak_ptr.h"
#include "taskfinder.h"
namespace {
// Reads a manifest from a |ManifestBytesIterator| producing a vector of bytes.
zx::result<std::vector<uint8_t>> DrainManifestBytesIterator(
fidl::ClientEnd<fuchsia_sys2::ManifestBytesIterator> iterator_client_end) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__);
fidl::SyncClient iterator(std::move(iterator_client_end));
std::vector<uint8_t> result;
while (true) {
auto next_res = iterator->Next();
if (next_res.is_error()) {
return zx::error(next_res.error_value().status());
}
if (next_res->infos().empty()) {
break;
}
result.insert(result.end(), next_res->infos().begin(), next_res->infos().end());
}
return zx::ok(std::move(result));
}
zx::result<fidl::Box<fuchsia_component_decl::Component>> GetResolvedDeclaration(
const std::string& moniker) {
zx::result<fidl::ClientEnd<fuchsia_sys2::RealmQuery>> client_end =
component::Connect<fuchsia_sys2::RealmQuery>("/svc/fuchsia.sys2.RealmQuery.root");
if (client_end.is_error()) {
FX_LOGS(WARNING) << "Unable to connect to RealmQuery. Component interaction is disabled";
return client_end.take_error();
}
fidl::SyncClient realm_query_client{std::move(*client_end)};
fidl::Result<::fuchsia_sys2::RealmQuery::GetResolvedDeclaration> declaration =
realm_query_client->GetResolvedDeclaration({moniker});
if (declaration.is_error()) {
return zx::error(ZX_ERR_BAD_PATH);
}
auto drain_res = DrainManifestBytesIterator(std::move(declaration->iterator()));
if (drain_res.is_error()) {
return fit::error(drain_res.error_value());
}
auto unpersist_res = fidl::InplaceUnpersist<fuchsia_component_decl::wire::Component>(
std::span<uint8_t>(drain_res->begin(), drain_res->size()));
if (unpersist_res.is_error()) {
return zx::error(unpersist_res.error_value().status());
}
return zx::ok(fidl::ToNatural(*unpersist_res));
}
zx::result<zx_koid_t> ReadElfJobId(const fidl::SyncClient<fuchsia_io::Directory>& directory) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__);
zx::result<fidl::Endpoints<fuchsia_io::File>> endpoints =
fidl::CreateEndpoints<fuchsia_io::File>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
fit::result<fidl::OneWayStatus> res =
directory->Open({{.path = "elf/job_id",
.flags = fuchsia_io::kPermReadable,
.options = {},
.object = endpoints->server.TakeChannel()}});
if (res.is_error()) {
return zx::error(ZX_ERR_IO);
}
fidl::SyncClient<fuchsia_io::File> job_id_file{std::move(endpoints->client)};
fidl::Result<fuchsia_io::File::Read> read_res =
job_id_file->Read({{.count = fuchsia_io::kMaxTransferSize}});
if (read_res.is_error()) {
return zx::error(ZX_ERR_IO);
}
std::string job_id_str(reinterpret_cast<const char*>(read_res->data().data()),
read_res->data().size());
char* end;
zx_koid_t job_id = std::strtoull(job_id_str.c_str(), &end, 10);
if (end != job_id_str.c_str() + job_id_str.size()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(job_id);
}
zx::result<zx_koid_t> MonikerToJobId(const std::string& moniker) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__);
zx::result<fidl::ClientEnd<fuchsia_sys2::RealmQuery>> client_end =
component::Connect<fuchsia_sys2::RealmQuery>("/svc/fuchsia.sys2.RealmQuery.root");
if (client_end.is_error()) {
FX_LOGS(WARNING) << "Unable to connect to RealmQuery. Attaching by moniker isn't supported!";
return client_end.take_error();
}
auto [directory_client_endpoint, directory_server] =
fidl::Endpoints<fuchsia_io::Directory>::Create();
fidl::SyncClient<fuchsia_io::Directory> directory_client{std::move(directory_client_endpoint)};
fidl::SyncClient realm_query_client{std::move(*client_end)};
fidl::Result<fuchsia_sys2::RealmQuery::OpenDirectory> open_result =
realm_query_client->OpenDirectory({{
.moniker = moniker,
.dir_type = fuchsia_sys2::OpenDirType::kRuntimeDir,
.object = std::move(directory_server),
}});
if (open_result.is_error()) {
FX_LOGS(WARNING) << "Unable to open the runtime directory of " << moniker << ": "
<< open_result.error_value();
return zx::error(ZX_ERR_BAD_PATH);
}
zx::result<zx_koid_t> job_id = ReadElfJobId(directory_client);
if (job_id.is_error()) {
FX_LOGS(WARNING) << "Unable to read component directory";
}
return job_id;
}
} // namespace
profiler::ComponentWatcher::ComponentEventHandler profiler::MakeOnStartHandler(
fxl::WeakPtr<profiler::Sampler> sampler) {
return [sampler = std::move(sampler)](std::string moniker, std::string) {
if (!sampler) {
return;
}
elf_search::Searcher searcher;
FX_LOGS(INFO) << "Attaching via moniker: " << moniker;
zx::result<zx_koid_t> job_id = MonikerToJobId(moniker);
if (job_id.is_error()) {
FX_PLOGS(ERROR, job_id.error_value()) << "Failed to get Job ID from moniker";
return;
}
TaskFinder tf;
tf.AddJob(*job_id);
zx::result<TaskFinder::FoundTasks> handles = tf.FindHandles();
if (handles.is_error()) {
FX_PLOGS(ERROR, handles.error_value()) << "Failed to find handle for: " << moniker;
return;
}
for (auto& [koid, handle] : handles->jobs) {
if (koid == job_id) {
zx::result<profiler::JobTarget> target =
profiler::MakeJobTarget(zx::job(handle.release()), searcher);
if (target.is_error()) {
FX_PLOGS(ERROR, target.status_value()) << "Failed to make target for: " << moniker;
return;
}
zx::result<> target_result = sampler->AddTarget(std::move(*target));
if (target_result.is_error()) {
FX_PLOGS(ERROR, target_result.error_value()) << "Failed to add target for: " << moniker;
return;
}
break;
}
}
};
}
zx::result<> profiler::TraverseRealm(const std::string& moniker,
const fit::function<zx::result<>(const std::string&)>& f) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__, "moniker", moniker);
if (zx::result self_res = f(moniker); self_res.is_error()) {
return self_res;
}
auto manifest = GetResolvedDeclaration(moniker);
if (manifest.is_error()) {
// If this instance isn't launched yet, that's okay. We'll register to be notified when it does
// launch. Skip it for now.
return zx::ok();
}
if (manifest->children()) {
for (const auto& child : *manifest->children()) {
if (!child.name()) {
return zx::error(ZX_ERR_BAD_PATH);
}
std::string child_moniker = moniker + "/" + *child.name();
if (zx::result descendents_res = TraverseRealm(child_moniker, f);
descendents_res.is_error()) {
return descendents_res;
}
}
}
return zx::ok();
}
zx::result<profiler::Moniker> profiler::Moniker::Parse(std::string_view moniker) {
// A valid moniker for launching in a dynamic collection looks like:
//
// parent_moniker/collection:name
//
// Where the parent_moniker and collection can be optional.
std::optional<std::string> parent;
if (size_t leaf_divider = moniker.find_last_of('/'); leaf_divider != std::string::npos) {
parent = moniker.substr(0, leaf_divider);
moniker = moniker.substr(leaf_divider + 1);
}
std::optional<std::string> collection;
if (size_t collection_divider = moniker.find_last_of(':');
collection_divider != std::string::npos) {
collection = moniker.substr(0, collection_divider);
moniker = moniker.substr(collection_divider + 1);
}
return zx::ok(Moniker{parent, collection, std::string{moniker}});
}
zx::result<std::unique_ptr<profiler::ControlledComponent>> profiler::ControlledComponent::Create(
const std::string& url, const std::string& moniker_string,
ComponentWatcher& component_watcher) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__, "moniker", moniker_string, "url", url);
zx::result moniker = profiler::Moniker::Parse(moniker_string);
if (moniker.is_error()) {
return moniker.take_error();
}
if (!moniker->collection.has_value()) {
FX_LOGS(ERROR) << "Failed to create a component at moniker '" << moniker_string
<< "'. Moniker is missing a collection";
return zx::error(ZX_ERR_BAD_PATH);
}
std::unique_ptr component =
std::make_unique<ControlledComponent>(url, *moniker, component_watcher);
auto client_end = component::Connect<fuchsia_sys2::LifecycleController>(
"/svc/fuchsia.sys2.LifecycleController.root");
if (client_end.is_error()) {
return client_end.take_error();
}
component->lifecycle_controller_client_ = fidl::SyncClient{std::move(*client_end)};
fidl::Result<fuchsia_sys2::LifecycleController::CreateInstance> create_res =
component->lifecycle_controller_client_->CreateInstance({{
.parent_moniker = moniker->parent.value_or("."),
.collection = *moniker->collection,
.decl = {{
.name = moniker->name,
.url = url,
.startup = fuchsia_component_decl::StartupMode::kLazy,
}},
}});
if (create_res.is_error()) {
FX_LOGS(ERROR) << "Failed to create " << moniker_string << ": " << create_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
fidl::Result<fuchsia_sys2::LifecycleController::ResolveInstance> resolve_res =
component->lifecycle_controller_client_->ResolveInstance({{moniker->ToString()}});
if (resolve_res.is_error()) {
FX_LOGS(ERROR) << "Failed to resolve " << moniker_string << ": " << resolve_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok(std::move(component));
}
zx::result<> profiler::ControlledComponent::Start(fxl::WeakPtr<Sampler> notify) {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__, "moniker", moniker_.ToString());
on_start_ = profiler::MakeOnStartHandler(std::move(notify));
zx::result<> watch_result = TraverseRealm(moniker_.ToString(), [this](std::string moniker) {
return component_watcher_.WatchForMoniker(
std::move(moniker), [this](std::string moniker, std::string url) {
on_start_.value()(std::move(moniker), std::move(url));
});
});
if (watch_result.is_error()) {
return watch_result;
}
auto [binder_client, binder_server] = fidl::Endpoints<fuchsia_component::Binder>::Create();
fidl::Result<fuchsia_sys2::LifecycleController::StartInstance> start_res =
lifecycle_controller_client_->StartInstance({{
.moniker = moniker_.ToString(),
.binder = std::move(binder_server),
}});
if (start_res.is_error()) {
FX_LOGS(ERROR) << "Failed to start component: " << start_res.error_value();
return zx::error(ZX_ERR_UNAVAILABLE);
}
return zx::ok();
}
zx::result<> profiler::ControlledComponent::Stop() {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__, "moniker", moniker_.ToString());
if (auto stop_res = lifecycle_controller_client_->StopInstance({{
.moniker = moniker_.ToString(),
}});
stop_res.is_error()) {
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok();
}
zx::result<> profiler::ControlledComponent::Destroy() {
TRACE_DURATION("cpu_profiler", __PRETTY_FUNCTION__, "moniker", moniker_.ToString());
if (auto destroy_res = lifecycle_controller_client_->DestroyInstance({{
.parent_moniker = moniker_.parent.value_or("."),
.child = {{.name = moniker_.name, .collection = moniker_.collection}},
}});
destroy_res.is_error()) {
FX_LOGS(ERROR) << "Failed to destroy " << moniker_.ToString() << ": "
<< destroy_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
needs_destruction_ = false;
return zx::ok();
}
profiler::ControlledComponent::~ControlledComponent() {
if (needs_destruction_) {
(void)Destroy();
}
}