| // 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 |