blob: 290828661eea908d3c2bcefabf5b83195bea2a56 [file] [log] [blame]
// Copyright 2022 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_store.h"
#include <fstream>
#include <string>
#include <utility>
#include "src/developer/forensics/crash_reports/constants.h"
#include "src/developer/forensics/crash_reports/item_location.h"
#include "src/developer/forensics/crash_reports/snapshot.h"
#include "src/developer/forensics/feedback/annotations/constants.h"
namespace forensics::crash_reports {
namespace {
// Helper function to make a shared_ptr from a rvalue-reference of a type.
template <typename T>
std::shared_ptr<T> MakeShared(T&& t) {
return std::make_shared<T>(static_cast<std::remove_reference_t<T>&&>(t));
}
} // namespace
SnapshotStore::SnapshotStore(feedback::AnnotationManager* annotation_manager,
std::string garbage_collected_snapshots_path,
const std::optional<SnapshotPersistence::Root>& temp_root,
const std::optional<SnapshotPersistence::Root>& persistent_root,
StorageSize max_archives_size)
: annotation_manager_(annotation_manager),
garbage_collected_snapshots_path_(std::move(garbage_collected_snapshots_path)),
persistence_(temp_root, persistent_root),
max_archives_size_(max_archives_size),
current_archives_size_(0u),
garbage_collected_snapshot_(
kGarbageCollectedSnapshotUuid,
feedback::Annotations({
{feedback::kDebugSnapshotErrorKey, ErrorOrString("garbage collected")},
{feedback::kDebugSnapshotPresentKey, ErrorOrString("false")},
})),
not_persisted_snapshot_(
kNotPersistedSnapshotUuid,
feedback::Annotations({
{feedback::kDebugSnapshotErrorKey, ErrorOrString("not persisted")},
{feedback::kDebugSnapshotPresentKey, ErrorOrString("false")},
})),
shutdown_snapshot_(kShutdownSnapshotUuid,
feedback::Annotations({
{feedback::kDebugSnapshotErrorKey, ErrorOrString("system shutdown")},
{feedback::kDebugSnapshotPresentKey, ErrorOrString("false")},
})),
no_uuid_snapshot_(kNoUuidSnapshotUuid,
feedback::Annotations({
{feedback::kDebugSnapshotErrorKey, ErrorOrString("missing uuid")},
{feedback::kDebugSnapshotPresentKey, ErrorOrString("false")},
})) {
// Load the file lines into a set of UUIDs.
std::ifstream file(garbage_collected_snapshots_path_);
for (std::string uuid; getline(file, uuid);) {
garbage_collected_snapshots_.insert(uuid);
}
}
Snapshot SnapshotStore::GetSnapshot(const std::string& uuid) {
auto BuildMissing = [this](const SpecialCaseSnapshot& special_case) {
return MissingSnapshot(annotation_manager_->ImmediatelyAvailable(), special_case.annotations);
};
if (uuid == kGarbageCollectedSnapshotUuid) {
return BuildMissing(garbage_collected_snapshot_);
}
if (uuid == kNotPersistedSnapshotUuid) {
return BuildMissing(not_persisted_snapshot_);
}
if (uuid == kShutdownSnapshotUuid) {
return BuildMissing(shutdown_snapshot_);
}
if (uuid == kNoUuidSnapshotUuid) {
return BuildMissing(no_uuid_snapshot_);
}
auto* data = FindSnapshotData(uuid);
if (!data) {
if (garbage_collected_snapshots_.find(uuid) != garbage_collected_snapshots_.end()) {
return BuildMissing(garbage_collected_snapshot_);
}
if (auto snapshot = persistence_.Get(uuid); snapshot.has_value()) {
return ManagedSnapshot::StoreShared(MakeShared(std::move(*snapshot)));
}
return BuildMissing(not_persisted_snapshot_);
}
return ManagedSnapshot::StoreWeak(data->archive);
}
std::vector<std::string> SnapshotStore::GetSnapshotUuids() const {
return persistence_.GetSnapshotUuids();
}
MissingSnapshot SnapshotStore::GetMissingSnapshot(const std::string& uuid) {
const auto snapshot = GetSnapshot(uuid);
FX_CHECK(std::holds_alternative<MissingSnapshot>(snapshot));
return std::get<MissingSnapshot>(snapshot);
}
void SnapshotStore::DeleteSnapshot(const std::string& uuid) {
if (persistence_.Contains(uuid)) {
persistence_.Delete(uuid);
return;
}
auto* data = FindSnapshotData(uuid);
// The snapshot was likely dropped due to size constraints.
if (!data) {
return;
}
DropArchive(data);
RecordAsGarbageCollected(uuid);
data_.erase(uuid);
insertion_order_.erase(std::remove(insertion_order_.begin(), insertion_order_.end(), uuid),
insertion_order_.end());
}
void SnapshotStore::DeleteAll() {
// The iterator in a range-based for loop would be invalidated by calls to |data_.erase|, causing
// bad behavior or even a crash. Use a while loop instead that grabs a new, valid iterator for
// each iteration.
while (data_.begin() != data_.end()) {
// Grab copy of uuid to avoid asan failure when DeleteSnapshot erases |data_.begin()|.
const std::string uuid = data_.begin()->first;
DeleteSnapshot(uuid);
}
persistence_.DeleteAll();
}
void SnapshotStore::AddSnapshot(const std::string& uuid, fuchsia::feedback::Attachment archive) {
FX_CHECK(!SnapshotExists(uuid)) << "Duplicate snapshot uuid '" << uuid << "' added to store";
auto& data = data_[uuid];
if (!archive.key.empty() && archive.value.vmo.is_valid()) {
data.archive_size += StorageSize::Bytes(archive.key.size());
data.archive_size += StorageSize::Bytes(archive.value.size);
if (data.archive_size > max_archives_size_) {
// Attempting to add to the store would needlessly garbage collect all snapshots despite
// failing to add.
FX_LOGS(WARNING) << "Snapshot for uuid '" << uuid << "' with a size of '"
<< data.archive_size.Get() << "' is larger than the store max";
data_.erase(uuid);
return;
}
current_archives_size_ += data.archive_size;
data.archive = MakeShared(ManagedSnapshot::Archive(archive));
}
insertion_order_.push_back(uuid);
while (!insertion_order_.empty() && SizeLimitsExceeded()) {
// We erase snapshots from |insertion_order_| when they get moved to disk.
FX_CHECK(SnapshotLocation(insertion_order_.front()) == ItemLocation::kMemory)
<< "Snapshot for uuid " << insertion_order_.front() << " doesn't exist in memory";
EnforceSizeLimits(insertion_order_.front());
insertion_order_.pop_front();
}
}
void SnapshotStore::EnforceSizeLimits(const std::string& uuid) {
auto* data = FindSnapshotData(uuid);
FX_CHECK(data);
// Drop |data| if necessary.
if (current_archives_size_ > max_archives_size_) {
DropArchive(data);
RecordAsGarbageCollected(uuid);
data_.erase(uuid);
}
}
ItemLocation SnapshotStore::MoveToPersistence(const std::string& uuid,
const bool only_consider_tmp) {
auto* data = FindSnapshotData(uuid);
FX_CHECK(data);
const auto location =
persistence_.Add(uuid, *data->archive, data->archive_size, only_consider_tmp);
if (!location.has_value()) {
return ItemLocation::kMemory;
}
// Snapshot successfully moved to disk; no longer needed in memory.
insertion_order_.erase(std::remove(insertion_order_.begin(), insertion_order_.end(), uuid),
insertion_order_.end());
DropArchive(data);
data_.erase(uuid);
return *location;
}
void SnapshotStore::MoveToTmp(const std::string& uuid) { return persistence_.MoveToTmp(uuid); }
bool SnapshotStore::SnapshotExists(const std::string& uuid) {
if (FindSnapshotData(uuid) != nullptr) {
return true;
}
// Snapshot not in memory; check disc.
return persistence_.Contains(uuid);
}
std::optional<ItemLocation> SnapshotStore::SnapshotLocation(const std::string& uuid) {
if (FindSnapshotData(uuid) != nullptr) {
return ItemLocation::kMemory;
}
// Snapshot not in memory; check disc.
return persistence_.SnapshotLocation(uuid);
}
size_t SnapshotStore::Size() const { return data_.size(); }
bool SnapshotStore::IsGarbageCollected(const std::string& uuid) const {
return garbage_collected_snapshots_.find(uuid) != garbage_collected_snapshots_.end();
}
bool SnapshotStore::SizeLimitsExceeded() const {
return current_archives_size_ > max_archives_size_;
}
void SnapshotStore::DropArchive(SnapshotData* data) {
data->archive = nullptr;
current_archives_size_ -= data->archive_size;
data->archive_size = StorageSize::Bytes(0u);
}
void SnapshotStore::RecordAsGarbageCollected(const std::string& uuid) {
if (garbage_collected_snapshots_.find(uuid) != garbage_collected_snapshots_.end()) {
return;
}
garbage_collected_snapshots_.insert(uuid);
// Append the UUID to the file on its own line.
std::ofstream file(garbage_collected_snapshots_path_, std::ofstream::out | std::ofstream::app);
file << uuid << "\n";
file.close();
}
SnapshotStore::SnapshotData* SnapshotStore::FindSnapshotData(const std::string& uuid) {
return (data_.find(uuid) == data_.end()) ? nullptr : &(data_.at(uuid));
}
} // namespace forensics::crash_reports