pw_kvs: Count erases + HeavyMaintenance

- Add counting of sectors erased. This count is only since instance
construction.
- Add HeavyMaintenance to be heavy and aggressive about garbage
collecting sectors with valid data.
- Don't erase sectors when doing garbage collection of sectors that are
already erased.

Change-Id: I4fb1647c1920cdc64a7a1029078575068a9156ce
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/20960
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: David Rogers <davidrogers@google.com>
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 71da378..752590d 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -53,7 +53,7 @@
       options_(options),
       initialized_(InitializationState::kNotInitialized),
       error_detected_(false),
-      error_stats_({}),
+      internal_stats_({}),
       last_transaction_id_(0) {}
 
 Status KeyValueStore::Init() {
@@ -97,12 +97,12 @@
 
     if (options_.recovery != ErrorRecovery::kManual) {
       size_t pre_fix_redundancy_errors =
-          error_stats_.missing_redundant_entries_recovered;
+          internal_stats_.missing_redundant_entries_recovered;
       Status recovery_status = FixErrors();
 
       if (recovery_status.ok()) {
         if (metadata_result == Status::OutOfRange()) {
-          error_stats_.missing_redundant_entries_recovered =
+          internal_stats_.missing_redundant_entries_recovered =
               pre_fix_redundancy_errors;
           INF("KVS init: Redundancy level successfully updated");
         } else {
@@ -301,9 +301,10 @@
   StorageStats stats{};
   const size_t sector_size = partition_.sector_size_bytes();
   bool found_empty_sector = false;
-  stats.corrupt_sectors_recovered = error_stats_.corrupt_sectors_recovered;
+  stats.sector_erase_count = internal_stats_.sector_erase_count;
+  stats.corrupt_sectors_recovered = internal_stats_.corrupt_sectors_recovered;
   stats.missing_redundant_entries_recovered =
-      error_stats_.missing_redundant_entries_recovered;
+      internal_stats_.missing_redundant_entries_recovered;
 
   for (const SectorDescriptor& sector : sectors_) {
     stats.in_use_bytes += sector.valid_bytes();
@@ -867,7 +868,7 @@
   return Status::Ok();
 }
 
-Status KeyValueStore::FullMaintenance() {
+Status KeyValueStore::FullMaintenanceHelper(MaintenanceType maintenance_type) {
   if (initialized_ == InitializationState::kNotInitialized) {
     return Status::FailedPrecondition();
   }
@@ -897,7 +898,8 @@
   // Is bytes in use over the threshold.
   StorageStats stats = GetStorageStats();
   bool over_usage_threshold = stats.in_use_bytes > threshold_bytes;
-  bool force_gc = over_usage_threshold || (update_status.size() > 0);
+  bool heavy = (maintenance_type == MaintenanceType::kHeavy);
+  bool force_gc = heavy || over_usage_threshold || (update_status.size() > 0);
 
   // TODO: look in to making an iterator method for cycling through sectors
   // starting from last_new_sector_.
@@ -982,6 +984,7 @@
     SectorDescriptor& sector_to_gc,
     std::span<const Address> reserved_addresses) {
   DBG("  Garbage Collect sector %u", sectors_.Index(sector_to_gc));
+
   // Step 1: Move any valid entries in the GC sector to other sectors
   if (sector_to_gc.valid_bytes() != 0) {
     for (EntryMetadata& metadata : entry_cache_) {
@@ -998,9 +1001,12 @@
   }
 
   // Step 2: Reinitialize the sector
-  sector_to_gc.mark_corrupt();
-  PW_TRY(partition_.Erase(sectors_.BaseAddress(sector_to_gc), 1));
-  sector_to_gc.set_writable_bytes(partition_.sector_size_bytes());
+  if (!sector_to_gc.Empty(partition_.sector_size_bytes())) {
+    sector_to_gc.mark_corrupt();
+    internal_stats_.sector_erase_count++;
+    PW_TRY(partition_.Erase(sectors_.BaseAddress(sector_to_gc), 1));
+    sector_to_gc.set_writable_bytes(partition_.sector_size_bytes());
+  }
 
   DBG("  Garbage Collect sector %u complete", sectors_.Index(sector_to_gc));
   return Status::Ok();
@@ -1098,7 +1104,7 @@
         DBG("   Found sector %u with corruption", sectors_.Index(sector));
         Status sector_status = GarbageCollectSector(sector, {});
         if (sector_status.ok()) {
-          error_stats_.corrupt_sectors_recovered += 1;
+          internal_stats_.corrupt_sectors_recovered += 1;
         } else if (repair_status.ok() ||
                    repair_status == Status::ResourceExhausted()) {
           repair_status = sector_status;
@@ -1156,7 +1162,7 @@
         unsigned(redundancy()));
     Status fill_status = AddRedundantEntries(metadata);
     if (fill_status.ok()) {
-      error_stats_.missing_redundant_entries_recovered += 1;
+      internal_stats_.missing_redundant_entries_recovered += 1;
       DBG("   Key missing copies added");
     } else {
       DBG("   Failed to add key missing copies");