| // 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 <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <lib/fit/promise.h> |
| #include <lib/fit/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 "src/developer/forensics/feedback_data/annotations/types.h" |
| #include "src/developer/forensics/feedback_data/annotations/utils.h" |
| #include "src/developer/forensics/feedback_data/attachments/screenshot_ptr.h" |
| #include "src/developer/forensics/feedback_data/attachments/types.h" |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/developer/forensics/feedback_data/image_conversion.h" |
| #include "src/developer/forensics/utils/archive.h" |
| #include "src/lib/fxl/strings/string_printf.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, const bool is_first_instance, |
| const AnnotationKeys& annotation_allowlist, |
| const AttachmentKeys& attachment_allowlist, cobalt::Logger* cobalt, |
| Datastore* datastore, InspectDataBudget* inspect_data_budget) |
| : dispatcher_(dispatcher), |
| services_(services), |
| metadata_(dispatcher_, clock, is_first_instance, annotation_allowlist, attachment_allowlist), |
| cobalt_(cobalt), |
| datastore_(datastore), |
| executor_(dispatcher_), |
| inspect_data_budget_(inspect_data_budget) {} |
| |
| 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; |
| |
| const uint64_t timer_id = cobalt_->StartTimer(); |
| auto promise = |
| ::fit::join_promises(datastore_->GetAnnotations(timeout), datastore_->GetAttachments(timeout)) |
| .and_then([this](std::tuple<::fit::result<Annotations>, ::fit::result<Attachments>>& |
| annotations_and_attachments) { |
| Snapshot snapshot; |
| std::map<std::string, std::string> attachments; |
| |
| const auto& annotations_result = std::get<0>(annotations_and_attachments); |
| if (annotations_result.is_ok()) { |
| snapshot.set_annotations(ToFeedbackAnnotationVector(annotations_result.value())); |
| } else { |
| FX_LOGS(WARNING) << "Failed to retrieve any annotations"; |
| } |
| |
| const auto& attachments_result = std::get<1>(annotations_and_attachments); |
| if (attachments_result.is_ok()) { |
| for (const auto& [key, value] : attachments_result.value()) { |
| if (value.HasValue()) { |
| attachments[key] = value.Value(); |
| } |
| } |
| } else { |
| FX_LOGS(WARNING) << "Failed to retrieve any attachments"; |
| } |
| |
| // We also add the annotations as a single extra attachment. |
| // This is useful for clients that surface the annotations differently in the UI |
| // but still want all the annotations to be easily downloadable in one file. |
| if (snapshot.has_annotations()) { |
| const auto annotations_json = ToJsonString(snapshot.annotations()); |
| if (annotations_json.has_value()) { |
| attachments[kAttachmentAnnotations] = annotations_json.value(); |
| } |
| } |
| |
| attachments[kAttachmentMetadata] = |
| metadata_.MakeMetadata(annotations_result, attachments_result, |
| datastore_->IsMissingNonPlatformAnnotations()); |
| |
| // We bundle the attachments into a single attachment. |
| if (!attachments.empty()) { |
| fuchsia::feedback::Attachment bundle; |
| bundle.key = kSnapshotFilename; |
| std::map<std::string, ArchiveFileStats> file_size_stats; |
| if (Archive(attachments, &(bundle.value), &file_size_stats)) { |
| inspect_data_budget_->UpdateBudget(file_size_stats); |
| cobalt_->LogCount(SnapshotVersion::kCobalt, (uint64_t)bundle.value.size); |
| snapshot.set_archive(std::move(bundle)); |
| } |
| } |
| |
| return ::fit::ok(std::move(snapshot)); |
| }) |
| .then([this, callback = std::move(callback), timer_id](::fit::result<Snapshot>& result) { |
| if (result.is_error()) { |
| cobalt_->LogElapsedTime(cobalt::SnapshotGenerationFlow::kFailure, timer_id); |
| callback(Snapshot()); |
| } else { |
| cobalt_->LogElapsedTime(cobalt::SnapshotGenerationFlow::kSuccess, timer_id); |
| callback(result.take_value()); |
| } |
| }); |
| |
| executor_.schedule_task(std::move(promise)); |
| } |
| |
| void DataProvider::GetScreenshot(ImageEncoding encoding, GetScreenshotCallback callback) { |
| auto promise = |
| TakeScreenshot( |
| dispatcher_, services_, |
| fit::Timeout(kScreenshotTimeout, |
| [this] { cobalt_->LogOccurrence(cobalt::TimedOutData::kScreenshot); })) |
| .and_then([encoding](fuchsia::ui::scenic::ScreenshotData& raw_screenshot) |
| -> ::fit::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: |
| if (!RawToPng(raw_screenshot.data, 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 ::fit::error(); |
| } |
| break; |
| } |
| return ::fit::ok(std::move(screenshot)); |
| }) |
| .then([callback = std::move(callback)](::fit::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 |