| // Copyright 2020 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/crash_reports/snapshot_collector.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fpromise/bridge.h> |
| #include <lib/fpromise/result.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "src/developer/forensics/crash_reports/constants.h" |
| #include "src/developer/forensics/crash_reports/report_id.h" |
| #include "src/developer/forensics/crash_reports/report_util.h" |
| #include "src/developer/forensics/crash_reports/snapshot.h" |
| #include "src/developer/forensics/feedback/annotations/constants.h" |
| #include "src/developer/forensics/feedback/annotations/types.h" |
| #include "src/lib/uuid/uuid.h" |
| |
| namespace forensics { |
| namespace crash_reports { |
| |
| namespace { |
| |
| template <typename V> |
| void AddAnnotation(const std::string& key, const V& value, feedback::Annotations& annotations) { |
| annotations.insert({key, ErrorOrString(std::to_string(value))}); |
| } |
| |
| template <> |
| void AddAnnotation<std::string>(const std::string& key, const std::string& value, |
| feedback::Annotations& annotations) { |
| annotations.insert({key, ErrorOrString(value)}); |
| } |
| |
| } // namespace |
| |
| SnapshotCollector::SnapshotCollector(async_dispatcher_t* dispatcher, timekeeper::Clock* clock, |
| feedback_data::DataProviderInternal* data_provider, |
| SnapshotStore* snapshot_store, Queue* queue, |
| zx::duration shared_request_window) |
| : dispatcher_(dispatcher), |
| clock_(clock), |
| data_provider_(data_provider), |
| snapshot_store_(snapshot_store), |
| queue_(queue), |
| shared_request_window_(shared_request_window) {} |
| |
| feedback::Annotations SnapshotCollector::GetMissingSnapshotAnnotations(const std::string& uuid) { |
| const auto missing_snapshot = snapshot_store_->GetMissingSnapshot(uuid); |
| |
| feedback::Annotations combined_annotations = missing_snapshot.Annotations(); |
| for (const auto& [key, val] : missing_snapshot.PresenceAnnotations()) { |
| combined_annotations.insert_or_assign(key, val); |
| } |
| |
| return combined_annotations; |
| } |
| |
| ::fpromise::promise<Report> SnapshotCollector::GetReport( |
| const zx::duration timeout, fuchsia::feedback::CrashReport fidl_report, |
| const ReportId report_id, const std::optional<timekeeper::time_utc> current_utc_time, |
| const Product& product, const bool is_hourly_snapshot, const ReportingPolicy reporting_policy) { |
| auto GetReport = [fidl_report = std::move(fidl_report), report_id, current_utc_time, product, |
| is_hourly_snapshot]( |
| const std::string& actual_uuid, const std::string& request_uuid, |
| feedback::Annotations annotations) mutable -> ::fpromise::result<Report> { |
| AddAnnotation(feedback::kSnapshotUuid, request_uuid, annotations); |
| return MakeReport(std::move(fidl_report), report_id, actual_uuid, annotations, current_utc_time, |
| product, is_hourly_snapshot); |
| }; |
| |
| // Only generate a snapshot if the report won't be immediately archived in the filesystem in |
| // order to save time during crash report creation. |
| if (reporting_policy == ReportingPolicy::kArchive) { |
| return fpromise::make_result_promise( |
| GetReport(/*actual_uuid=*/kNoUuidSnapshotUuid, /*request_uuid=*/kNoUuidSnapshotUuid, |
| GetMissingSnapshotAnnotations(kNoUuidSnapshotUuid))); |
| } |
| |
| const zx::time current_time{clock_->Now()}; |
| |
| std::string uuid; |
| |
| if (UseLatestRequest()) { |
| uuid = snapshot_requests_.back()->uuid; |
| } else { |
| uuid = MakeNewSnapshotRequest(current_time, timeout); |
| } |
| |
| auto* request = FindSnapshotRequest(uuid); |
| FX_CHECK(request); |
| request->promise_ids.insert(report_id); |
| |
| // The snapshot for |uuid| may not be ready, so the logic for returning |uuid| to the client |
| // needs to be wrapped in an asynchronous task that can be re-executed when the conditions for |
| // returning a UUID are met, e.g., the snapshot for |uuid| is received from |data_provider_| or |
| // the system is shutting down. |
| return ::fpromise::make_promise( |
| [this, uuid, request, report_id, GetReport = std::move(GetReport)]( |
| ::fpromise::context& context) mutable -> ::fpromise::result<Report> { |
| auto erase_request_task = |
| fit::defer([this, report_id] { report_results_.erase(report_id); }); |
| |
| if (shutdown_) { |
| return GetReport(/*actual_uuid=*/kShutdownSnapshotUuid, /*request_uuid=*/uuid, |
| GetMissingSnapshotAnnotations(kShutdownSnapshotUuid)); |
| } |
| |
| // The snapshot data was deleted before the promise executed. This should only occur if a |
| // snapshot is dropped immediately after it is received because its annotations and archive |
| // are too large and it is one of the oldest in the FIFO. |
| if (snapshot_store_->IsGarbageCollected(uuid)) { |
| return GetReport(/*actual_uuid=*/kGarbageCollectedSnapshotUuid, /*request_uuid=*/uuid, |
| GetMissingSnapshotAnnotations(kGarbageCollectedSnapshotUuid)); |
| } |
| |
| if (report_results_.find(report_id) != report_results_.end()) { |
| return GetReport(/*actual_uuid=*/uuid, /*request_uuid=*/uuid, |
| *report_results_[report_id].annotations); |
| } |
| |
| request->blocked_promises.push_back(context.suspend_task()); |
| erase_request_task.cancel(); |
| return ::fpromise::pending(); |
| }); |
| } |
| |
| void SnapshotCollector::Shutdown() { |
| // The destructor of snapshot requests will unblock all pending promises to return |
| // |shutdown_snapshot_|. |
| shutdown_ = true; |
| snapshot_requests_.clear(); |
| } |
| |
| std::string SnapshotCollector::MakeNewSnapshotRequest(const zx::time start_time, |
| const zx::duration timeout) { |
| const auto uuid = uuid::Generate(); |
| snapshot_requests_.emplace_back(std::unique_ptr<SnapshotRequest>(new SnapshotRequest{ |
| .uuid = uuid, |
| .promise_ids = {}, |
| .blocked_promises = {}, |
| .delayed_get_snapshot = async::TaskClosure(), |
| })); |
| |
| snapshot_requests_.back()->delayed_get_snapshot.set_handler([this, timeout, uuid]() { |
| zx::duration collection_timeout_per_data = timeout; |
| data_provider_->GetSnapshotInternal( |
| collection_timeout_per_data, uuid, |
| [this, uuid](feedback::Annotations annotations, fuchsia::feedback::Attachment archive) { |
| CompleteWithSnapshot(uuid, std::move(annotations), std::move(archive)); |
| }); |
| }); |
| snapshot_requests_.back()->delayed_get_snapshot.PostForTime(dispatcher_, |
| start_time + shared_request_window_); |
| |
| return uuid; |
| } |
| |
| void SnapshotCollector::CompleteWithSnapshot(const std::string& uuid, |
| feedback::Annotations annotations, |
| fuchsia::feedback::Attachment archive) { |
| // We clear snapshot_requests_ as soon as we receive the shutdown signal. |
| if (shutdown_) { |
| return; |
| } |
| |
| auto* request = FindSnapshotRequest(uuid); |
| FX_CHECK(request); |
| |
| // Add annotations about the snapshot. These are not "presence" annotations because |
| // they're unchanging and not the result of the SnapshotManager's data management. |
| AddAnnotation("debug.snapshot.shared-request.num-clients", request->promise_ids.size(), |
| annotations); |
| |
| if (archive.key.empty() || !archive.value.vmo.is_valid()) { |
| AddAnnotation(feedback::kDebugSnapshotPresentKey, std::string("false"), annotations); |
| } |
| |
| // The snapshot request is completed and unblock all promises that need |annotations| and |
| // |archive|. |
| const auto shared_annotations = std::make_shared<feedback::Annotations>(std::move(annotations)); |
| for (auto id : request->promise_ids) { |
| report_results_[id] = ReportResults{ |
| .uuid = uuid, |
| .annotations = shared_annotations, |
| }; |
| |
| queue_->AddReportUsingSnapshot(uuid, id); |
| } |
| |
| snapshot_store_->AddSnapshot(uuid, std::move(archive)); |
| |
| snapshot_requests_.erase(std::remove_if( |
| snapshot_requests_.begin(), snapshot_requests_.end(), |
| [uuid](const std::unique_ptr<SnapshotRequest>& request) { return uuid == request->uuid; })); |
| } |
| |
| bool SnapshotCollector::UseLatestRequest() const { |
| if (snapshot_requests_.empty()) { |
| return false; |
| } |
| |
| // Whether the FIDL call for the latest request has already been made or not. If it has, the |
| // snapshot might not contain all the logs up until now for instance so it's better to create a |
| // new request. |
| return snapshot_requests_.back()->delayed_get_snapshot.is_pending(); |
| } |
| |
| SnapshotCollector::SnapshotRequest* SnapshotCollector::FindSnapshotRequest( |
| const std::string& uuid) { |
| auto request = std::find_if( |
| snapshot_requests_.begin(), snapshot_requests_.end(), |
| [uuid](const std::unique_ptr<SnapshotRequest>& request) { return uuid == request->uuid; }); |
| return (request == snapshot_requests_.end()) ? nullptr : request->get(); |
| } |
| |
| } // namespace crash_reports |
| } // namespace forensics |