forked from espressif/esp-idf
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:
@@ -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)
|
||||
|
||||
@@ -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` 选项。
|
||||
|
||||
动态调频和外设驱动
|
||||
------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user