blob: 930aacdfdabb6b06ecd6bd3ca94cdb6143922d99 [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/annotations/board_info_provider.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fit/sequencer.h>
#include <zircon/errors.h>
#include <optional>
#include "src/developer/feedback/feedback_agent/constants.h"
#include "src/developer/feedback/utils/promise.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
namespace {
using fuchsia::feedback::Annotation;
using fuchsia::hwinfo::BoardInfo;
} // namespace
BoardInfoProvider::BoardInfoProvider(const std::set<std::string>& annotations_to_get,
async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services,
zx::duration timeout, Cobalt* cobalt)
: annotations_to_get_(annotations_to_get),
dispatcher_(dispatcher),
services_(services),
timeout_(timeout),
cobalt_(cobalt) {
const auto supported_annotations = GetSupportedAnnotations();
std::vector<std::string> not_supported;
for (const auto& annotation : annotations_to_get_) {
if (supported_annotations.find(annotation) == supported_annotations.end()) {
FX_LOGS(WARNING) << "annotation " << annotation << " not supported by BoardInfoProvider";
not_supported.push_back(annotation);
}
}
for (auto annotation : not_supported) {
annotations_to_get_.erase(annotation);
}
}
std::set<std::string> BoardInfoProvider::GetSupportedAnnotations() {
return {
kAnnotationHardwareBoardName,
kAnnotationHardwareBoardRevision,
};
}
fit::promise<std::vector<Annotation>> BoardInfoProvider::GetAnnotations() {
std::vector<fit::promise<Annotation>> annotations;
auto board_info_ptr = std::make_unique<internal::BoardInfoPtr>(dispatcher_, services_, cobalt_);
auto board_info = board_info_ptr->GetBoardInfo(timeout_);
return ExtendArgsLifetimeBeyondPromise(std::move(board_info), /*args=*/std::move(board_info_ptr))
.and_then([annotations_to_get =
annotations_to_get_](const std::map<std::string, std::string>& board_info) {
std::vector<Annotation> annotations;
for (const auto& key : annotations_to_get) {
if (board_info.find(key) == board_info.end()) {
FX_LOGS(WARNING) << "Failed to build annotation " << key;
continue;
}
Annotation annotation;
annotation.key = key;
annotation.value = board_info.at(key);
annotations.push_back(std::move(annotation));
}
return fit::ok(std::move(annotations));
});
}
namespace internal {
BoardInfoPtr::BoardInfoPtr(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services,
Cobalt* cobalt)
: dispatcher_(dispatcher), services_(services), cobalt_(cobalt) {}
fit::promise<std::map<std::string, std::string>> BoardInfoPtr::GetBoardInfo(zx::duration timeout) {
FXL_CHECK(!has_called_get_board_info_) << "GetBoardInfo() is not intended to be called twice";
has_called_get_board_info_ = true;
// 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.
//
// We wrap the delayed task in a CancelableClosure so we can cancel it when the fit::bridge is
// completed another way.
//
// It is safe to pass "this" to the fit::function as the callback won't be callable when the
// CancelableClosure goes out of scope, which is before "this".
done_after_timeout_.Reset([this] {
if (!done_.completer) {
return;
}
FX_LOGS(ERROR) << "Hardware board info retrieval timed out";
cobalt_->LogOccurrence(TimedOutData::kBoardInfo);
done_.completer.complete_error();
});
board_ptr_ = services_->Connect<fuchsia::hwinfo::Board>();
const zx_status_t post_status = async::PostDelayedTask(
dispatcher_, [cb = done_after_timeout_.callback()] { cb(); }, timeout);
if (post_status != ZX_OK) {
FX_PLOGS(ERROR, post_status) << "Failed to post delayed task";
FX_LOGS(ERROR) << "Skipping hardware board info retrieval as it is not safe without a timeout";
return fit::make_result_promise<std::map<std::string, std::string>>(fit::error());
}
board_ptr_.set_error_handler([this](zx_status_t status) {
if (!done_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "Lost connection to fuchsia.hwinfo.Board";
done_.completer.complete_error();
});
board_ptr_->GetInfo([this](BoardInfo info) {
std::map<std::string, std::string> board_info;
if (info.has_name()) {
board_info[kAnnotationHardwareBoardName] = info.name();
}
if (info.has_revision()) {
board_info[kAnnotationHardwareBoardRevision] = info.revision();
}
done_.completer.complete_ok(std::move(board_info));
});
return done_.consumer.promise_or(fit::error())
.then([this](fit::result<std::map<std::string, std::string>>& result) {
done_after_timeout_.Cancel();
return std::move(result);
});
}
} // namespace internal
} // namespace feedback