mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
docs(nvs_flash): improved description of NVS space consumption
- Improved description of space required to store data types into NVS partition - Example showing nvs statistics extended with a code demonstrating the fragmentation effect
This commit is contained in:
committed by
Zhang Shuxian
parent
5371ee669b
commit
56a01ca822
@@ -18,6 +18,42 @@ Statistics obtained via [nvs_get_stats()](https://docs.espressif.com/projects/es
|
||||
|
||||
Detailed functional description of NVS and API is provided in [documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_flash.html).
|
||||
|
||||
## Blob Storage-Overhead Measurement
|
||||
|
||||
In addition to the basic statistics demonstration, the example can measure how much usable storage a blob actually consumes, taking the NVS metadata and free-space fragmentation into account. This part is enabled by default and can be turned off via `idf.py menuconfig` → *Example Configuration* → *Run NVS blob storage-overhead measurement*.
|
||||
|
||||
The measurement sweeps a matrix of **partition sizes** × **blob sizes** and, for each cell, fills the partition to capacity and reports the heap demand, NVS entry usage and the resulting storage overhead.
|
||||
|
||||
### Variable partition size
|
||||
|
||||
The example uses a custom partition table (`partitions.csv`) that defines several NVS partitions of different sizes (`nvs_16k`, `nvs_32k`, `nvs_64k`). The measurement iterates over them using `nvs_flash_init_partition()` / `nvs_get_stats()`, so the influence of the partition size on the relative overhead becomes directly visible.
|
||||
|
||||
### Variable blob size
|
||||
|
||||
For each partition, blobs of increasing size (128, 256, 512, 1024, 2048 and 4096 bytes) are stored with unique keys until `nvs_set_blob()` returns `ESP_ERR_NVS_NOT_ENOUGH_SPACE`. The number of stored blobs is then compared against the theoretical (ideal) count derived from the documented per-blob entry cost:
|
||||
|
||||
```
|
||||
entries_per_blob = 1 (BLOB_INDEX) + k (per-page BLOB_DATA chunk headers) + ceil(blob_size / 32)
|
||||
```
|
||||
|
||||
where `k` is the number of pages the blob data is split across.
|
||||
|
||||
### Worst-case fragmentation
|
||||
|
||||
By design, each NVS page is a 4096-byte flash sector holding 126 usable 32-byte entries. A string occupies `1 + ceil((len + 1) / 32)` entries and must fit contiguously within a single page, while a blob may split its data into per-page chunks.
|
||||
|
||||
To demonstrate the worst case, the example optionally pre-populates a partition so that **every page is filled up to its last 2 entries** (enabled via *Add a worst-case (pre-fragmented) measurement pass*). This is achieved by writing large strings:
|
||||
|
||||
* the first string is sized to leave 2 free entries on page 0 while accounting for the namespace entry,
|
||||
* every following string fills a fresh page to 124 entries, leaving exactly 2 free.
|
||||
|
||||
After this step the partition reports a large amount of `free_entries`, but the largest contiguous run of free entries on any page is only 2. The consequences are then measured by filling the partition with blobs:
|
||||
|
||||
* each blob chunk can only use 1 chunk-header + 1 data entry per page, so roughly half of the consumed space becomes metadata overhead,
|
||||
* because the number of chunks per blob is bounded, large blobs may become unstoreable even though many `free_entries` remain.
|
||||
|
||||
This makes the relationship between fragmentation, remaining free space and resulting overhead measurable, instead of presenting a single (best-case) number that could create false expectations.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware required
|
||||
@@ -38,6 +74,8 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
|
||||
|
||||
## Example Output
|
||||
|
||||
The first part of the output shows the basic statistics demonstration:
|
||||
|
||||
```
|
||||
...
|
||||
I (265) nvs_statistics_example: Erasing the contents of the default NVS partition...
|
||||
@@ -49,71 +87,49 @@ I (485) nvs_statistics_example: Free NVS entries: 755
|
||||
I (495) nvs_statistics_example: Available NVS entries: 629
|
||||
I (495) nvs_statistics_example: Total NVS entries: 756
|
||||
I (505) nvs_statistics_example: Namespace count: 1
|
||||
I (505) nvs_statistics_example: Writing mock data key-value pairs to NVS namespace '_mock_data'...
|
||||
I (525) nvs_statistics_example: Committing data in NVS namespace '_mock_data'...
|
||||
I (525) nvs_statistics_example: Getting post-commit NVS statistics...
|
||||
I (525) nvs_statistics_example: NVS statistics:
|
||||
I (535) nvs_statistics_example: Used NVS entries: 30
|
||||
I (535) nvs_statistics_example: Free NVS entries: 726
|
||||
I (545) nvs_statistics_example: Available NVS entries: 600
|
||||
I (545) nvs_statistics_example: Total NVS entries: 756
|
||||
I (555) nvs_statistics_example: Namespace count: 1
|
||||
I (555) nvs_statistics_example: Newly used entries match expectation.
|
||||
I (565) nvs_statistics_example: Newly used entries: 29, expected: 29.
|
||||
I (575) nvs_statistics_example: NVS handle for namespace '_mock_data' closed.
|
||||
I (575) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_data'...
|
||||
I (585) nvs_statistics_example: Reading stored data from NVS namespace '_mock_data'...
|
||||
I (595) nvs_statistics_example: Read key-value pair from NVS: 'wifi_ssid':'HomeNetwork'
|
||||
I (605) nvs_statistics_example: Read key-value pair from NVS: 'wifi_pass':'MySecretPass'
|
||||
I (605) nvs_statistics_example: Read key-value pair from NVS: 'dev_name':'LivingRoomThermostat'
|
||||
I (615) nvs_statistics_example: Read key-value pair from NVS: 'temp_unit':'Celsius'
|
||||
I (625) nvs_statistics_example: Read key-value pair from NVS: 'target_temp':'22'
|
||||
I (635) nvs_statistics_example: Read key-value pair from NVS: 'eco_mode':'false'
|
||||
I (635) nvs_statistics_example: Read key-value pair from NVS: 'fw_version':'1.2.3'
|
||||
I (645) nvs_statistics_example: Read key-value pair from NVS: 'led_bright':'80'
|
||||
I (655) nvs_statistics_example: Read key-value pair from NVS: 'auto_update':'true'
|
||||
I (665) nvs_statistics_example: Read key-value pair from NVS: 'last_sync':'2025-01-01T08:00:00Z'
|
||||
I (665) nvs_statistics_example: Read key-value pair from NVS: 'user_lang':'en'
|
||||
I (675) nvs_statistics_example: Read key-value pair from NVS: 'long_token':'2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b'
|
||||
I (685) nvs_statistics_example: Read key-value pair from NVS: 'very_long_token':'7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b'
|
||||
I (705) nvs_statistics_example: NVS handle for namespace '_mock_data' closed.
|
||||
I (705) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_backup'...
|
||||
I (715) nvs_statistics_example: Getting NVS statistics...
|
||||
I (725) nvs_statistics_example: NVS statistics:
|
||||
I (725) nvs_statistics_example: Used NVS entries: 31
|
||||
I (735) nvs_statistics_example: Free NVS entries: 725
|
||||
I (735) nvs_statistics_example: Available NVS entries: 599
|
||||
I (745) nvs_statistics_example: Total NVS entries: 756
|
||||
I (745) nvs_statistics_example: Namespace count: 2
|
||||
I (755) nvs_statistics_example: Writing mock data key-value pairs to NVS namespace '_mock_backup'...
|
||||
I (765) nvs_statistics_example: Committing data in NVS namespace '_mock_backup'...
|
||||
I (765) nvs_statistics_example: Getting post-commit NVS statistics...
|
||||
I (775) nvs_statistics_example: NVS statistics:
|
||||
I (775) nvs_statistics_example: Used NVS entries: 60
|
||||
I (785) nvs_statistics_example: Free NVS entries: 696
|
||||
I (785) nvs_statistics_example: Available NVS entries: 570
|
||||
I (795) nvs_statistics_example: Total NVS entries: 756
|
||||
I (795) nvs_statistics_example: Namespace count: 2
|
||||
I (805) nvs_statistics_example: Newly used entries match expectation.
|
||||
I (805) nvs_statistics_example: Newly used entries: 29, expected: 29.
|
||||
I (815) nvs_statistics_example: NVS handle for namespace '_mock_backup' closed.
|
||||
I (825) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_backup'...
|
||||
I (835) nvs_statistics_example: Reading stored data from NVS namespace '_mock_backup'...
|
||||
I (835) nvs_statistics_example: Read key-value pair from NVS: 'wifi_ssid':'HomeNetwork'
|
||||
I (845) nvs_statistics_example: Read key-value pair from NVS: 'wifi_pass':'MySecretPass'
|
||||
I (855) nvs_statistics_example: Read key-value pair from NVS: 'dev_name':'LivingRoomThermostat'
|
||||
I (865) nvs_statistics_example: Read key-value pair from NVS: 'temp_unit':'Celsius'
|
||||
I (865) nvs_statistics_example: Read key-value pair from NVS: 'target_temp':'22'
|
||||
I (875) nvs_statistics_example: Read key-value pair from NVS: 'eco_mode':'false'
|
||||
I (885) nvs_statistics_example: Read key-value pair from NVS: 'fw_version':'1.2.3'
|
||||
I (895) nvs_statistics_example: Read key-value pair from NVS: 'led_bright':'80'
|
||||
I (895) nvs_statistics_example: Read key-value pair from NVS: 'auto_update':'true'
|
||||
I (905) nvs_statistics_example: Read key-value pair from NVS: 'last_sync':'2025-01-01T08:00:00Z'
|
||||
I (915) nvs_statistics_example: Read key-value pair from NVS: 'user_lang':'en'
|
||||
I (925) nvs_statistics_example: Read key-value pair from NVS: 'long_token':'2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b'
|
||||
I (935) nvs_statistics_example: Read key-value pair from NVS: 'very_long_token':'7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b'
|
||||
I (945) nvs_statistics_example: NVS handle for namespace '_mock_backup' closed.
|
||||
I (955) nvs_statistics_example: Returning from app_main().
|
||||
I (955) main_task: Returned from app_main()
|
||||
...
|
||||
```
|
||||
I (565) nvs_statistics_example: Newly used entries match expectation.
|
||||
...
|
||||
```
|
||||
|
||||
The second part reports the blob storage overhead for each partition / blob size, in both a pristine and a pre-fragmented partition:
|
||||
|
||||
```
|
||||
...
|
||||
I (1265) nvs_statistics_example: Starting NVS blob storage-overhead measurement...
|
||||
|
||||
NVS BLOB TEST - 128 B (partition 'nvs_16k', pristine):
|
||||
======================
|
||||
heap before NVS init: 299220 B
|
||||
heap after NVS init: 255472 B (diff 43748 B)
|
||||
|
||||
available heap after fill: 254100 B (diff 1372 B)
|
||||
expected blobs count: 21
|
||||
stored blobs count: 20
|
||||
used_entries: 120 (3840 B)
|
||||
free_entries: 6 (192 B)
|
||||
total_entries: 126 (4032 B)
|
||||
STORAGE OVERHEAD: 36%
|
||||
|
||||
NVS BLOB TEST - 128 B (partition 'nvs_16k', fragmented):
|
||||
======================
|
||||
heap before NVS init: 299220 B
|
||||
heap after NVS init: 255472 B (diff 43748 B)
|
||||
fragmentation strings written: 3
|
||||
|
||||
available heap after fill: 254100 B (diff 1372 B)
|
||||
expected blobs count: 21
|
||||
stored blobs count: 2
|
||||
used_entries: 124 (3968 B)
|
||||
free_entries: 2 (64 B)
|
||||
total_entries: 126 (4032 B)
|
||||
STORAGE OVERHEAD: 98%
|
||||
...
|
||||
I (9000) nvs_statistics_example: NVS blob storage-overhead measurement done.
|
||||
I (9010) nvs_statistics_example: Returning from app_main().
|
||||
...
|
||||
```
|
||||
|
||||
> The exact numbers depend on the target, partition size and blob size; the values above are illustrative. The key takeaway is the difference in `STORAGE OVERHEAD` and `stored blobs count` between the pristine and fragmented passes.
|
||||
|
||||
To reset NVS data, erase the contents of flash memory using `idf.py erase-flash`, then upload the program again as described above.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_RUN_OVERHEAD_MEASUREMENT
|
||||
bool "Run NVS blob storage-overhead measurement"
|
||||
default y
|
||||
help
|
||||
When enabled, the example sweeps several NVS partition sizes and blob
|
||||
sizes, fills each partition to capacity with blobs and reports the
|
||||
resulting heap demand, entry usage and storage overhead.
|
||||
|
||||
config EXAMPLE_INDUCE_FRAGMENTATION
|
||||
bool "Add a worst-case (pre-fragmented) measurement pass"
|
||||
depends on EXAMPLE_RUN_OVERHEAD_MEASUREMENT
|
||||
default y
|
||||
help
|
||||
When enabled, every measured partition is first pre-populated with
|
||||
large strings that leave only two free entries per NVS page. This
|
||||
scatters the free space into non-coalescable gaps and demonstrates
|
||||
the worst-case blob storage overhead caused by fragmentation.
|
||||
|
||||
endmenu
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -15,7 +15,10 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_check.h"
|
||||
@@ -27,6 +30,26 @@
|
||||
#define MOCK_DATA_NAMESPACE "_mock_data"
|
||||
#define MOCK_DATA_BACKUP_NAMESPACE "_mock_backup"
|
||||
|
||||
/* NVS on-flash geometry. These are by-design constants of the NVS format (one
|
||||
* 4096-byte flash sector per page, 32-byte entries, 126 usable entries per
|
||||
* page). They are not exposed through the public API, so they are mirrored here
|
||||
* to allow precise control over page-level fragmentation below. */
|
||||
#define NVS_ENTRY_SIZE 32
|
||||
#define NVS_ENTRIES_PER_PAGE 126
|
||||
#define NVS_PAGE_CHUNK_MAX_SIZE (NVS_ENTRY_SIZE * (NVS_ENTRIES_PER_PAGE - 1)) // 4000 B
|
||||
|
||||
/* String lengths (strlen, excluding the NUL terminator) used to fragment a
|
||||
* partition. A string of length L occupies 1 header entry + ceil((L+1)/32) data
|
||||
* entries and must fit contiguously within a single page.
|
||||
* - FRAG_STR_LEN fills a fresh page to 124 entries, leaving exactly 2 free.
|
||||
* - FRAG_STR_FIRST_LEN is one entry shorter to account for the namespace entry
|
||||
* that is written on the first page, so that page also keeps 2 free entries. */
|
||||
#define FRAG_STR_LEN (123 * NVS_ENTRY_SIZE - 1) // 3935 -> 124 entries
|
||||
#define FRAG_STR_FIRST_LEN (122 * NVS_ENTRY_SIZE - 1) // 3903 -> 123 entries
|
||||
|
||||
#define FRAG_NAMESPACE "_frag"
|
||||
#define BLOB_NAMESPACE "_blobs"
|
||||
|
||||
static const char *TAG = "nvs_statistics_example";
|
||||
|
||||
// Maximum key character length is 15 (NVS_KEY_NAME_MAX_SIZE-1)
|
||||
@@ -195,6 +218,200 @@ static esp_err_t read_mock_data_from_namespace(const char* namespace_name)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_RUN_OVERHEAD_MEASUREMENT
|
||||
|
||||
// Partitions of various sizes (declared in partitions.csv) swept by the measurement.
|
||||
static const char* measured_partitions[] = {
|
||||
"nvs_16k",
|
||||
"nvs_32k",
|
||||
"nvs_64k",
|
||||
};
|
||||
|
||||
// Blob sizes (in bytes) measured for each partition.
|
||||
static const size_t blob_sizes[] = {128, 256, 512, 1024, 2048, 4096};
|
||||
|
||||
// Theoretical entries consumed by a single blob in a non-fragmented partition:
|
||||
// 1 BLOB_INDEX entry + 'chunks' chunk-header entries + ceil(size/32) data entries.
|
||||
static size_t entries_per_blob_ideal(size_t blob_size)
|
||||
{
|
||||
size_t chunks = (blob_size + NVS_PAGE_CHUNK_MAX_SIZE - 1) / NVS_PAGE_CHUNK_MAX_SIZE;
|
||||
if (chunks == 0) {
|
||||
chunks = 1;
|
||||
}
|
||||
size_t data_entries = (blob_size + NVS_ENTRY_SIZE - 1) / NVS_ENTRY_SIZE;
|
||||
return 1 + chunks + data_entries;
|
||||
}
|
||||
|
||||
// Pre-populate a partition with large strings so that every page is filled up to
|
||||
// its last 2 entries. Returns the number of strings written.
|
||||
static size_t fragment_partition(const char* partition_name)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open_from_partition(partition_name, FRAG_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) opening fragmentation handle on '%s'!", esp_err_to_name(err), partition_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* buffer = malloc(FRAG_STR_LEN + 1);
|
||||
if (buffer == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate fragmentation buffer!");
|
||||
nvs_close(handle);
|
||||
return 0;
|
||||
}
|
||||
memset(buffer, 'A', FRAG_STR_LEN);
|
||||
buffer[FRAG_STR_LEN] = '\0';
|
||||
|
||||
size_t count = 0;
|
||||
char key[16];
|
||||
|
||||
// First string is one entry shorter to compensate for the namespace entry
|
||||
// written on page 0, so that page also retains exactly 2 free entries.
|
||||
buffer[FRAG_STR_FIRST_LEN] = '\0';
|
||||
snprintf(key, sizeof(key), "f%05u", (unsigned)count);
|
||||
err = nvs_set_str(handle, key, buffer);
|
||||
buffer[FRAG_STR_FIRST_LEN] = 'A';
|
||||
if (err == ESP_OK && nvs_commit(handle) == ESP_OK) {
|
||||
count++;
|
||||
}
|
||||
|
||||
// Remaining full-page strings until the partition cannot hold another one.
|
||||
while (true) {
|
||||
snprintf(key, sizeof(key), "f%05u", (unsigned)count);
|
||||
err = nvs_set_str(handle, key, buffer);
|
||||
if (err == ESP_ERR_NVS_NOT_ENOUGH_SPACE) {
|
||||
break;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) writing fragmentation string!", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
if (nvs_commit(handle) != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
nvs_close(handle);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Fill a partition with same-sized blobs until it runs out of space.
|
||||
// Returns the number of blobs successfully stored.
|
||||
static size_t fill_with_blobs(const char* partition_name, size_t blob_size)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open_from_partition(partition_name, BLOB_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) opening blob handle on '%s'!", esp_err_to_name(err), partition_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* blob = malloc(blob_size);
|
||||
if (blob == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate %u B blob buffer!", (unsigned)blob_size);
|
||||
nvs_close(handle);
|
||||
return 0;
|
||||
}
|
||||
memset(blob, 0x5A, blob_size);
|
||||
|
||||
size_t count = 0;
|
||||
char key[16];
|
||||
while (true) {
|
||||
snprintf(key, sizeof(key), "b%05u", (unsigned)count);
|
||||
err = nvs_set_blob(handle, key, blob, blob_size);
|
||||
if (err == ESP_ERR_NVS_NOT_ENOUGH_SPACE) {
|
||||
break;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) writing blob!", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
err = nvs_commit(handle);
|
||||
if (err == ESP_ERR_NVS_NOT_ENOUGH_SPACE) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
free(blob);
|
||||
nvs_close(handle);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Run one measurement cell: erase + init a partition, optionally fragment it,
|
||||
// fill it with blobs of the given size and report heap/entry/overhead statistics.
|
||||
static void measure_blob_overhead(const char* partition_name, size_t blob_size, bool fragment)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_erase_partition(partition_name));
|
||||
|
||||
uint32_t heap_before_init = esp_get_free_heap_size();
|
||||
ESP_ERROR_CHECK(nvs_flash_init_partition(partition_name));
|
||||
uint32_t heap_after_init = esp_get_free_heap_size();
|
||||
|
||||
nvs_stats_t stats;
|
||||
ESP_ERROR_CHECK(nvs_get_stats(partition_name, &stats));
|
||||
size_t total_entries = stats.total_entries;
|
||||
|
||||
size_t frag_strings = 0;
|
||||
if (fragment) {
|
||||
frag_strings = fragment_partition(partition_name);
|
||||
}
|
||||
|
||||
size_t stored = fill_with_blobs(partition_name, blob_size);
|
||||
uint32_t heap_after_fill = esp_get_free_heap_size();
|
||||
|
||||
ESP_ERROR_CHECK(nvs_get_stats(partition_name, &stats));
|
||||
|
||||
size_t per_blob = entries_per_blob_ideal(blob_size);
|
||||
size_t expected = (per_blob != 0) ? (total_entries / per_blob) : 0;
|
||||
size_t payload = stored * blob_size;
|
||||
size_t capacity = total_entries * NVS_ENTRY_SIZE;
|
||||
int overhead_pct = (capacity != 0) ? (int)(100 - (100ULL * payload) / capacity) : 0;
|
||||
|
||||
printf("\n");
|
||||
printf("NVS BLOB TEST - %u B (partition '%s', %s):\n",
|
||||
(unsigned)blob_size, partition_name, fragment ? "fragmented" : "pristine");
|
||||
printf("======================\n");
|
||||
printf("heap before NVS init: %" PRIu32 " B\n", heap_before_init);
|
||||
printf("heap after NVS init: %" PRIu32 " B (diff %" PRId32 " B)\n",
|
||||
heap_after_init, (int32_t)(heap_before_init - heap_after_init));
|
||||
if (fragment) {
|
||||
printf("fragmentation strings written: %u\n", (unsigned)frag_strings);
|
||||
}
|
||||
printf("\n");
|
||||
printf("available heap after fill: %" PRIu32 " B (diff %" PRId32 " B)\n",
|
||||
heap_after_fill, (int32_t)(heap_after_init - heap_after_fill));
|
||||
printf("expected blobs count: %u\n", (unsigned)expected);
|
||||
printf("stored blobs count: %u\n", (unsigned)stored);
|
||||
printf("used_entries: %u (%u B)\n", stats.used_entries, (unsigned)(stats.used_entries * NVS_ENTRY_SIZE));
|
||||
printf("free_entries: %u (%u B)\n", stats.free_entries, (unsigned)(stats.free_entries * NVS_ENTRY_SIZE));
|
||||
printf("total_entries: %u (%u B)\n", stats.total_entries, (unsigned)(stats.total_entries * NVS_ENTRY_SIZE));
|
||||
printf("STORAGE OVERHEAD: %d%%\n", overhead_pct);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_deinit_partition(partition_name));
|
||||
}
|
||||
|
||||
static void run_overhead_measurement(void)
|
||||
{
|
||||
const size_t partition_count = sizeof(measured_partitions) / sizeof(measured_partitions[0]);
|
||||
const size_t blob_size_count = sizeof(blob_sizes) / sizeof(blob_sizes[0]);
|
||||
|
||||
ESP_LOGI(TAG, "Starting NVS blob storage-overhead measurement...");
|
||||
for (size_t p = 0; p < partition_count; p++) {
|
||||
for (size_t b = 0; b < blob_size_count; b++) {
|
||||
measure_blob_overhead(measured_partitions[p], blob_sizes[b], false);
|
||||
#if CONFIG_EXAMPLE_INDUCE_FRAGMENTATION
|
||||
measure_blob_overhead(measured_partitions[p], blob_sizes[b], true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "NVS blob storage-overhead measurement done.");
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_RUN_OVERHEAD_MEASUREMENT
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Erase the contents of the default NVS partition for clean run of this example
|
||||
@@ -230,5 +447,9 @@ void app_main(void)
|
||||
ESP_LOGE(TAG, "Error (%s) reading back stored data from namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_BACKUP_NAMESPACE);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_RUN_OVERHEAD_MEASUREMENT
|
||||
run_overhead_measurement();
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Returning from app_main().");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Name, Type, SubType, Offset, Size
|
||||
factory, app, factory, 0x10000, 1M
|
||||
nvs, data, nvs, , 0x6000
|
||||
phy_init, data, phy, , 0x1000
|
||||
# Partitions of different sizes used by the storage-overhead measurement.
|
||||
nvs_16k, data, nvs, , 0x4000
|
||||
nvs_32k, data, nvs, , 0x8000
|
||||
nvs_64k, data, nvs, , 0x10000
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
@@ -14,4 +14,9 @@ def test_examples_nvs_statistics(dut: Dut) -> None:
|
||||
dut.expect('Getting post-commit NVS statistics...', timeout=5)
|
||||
dut.expect('Newly used entries match expectation.', timeout=5)
|
||||
|
||||
# Blob storage-overhead measurement (sweeps partition and blob sizes).
|
||||
dut.expect('Starting NVS blob storage-overhead measurement...', timeout=10)
|
||||
dut.expect('STORAGE OVERHEAD:', timeout=60)
|
||||
dut.expect('NVS blob storage-overhead measurement done.', timeout=120)
|
||||
|
||||
dut.expect('Returning from app_main().', timeout=5)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
Reference in New Issue
Block a user