diff --git a/components/esp_pm/include/esp_pm.h b/components/esp_pm/include/esp_pm.h index cb6919084b..8ba33b16da 100644 --- a/components/esp_pm/include/esp_pm.h +++ b/components/esp_pm/include/esp_pm.h @@ -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) diff --git a/components/esp_pm/pm_locks.c b/components/esp_pm/pm_locks.c index bfdc6436ac..2a422a0ce6 100644 --- a/components/esp_pm/pm_locks.c +++ b/components/esp_pm/pm_locks.c @@ -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"); diff --git a/components/esp_pm/test_apps/esp_pm/main/test_pm.c b/components/esp_pm/test_apps/esp_pm/main/test_pm.c index 9f69163629..bdb948f4fe 100644 --- a/components/esp_pm/test_apps/esp_pm/main/test_pm.c +++ b/components/esp_pm/test_apps/esp_pm/main/test_pm.c @@ -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 */ @@ -43,6 +43,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) diff --git a/components/esp_pm/test_apps/esp_pm/sdkconfig.defaults b/components/esp_pm/test_apps/esp_pm/sdkconfig.defaults index a96750cd3b..9ab10ee2a7 100644 --- a/components/esp_pm/test_apps/esp_pm/sdkconfig.defaults +++ b/components/esp_pm/test_apps/esp_pm/sdkconfig.defaults @@ -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