From 0c2377726fc563285cdb68536af93b0777bfc3a4 Mon Sep 17 00:00:00 2001 From: Tursiae Date: Sun, 9 Feb 2025 14:34:16 +1100 Subject: Console: Update doco to point to `sdkconfig.local`, and handle no-stats cases. When `CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS` is not set, the FreeRTOS scheduler will not keep track of task runtime statistics, and the `tasks` command on the console will show `nan%` for the usage. This adds a recommendation for the user to enable the `...STATS` config in their build, and also updates the guidance for `configUSE_TRACE_FACILITY` to point at the supported `sdkconfig.local` configuration pathway, instead of pointing at the `#define` that's deeper in the configuration stack. Also, the sampling period is dropped from 2.5s to 10ms when the runtime stats are not enabled; given that we're not measuring any usage, it's not worth sleeping any longer than that. We might even be able to drop to zero? --- src/tangara/app_console/app_console.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'src/tangara') diff --git a/src/tangara/app_console/app_console.cpp b/src/tangara/app_console/app_console.cpp index 21dec56a..7aa7fccf 100644 --- a/src/tangara/app_console/app_console.cpp +++ b/src/tangara/app_console/app_console.cpp @@ -207,8 +207,11 @@ void RegisterDbInit() { int CmdTasks(int argc, char** argv) { #if (configUSE_TRACE_FACILITY == 0) - std::cout << "configUSE_TRACE_FACILITY must be enabled" << std::endl; - std::cout << "also consider configTASKLIST_USE_COREID" << std::endl; + std::cout + << "FreeRTOS is not configured to track task info." << std::endl + << "Enable CONFIG_FREERTOS_USE_TRACE_FACILITY=y in " << std::endl + << "sdkconfig.local, and also consider enabling " << std::endl + << "CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y" << std::endl; return 1; #endif @@ -218,6 +221,14 @@ int CmdTasks(int argc, char** argv) { return 1; } + // Sample the task runtime percentage over 2.5 seconds if collecting + // task statistics. Else, we don't need to sample for as long. +#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + const int kSamplePeriodMs = 2500; +#else + const int kSamplePeriodMs = 10; +#endif + // Pad the number of tasks so that uxTaskGetSystemState still returns info // if new tasks are started during measurement. size_t num_tasks = uxTaskGetNumberOfTasks() + 4; @@ -229,11 +240,21 @@ int CmdTasks(int argc, char** argv) { size_t start_num_tasks = uxTaskGetSystemState(start_status, num_tasks, &start_elapsed_ticks); - vTaskDelay(pdMS_TO_TICKS(2500)); + vTaskDelay(pdMS_TO_TICKS(kSamplePeriodMs)); size_t end_num_tasks = uxTaskGetSystemState(end_status, num_tasks, &end_elapsed_ticks); + uint32_t elapsed_ticks = end_elapsed_ticks - start_elapsed_ticks; + + if (0 == elapsed_ticks) { + std::cout << "Warning: the scheduler is not recording run-time" << std::endl + << "statistics, and this means that detailed task" << std::endl + << "information is not available." << std::endl + << "Enable CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS" << std::endl + << "in sdkconfig.local to capture these stats." << std::endl; + } + std::vector> info_strings; for (int i = 0; i < start_num_tasks; i++) { int k = -1; @@ -279,8 +300,12 @@ int CmdTasks(int argc, char** argv) { str << "\t\t"; } +#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS str << std::fixed << std::setprecision(1) << (time_percent * 100); str << "%"; +#else + str << "(unavailable)"; +#endif info_strings.push_back({run_time, std::pmr::string{str.str()}}); } -- cgit v1.2.3 From dcd39a75e8b4be170e27eb2e7eb1b4f755ba32f2 Mon Sep 17 00:00:00 2001 From: Tursiae Date: Mon, 10 Feb 2025 16:12:54 +1100 Subject: Also document the `idf.py menuconfig` approach. --- src/tangara/app_console/app_console.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src/tangara') diff --git a/src/tangara/app_console/app_console.cpp b/src/tangara/app_console/app_console.cpp index 7aa7fccf..e4e42786 100644 --- a/src/tangara/app_console/app_console.cpp +++ b/src/tangara/app_console/app_console.cpp @@ -209,9 +209,12 @@ int CmdTasks(int argc, char** argv) { #if (configUSE_TRACE_FACILITY == 0) std::cout << "FreeRTOS is not configured to track task info." << std::endl - << "Enable CONFIG_FREERTOS_USE_TRACE_FACILITY=y in " << std::endl - << "sdkconfig.local, and also consider enabling " << std::endl - << "CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y" << std::endl; + << "You can enable task tracing via sdkconfig.local, by" << std::endl + << "setting CONFIG_FREERTOS_USE_TRACE_FACILITY=y. Alternately," << std::endl + << "use idf.py menuconfig to enable Components / FreeRTOS /" << std::endl + << "Kernel / configUSE_TRACE_FACILITY to do the same." << std::endl + << "Also consider 'Enable display of xCoreID in vTaskList'," << std::endl + << "or CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y" << std::endl; return 1; #endif @@ -252,7 +255,9 @@ int CmdTasks(int argc, char** argv) { << "statistics, and this means that detailed task" << std::endl << "information is not available." << std::endl << "Enable CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS" << std::endl - << "in sdkconfig.local to capture these stats." << std::endl; + << "in sdkconfig.local to capture these stats, or" << std::endl + << "via idf.py menuconfig, in Components/FreeRTOS/" << std::endl + << "Kernel/configGENERATE_RUN_TIME_STATS." << std::endl; } std::vector> info_strings; -- cgit v1.2.3 From 187cd772ee4027f252b03788cb1c0774d62ef207 Mon Sep 17 00:00:00 2001 From: Tursiae Date: Mon, 10 Feb 2025 16:58:20 +1100 Subject: s/sdkconfig.local/sdkconfig/g --- src/tangara/app_console/app_console.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/tangara') diff --git a/src/tangara/app_console/app_console.cpp b/src/tangara/app_console/app_console.cpp index e4e42786..ce6c75d9 100644 --- a/src/tangara/app_console/app_console.cpp +++ b/src/tangara/app_console/app_console.cpp @@ -209,10 +209,10 @@ int CmdTasks(int argc, char** argv) { #if (configUSE_TRACE_FACILITY == 0) std::cout << "FreeRTOS is not configured to track task info." << std::endl - << "You can enable task tracing via sdkconfig.local, by" << std::endl - << "setting CONFIG_FREERTOS_USE_TRACE_FACILITY=y. Alternately," << std::endl - << "use idf.py menuconfig to enable Components / FreeRTOS /" << std::endl - << "Kernel / configUSE_TRACE_FACILITY to do the same." << std::endl + << "You can enable task tracing via sdkconfig, by setting" << std::endl + << "CONFIG_FREERTOS_USE_TRACE_FACILITY=y. Alternately, use" << std::endl + << "idf.py menuconfig to enable Components/FreeRTOS/Kernel" << std::endl + << "configUSE_TRACE_FACILITY to do the same." << std::endl << std::endl << "Also consider 'Enable display of xCoreID in vTaskList'," << std::endl << "or CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y" << std::endl; return 1; @@ -255,9 +255,9 @@ int CmdTasks(int argc, char** argv) { << "statistics, and this means that detailed task" << std::endl << "information is not available." << std::endl << "Enable CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS" << std::endl - << "in sdkconfig.local to capture these stats, or" << std::endl - << "via idf.py menuconfig, in Components/FreeRTOS/" << std::endl - << "Kernel/configGENERATE_RUN_TIME_STATS." << std::endl; + << "in sdkconfig to capture these stats, or via" << std::endl + << "idf.py menuconfig, in Components/FreeRTOS/Kernel" << std::endl + << "configGENERATE_RUN_TIME_STATS." << std::endl; } std::vector> info_strings; -- cgit v1.2.3 From fe7c26d27d47f6087b67af9fe740f674078b5da1 Mon Sep 17 00:00:00 2001 From: Tursiae Date: Sun, 9 Feb 2025 18:25:16 +1100 Subject: TTS: Avoid exhausting the WorkerPool with concurrent TTS playback. Reported in issue #258. As of v1.2.0, if /.tangara-tts/ samples are present on the SD card, and >= 4 menu items with matching TTS samples are highlighted in the UI, and no audio output (headphones or BT sink) is connected, the `tts::Player`'s invocation of lambdas on the WorkerPool will result in worker task exhaustion. This is because we get stuck in state where the `drivers::PcmBuffer` is not accepting any new samples, and the inner loop in `Player::decodeToSink` that pushes to the output isn't checking to see whether playback was cancelled. So the loop never terminates, and we consume that worker slot. Repeat with another 3 menu items, and, hey, all four worker threads are consumed with TTS that will not terminate until headphones/BT are connected. --- src/tangara/tts/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/tangara') diff --git a/src/tangara/tts/player.cpp b/src/tangara/tts/player.cpp index 46e8c48a..9cc7a1f7 100644 --- a/src/tangara/tts/player.cpp +++ b/src/tangara/tts/player.cpp @@ -174,7 +174,7 @@ auto Player::decodeToSink(const codecs::ICodec::OutputFormat& format, // The mixin PcmBuffer should almost always be draining, so we can force // samples into it more aggressively than with the main music PcmBuffer. - while (!stereo_buf.isEmpty()) { + while (!stereo_buf.isEmpty() && !stream_cancelled_) { size_t sent = output_.send(stereo_buf.readAcquire()); stereo_buf.readCommit(sent); } -- cgit v1.2.3 From 167fea6b5b87a1cd97165d4e425699c2ecdb2bbf Mon Sep 17 00:00:00 2001 From: Tursiae Date: Mon, 10 Feb 2025 00:02:57 +1100 Subject: TTS: Better document the ownership and task/control flow. --- src/tangara/tts/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/tangara') diff --git a/src/tangara/tts/README.md b/src/tangara/tts/README.md index 63d587da..54aae8ef 100644 --- a/src/tangara/tts/README.md +++ b/src/tangara/tts/README.md @@ -47,3 +47,37 @@ logs a `WARN`ing each time it cannot find a TTS sample. You can enable these log messages on the console by using the command `loglevel warn`, and then manipulating the click wheel to move through the UI to discover other missing TTS samples. + +## Tasks and Event Passing + +### In the `ui` task + +There are two main threads involved with running TTS - firstly, the `ui` +task, which is rooted in `ui::UiTask::Main()` (`src/tangara/ui/lvgl_task.hpp)`. +By way of the LVGL stack, eventually navigation in the UI results in sending +a `tts::SelectionChanged` message from `input::TextToSpeech::describe()` +(`src/tangara/input/feedback_tts.cpp`) to `tts::Provider::feed()` +(`src/tangara/tts/provider.cpp`), all in the UI task. + +The `tts::Provider` is responsible for translating the UI string from a lump +of text to a TTS sample filename, which is then passed along to the player +in `tts::Player::playFile()` (`src/tangara/tts/player.cpp`), still on the UI +thread. + +The UI task has a smaller stack than the `worker_X` tasks, and are not +appropriate to use for audio decoding work, both because they would block any +UI updates, and also have insufficient stack space for audio decode activity. + +### Transitioning to the `WorkerPool` background threads + +`playFile()` uses `tasks::WorkerPool::Dispatch()` to fire off a lambda in a +different task - one of the background `worker_X` tasks, owned by `WorkerPool`. +Control returns to the UI thread in under 2ms, so it remains pretty responsive +throughout this flow. + +The background worker uses `tts::Player::openAndDecode` to do the bulk of the +decode/resample/playback work, and it is on this background task that the +majority of the work occurs. Note that there is nothing stopping the TTS worker +from consuming a number of worker tasks at once, but we rely on the interaction +of stream cancellation behaviour between the workers to ensure that previous +samples' playback is promptly terminated after a new sample is requested. -- cgit v1.2.3