// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_FATAL_CRASH_FATAL_CRASH_EVENTS_OBSERVER_REPORTED_LOCAL_ID_MANAGER_H_
#define CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_FATAL_CRASH_FATAL_CRASH_EVENTS_OBSERVER_REPORTED_LOCAL_ID_MANAGER_H_
#include <queue>
#include <string>
#include <type_traits>
#include <unordered_map>
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/ash/policy/reporting/metrics_reporting/fatal_crash/fatal_crash_events_observer.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_events.mojom.h"
namespace reporting {
class FatalCrashEventsObserver::ReportedLocalIdManager {
public:
// Callback type once the save file is loaded.
using SaveFileLoadedCallback = base::OnceCallback<void()>;
// The result of `ShouldReport`.
enum class ShouldReportResult : uint8_t {
kYes = 0u,
kNegativeTimestamp,
kHasBeenReported,
kCrashTooOldAndMaxNumOfSavedLocalIdsReached,
kMaxValue = kCrashTooOldAndMaxNumOfSavedLocalIdsReached
};
// Create a `ReportedLocalIdManager` instance.
//
// Params:
//
// - save_file_path: Path to the save file.
// - save_file_loaded_callback: The value of `save_file_loaded_callback_`. See
// its document.
// - io_task_runner: The task runner to run IO tasks on. If nullptr, the
// constructor would create a default task runner.
static std::unique_ptr<ReportedLocalIdManager> Create(
base::FilePath save_file_path,
SaveFileLoadedCallback save_file_loaded_callback,
scoped_refptr<base::SequencedTaskRunner> io_task_runner);
ReportedLocalIdManager(const ReportedLocalIdManager&) = delete;
ReportedLocalIdManager& operator=(const ReportedLocalIdManager&) = delete;
virtual ~ReportedLocalIdManager();
// Returns true if a crash with the local ID has already been reported.
bool HasBeenReported(const std::string& local_id) const;
// Returns a value to indicate that the timestamp is negative, the local ID
// is already in the reported Local IDs or the timestamp is no later than
// the earliest timestamp corresponding to reported local IDs. Otherwise,
// returns kYes.
//
// Not a const method because it calls `GetEarliestLocalIdEntry`, which
// involves cleaning up saved local IDs.
ShouldReportResult ShouldReport(const std::string& local_id,
int64_t capture_timestamp_us);
// Updates local ID. Does nothing and returns false if a crash with the
// given local ID and capture timestamp should not be reported. Otherwise,
// writes the update to the save file; if there are more than maximum
// allowed local IDs, remove the oldest one.
bool UpdateLocalId(const std::string& local_id, int64_t capture_timestamp_us);
// Same as `UpdateLocalId`, except not writing changes to the save file.
// Useful when loading from save file.
bool UpdateLocalIdInRam(const std::string& local_id,
int64_t capture_timestamp_us);
// Adds a local ID and its corresponding capture timestamp. Returns true if
// the local ID can be saved to memory.
bool Add(const std::string& local_id, int64_t capture_timestamp_us);
// Removes a local ID, e.g., it has been reported again as uploaded. If the
// local ID is not found, does nothing.
void Remove(const std::string& local_id);
// Indicates whether the save file has been loaded.
bool IsSaveFileLoaded() const;
private:
// Give `TestEnvironment` the access to `kMaxNumOfLocalIds`.
friend class FatalCrashEventsObserver::TestEnvironment;
// `LocalIdEntry` comparator for `local_id_entry_queue_`, based on the
// timestamp.
class LocalIdEntryComparator {
public:
bool operator()(const LocalIdEntry& a, const LocalIdEntry& b) const;
};
// Assert no data member, therefore there's no need in explicitly defining
// the constructors and destructor of `LocalIdEntryComparator`.
static_assert(std::is_empty_v<LocalIdEntryComparator>);
// The maximum number of local IDs to save.
static constexpr size_t kMaxNumOfLocalIds = 128u;
// The maximum size of the priority queue before reconstructing it.
static constexpr size_t kMaxSizeOfLocalIdEntryQueue{kMaxNumOfLocalIds * 10u};
ReportedLocalIdManager(
base::FilePath save_file_path,
SaveFileLoadedCallback save_file_loaded_callback,
scoped_refptr<base::SequencedTaskRunner> io_task_runner);
// Loads save file. Logs and ignores errors. If there is a parsing error,
// still loads all lines before the line on which the error occurs. Does not
// clear existing local IDs in RAM.
void LoadSaveFile();
// Resume loading save file after the IO part is done.
void ResumeLoadingSaveFile(const std::string& content);
// Writes save file based on the currently saved reported local IDs. Ignores
// and logs any errors encountered. If the device reboots before the write
// succeeds next time, this may lead to a repeated report of an unuploaded
// crash, which is, however, better than the opposite, i.e., missing
// unuploaded crash.
void WriteSaveFile();
// Cleans up local IDs corresponding to crashes that have been reported
// again as uploaded in an efficient manner.
void CleanUpLocalIdEntryQueue();
// (Re)constructs `local_id_entry_queue_` from `local_ids_`.
void ReconstructLocalIdEntries();
// Gets the local ID entry corresponding to the earliest unuploaded crash.
// Must be used before the earliest local ID is removed by any operations on
// `local_id_entry_queue_`.
const LocalIdEntry& GetEarliestLocalIdEntry();
// Remove the local ID entry corresponding to the earliest unuploaded crash.
void RemoveEarliestLocalIdEntry();
// This instance is always on the same sequence as that of the owning
// `FatalCrashEventsObserver` instance.
SEQUENCE_CHECKER(sequence_checker_);
// The file that saves reported local IDs. It is in the CSV format: csv
// format (Column 0: Local ID, Column 1: capture timestamps).
const base::FilePath save_file_ GUARDED_BY_CONTEXT(sequence_checker_);
// The temporary save file that was written to before updating `save_file_`.
const base::FilePath save_file_tmp_ GUARDED_BY_CONTEXT(sequence_checker_){
save_file_.AddExtension(".tmp")};
// A map that maps local IDs to their respective capture timestamps in
// microseconds. Only local IDs corresponding to crashes that has been
// reported as unuploaded crashes are here. If a crash has been uploaded
// then, its local ID would then be removed from this map.
std::unordered_map</*local_id*/ std::string, /*timestamp*/ int64_t> local_ids_
GUARDED_BY_CONTEXT(sequence_checker_);
// The priority queue that makes popping out the oldest crash efficient. The
// local IDs in this priority queue constitute a super set of those in
// `local_ids_`, which implies that some local IDs corresponding to crashes
// that have already been reported again as uploaded crashes. This is to
// avoid removing removing non-top elements from a priority queue as this
// operation is inefficient.
std::priority_queue<LocalIdEntry,
std::vector<LocalIdEntry>,
LocalIdEntryComparator>
local_id_entry_queue_ GUARDED_BY_CONTEXT(sequence_checker_);
// Indicates whether loading has finished.
bool save_file_loaded_ GUARDED_BY_CONTEXT(sequence_checker_){false};
// Called when the save file is loaded. Should only be called once because the
// save file is only loaded once throughout the lifetime of this class
// instance.
SaveFileLoadedCallback save_file_loaded_callback_
GUARDED_BY_CONTEXT(sequence_checker_);
// The task runner that performs IO.
const scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
// The counter that keeps track of the number of IO tasks posted. This is
// mostly used to avoid duplicate IO tasks, i.e., a later save file writing
// task is posted and there's no need to executing an earlier file writing
// task that has not started. It is always deleted on the IO thread so that
// when an instance of this class is destroyed, this counter remains
// accessible for all tasks posted to the IO thread.
const std::unique_ptr<std::atomic<uint64_t>, base::OnTaskRunnerDeleter>
latest_save_file_writing_task_id_{
new std::atomic<uint64_t>(0u),
base::OnTaskRunnerDeleter(io_task_runner_)};
base::WeakPtrFactory<ReportedLocalIdManager> weak_factory_{this};
};
} // namespace reporting
#endif // CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_FATAL_CRASH_FATAL_CRASH_EVENTS_OBSERVER_REPORTED_LOCAL_ID_MANAGER_H_