feat(examples/storage): Add NVS statistics example

Add a new example showcasing how to obtain and interpret statistics
about the usage of an NVS partition.

Also demonstrates how stats change after writing some key-value pairs
in 2 separate namespaces.
This commit is contained in:
Martin Havlik
2025-10-06 15:19:06 +02:00
parent 70b28a8d8a
commit 7d756ebd0a
8 changed files with 390 additions and 0 deletions

View File

@@ -18,6 +18,8 @@ The examples are grouped into sub-directories by category. Each category directo
* `nvs_rw_value` example demonstrates how to read and write a single integer value using NVS.
* `nvs_rw_value_cxx` example demonstrates how to read and write a single integer value using NVS (it uses the C++ NVS handle API).
* `nvs_console` example demonstrates how to use NVS through an interactive console interface.
* `nvs_statistics` example demonstrates how to obtain and interpret stats about used/available NVS storage entries in given NVS partition.
* `nvs_iteration` TODO
* `partition_api` examples demonstrate how to use different partition APIs.
* `parttool` example demonstrates common operations the partitions tool allows the user to perform.
* `sd_card` examples demonstrate how to use an SD card with an ESP device.

View File

@@ -44,3 +44,10 @@ examples/storage/nvs/nvsgen:
disable_test:
- if: IDF_TARGET != "esp32"
reason: only one target needed
examples/storage/nvs/nvs_statistics:
depends_components:
- nvs_flash
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32c3"]
reason: only one target per arch needed

View File

@@ -0,0 +1,8 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(nvs_statistics)

View File

@@ -0,0 +1,119 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
# Non-Volatile Storage (NVS) Statistics Example
This example demonstrates the usage of obtaining and interpreting statistics about the a given NVS partition, namely free/used/available/total entries and namespace count.
The default "nvs" partition is first erased, then a mock string data configuration is written to 2 different namespaces, followed by checking the changed statistics and mainly the number of newly used NVS entries.
Statistics obtained via [nvs_get_stats()](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html#_CPPv413nvs_get_statsPKcP11nvs_stats_t) are the following:
| | |
| -------------- | ------------------- |
| `available entries` | Essential statistic, specifies free non-reserved entries available for data storage. |
| `used entries` | Includes 1 overhead entry per namespace + actual data entries used by data storage. |
| `free entries` | Free entries are both the free available to the user entries, and free but internally reserved (not available to the user). |
| `total entries` | Number of all entries (free + used). |
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).
## How to use example
### Hardware required
This example does not require any special hardware, and can be run on any common development board.
### Build and flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
...
I (265) nvs_statistics_example: Erasing the contents of the default NVS partition...
I (475) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_data'...
I (485) nvs_statistics_example: Getting NVS statistics...
I (485) nvs_statistics_example: NVS statistics:
I (485) nvs_statistics_example: Used NVS entries: 1
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()
...
```

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "nvs_statistics_example.c"
PRIV_REQUIRES nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,234 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Non-Volatile Storage (NVS) Statistics Example
For other examples please check:
https://github.com/espressif/esp-idf/tree/master/examples
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#define MOCK_DATA_NAMESPACE "_mock_data"
#define MOCK_DATA_BACKUP_NAMESPACE "_mock_backup"
static const char *TAG = "nvs_statistics_example";
// Maximum key character length is 15 (NVS_KEY_NAME_MAX_SIZE-1)
static const char* mock_data_keys[] = {
"wifi_ssid",
"wifi_pass",
"dev_name",
"temp_unit",
"target_temp",
"eco_mode",
"fw_version",
"led_bright",
"auto_update",
"last_sync",
"user_lang",
"long_token",
"very_long_token"
};
// String values are limited in length by NVS implementation to 4000 bytes (including null character)
// Each string value occupies 1 overhead NVS entry + 1 NVS entry per each 32 characters including null character
#define CHARS_PER_STRING_VALUE_ENTRY 32
static const char* mock_data_values[] = {
"HomeNetwork",
"MySecretPass",
"LivingRoomThermostat",
"Celsius",
"22",
"false",
"1.2.3",
"80",
"true",
"2025-01-01T08:00:00Z",
"en",
"2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b",
"7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b"
};
static const size_t mock_data_count = sizeof(mock_data_keys) / sizeof(mock_data_keys[0]);
static void print_nvs_stats(nvs_stats_t *nvs_stats)
{
ESP_LOGI(TAG, "NVS statistics:");
ESP_LOGI(TAG, "Used NVS entries: %u", nvs_stats->used_entries);
ESP_LOGI(TAG, "Free NVS entries: %u", nvs_stats->free_entries);
ESP_LOGI(TAG, "Available NVS entries: %u", nvs_stats->available_entries);
ESP_LOGI(TAG, "Total NVS entries: %u", nvs_stats->total_entries);
ESP_LOGI(TAG, "Namespace count: %u", nvs_stats->namespace_count);
}
static size_t calculate_expected_entries_for_string_array(const char** string_array, size_t array_size)
{
size_t expected_newly_used_entries = 0;
for (int i = 0; i < array_size; i++) {
// Each string occupies 1 overhead entry + 1 entry per each 32 characters including null character
size_t str_len = strlen(string_array[i]);
size_t span = 1 + ((str_len + CHARS_PER_STRING_VALUE_ENTRY - 1) / CHARS_PER_STRING_VALUE_ENTRY); // 1 overhead + integer division rounding up
expected_newly_used_entries += span;
}
return expected_newly_used_entries;
}
static esp_err_t save_mock_data_to_namespace(const char* namespace_name)
{
ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle for namespace '%s'...", namespace_name);
nvs_handle_t my_handle;
// Opening NVS storage handle uses up 1 entry for the namespace
esp_err_t ret = nvs_open(namespace_name, NVS_READWRITE, &my_handle);
ESP_RETURN_ON_ERROR(ret, TAG, "Error (%s) opening NVS handle for namespace '%s'!", esp_err_to_name(ret), namespace_name);
ESP_LOGI(TAG, "Getting NVS statistics...");
nvs_stats_t nvs_stats;
ret = nvs_get_stats(NVS_DEFAULT_PART_NAME, &nvs_stats);
ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) getting NVS statistics!", esp_err_to_name(ret));
// Print the pre-write NVS statistics
print_nvs_stats(&nvs_stats);
ESP_LOGI(TAG, "Writing mock data key-value pairs to NVS namespace '%s'...", namespace_name);
for (int i = 0; i < mock_data_count; i++) {
// Write string values
ret = nvs_set_str(my_handle, mock_data_keys[i], mock_data_values[i]);
// Don't break/abort on error, try to continue writing
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) writing '%s':'%s' pair!", esp_err_to_name(ret), mock_data_keys[i], mock_data_values[i]);
}
}
ESP_LOGI(TAG, "Committing data in NVS namespace '%s'...", namespace_name);
ret = nvs_commit(my_handle);
ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) committing data for namespace '%s'!", esp_err_to_name(ret), namespace_name);
ESP_LOGI(TAG, "Getting post-commit NVS statistics...");
nvs_stats_t new_nvs_stats;
ret = nvs_get_stats(NVS_DEFAULT_PART_NAME, &new_nvs_stats);
ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) getting NVS statistics!", esp_err_to_name(ret));
// Print the post-write NVS statistics
print_nvs_stats(&new_nvs_stats);
size_t expected_newly_used_entries = calculate_expected_entries_for_string_array(mock_data_values, mock_data_count);
if (new_nvs_stats.used_entries - nvs_stats.used_entries != expected_newly_used_entries) {
ESP_LOGE(TAG, "Newly used entries: %u, expected: %u.",
new_nvs_stats.used_entries - nvs_stats.used_entries,
expected_newly_used_entries);
} else {
ESP_LOGI(TAG, "Newly used entries match expectation.");
ESP_LOGI(TAG, "Newly used entries: %u, expected: %u.",
new_nvs_stats.used_entries - nvs_stats.used_entries,
expected_newly_used_entries);
}
nvs_close_label:
// Close the storage handle, freeing allocated resources
nvs_close(my_handle);
ESP_LOGI(TAG, "NVS handle for namespace '%s' closed.", namespace_name);
return ESP_OK;
}
static esp_err_t read_mock_data_from_namespace(const char* namespace_name)
{
ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle for namespace '%s'...", namespace_name);
nvs_handle_t my_handle;
esp_err_t ret = nvs_open(namespace_name, NVS_READONLY, &my_handle);
ESP_RETURN_ON_ERROR(ret, TAG, "Error (%s) opening NVS handle for namespace '%s'!", esp_err_to_name(ret), namespace_name);
ESP_LOGI(TAG, "Reading stored data from NVS namespace '%s'...", namespace_name);
for (int i = 0; i < mock_data_count; i++) {
size_t required_size = 0;
// Obtain required size of the string value including null character
ret = nvs_get_str(my_handle, mock_data_keys[i], NULL, &required_size);
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGE(TAG, "Error (%s) getting required size for key '%s'!", esp_err_to_name(ret), mock_data_keys[i]);
continue;
}
if (ret == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGW(TAG, "Key '%s' not found in namespace '%s'!", mock_data_keys[i], namespace_name);
continue;
}
// Allocate memory for the string value
char* value = malloc(required_size);
if (value == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for key '%s'!", mock_data_keys[i]);
continue;
}
// Read the string value
ret = nvs_get_str(my_handle, mock_data_keys[i], value, &required_size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) reading key '%s'!", esp_err_to_name(ret), mock_data_keys[i]);
free(value);
continue;
}
ESP_LOGI(TAG, "Read key-value pair from NVS: '%s':'%s'", mock_data_keys[i], value);
free(value);
}
// Close the storage handle, freeing allocated resources
nvs_close(my_handle);
ESP_LOGI(TAG, "NVS handle for namespace '%s' closed.", namespace_name);
return ESP_OK;
}
void app_main(void)
{
// Erase the contents of the default NVS partition for clean run of this example
ESP_LOGI(TAG, "Erasing the contents of the default NVS partition...");
ESP_ERROR_CHECK(nvs_flash_erase());
// Initialize NVS on default "nvs" partition
esp_err_t ret = nvs_flash_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) initializing NVS on default 'nvs' partition!", esp_err_to_name(ret));
return;
}
// Write and read mock data
ret = save_mock_data_to_namespace(MOCK_DATA_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) saving mock data data to namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE);
}
ret = read_mock_data_from_namespace(MOCK_DATA_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) reading back stored data from namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE);
}
// Write and read mock data "backup"
ret = save_mock_data_to_namespace(MOCK_DATA_BACKUP_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) saving mock data to namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_BACKUP_NAMESPACE);
}
ret = read_mock_data_from_namespace(MOCK_DATA_BACKUP_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (%s) reading back stored data from namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_BACKUP_NAMESPACE);
}
ESP_LOGI(TAG, "Returning from app_main().");
}

View File

@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
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)
dut.expect('Getting post-commit NVS statistics...', timeout=5)
dut.expect('Newly used entries match expectation.', timeout=5)
dut.expect('Returning from app_main().', timeout=5)