blob: 67d9a647acc8469b7926ec2a48b53feac56861a1 [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/forensics/crash_reports/queue.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include "src/developer/forensics/crash_reports/constants.h"
#include "src/developer/forensics/crash_reports/info/queue_info.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace forensics {
namespace crash_reports {
using UploadPolicy = Settings::UploadPolicy;
Queue::Queue(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
std::shared_ptr<InfoContext> info_context, LogTags* tags, CrashServer* crash_server,
SnapshotManager* snapshot_manager)
: dispatcher_(dispatcher),
services_(services),
tags_(tags),
store_(tags_, info_context, kStorePath, kStoreMaxSize),
crash_server_(crash_server),
snapshot_manager_(snapshot_manager),
info_(std::move(info_context)) {
FX_CHECK(dispatcher_);
FX_CHECK(crash_server_);
upload_all_every_fifteen_minutes_task_.set_handler([this]() { UploadAllEveryFifteenMinutes(); });
// TODO(fxbug.dev/56448): Initialize queue with the reports in the store. We need to be able to
// distinguish archived reports from reports that have not been uploaded yet.
}
bool Queue::Contains(const ReportId report_id) const {
return std::find(pending_reports_.begin(), pending_reports_.end(), report_id) !=
pending_reports_.end();
}
bool Queue::Add(Report report) {
// Attempt to upload a report before putting it in the store.
if (state_ == State::Upload) {
if (Upload(report)) {
return true;
}
}
const auto report_id = report.Id();
std::vector<ReportId> garbage_collected_reports;
const bool success = store_.Add(std::move(report), &garbage_collected_reports);
for (const auto& id : garbage_collected_reports) {
GarbageCollect(id);
}
if (!success) {
FreeResources(report_id);
return false;
}
if (state_ == State::Archive) {
Archive(report_id);
} else {
pending_reports_.push_back(report_id);
}
return true;
}
bool Queue::Upload(const Report& report) {
upload_attempts_[report.Id()]++;
info_.RecordUploadAttemptNumber(upload_attempts_[report.Id()]);
std::string server_report_id;
const auto response = crash_server_->MakeRequest(report, &server_report_id);
switch (response) {
case CrashServer::UploadStatus::kSuccess:
FX_LOGST(INFO, tags_->Get(report.Id()))
<< "Successfully uploaded report at https://crash.corp.google.com/" << server_report_id;
info_.MarkReportAsUploaded(server_report_id, upload_attempts_[report.Id()]);
FreeResources(report);
return true;
case CrashServer::UploadStatus::kThrottled:
info_.MarkReportAsThrottledByServer(upload_attempts_[report.Id()]);
FreeResources(report);
return true;
case CrashServer::UploadStatus::kFailure:
return false;
}
}
void Queue::Archive(const ReportId report_id) {
FX_LOGST(INFO, tags_->Get(report_id)) << "Archiving local report under /tmp/reports";
info_.MarkReportAsArchived(upload_attempts_[report_id]);
FreeResources(report_id);
}
void Queue::GarbageCollect(const ReportId report_id) {
FX_LOGST(INFO, tags_->Get(report_id)) << "Garbage collected local report";
info_.MarkReportAsGarbageCollected(upload_attempts_[report_id]);
FreeResources(report_id);
pending_reports_.erase(std::remove(pending_reports_.begin(), pending_reports_.end(), report_id),
pending_reports_.end());
}
void Queue::FreeResources(const ReportId report_id) {
if (const auto report = store_.Get(report_id); report != std::nullopt) {
FreeResources(std::move(report.value()));
return;
}
// The report no longer exists in the store.
tags_->Unregister(report_id);
upload_attempts_.erase(report_id);
}
void Queue::FreeResources(const Report& report) {
snapshot_manager_->Release(report.SnapshotUuid());
tags_->Unregister(report.Id());
upload_attempts_.erase(report.Id());
}
size_t Queue::UploadAll() {
std::vector<ReportId> new_pending_reports;
for (const auto& report_id : pending_reports_) {
std::optional<Report> report = store_.Get(report_id);
if (!report.has_value()) {
// |pending_reports_| is kept in sync with |store_| so Get should only ever fail if the report
// is deleted from the store by an external influence, e.g., the filesystem flushes /cache.
FreeResources(report_id);
continue;
}
if (!Upload(report.value())) {
new_pending_reports.push_back(report_id);
}
}
pending_reports_.swap(new_pending_reports);
// |new_pending_reports| now contains the pending reports before attempting to upload them.
return new_pending_reports.size() - pending_reports_.size();
}
size_t Queue::ArchiveAll() {
for (const auto& report_id : pending_reports_) {
Archive(report_id);
}
const size_t successful = pending_reports_.size();
pending_reports_.clear();
return successful;
}
// The queue is inheritly conservative with uploading crash reports meaning that a report that is
// forbidden from being uploaded will never be uploaded while crash reports that are permitted to
// be uploaded may later be considered to be forbidden. This is due to the fact that when uploads
// are disabled all reports are immediately archived after having been added to the queue, thus we
// never have to worry that a report that shouldn't be uploaded ends up being uploaded when the
// upload policy changes.
void Queue::WatchSettings(Settings* settings) {
settings->RegisterUploadPolicyWatcher([this](const UploadPolicy& upload_policy) {
switch (upload_policy) {
case UploadPolicy::DISABLED:
state_ = State::Archive;
upload_all_every_fifteen_minutes_task_.Cancel();
ArchiveAll();
break;
case UploadPolicy::ENABLED:
state_ = State::Upload;
UploadAllEveryFifteenMinutes();
break;
case UploadPolicy::LIMBO:
state_ = State::LeaveAsPending;
upload_all_every_fifteen_minutes_task_.Cancel();
break;
}
});
}
void Queue::WatchNetwork(NetworkWatcher* network_watcher) {
network_watcher->Register([this](const bool network_is_reachable) {
if (network_is_reachable) {
// Save the size of |pending_reports_| because UploadAll mutates |pending_reports_|.
if (const auto pending = pending_reports_.size(); state_ == State::Upload && pending > 0) {
const auto uploaded = UploadAll();
FX_LOGS(INFO) << fxl::StringPrintf(
"Successfully uploaded %zu of %zu pending crash reports on network reachable ",
uploaded, pending);
}
}
});
}
void Queue::UploadAllEveryFifteenMinutes() {
if (const auto pending = pending_reports_.size(); state_ == State::Upload && pending > 0) {
const auto uploaded = UploadAll();
FX_LOGS(INFO) << fxl::StringPrintf(
"Successfully uploaded %zu of %zu pending crash reports as part of the "
"15-minute periodic uploaded",
uploaded, pending);
}
if (const auto status =
upload_all_every_fifteen_minutes_task_.PostDelayed(dispatcher_, zx::min(15));
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Error posting periodic upload task to async loop. Won't retry.";
}
}
} // namespace crash_reports
} // namespace forensics