blob: 4191575a126fdf236b3c9400a311b1ad06ce910a [file] [log] [blame]
// Copyright 2022 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/forensics/feedback/attachments/inspect.h"
#include <fuchsia/diagnostics/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/result.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <memory>
#include <string>
#include <vector>
#include "src/developer/forensics/feedback_data/constants.h"
#include "src/developer/forensics/utils/errors.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/strings/join_strings.h"
namespace forensics::feedback {
Inspect::Inspect(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
std::unique_ptr<backoff::Backoff> backoff,
feedback_data::InspectDataBudget* data_budget)
: dispatcher_(dispatcher),
services_(std::move(services)),
backoff_(std::move(backoff)),
data_budget_(data_budget) {
archive_accessor_.set_error_handler([this](const zx_status_t status) {
FX_LOGS(WARNING) << "Lost connection to " << feedback_data::kArchiveAccessorName;
auto self = ptr_factory_.GetWeakPtr();
async::PostDelayedTask(
dispatcher_,
[self] {
if (self) {
self->services_->Connect(self->archive_accessor_.NewRequest(self->dispatcher_),
feedback_data::kArchiveAccessorName);
}
},
backoff_->GetNext());
});
services_->Connect(archive_accessor_.NewRequest(dispatcher_),
feedback_data::kArchiveAccessorName);
}
namespace {
// Manages collecting Inspect data and calling |complete| with done.
class InspectCollector {
public:
InspectCollector(::fit::callback<void(std::optional<Error>)> complete);
void Run();
::fidl::InterfaceRequest<fuchsia::diagnostics::BatchIterator> NewRequest(
async_dispatcher_t* dispatcher);
const std::vector<std::string>& Inspect() const;
private:
::fit::callback<void(std::optional<Error>)> complete_;
fuchsia::diagnostics::BatchIteratorPtr batch_iterator_;
std::vector<std::string> inspect_;
};
InspectCollector::InspectCollector(::fit::callback<void(std::optional<Error>)> complete)
: complete_(std::move(complete)) {
batch_iterator_.set_error_handler([this](const zx_status_t status) mutable {
if (complete_ != nullptr) {
FX_PLOGS(WARNING, status) << "Lost connection to fuchsia.diagnostics.BatchIterator";
complete_(Error::kConnectionError);
}
});
}
::fidl::InterfaceRequest<fuchsia::diagnostics::BatchIterator> InspectCollector::NewRequest(
async_dispatcher_t* dispatcher) {
return batch_iterator_.NewRequest(dispatcher);
}
const std::vector<std::string>& InspectCollector::Inspect() const { return inspect_; }
void InspectCollector::Run() {
if (complete_ == nullptr) {
return;
}
FX_CHECK(batch_iterator_.is_bound())
<< "Attempting to collect Inspect without BatchIterator connection";
batch_iterator_->GetNext([this](auto result) mutable {
// Inspect collection has completed elsewhere, stop.
if (complete_ == nullptr) {
return;
}
// Get the next batch of Inspect data when done.
auto get_next = ::fit::defer([this] { Run(); });
// Try again if the result was an error.
if (result.is_err()) {
FX_LOGS(WARNING) << "Failed to retrieve Inspect batch: "
<< static_cast<uint64_t>(result.err());
return;
}
const auto& batch = result.response().batch;
// All of the Inspect data has been collected.
if (batch.empty()) {
complete_(std::nullopt);
get_next.cancel();
return;
}
for (const auto& chunk : batch) {
if (!chunk.is_json()) {
FX_LOGS(WARNING) << "Invalid JSON Inspect chunk, skipping";
continue;
}
if (std::string json; fsl::StringFromVmo(chunk.json(), &json)) {
inspect_.push_back(std::move(json));
} else {
FX_LOGS(WARNING) << "Failed to convert Inspect chunk to string, skipping";
}
}
});
}
} // namespace
::fpromise::promise<AttachmentValue> Inspect::Get(const zx::duration timeout) {
if (!archive_accessor_.is_bound()) {
return ::fpromise::make_ok_promise(AttachmentValue(Error::kConnectionError));
}
::fpromise::bridge<void, Error> bridge;
// Construct a promise and an object that can be used to complete the promise with a value at a
// later point in time.
auto consume = bridge.consumer.promise_or(::fpromise::error(Error::kLogicError));
::fit::callback<void(std::optional<Error>)> complete =
[completer = std::move(bridge.completer)](std::optional<Error> error) mutable {
if (error.has_value()) {
completer.complete_error(*error);
} else {
completer.complete_ok();
}
};
auto collector = std::make_unique<InspectCollector>(complete.share());
fuchsia::diagnostics::StreamParameters params;
params.set_data_type(fuchsia::diagnostics::DataType::INSPECT)
.set_format(fuchsia::diagnostics::Format::JSON)
.set_stream_mode(fuchsia::diagnostics::StreamMode::SNAPSHOT)
.set_client_selector_configuration(
fuchsia::diagnostics::ClientSelectorConfiguration::WithSelectAll(true));
if (const auto budget = data_budget_->SizeInBytes(); budget.has_value()) {
fuchsia::diagnostics::PerformanceConfiguration performance;
performance.set_max_aggregate_content_size_bytes(*budget);
params.set_performance_configuration(std::move(performance));
}
archive_accessor_->StreamDiagnostics(std::move(params), collector->NewRequest(dispatcher_));
collector->Run();
async::PostDelayedTask(
dispatcher_,
[complete = complete.share()]() mutable {
if (complete != nullptr) {
complete(Error::kTimeout);
}
},
timeout);
// Keep |collector| alive until Inspect collection has completed (for any reason).
return consume.then([collector = std::move(collector), complete = std::move(complete)](
const ::fpromise::result<void, Error>& result) mutable
-> ::fpromise::result<AttachmentValue> {
auto& inspect = collector->Inspect();
if (inspect.empty()) {
FX_LOGS(WARNING) << "Inspect data was empty";
return ::fpromise::ok(result.is_ok() ? Error::kMissingValue : result.error());
}
std::string joined_data = "[\n";
joined_data += fxl::JoinStrings(inspect, ",\n");
joined_data += "\n]";
AttachmentValue value = (result.is_ok())
? AttachmentValue(std::move(joined_data))
: AttachmentValue(std::move(joined_data), result.error());
return ::fpromise::ok(std::move(value));
});
}
} // namespace forensics::feedback