Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/task monitor api #171

Merged
merged 4 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/monitor/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ idf.py -p PORT flash monitor

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

## Example Output

![CleanShot 2024-03-05 at 14 36 04](https://github.com/esp-cpp/espp/assets/213467/342e4d78-deee-4361-9e58-697e6dc6f73c)
47 changes: 29 additions & 18 deletions components/monitor/example/main/monitor_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "task.hpp"
#include "task_monitor.hpp"

// must be after tabulate.hpp
#include <tabulate/markdown_exporter.hpp>

// for doing work
#include <math.h>

Expand All @@ -24,9 +27,8 @@ extern "C" void app_main(void) {
auto seconds_since_start = std::chrono::duration<float>(now - start).count();
// do some work
float x = 2.0f * M_PI * sin(exp(task_id) * seconds_since_start);
fmt::print("[Task {}]: {:.03}\n", task_id, x);
// sleep
std::this_thread::sleep_for(100ms);
std::this_thread::sleep_for((10ms * std::abs(x)) + 1ms);
// don't want to stop the task
return false;
};
Expand All @@ -47,17 +49,16 @@ extern "C" void app_main(void) {
}

{
//! [get_latest_info example]
//! [get_latest_info_vector example]
// create threads
auto start = std::chrono::high_resolution_clock::now();
auto task_fn = [&start](int task_id, auto &, auto &) {
auto now = std::chrono::high_resolution_clock::now();
auto seconds_since_start = std::chrono::duration<float>(now - start).count();
// do some work
float x = 2.0f * M_PI * sin(exp(task_id) * seconds_since_start);
fmt::print("[Task {}]: {:.03}\n", task_id, x);
// sleep
std::this_thread::sleep_for(100ms);
std::this_thread::sleep_for((10ms * std::abs(x)) + 1ms);
// don't want to stop the task
return false;
};
Expand All @@ -73,24 +74,25 @@ extern "C" void app_main(void) {
tasks[i]->start();
}
// now sleep for a while to let the monitor do its thing
for (int i = 0; i < 5; i++) {
fmt::print("{}\n", espp::TaskMonitor::get_latest_info());
std::this_thread::sleep_for(1s);
std::this_thread::sleep_for(1s);
auto task_info = espp::TaskMonitor::get_latest_info_vector();
for (const auto &info : task_info) {
fmt::print("{}\n", info);
}
//! [get_latest_info example]
//! [get_latest_info_vector example]
}

{
//! [get_latest_info_table example]
//! [get_latest_info example]
// create threads
auto start = std::chrono::high_resolution_clock::now();
auto task_fn = [&start](int task_id, auto &, auto &) {
auto now = std::chrono::high_resolution_clock::now();
auto seconds_since_start = std::chrono::duration<float>(now - start).count();
// do some work, but don't print it so that the table is easier to read
volatile float x = 2.0f * M_PI * sin(exp(task_id) * seconds_since_start);
// do some work
float x = 2.0f * M_PI * sin(exp(task_id) * seconds_since_start);
// sleep
std::this_thread::sleep_for(100ms);
std::this_thread::sleep_for((10ms * std::abs(x)) + 1ms);
// don't want to stop the task
return false;
};
Expand All @@ -106,11 +108,20 @@ extern "C" void app_main(void) {
tasks[i]->start();
}
// now sleep for a while to let the monitor do its thing
for (int i = 0; i < 5; i++) {
espp::TaskMonitor::get_latest_info_table(std::cout);
std::this_thread::sleep_for(1s);
}
//! [get_latest_info_table example]
std::this_thread::sleep_for(1s);
// single line string
fmt::print("Task Monitor Info (single line):\n");
fmt::print("{}\n", espp::TaskMonitor::get_latest_info_string());
// pretty table
fmt::print("Task Monitor Info (pretty table):\n");
auto task_table = espp::TaskMonitor::get_latest_info_table();
std::cout << task_table << std::endl;
// markdown table
fmt::print("Converting the table above to markdown:\n");
tabulate::MarkdownExporter exporter;
auto markdown = exporter.dump(task_table);
std::cout << markdown << std::endl;
//! [get_latest_info example]
}

fmt::print("Monitor example finished!\n");
Expand Down
2 changes: 1 addition & 1 deletion components/monitor/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
143 changes: 82 additions & 61 deletions components/monitor/include/task_monitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,30 @@ namespace espp {
* (default) or print out the stats for you to analyze. Finally, the
* monitoring period can be configured as well.
*
* @note You can use the static TaskMonitor::get_latest_info() to get a
* string with the latest info without needing to construct a class /
* start the task.
*
* \section task_monitor_ex1 Basic Task Monitor Example
* \snippet monitor_example.cpp TaskMonitor example
*
* \section task_monitor_ex2 get_latest_info() Example
* \snippet monitor_example.cpp get_latest_info example
* \section task_monitor_ex2 get_latest_info_vector() Example
* \snippet monitor_example.cpp get_latest_info_vector example
*
* \section task_monitor_ex3 get_latest_info_table() Example
* \snippet monitor_example.cpp get_latest_info_table example
* \section task_monitor_ex3 get_latest_info_*() Example
* \snippet monitor_example.cpp get_latest_info example
*/
class TaskMonitor : public BaseComponent {
public:
/**
* Info structure for each task monitored.
*/
struct TaskInfo {
std::string name; /**< Name of the task. */
uint32_t cpu_percent; /**< % CPU run time the task has used. */
uint32_t high_water_mark; /**< Stack high water mark (bytes). */
uint32_t priority; /**< Current priority of the task. */
};

/**
* Config structure for TaskMonitor object.
*/
struct Config {
std::chrono::duration<float> period; /**< Period (s) the TaskMonitor::task_callback runs at. */
size_t task_stack_size_bytes{
Expand Down Expand Up @@ -70,35 +79,15 @@ class TaskMonitor : public BaseComponent {
* * % CPU run time the task has used
* * stack high water mark (bytes)
* * current priority of the task
*
* Where each entry is separated by ',' and each set of task data is
* separated by ';'.
*
* @note There is no newline returned.
*
* This is a static function, so it can be called without having to
* instantiate a TaskMonitor object.
*
* @return std::string containing sequence of entries, formatted:
*
* name, cpu%, high_water_mark, priority;;
* @return std::vector<TaskInfo> vector containing info for each task.
*/
static std::string get_latest_info() {
static std::vector<TaskInfo> get_latest_info_vector() {
std::vector<TaskInfo> task_info;
#if CONFIG_FREERTOS_USE_TRACE_FACILITY && CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
// make this static so that we don't allocate on the stack each time we
// call this function (and then deallocate later). NOTE(WARN): doing this
// makes this function non-reentrant and not thread-safe.
static char info_str[1024] = {0};

TaskStatus_t *pxTaskStatusArray;
volatile UBaseType_t uxArraySize;
uint32_t ulTotalRunTime, ulStatsAsPercentage;

char *pcWriteBuffer = &info_str[0];

// Make sure the write buffer does not contain a string.
*pcWriteBuffer = 0x00;

// Take a snapshot of the number of tasks in case it changes while this
// function is executing.
uxArraySize = uxTaskGetNumberOfTasks();
Expand Down Expand Up @@ -134,64 +123,85 @@ class TaskMonitor : public BaseComponent {
ulStatsAsPercentage = pxTaskStatusArray[x].ulRunTimeCounter / ulTotalRunTime;

if (ulStatsAsPercentage > 0UL) {
sprintf(pcWriteBuffer, "%s,%lu%%,%ld,%d;;", pxTaskStatusArray[x].pcTaskName,
ulStatsAsPercentage, high_water_mark, priority);
task_info.push_back(
{pxTaskStatusArray[x].pcTaskName, ulStatsAsPercentage, high_water_mark, priority});
} else {
// If the percentage is zero here then the task has
// consumed less than 1% of the total run time.
sprintf(pcWriteBuffer, "%s,<1%%,%ld,%d;;", pxTaskStatusArray[x].pcTaskName,
high_water_mark, priority);
task_info.push_back({pxTaskStatusArray[x].pcTaskName, 0, high_water_mark, priority});
}

pcWriteBuffer += strlen((char *)pcWriteBuffer);
}
}
// The array is no longer needed, free the memory it consumes.
vPortFree(pxTaskStatusArray);
}
return std::string{info_str};
#else
return "";
#endif
return task_info;
}

/**
* @brief Get information about all the tasks running.
* Will provide for each task the following information:
* * name
* * % CPU run time the task has used
* * stack high water mark (bytes)
* * current priority of the task
*
* Where each entry is separated by ',' and each set of task data is
* separated by ';'.
*
* @note There is no newline returned.
*
* This is a static function, so it can be called without having to
* instantiate a TaskMonitor object.
*
* @return std::string containing sequence of entries, formatted:
*
* name, cpu%, high_water_mark, priority;;
*
* @note This function calls TaskMonitor::get_latest_info_vector() and then
* formats the data into a single line string separated by , and ;.
*/
static std::string get_latest_info_string() {
std::string info = "";
auto task_info = get_latest_info_vector();
for (const auto &t : task_info) {
if (t.cpu_percent > 0.0f) {
info += fmt::format("{},{},{},{};", t.name, t.cpu_percent, t.high_water_mark, t.priority);
} else {
info += fmt::format("{},<1%,{},{};", t.name, 0, t.high_water_mark, t.priority);
}
}
return info;
}

/**
* @brief Print the latest task information in a nice table format.
* This is a static function, so it can be called without having to
* instantiate a TaskMonitor object.
* @param os std::ostream to write the table to.
* @note This function calls TaskMonitor::get_latest_info() and then formats
* the data into a table using the tabulate library, and then writes
* the table to the provided std::ostream.
* @return A tabulate::Table object which can be streamed to a std::ostream.
* @note This function calls TaskMonitor::get_latest_info_vector() and then
* formats the data into a table using the tabulate library.
*/
static void get_latest_info_table(std::ostream &os) {
std::string info = get_latest_info();
static auto get_latest_info_table() {
using namespace tabulate;
using Row_t = Table::Row_t;
Table table;
table.add_row({"Task Name", "CPU %", "High Water Mark", "Priority"});

std::string task_info;
std::istringstream iss(info);
while (std::getline(iss, task_info, ';')) {
std::istringstream task_iss(task_info);
std::string task_data;
Row_t row;
while (std::getline(task_iss, task_data, ',')) {
row.push_back(task_data);
}
if (row.size() == 4) {
table.add_row(row);
}
auto task_info = get_latest_info_vector();
for (const auto &t : task_info) {
std::string percent = t.cpu_percent > 0 ? fmt::format("{} %", t.cpu_percent) : "<1%";
table.add_row(
{t.name, percent, fmt::format("{} B", t.high_water_mark), fmt::format("{}", t.priority)});
}
os << table << std::endl;
return table;
}

protected:
bool task_callback(std::mutex &m, std::condition_variable &cv) {
auto start = std::chrono::high_resolution_clock::now();
// print out the monitor information
fmt::print("[TM]{}\n", get_latest_info());
fmt::print("[TM]{}\n", get_latest_info_string());
// sleep until our period is up
{
std::unique_lock<std::mutex> lk(m);
Expand All @@ -205,3 +215,14 @@ class TaskMonitor : public BaseComponent {
std::unique_ptr<Task> task_;
};
} // namespace espp

// for printing TaskMonitor::TaskInfo using libfmt
template <> struct fmt::formatter<espp::TaskMonitor::TaskInfo> {
constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const espp::TaskMonitor::TaskInfo &t, FormatContext &ctx) {
return fmt::format_to(ctx.out(),
"TaskInfo(name={}, cpu_percent={}, high_water_mark={}, priority={})",
t.name, t.cpu_percent, t.high_water_mark, t.priority);
}
};
2 changes: 1 addition & 1 deletion docs/adc/adc_types.html
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADC Types</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/d5ae840d/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/75fe6700/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand Down
4 changes: 2 additions & 2 deletions docs/adc/ads1x15.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADS1x15 I2C ADC</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/d5ae840d/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/75fe6700/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -168,7 +168,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/d5ae840d/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/75fe6700/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
</ul>
</section>
<section id="classes">
Expand Down
4 changes: 2 additions & 2 deletions docs/adc/ads7138.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADS7138 I2C ADC</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/d5ae840d/docs/en/adc/ads7138.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/75fe6700/docs/en/adc/ads7138.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -173,7 +173,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/d5ae840d/components/ads7138/include/ads7138.hpp">components/ads7138/include/ads7138.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/75fe6700/components/ads7138/include/ads7138.hpp">components/ads7138/include/ads7138.hpp</a></p></li>
</ul>
</section>
<section id="classes">
Expand Down
4 changes: 2 additions & 2 deletions docs/adc/continuous_adc.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>Continuous ADC</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/d5ae840d/docs/en/adc/continuous_adc.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/75fe6700/docs/en/adc/continuous_adc.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -173,7 +173,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/d5ae840d/components/adc/include/continuous_adc.hpp">components/adc/include/continuous_adc.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/75fe6700/components/adc/include/continuous_adc.hpp">components/adc/include/continuous_adc.hpp</a></p></li>
</ul>
</section>
<section id="classes">
Expand Down
2 changes: 1 addition & 1 deletion docs/adc/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
<li><a href="../index.html" class="icon icon-home"></a> &raquo;</li>
<li>ADC APIs</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/d5ae840d/docs/en/adc/index.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/75fe6700/docs/en/adc/index.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand Down
Loading
Loading