Merge branch 'feat/support_get_pm_lock_aquired_counts' into 'master'

feat(esp_pm): add APIs to get PM lock statistics

Closes IDFGH-16676

See merge request espressif/esp-idf!43002
This commit is contained in:
Wu Zheng Hui
2025-11-19 10:52:40 +08:00
6 changed files with 301 additions and 4 deletions
+49 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -196,6 +196,54 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle);
*/
esp_err_t esp_pm_dump_locks(FILE* stream);
/**
* @brief Structure to store PM lock statistics for each lock type
*/
typedef struct {
size_t created; /*!< Number of locks of this type that have been created */
size_t acquired; /*!< Total number of times locks of this type have been acquired */
} esp_pm_lock_stats_t;
/**
* @brief Structure to store statistics for a single PM lock instance
*/
typedef struct {
size_t acquired; /*!< Current reference count of the lock (number of times it has been acquired without corresponding release) */
#ifdef CONFIG_PM_PROFILING
size_t times_taken; /*!< Number of times the lock has been taken (from not held to held state) */
int64_t time_held; /*!< Total time the lock has been held (in microseconds) */
#endif
} esp_pm_lock_instance_stats_t;
/**
* @brief Get statistics for all PM lock types
*
* This function returns the number of locks created for each lock type
* and the total number of times locks of each type have been acquired.
*
* @param stats pointer to array of esp_pm_lock_stats_t with ESP_PM_LOCK_MAX elements
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if stats pointer is invalid
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
*/
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX]);
/**
* @brief Get statistics for a single PM lock instance
*
* This function returns statistics for a specific lock instance,
* including the number of times it has been acquired and released.
*
* @param handle handle of the lock to get statistics for
* @param stats pointer to esp_pm_lock_instance_stats_t structure to fill
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if handle or stats pointer is invalid
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
*/
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats);
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
/**
* @brief Function prototype for light sleep callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE)
+68 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -147,6 +147,72 @@ out:
return ret;
}
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX])
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
if (stats == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Initialize stats array
for (int i = 0; i < ESP_PM_LOCK_MAX; i++) {
stats[i].created = 0;
stats[i].acquired = 0;
}
_lock_acquire(&s_list_lock);
// Iterate through all locks and accumulate stats
esp_pm_lock_t* it;
SLIST_FOREACH(it, &s_list, next) {
if (it->type < ESP_PM_LOCK_MAX) {
stats[it->type].created++;
// Sum the count of currently held locks
stats[it->type].acquired += it->count;
}
}
_lock_release(&s_list_lock);
return ESP_OK;
}
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats)
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
if (handle == NULL || stats == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Initialize stats structure
stats->acquired = 0;
#ifdef WITH_PROFILING
stats->times_taken = 0;
stats->time_held = 0;
#endif
portENTER_CRITICAL(&handle->spinlock);
stats->acquired = handle->count;
#ifdef WITH_PROFILING
stats->times_taken = handle->times_taken;
stats->time_held = handle->time_held;
// If the lock is currently held, add the time since it was last taken
if (handle->count > 0) {
pm_time_t now = pm_get_time();
stats->time_held += now - handle->last_taken;
}
#endif
portEXIT_CRITICAL(&handle->spinlock);
return ESP_OK;
}
esp_err_t esp_pm_dump_locks(FILE* stream)
{
#ifndef CONFIG_PM_ENABLE
@@ -160,7 +226,7 @@ esp_err_t esp_pm_dump_locks(FILE* stream)
_lock_acquire(&s_list_lock);
#ifdef WITH_PROFILING
fprintf(stream, "Time since bootup: %lld us\n", cur_time);
fprintf(stream, "Time since boot up: %lld us\n", cur_time);
#endif
fprintf(stream, "Lock stats:\n");
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -44,6 +44,154 @@ TEST_CASE("Can dump power management lock stats", "[pm]")
esp_pm_dump_locks(stdout);
}
TEST_CASE("Test get PM lock statistics API", "[pm]")
{
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_stats_t init_stats[ESP_PM_LOCK_MAX], stats[ESP_PM_LOCK_MAX];
// Get initial stats
TEST_ESP_OK(esp_pm_get_lock_stats_all(init_stats));
// Create a few locks of different types
esp_pm_lock_handle_t lock1, lock2, lock3;
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "cpu_lock", &lock1));
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "apb_lock", &lock2));
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "sleep_lock", &lock3));
// Check stats after creating locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
TEST_ASSERT_EQUAL(1, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
TEST_ASSERT_EQUAL(1, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
// Acquire locks multiple times
TEST_ESP_OK(esp_pm_lock_acquire(lock1));
TEST_ESP_OK(esp_pm_lock_acquire(lock1)); // Acquire again (recursive)
TEST_ESP_OK(esp_pm_lock_acquire(lock2));
// Check stats after acquiring locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
// Count total held locks (sum of all lock counts)
TEST_ASSERT_EQUAL(2, stats[ESP_PM_CPU_FREQ_MAX].acquired - init_stats[ESP_PM_CPU_FREQ_MAX].acquired); // lock1 acquired twice
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].acquired - init_stats[ESP_PM_APB_FREQ_MAX].acquired); // lock2 acquired once
// Release locks
TEST_ESP_OK(esp_pm_lock_release(lock1));
TEST_ESP_OK(esp_pm_lock_release(lock1)); // Release second acquisition
TEST_ESP_OK(esp_pm_lock_release(lock2));
// Delete locks
TEST_ESP_OK(esp_pm_lock_delete(lock1));
TEST_ESP_OK(esp_pm_lock_delete(lock2));
TEST_ESP_OK(esp_pm_lock_delete(lock3));
// Check stats after deleting locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
TEST_ASSERT_EQUAL(0, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
TEST_ASSERT_EQUAL(0, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
TEST_ASSERT_EQUAL(0, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
// Test error cases
// NULL stats pointer
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_get_lock_stats_all(NULL));
#else
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX];
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_get_lock_stats_all(stats));
#endif
}
TEST_CASE("Test get PM lock instance statistics API", "[pm]")
{
#ifdef CONFIG_PM_ENABLE
// Create a lock
esp_pm_lock_handle_t lock;
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "test_lock", &lock));
// Get initial stats for the lock
esp_pm_lock_instance_stats_t lock_stats;
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
#if CONFIG_PM_PROFILING
TEST_ASSERT_EQUAL(0, lock_stats.times_taken);
TEST_ASSERT_EQUAL(0, lock_stats.time_held);
#endif
// Acquire the lock multiple times
TEST_ESP_OK(esp_pm_lock_acquire(lock));
TEST_ESP_OK(esp_pm_lock_acquire(lock));
TEST_ESP_OK(esp_pm_lock_acquire(lock));
// Get stats again
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(3, lock_stats.acquired);
#if CONFIG_PM_PROFILING
TEST_ASSERT_EQUAL(1, lock_stats.times_taken);
// The time_held should be greater than 0 if the lock is currently held
// We can't predict the exact value, so we just check that it's non-negative
TEST_ASSERT_GREATER_OR_EQUAL(0, lock_stats.time_held);
// Store the time_held value for later comparison
int32_t first_time_held = (int32_t)lock_stats.time_held;
int64_t start_time = esp_timer_get_time();
// Delay for a short period to increase the held time
vTaskDelay(100 / portTICK_PERIOD_MS);
int64_t end_time = esp_timer_get_time();
uint32_t expected_time_held = first_time_held + (uint32_t)(end_time - start_time);
// Get stats again to check that time_held has increased
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_GREATER_THAN(first_time_held, lock_stats.time_held);
// Check that time_held is within a reasonable range using TEST_ASSERT_UINT64_WITHIN
TEST_ASSERT_UINT32_WITHIN(10000, (uint32_t)expected_time_held, (uint32_t)lock_stats.time_held); // Allow 10ms tolerance
#endif
// Release the lock once
TEST_ESP_OK(esp_pm_lock_release(lock));
// Get stats again
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(2, lock_stats.acquired);
// Release remaining locks
TEST_ESP_OK(esp_pm_lock_release(lock));
TEST_ESP_OK(esp_pm_lock_release(lock));
#if CONFIG_PM_PROFILING
// After releasing all locks, get stats to check time_held is updated correctly
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
// Store the time_held value after releasing all locks
int64_t time_after_release = lock_stats.time_held;
// Delay for a short period
vTaskDelay(100 / portTICK_PERIOD_MS);
// Get stats again - time_held should not change since lock is not held
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(time_after_release, lock_stats.time_held);
#endif
// Test error cases
// NULL handle
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(NULL, &lock_stats));
// NULL stats pointer
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(lock, NULL));
// Clean up
TEST_ESP_OK(esp_pm_lock_delete(lock));
#else
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
esp_pm_lock_handle_t lock;
esp_pm_lock_instance_stats_t lock_stats;
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_lock_get_stats(lock, &lock_stats));
#endif
}
#ifdef CONFIG_PM_ENABLE
static void switch_freq(int mhz)
@@ -6,3 +6,4 @@ CONFIG_ESP_TASK_WDT_INIT=n
# SMP FreeRTOS currently does not support power management IDF-4997
CONFIG_FREERTOS_SMP=n
CONFIG_PM_PROFILING=y
@@ -92,6 +92,23 @@ Light-sleep duration is chosen to wake up the chip before the nearest event (tas
To skip unnecessary wake-up, you can consider initializing an ``esp_timer`` with the ``skip_unhandled_events`` option as ``true``. Timers with this flag will not wake up the system and it helps to reduce consumption.
Debugging and Profiling
-----------------------
The power management subsystem provides several functions to help debug and profile power management lock usage in applications:
- :cpp:func:`esp_pm_dump_locks` - Dumps a list of all currently created locks to a specified stream, showing their types, names, and current acquisition status.
- :cpp:func:`esp_pm_get_lock_stats_all` - Retrieves statistics for all PM lock types, including the number of locks created and the number currently acquired.
- :cpp:func:`esp_pm_lock_get_stats` - Gets detailed statistics for a specific lock instance, including acquisition count and (if profiling is enabled) the number of times taken and total time held.
These functions are particularly useful for:
1. Identifying leaks where locks are acquired but never released
2. Understanding which components are preventing power savings
3. Optimizing power consumption by analyzing lock usage patterns
4. Debugging issues related to lock management in applications
To enable profiling features (timing information for individual locks), enable the :ref:`CONFIG_PM_PROFILING` option in menuconfig.
Dynamic Frequency Scaling and Peripheral Drivers
------------------------------------------------
@@ -92,6 +92,23 @@ ESP-IDF 中集成的电源管理算法可以根据应用程序组件的需求,
为了跳过不必要的唤醒,可以将 ``skip_unhandled_events`` 选项设置为 ``true`` 来初始化 ``esp_timer``。带有此标志的定时器不会唤醒系统,有助于减少功耗。
调试和性能分析
-----------------------
电源管理子系统提供了几个函数来帮助调试和分析应用程序中的电源管理锁使用情况:
- :cpp:func:`esp_pm_dump_locks` - 将所有当前创建的锁列表转储到指定流,显示其类型、名称和当前获取状态。
- :cpp:func:`esp_pm_get_lock_stats_all` - 获取所有 PM 锁类型的统计信息,包括创建的锁数量和当前持有数。
- :cpp:func:`esp_pm_lock_get_stats` - 获取特定锁实例的详细统计信息,包括获取计数(如果启用性能分析)和总占用时间。
这些函数特别适用于:
1. 识别获取但从未释放的锁导致的泄漏
2. 了解哪些组件阻止了节能
3. 通过分析锁使用模式来优化功耗
4. 调试与应用程序中锁管理相关的问题
要启用性能分析功能(单个锁的计时信息),请在 menuconfig 中启用 :ref:`CONFIG_PM_PROFILING` 选项。
动态调频和外设驱动
------------------------------------------------