| // 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/forensics/feedback_data/data_provider.h" |
| |
| #include <fuchsia/feedback/cpp/fidl.h> |
| #include <lib/fpromise/promise.h> |
| #include <lib/fpromise/result.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/time.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <map> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/developer/forensics/feedback/annotations/annotation_manager.h" |
| #include "src/developer/forensics/feedback/annotations/encode.h" |
| #include "src/developer/forensics/feedback/annotations/types.h" |
| #include "src/developer/forensics/feedback/attachments/types.h" |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/developer/forensics/feedback_data/image_conversion.h" |
| #include "src/developer/forensics/feedback_data/screenshot.h" |
| #include "src/developer/forensics/utils/archive.h" |
| #include "src/developer/forensics/utils/cobalt/metrics.h" |
| #include "src/lib/fsl/vmo/sized_vmo.h" |
| #include "src/lib/fsl/vmo/vector.h" |
| #include "src/lib/uuid/uuid.h" |
| |
| namespace forensics { |
| namespace feedback_data { |
| namespace { |
| |
| using fuchsia::feedback::ImageEncoding; |
| using fuchsia::feedback::Screenshot; |
| using fuchsia::feedback::Snapshot; |
| |
| // Timeout for a single asynchronous piece of data, e.g., syslog collection, if the client didn't |
| // specify one. |
| // |
| // 30s seems reasonable to collect everything. |
| const zx::duration kDefaultDataTimeout = zx::sec(30); |
| |
| // Timeout for requesting the screenshot from Scenic. |
| // |
| // 10 seconds seems reasonable to take a screenshot. |
| const zx::duration kScreenshotTimeout = zx::sec(10); |
| |
| } // namespace |
| |
| DataProvider::DataProvider(async_dispatcher_t* dispatcher, |
| std::shared_ptr<sys::ServiceDirectory> services, |
| timekeeper::Clock* clock, RedactorBase* redactor, |
| const bool is_first_instance, |
| const std::set<std::string>& annotation_allowlist, |
| const feedback::AttachmentKeys& attachment_allowlist, |
| cobalt::Logger* cobalt, feedback::AnnotationManager* annotation_manager, |
| feedback::AttachmentManager* attachment_manager, |
| InspectDataBudget* inspect_data_budget) |
| : dispatcher_(dispatcher), |
| services_(std::move(services)), |
| metadata_(dispatcher_, clock, redactor, is_first_instance, annotation_allowlist, |
| attachment_allowlist), |
| cobalt_(cobalt), |
| annotation_manager_(annotation_manager), |
| annotation_metrics_(cobalt_), |
| attachment_manager_(attachment_manager), |
| attachment_metrics_(cobalt_), |
| executor_(dispatcher_), |
| inspect_data_budget_(inspect_data_budget) {} |
| |
| ::fpromise::promise<feedback::Annotations> DataProvider::GetAnnotations( |
| const zx::duration timeout) { |
| return annotation_manager_->GetAll(timeout).and_then([this](feedback::Annotations& annotations) { |
| annotation_metrics_.LogMetrics(annotations); |
| return ::fpromise::ok(std::move(annotations)); |
| }); |
| } |
| |
| ::fpromise::promise<feedback::Attachments> DataProvider::GetAttachments( |
| const zx::duration timeout) { |
| return attachment_manager_->GetAttachments(timeout).and_then( |
| [this](feedback::Attachments& attachments) { |
| attachment_metrics_.LogMetrics(attachments); |
| return ::fpromise::ok(std::move(attachments)); |
| }); |
| } |
| |
| void DataProvider::GetAnnotations(fuchsia::feedback::GetAnnotationsParameters params, |
| GetAnnotationsCallback callback) { |
| const auto timeout = (params.has_collection_timeout_per_annotation()) |
| ? zx::duration(params.collection_timeout_per_annotation()) |
| : kDefaultDataTimeout; |
| |
| // TODO(fxbug.dev/74102): Track how long GetAnnotations took via Cobalt. |
| executor_.schedule_task(GetAnnotations(timeout).and_then( |
| [callback = std::move(callback)](feedback::Annotations& annotations) { |
| callback(feedback::Encode<fuchsia::feedback::Annotations>(annotations)); |
| })); |
| } |
| |
| void DataProvider::GetSnapshot(fuchsia::feedback::GetSnapshotParameters params, |
| GetSnapshotCallback callback) { |
| const zx::duration timeout = (params.has_collection_timeout_per_data()) |
| ? zx::duration(params.collection_timeout_per_data()) |
| : kDefaultDataTimeout; |
| |
| std::optional<zx::channel> channel; |
| if (params.has_response_channel()) { |
| channel = std::move(*params.mutable_response_channel()); |
| } |
| |
| FX_LOGS(INFO) << "Generating snapshot"; |
| GetSnapshotInternal(timeout, [this, callback = std::move(callback), channel = std::move(channel)]( |
| const feedback::Annotations& annotations, |
| fsl::SizedVmo archive) mutable { |
| FX_LOGS(INFO) << "Generated snapshot"; |
| Snapshot snapshot; |
| |
| // Add the annotations to the FIDL object and as file in the snapshot itself. |
| if (auto fidl = feedback::Encode<fuchsia::feedback::Annotations>(annotations); |
| fidl.has_annotations()) { |
| snapshot.set_annotations(fidl.annotations()); |
| } |
| |
| if (archive.vmo().is_valid()) { |
| if (channel) { |
| ServeArchive(std::move(archive), std::move(channel.value())); |
| } else { |
| snapshot.set_archive({.key = kSnapshotFilename, .value = std::move(archive).ToTransport()}); |
| } |
| } |
| |
| callback(std::move(snapshot)); |
| }); |
| } |
| |
| void DataProvider::GetSnapshotInternal(zx::duration timeout, GetSnapshotInternalCallback callback) { |
| GetSnapshotInternal(timeout, [callback = std::move(callback)]( |
| const feedback::Annotations& annotations, |
| fsl::SizedVmo archive) mutable { |
| callback(annotations, {.key = kSnapshotFilename, .value = std::move(archive).ToTransport()}); |
| }); |
| } |
| |
| void DataProvider::GetSnapshotInternal( |
| zx::duration timeout, fit::callback<void(feedback::Annotations, fsl::SizedVmo)> callback) { |
| const uint64_t timer_id = cobalt_->StartTimer(); |
| |
| auto join = ::fpromise::join_promises(GetAnnotations(timeout), GetAttachments(timeout)); |
| using result_t = decltype(join)::value_type; |
| |
| auto promise = |
| join.and_then([this, timer_id, callback = std::move(callback)](result_t& results) mutable { |
| FX_CHECK(std::get<0>(results).is_ok()) << "Impossible annotation collection failure"; |
| FX_CHECK(std::get<1>(results).is_ok()) << "Impossible attachment collection failure"; |
| |
| const auto& annotations = std::get<0>(results).value(); |
| const auto& attachments = std::get<1>(results).value(); |
| std::map<std::string, std::string> snapshot_files; |
| |
| // Add the annotations to |snapshot_files| |
| auto file = feedback::Encode<std::string>(annotations); |
| snapshot_files[kAttachmentAnnotations] = std::move(file); |
| |
| // Add the attachments to |snapshot_files| |
| for (const auto& [key, value] : attachments) { |
| if (value.HasValue()) { |
| snapshot_files[key] = value.Value(); |
| } |
| } |
| |
| snapshot_files[kAttachmentMetadata] = |
| metadata_.MakeMetadata(annotations, attachments, uuid::Generate(), |
| annotation_manager_->IsMissingNonPlatformAnnotations()); |
| |
| fsl::SizedVmo archive; |
| |
| // We bundle the attachments into a single archive. |
| if (std::map<std::string, ArchiveFileStats> file_size_stats; |
| Archive(snapshot_files, &archive, &file_size_stats)) { |
| inspect_data_budget_->UpdateBudget(file_size_stats); |
| cobalt_->LogCount(SnapshotVersion::kCobalt, archive.size()); |
| cobalt_->LogElapsedTime(cobalt::SnapshotGenerationFlow::kSuccess, timer_id); |
| } else { |
| cobalt_->LogElapsedTime(cobalt::SnapshotGenerationFlow::kFailure, timer_id); |
| archive.vmo().reset(); |
| } |
| callback(annotations, std::move(archive)); |
| return ::fpromise::ok(); |
| }); |
| |
| executor_.schedule_task(std::move(promise)); |
| } |
| |
| bool DataProvider::ServeArchive(fsl::SizedVmo archive, zx::channel server_end) { |
| const size_t archive_index = next_served_archive_index_++; |
| auto served_archive = std::make_unique<ServedArchive>(std::move(archive)); |
| |
| if (!served_archive->Serve(std::move(server_end), dispatcher_, [this, archive_index]() mutable { |
| served_archives_.erase(archive_index); |
| })) { |
| return false; |
| } |
| |
| served_archives_.emplace(archive_index, std::move(served_archive)); |
| return true; |
| } |
| |
| ServedArchive::ServedArchive(fsl::SizedVmo archive) |
| : file_(std::move(archive.vmo()), archive.size()) {} |
| |
| bool ServedArchive::Serve(zx::channel server_end, async_dispatcher_t* dispatcher, |
| std::function<void()> completed) { |
| channel_closed_observer_ = |
| std::make_unique<async::WaitOnce>(server_end.get(), ZX_CHANNEL_PEER_CLOSED); |
| if (const auto status = |
| file_.Serve(fuchsia::io::OpenFlags::RIGHT_READABLE, std::move(server_end)); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Cannot serve archive"; |
| return false; |
| } |
| |
| if (const auto status = channel_closed_observer_->Begin( |
| dispatcher, [completed = std::move(completed)](...) { completed(); }); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Cannot attach observer to server end"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void DataProvider::GetScreenshot(ImageEncoding encoding, GetScreenshotCallback callback) { |
| auto promise = |
| TakeScreenshot(dispatcher_, services_, kScreenshotTimeout) |
| .or_else([this](const Error& error) { |
| if (error != Error::kTimeout) { |
| return ::fpromise::error(); |
| } |
| |
| cobalt_->LogOccurrence(cobalt::TimedOutData::kScreenshot); |
| return ::fpromise::error(); |
| }) |
| .and_then([encoding](ScreenshotData& raw_screenshot) -> ::fpromise::result<Screenshot> { |
| Screenshot screenshot; |
| screenshot.dimensions_in_px.height = raw_screenshot.info.height; |
| screenshot.dimensions_in_px.width = raw_screenshot.info.width; |
| switch (encoding) { |
| case ImageEncoding::PNG: |
| std::vector<uint8_t> raw; |
| if (!fsl::VectorFromVmo(raw_screenshot.data, &raw)) { |
| FX_LOGS(ERROR) << "Failed to read raw screenshot VMO"; |
| return ::fpromise::error(); |
| } |
| if (!RawToPng(raw, raw_screenshot.info.height, raw_screenshot.info.width, |
| raw_screenshot.info.stride, raw_screenshot.info.pixel_format, |
| &screenshot.image)) { |
| FX_LOGS(ERROR) << "Failed to convert raw screenshot to PNG"; |
| return ::fpromise::error(); |
| } |
| break; |
| } |
| return ::fpromise::ok(std::move(screenshot)); |
| }) |
| .then([callback = std::move(callback)](::fpromise::result<Screenshot>& result) { |
| if (!result.is_ok()) { |
| callback(/*screenshot=*/nullptr); |
| } else { |
| callback(std::make_unique<Screenshot>(result.take_value())); |
| } |
| }); |
| executor_.schedule_task(std::move(promise)); |
| } |
| |
| } // namespace feedback_data |
| } // namespace forensics |