blob: 640b41085ec963a3ca2080ff46aeb8d28b5340db [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/feedback/feedback_agent/attachments/inspect_ptr.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/result.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <string>
#include <vector>
#include "src/developer/feedback/utils/cobalt_metrics.h"
#include "src/developer/feedback/utils/promise.h"
#include "src/lib/fsl/vmo/sized_vmo.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/inspect_deprecated/query/discover.h"
#include "src/lib/inspect_deprecated/query/json_formatter.h"
#include "src/lib/inspect_deprecated/query/location.h"
#include "src/lib/inspect_deprecated/query/read.h"
#include "src/lib/inspect_deprecated/query/source.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
fit::promise<fuchsia::mem::Buffer> CollectInspectData(async_dispatcher_t* timeout_dispatcher,
zx::duration timeout,
std::shared_ptr<Cobalt> cobalt,
async::Executor* collection_executor) {
std::unique_ptr<Inspect> inspect =
std::make_unique<Inspect>(timeout_dispatcher, std::move(cobalt), collection_executor);
// We must store the promise in a variable due to the fact that the order of evaluation of
// function parameters is undefined.
auto inspect_data = inspect->Collect(timeout);
return ExtendArgsLifetimeBeyondPromise(/*promise=*/std::move(inspect_data),
/*args=*/std::move(inspect));
}
Inspect::Inspect(async_dispatcher_t* timeout_dispatcher, std::shared_ptr<Cobalt> cobalt,
async::Executor* collection_executor)
: timeout_dispatcher_(timeout_dispatcher),
cobalt_(std::move(cobalt)),
collection_executor_(collection_executor) {}
fit::promise<fuchsia::mem::Buffer> Inspect::Collect(zx::duration timeout) {
FXL_CHECK(!has_called_collect_) << "Collect() is not intended to be called twice";
has_called_collect_ = true;
// We use a fit::bridge to create a fit::promise that will be completed when the Inspect data
// collection is done, returning the Inspect data.
//
// We use a shared_ptr to share the bridge between this, the dispatcher on which we post the
// delayed task to timeout and the executor on which we run the Inspect data collection.
collection_done_ = std::make_shared<fit::bridge<fuchsia::mem::Buffer>>();
collection_done_lock_ = std::make_shared<std::mutex>();
// fit::promise does not have the notion of a timeout. So we post a delayed task that will call
// the completer after the timeout and return an error.
if (const zx_status_t status = async::PostDelayedTask(
timeout_dispatcher_,
[collection_done = collection_done_, collection_done_lock = collection_done_lock_,
cobalt = cobalt_] {
{ // We keep the lock_guard's scope to a minimum.
std::lock_guard<std::mutex> lock(*collection_done_lock);
if (!collection_done->completer) {
return;
}
collection_done->completer.complete_error();
}
cobalt->LogOccurrence(TimedOutData::kInspect);
FX_LOGS(ERROR) << "Inspect data collection timed out";
},
timeout);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to post delayed task";
FX_LOGS(ERROR) << "Skipping Inspect data collection as it is not safe without a timeout";
return fit::make_result_promise<fuchsia::mem::Buffer>(fit::error());
}
// The Inspect data collection has two steps:
// * synchronous discovery of all the Inspect entrypoints under the realm of the calling
// component.
// * asynchronous connection to each entrypoint and read of its Inspect data.
collection_executor_->schedule_task(
// We wrap the synchronous discovery in a fit::promise to guarantee that it is executed on the
// executor and not on this thread.
fit::make_promise(
[]() -> fit::result<std::vector<fit::promise<inspect_deprecated::Source, std::string>>> {
auto locations = inspect_deprecated::SyncFindPaths("/hub");
if (locations.empty()) {
FX_LOGS(ERROR) << "Failed to find any Inspect locations";
return fit::error();
}
std::vector<fit::promise<inspect_deprecated::Source, std::string>> sources;
for (auto location : locations) {
if (location.directory_path.find("system_objects") == std::string::npos) {
sources.push_back(inspect_deprecated::ReadLocation(std::move(location)));
}
}
if (sources.empty()) {
FX_LOGS(ERROR) << "Failed to find any non-system-objects Inspect locations";
return fit::error();
}
return fit::ok(std::move(sources));
})
.and_then(
[](std::vector<fit::promise<inspect_deprecated::Source, std::string>>& sources) {
return fit::join_promise_vector(std::move(sources));
})
.and_then([](std::vector<fit::result<inspect_deprecated::Source, std::string>>& sources)
-> fit::result<fuchsia::mem::Buffer> {
std::vector<inspect_deprecated::Source> ok_sources;
for (auto& source : sources) {
if (source.is_ok()) {
inspect_deprecated::Source ok_source = source.take_value();
ok_source.SortHierarchy();
ok_sources.push_back(std::move(ok_source));
} else {
FX_LOGS(ERROR) << "Failed to read one Inspect source: " << source.take_error();
}
}
if (ok_sources.empty()) {
FX_LOGS(WARNING) << "No valid Inspect sources found";
return fit::error();
}
fsl::SizedVmo vmo;
if (!fsl::VmoFromString(inspect_deprecated::JsonFormatter(
inspect_deprecated::JsonFormatter::Options{},
inspect_deprecated::Formatter::PathFormat::ABSOLUTE)
.FormatSourcesRecursive(ok_sources),
&vmo)) {
FX_LOGS(ERROR) << "Failed to convert Inspect data JSON string to vmo";
return fit::error();
}
return fit::ok(std::move(vmo).ToTransport());
})
.then([collection_done = collection_done_, collection_done_lock = collection_done_lock_](
fit::result<fuchsia::mem::Buffer>& result) {
std::lock_guard<std::mutex> lock(*collection_done_lock);
if (!collection_done->completer) {
return;
}
if (result.is_error()) {
collection_done->completer.complete_error();
} else {
collection_done->completer.complete_ok(result.take_value());
}
}));
return collection_done_->consumer.promise_or(fit::error()).or_else([]() {
FX_LOGS(ERROR) << "Failed to get Inspect data";
return fit::error();
});
}
} // namespace feedback