aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Rogers <davidrogers@google.com>2020-03-13 19:19:03 -0700
committerDavid Rogers <davidrogers@google.com>2020-03-24 22:16:39 -0700
commit5cc5ce87618e52ec8a617df064a86cc29caf1b37 (patch)
tree168fcb1d41f929fa4fe1e7f8da3ca854d6fdc6b3
parentc84393fccaa30c654eb5ab09dafd91acf146eba4 (diff)
downloadpigweed-5cc5ce87618e52ec8a617df064a86cc29caf1b37.tar.gz
pw_kvs: Add test flash partition that keeps stats
Add test flash partition for keeping track of sector erase counts. Add helper methods for saving the combined KVS and flash partition erase stats to a csv file. Add support for the new test partition to kvs fuzz test and map test. Example output: Test Name,Total Erases,Utilization Percentage,Transaction Count,Entry Count, Sector 0,Sector 1,Sector 2,Sector 3,Sector 4,Sector 5 fuzz Put_VaryingKeysAndValues,232,30,8064,63,46,34,33,45,33,41 Change-Id: I5f714d2be8ca754aca1303eaf6516f10b561fcaa
-rw-r--r--pw_kvs/BUILD19
-rw-r--r--pw_kvs/BUILD.gn14
-rw-r--r--pw_kvs/flash_partition_with_stats.cc81
-rw-r--r--pw_kvs/key_value_store_fuzz_test.cc16
-rw-r--r--pw_kvs/key_value_store_map_test.cc21
-rw-r--r--pw_kvs/public/pw_kvs/flash_partition_with_stats.h100
6 files changed, 248 insertions, 3 deletions
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index 73a1b19e5..e91500fef 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -88,6 +88,23 @@ pw_cc_library(
],
)
+pw_cc_library(
+ name = "test_partition",
+ srcs = [
+ "flash_partition_with_stats.cc",
+ ],
+ hdrs = [
+ "public/pw_kvs/flash_partition_with_stats.h",
+ ],
+ includes = ["public"],
+ visibility = ["//visibility:private"],
+ deps = [
+ "//pw_kvs",
+ "//pw_log",
+ "//pw_status",
+ ],
+)
+
pw_cc_test(
name = "alignment_test",
srcs = [
@@ -159,6 +176,7 @@ pw_cc_test(
deps = [
":crc16",
":pw_kvs",
+ ":test_partition",
":test_utils",
"//pw_checksum",
],
@@ -170,6 +188,7 @@ pw_cc_test(
deps = [
":crc16",
":pw_kvs",
+ ":test_partition",
":test_utils",
"//pw_checksum",
],
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index 4f4a9af4a..6e9cf44e9 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -89,6 +89,18 @@ source_set("test_utils") {
]
}
+source_set("test_partition") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_kvs/flash_partition_with_stats.h" ]
+ sources = [ "flash_partition_with_stats.cc" ] + public
+ visibility = [ ":*" ]
+ public_deps = [
+ dir_pw_kvs,
+ dir_pw_log,
+ dir_pw_status,
+ ]
+}
+
executable("debug_cli") {
sources = [ "debug_cli.cc" ]
deps = [
@@ -168,6 +180,7 @@ pw_test("key_value_store_fuzz_test") {
deps = [
":crc16",
":pw_kvs",
+ ":test_partition",
":test_utils",
]
sources = [ "key_value_store_fuzz_test.cc" ]
@@ -177,6 +190,7 @@ pw_test("key_value_store_map_test") {
deps = [
":crc16",
":pw_kvs",
+ ":test_partition",
":test_utils",
dir_pw_checksum,
]
diff --git a/pw_kvs/flash_partition_with_stats.cc b/pw_kvs/flash_partition_with_stats.cc
new file mode 100644
index 000000000..f966756d3
--- /dev/null
+++ b/pw_kvs/flash_partition_with_stats.cc
@@ -0,0 +1,81 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_kvs/flash_partition_with_stats.h"
+
+#include <cstdio>
+
+#include "pw_kvs/flash_memory.h"
+#include "pw_log/log.h"
+
+namespace pw::kvs {
+
+Status FlashPartitionWithStats::SaveStorageStats(const KeyValueStore& kvs,
+ const char* label) {
+ // If size is zero saving stats is disabled so do not save any stats.
+ if (sector_counters_.size() == 0) {
+ return Status::OK;
+ }
+
+ KeyValueStore::StorageStats stats = kvs.GetStorageStats();
+ size_t utilization_percentage = (stats.in_use_bytes * 100) / size_bytes();
+
+ const char* file_name = "flash_stats.csv";
+ std::FILE* out_file = std::fopen(file_name, "a+");
+ if (out_file == nullptr) {
+ PW_LOG_ERROR("Failed to dump to %s", file_name);
+ return Status::NOT_FOUND;
+ }
+
+ // If file is empty add the header row.
+ std::fseek(out_file, 0, SEEK_END);
+ if (std::ftell(out_file) == 0) {
+ std::fprintf(out_file,
+ "Test Name,Total Erases,Utilization Percentage,Transaction "
+ "Count,Entry Count");
+ for (size_t i = 0; i < sector_counters_.size(); i++) {
+ std::fprintf(out_file, ",Sector %zu", i);
+ }
+ std::fprintf(out_file, "\n");
+ }
+
+ std::fprintf(out_file, "\"%s\",%zu", label, total_erase_count());
+ std::fprintf(out_file,
+ ",%zu,%u,%zu",
+ utilization_percentage,
+ kvs.transaction_count(),
+ kvs.size());
+
+ for (size_t counter : sector_erase_counters()) {
+ std::fprintf(out_file, ",%zu", counter);
+ }
+
+ std::fprintf(out_file, "\n");
+ std::fclose(out_file);
+ return Status::OK;
+}
+
+Status FlashPartitionWithStats::Erase(Address address, size_t num_sectors) {
+ size_t base_index = address / FlashPartition::sector_size_bytes();
+ if (base_index < sector_counters_.size()) {
+ num_sectors = std::min(num_sectors, (sector_counters_.size() - base_index));
+ for (size_t i = 0; i < num_sectors; i++) {
+ sector_counters_[base_index + i]++;
+ }
+ }
+
+ return FlashPartition::Erase(address, num_sectors);
+}
+
+} // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_fuzz_test.cc b/pw_kvs/key_value_store_fuzz_test.cc
index 630dbca6b..f5bf28997 100644
--- a/pw_kvs/key_value_store_fuzz_test.cc
+++ b/pw_kvs/key_value_store_fuzz_test.cc
@@ -14,6 +14,7 @@
#include "gtest/gtest.h"
#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/flash_partition_with_stats.h"
#include "pw_kvs/in_memory_fake_flash.h"
#include "pw_kvs/key_value_store.h"
@@ -22,12 +23,19 @@ namespace {
using std::byte;
+#ifndef PW_KVS_FUZZ_ITERATIONS
+#define PW_KVS_FUZZ_ITERATIONS 2
+#endif // PW_KVS_FUZZ_ITERATIONS
+constexpr int kFuzzIterations = PW_KVS_FUZZ_ITERATIONS;
+
constexpr size_t kMaxEntries = 256;
constexpr size_t kMaxUsableSectors = 256;
// 4 x 4k sectors, 16 byte alignment
FakeFlashBuffer<4 * 1024, 6> test_flash(16);
-FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
+
+FlashPartitionWithStatsBuffer<kMaxEntries> test_partition(
+ &test_flash, 0, test_flash.sector_count());
ChecksumCrc16 checksum;
@@ -48,7 +56,9 @@ TEST_F(EmptyInitializedKvs, Put_VaryingKeysAndValues) {
"34567890123"; // 64 (with final \0);
static_assert(sizeof(value) == 64);
- for (int i = 0; i < 2; ++i) {
+ test_partition.ResetCounters();
+
+ for (int i = 0; i < kFuzzIterations; ++i) {
for (unsigned key_size = 1; key_size < sizeof(value); ++key_size) {
for (unsigned value_size = 0; value_size < sizeof(value); ++value_size) {
ASSERT_EQ(Status::OK,
@@ -57,6 +67,8 @@ TEST_F(EmptyInitializedKvs, Put_VaryingKeysAndValues) {
}
}
}
+
+ test_partition.SaveStorageStats(kvs_, "fuzz Put_VaryingKeysAndValues");
}
} // namespace
diff --git a/pw_kvs/key_value_store_map_test.cc b/pw_kvs/key_value_store_map_test.cc
index a00ff423f..1f46d1488 100644
--- a/pw_kvs/key_value_store_map_test.cc
+++ b/pw_kvs/key_value_store_map_test.cc
@@ -28,6 +28,7 @@
#include "gtest/gtest.h"
#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/flash_partition_with_stats.h"
#include "pw_kvs/in_memory_fake_flash.h"
#include "pw_kvs/internal/entry.h"
#include "pw_kvs/key_value_store.h"
@@ -111,6 +112,8 @@ class KvsTester {
return value;
};
+ partition_.ResetCounters();
+
for (int i = 0; i < iterations; ++i) {
if (options != kNone && random_int() % 10 == 0) {
Init();
@@ -148,6 +151,21 @@ class KvsTester {
GCPartial();
}
}
+
+ // Only save for tests that have enough data to be interesting.
+ if (partition_.sector_count() > 2 && partition_.total_erase_count() > 20) {
+ pw::StringBuffer<64> label;
+ label << "Random";
+ label << partition_.sector_count();
+ label << "Sector";
+ label << iterations;
+ label << ((options != kNone) ? "Reinit" : "");
+ label << ((options == kReinitWithFullGC) ? "FullGC" : "");
+ label << ((options == kReinitWithPartialGC) ? "PartialGC" : "");
+ label << ((kvs_.redundancy() > 1) ? "Redundant" : "");
+
+ partition_.SaveStorageStats(kvs_, label.data());
+ }
}
void Test_Put() {
@@ -374,7 +392,8 @@ class KvsTester {
static FakeFlashBuffer<kParams.sector_size,
(kParams.sector_count * kParams.redundancy)>
flash_;
- FlashPartition partition_;
+
+ FlashPartitionWithStatsBuffer<kMaxEntries> partition_;
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, kParams.redundancy> kvs_;
std::unordered_map<std::string, std::string> map_;
diff --git a/pw_kvs/public/pw_kvs/flash_partition_with_stats.h b/pw_kvs/public/pw_kvs/flash_partition_with_stats.h
new file mode 100644
index 000000000..0c86d2e79
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/flash_partition_with_stats.h
@@ -0,0 +1,100 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_containers/vector.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/key_value_store.h"
+#include "pw_status/status.h"
+
+#ifndef PW_KVS_RECORD_PARTITION_STATS
+// PW_KVS_RECORD_PARTITION_STATS enables saving stats.
+#define PW_KVS_RECORD_PARTITION_STATS 0
+#endif // PW_KVS_RECORD_PARTITION_STATS
+
+namespace pw::kvs {
+
+class FlashPartitionWithStats : public FlashPartition {
+ public:
+ // Save flash partition and KVS storage stats. Does not save if
+ // sector_counters_ is zero.
+ Status SaveStorageStats(const KeyValueStore& kvs, const char* label);
+
+ using FlashPartition::Erase;
+
+ Status Erase(Address address, size_t num_sectors) override;
+
+ span<size_t> sector_erase_counters() {
+ return span(sector_counters_.data(), sector_counters_.size());
+ }
+
+ size_t total_erase_count() const {
+ size_t total_erases = 0;
+ for (const size_t& sector_count : sector_counters_) {
+ total_erases += sector_count;
+ }
+ return total_erases;
+ }
+
+ void ResetCounters() { sector_counters_.assign(sector_count(), 0); }
+
+ protected:
+ FlashPartitionWithStats(
+ Vector<size_t>& sector_counters,
+ FlashMemory* flash,
+ uint32_t start_sector_index,
+ uint32_t sector_count,
+ uint32_t alignment_bytes = 0, // Defaults to flash alignment
+ PartitionPermission permission = PartitionPermission::kReadAndWrite)
+ : FlashPartition(flash,
+ start_sector_index,
+ sector_count,
+ alignment_bytes,
+ permission),
+ sector_counters_(sector_counters) {
+ sector_counters_.assign(FlashPartition::sector_count(), 0);
+ }
+
+ private:
+ Vector<size_t>& sector_counters_;
+};
+
+template <size_t kMaxSectors>
+class FlashPartitionWithStatsBuffer : public FlashPartitionWithStats {
+ public:
+ FlashPartitionWithStatsBuffer(
+ FlashMemory* flash,
+ uint32_t start_sector_index,
+ uint32_t sector_count,
+ uint32_t alignment_bytes = 0, // Defaults to flash alignment
+ PartitionPermission permission = PartitionPermission::kReadAndWrite)
+ : FlashPartitionWithStats(sector_counters_,
+ flash,
+ start_sector_index,
+ sector_count,
+ alignment_bytes,
+ permission) {}
+
+ FlashPartitionWithStatsBuffer(FlashMemory* flash)
+ : FlashPartitionWithStatsBuffer(
+ flash, 0, flash->sector_count(), flash->alignment_bytes()) {}
+
+ private:
+ // If PW_KVS_RECORD_PARTITION_STATS is not set, use zero size vector which
+ // will not save any stats.
+ Vector<size_t, PW_KVS_RECORD_PARTITION_STATS ? kMaxSectors : 0>
+ sector_counters_;
+};
+
+} // namespace pw::kvs