diff --git a/rims_app/proto/utilization.proto b/rims_app/proto/utilization.proto index c2cfef7bc74..715656221d0 100644 --- a/rims_app/proto/utilization.proto +++ b/rims_app/proto/utilization.proto @@ -3,18 +3,59 @@ syntax = "proto3"; import "nanopb.proto"; package utilization; -message Utilization +enum Error { + NoError = 0; +} + +message UtilizationSummary { - string name = 1 [ (nanopb).max_size = 32 ]; - uint32 stackSize = 2; - uint32 stackUsed = 3; - uint32 utilization = 4; -}; + uint32 avg_cpu_percent = 1; + uint32 max_stack_percent = 2; + uint32 thread_count = 3; + uint32 cpu_idle = 4; +} + +message SystemStats +{ + uint32 cpu_utilization_percent = 1; + uint32 max_stack_utilization_percent = 2; // e.g., worst stack usage + uint32 heap_free_percent = 3; // if heap used + uint32 num_threads_over_threshold = 4; // e.g., stack over 80% +} + +message UtilizationBroadcastConfigRequest +{ + uint32 period_ms = 1; +} + +message UtilizationBroadcastConfigResponse +{ +} + +message UtilizationSummaryRequest +{ +} + +message UtilizationSummaryResponse +{ + UtilizationSummary utilization_summary = 1; + Error error = 2; +} + +message UtilizationBroadcast +{ + SystemStats system_stats = 1; +} // only those messages are send through wire at temperature endpoint message IngressMessage { uint32 request_id = 255; + oneof data + { + UtilizationBroadcastConfigRequest utilization_broadcast_config_request = 1; + UtilizationSummaryRequest utilization_summary_request = 2; + } }; message EgressMessage @@ -22,7 +63,9 @@ message EgressMessage optional uint32 request_id = 255; oneof data { + UtilizationBroadcastConfigResponse utilization_broadcast_config_response = 1; + UtilizationSummaryResponse utilization_summary_response = 2; // broadcast - Utilization utilization = 16; + UtilizationBroadcast utilization = 16; } }; diff --git a/rims_app/src/ctrl.hpp b/rims_app/src/ctrl.hpp index a4a42579528..1f94f76273d 100644 --- a/rims_app/src/ctrl.hpp +++ b/rims_app/src/ctrl.hpp @@ -44,7 +44,7 @@ template struct global_ipc_request call(const RequestT &request) { static zephyr::semaphore::sem sem{0, 1}; - return defaultIpcHandler(ctrlIngressQueue, temperature_receiver_tag, sem, request); + return defaultIpcHandler(ctrlIngressQueue, ctrl_receiver_tag, sem, request); } }; diff --git a/rims_app/src/main.cpp b/rims_app/src/main.cpp index b8786719943..f5ed76e119a 100644 --- a/rims_app/src/main.cpp +++ b/rims_app/src/main.cpp @@ -26,7 +26,7 @@ using namespace rims; /// exception handling takes ~800 bytes from the stack when thrown, we need to make sure there is at least this much stack left for each thread /// We need to drop exceptions ;( most of the stack space seems to be used for stack unwinding and other stuff /// -static K_THREAD_STACK_DEFINE(k_messengerStack, 1200); +static K_THREAD_STACK_DEFINE(k_messengerStack, 1500); TStack messengerStack{k_messengerStack, K_THREAD_STACK_SIZEOF(k_messengerStack)}; static K_THREAD_STACK_DEFINE(k_uartStack, 1300); @@ -38,7 +38,7 @@ TStack temperatureSamplerStack{k_temperatureSamplerStack, K_THREAD_STACK_SIZEOF( static K_THREAD_STACK_DEFINE(k_zeroCrossDetectionStack, 1300); TStack zeroCrossDetectionStack{k_zeroCrossDetectionStack, K_THREAD_STACK_SIZEOF(k_zeroCrossDetectionStack)}; -static K_THREAD_STACK_DEFINE(k_phaseModulationStack, 1500); +static K_THREAD_STACK_DEFINE(k_phaseModulationStack, 1700); TStack phaseModulationStack{k_phaseModulationStack, K_THREAD_STACK_SIZEOF(k_phaseModulationStack)}; static K_THREAD_STACK_DEFINE(k_gpioStack, 700); @@ -79,7 +79,7 @@ gpio_dt_spec status = GPIO_DT_SPEC_GET(DT_NODELABEL(status_led), gpios); int main() { using namespace rims; - auto littlesleep = []() { std::this_thread::sleep_for(std::chrono::milliseconds{100}); }; + auto littlesleep = []() { std::this_thread::sleep_for(std::chrono::milliseconds{200}); }; messengerTread.init(messengerStack); uartThread.init(uartStack); @@ -137,4 +137,7 @@ int main() { threadAnalyzer->start(); littlesleep(); } + while(true){ + littlesleep(); + } } diff --git a/rims_app/src/message_type_id.hpp b/rims_app/src/message_type_id.hpp index 65797185bae..56ba58495a7 100644 --- a/rims_app/src/message_type_id.hpp +++ b/rims_app/src/message_type_id.hpp @@ -16,5 +16,6 @@ constexpr int operation_receiver_tag = 6; constexpr int messenger_receiver_tag = 7; constexpr int uart_receiver_tag = 8; constexpr int cobs_receiver_tag = 9; +constexpr int utilization_receiver_tag = 10; } // namespace rims diff --git a/rims_app/src/messenger.cpp b/rims_app/src/messenger.cpp index ce2476034c0..08e9b7bd506 100644 --- a/rims_app/src/messenger.cpp +++ b/rims_app/src/messenger.cpp @@ -7,10 +7,11 @@ #include "operation.hpp" #include "config.hpp" -#include "gpio.hpp" #include "ctrl.hpp" +#include "gpio.hpp" #include "temperature_measurements.hpp" #include "uart.hpp" +#include "utilization.hpp" #include "pb.h" #include "pb_decode.h" @@ -23,6 +24,7 @@ #include "proto/message.pb.h" #include "proto/operation.pb.h" #include "proto/temperature.pb.h" +#include "proto/utilization.pb.h" #include "id.hpp" @@ -217,6 +219,7 @@ void MessengerThread::threadMain() { logEgressQueue.k_event_init(_events.at(4)); gpioEgressQueue.k_event_init(_events.at(5)); operationEgressQueue.k_event_init(_events.at(6)); + utilizationEgressQueue.k_event_init(_events.at(7)); while (1) { auto ret = zephyr::event_pool::k_poll_forever(_events); @@ -228,6 +231,7 @@ void MessengerThread::threadMain() { zephyr::event_pool::k_poll_handle(_events[4], std::bind(&MessengerThread::event_logEgress, this)); zephyr::event_pool::k_poll_handle(_events[5], std::bind(&MessengerThread::event_gpioEgress, this)); zephyr::event_pool::k_poll_handle(_events[6], std::bind(&MessengerThread::event_operation, this)); + zephyr::event_pool::k_poll_handle(_events[7], std::bind(&MessengerThread::event_utilization, this)); } } } @@ -309,6 +313,10 @@ void MessengerThread::event_dataArrived() { CHECK(handle_opIngressMsg(id, stream, buf.device)); break; } + case utilization_receiver_tag: { + CHECK(handle_utilizationIngressMsg(id, stream, buf.device)); + break; + } default: { std::span b{(std::byte *)buf.data.data(), buf.data.size()}; AsyncUART::transmit(buf.device, b); @@ -353,40 +361,45 @@ void MessengerThread::event_dataArrived() { } void MessengerThread::event_temperatureEgress() { - temperatureEgressQueue.try_consume([&](temperature_EgressMessage &out) { // + temperatureEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, temperature_receiver_tag, out.request_id ? std::optional{out.request_id} : std::nullopt); }); } void MessengerThread::event_ctrlEgress() { - ctrlEgressQueue.try_consume([&](ctrl_EgressMessage &out) { // + ctrlEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, ctrl_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); }); } void MessengerThread::event_configEgress() { - configEgressQueue.try_consume([&](config_EgressMessage &out) { // + configEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, config_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); }); } void MessengerThread::event_logEgress() { - logEgressQueue.try_consume([&](log_EgressMessage &out) { // + logEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, logging_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); }); } void MessengerThread::event_gpioEgress() { - gpioEgressQueue.try_consume([&](gpio_EgressMessage &out) { // + gpioEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, gpio_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); }); } void MessengerThread::event_operation() { - operationEgressQueue.try_consume([&](op_EgressMessage &out) { // + operationEgressQueue.try_consume([&](auto &out) { // push({(std::byte *)&out, sizeof(out)}, operation_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); }); } +void MessengerThread::event_utilization() { + utilizationEgressQueue.try_consume([&](auto &out) { // + push({(std::byte *)&out, sizeof(out)}, utilization_receiver_tag, out.has_request_id ? std::optional{out.request_id} : std::nullopt); + }); +} struct SubsystemMessages { uint32_t subsystem_id; @@ -395,13 +408,14 @@ struct SubsystemMessages { uint32_t tag; }; -constexpr std::array subsystems = { // +constexpr std::array subsystems = { // SubsystemMessages{temperature_receiver_tag, temperature_IngressMessage_msg, temperature_EgressMessage_msg, temperature_IngressMessage_request_id_tag}, SubsystemMessages{ctrl_receiver_tag, ctrl_IngressMessage_msg, ctrl_EgressMessage_msg, ctrl_IngressMessage_request_id_tag}, SubsystemMessages{config_receiver_tag, config_IngressMessage_msg, config_EgressMessage_msg, config_IngressMessage_request_id_tag}, SubsystemMessages{logging_receiver_tag, log_IngressMessage_msg, log_EgressMessage_msg, log_IngressMessage_request_id_tag}, SubsystemMessages{gpio_receiver_tag, gpio_IngressMessage_msg, gpio_EgressMessage_msg, gpio_IngressMessage_request_id_tag}, - SubsystemMessages{operation_receiver_tag, op_IngressMessage_msg, op_EgressMessage_msg, op_IngressMessage_request_id_tag} + SubsystemMessages{operation_receiver_tag, op_IngressMessage_msg, op_EgressMessage_msg, op_IngressMessage_request_id_tag}, + SubsystemMessages{utilization_receiver_tag, utilization_IngressMessage_msg, utilization_EgressMessage_msg, utilization_IngressMessage_request_id_tag} }; constexpr const pb_msgdesc_t &ingress(uint32_t tag) { @@ -455,28 +469,32 @@ std::expected MessengerThread::defaultHandler(uint32_t subsystem_id } std::expected MessengerThread::handle_temperatureIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { - // try to decode stream, and put it to IngressQueue - if (not temperatureIngressQueue.try_produce([&](temperature_IngressMessage &request) { return defaultHandler(temperature_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + if (not temperatureIngressQueue.try_produce([&](auto &request) { return defaultHandler(temperature_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; return {}; } std::expected MessengerThread::handle_ctrlIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { - if (not ctrlIngressQueue.try_produce([&](ctrl_IngressMessage &request) { return defaultHandler(ctrl_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + if (not ctrlIngressQueue.try_produce([&](auto &request) { return defaultHandler(ctrl_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; return {}; } std::expected MessengerThread::handle_configIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { - if (not configIngressQueue.try_produce([&](config_IngressMessage &request) { return defaultHandler(config_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + if (not configIngressQueue.try_produce([&](auto &request) { return defaultHandler(config_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; return {}; } std::expected MessengerThread::handle_gpioIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { - if (not gpioIngressQueue.try_produce([&](gpio_IngressMessage &request) { return defaultHandler(gpio_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + if (not gpioIngressQueue.try_produce([&](auto &request) { return defaultHandler(gpio_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; return {}; } std::expected MessengerThread::handle_opIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { - if (not operationIngressQueue.try_produce([&](op_IngressMessage &request) { return defaultHandler(operation_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + if (not operationIngressQueue.try_produce([&](auto &request) { return defaultHandler(operation_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; + return {}; +} + +std::expected MessengerThread::handle_utilizationIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev) { + if (not utilizationIngressQueue.try_produce([&](auto &request) { return defaultHandler(utilization_receiver_tag, &request, id, stream, dev); })) return std::unexpected{messenger::request_queue_full{}}; return {}; } diff --git a/rims_app/src/messenger.hpp b/rims_app/src/messenger.hpp index 28965473c8f..bb0bc051231 100644 --- a/rims_app/src/messenger.hpp +++ b/rims_app/src/messenger.hpp @@ -159,6 +159,7 @@ class MessengerThread : public ZephyrThread { void event_logEgress(); void event_gpioEgress(); void event_operation(); + void event_utilization(); using handler_ret_t = std::expected; @@ -169,10 +170,11 @@ class MessengerThread : public ZephyrThread { NDIS handler_ret_t handle_configIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev); NDIS handler_ret_t handle_gpioIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev); NDIS handler_ret_t handle_opIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev); + NDIS handler_ret_t handle_utilizationIngressMsg(WireFormatID id, pb_istream_t &stream, AsyncUART *dev); void push(std::span bytes, uint32_t subsystem_id, std::optional requestid); - std::array _events; + std::array _events; public: // a pair of subsystem_id and request requestId diff --git a/rims_app/src/pb_compile_tests.cpp b/rims_app/src/pb_compile_tests.cpp index 10abd29f0db..e9eefc6eb1d 100644 --- a/rims_app/src/pb_compile_tests.cpp +++ b/rims_app/src/pb_compile_tests.cpp @@ -6,10 +6,12 @@ #include #include #include +#include static_assert(CONFIG_PROTO_CONFIGURATION_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); static_assert(LOG_PROTO_LOG_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); static_assert(TEMPERATURE_PROTO_TEMPERATURE_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); static_assert(CTRL_PROTO_CTRL_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); static_assert(GPIO_PROTO_GPIO_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); +static_assert(UTILIZATION_PROTO_UTILIZATION_PB_H_MAX_SIZE <= sizeof(messenger_Message::data.bytes)); // static_assert(OP_PROTO_OPERATION_PB_H_MAX_SIZE <= sizeof(Message::data.bytes)); diff --git a/rims_app/src/utilization.cpp b/rims_app/src/utilization.cpp index 7cdbf70d0c1..b56b098765f 100644 --- a/rims_app/src/utilization.cpp +++ b/rims_app/src/utilization.cpp @@ -1,17 +1,99 @@ #include "utilization.hpp" - -#include #include "log.hpp" +#include +#include +#include +#include namespace rims { -void mythread_analyzer_cb(struct thread_analyzer_info *info) { - ULOG_DEBUG( - "thread name, memfree, cpu : %s, %d, %d", - info->name, - info->stack_size - info->stack_used, - info->utilization // cpu usage in % - ); +K_FIFO_DEFINE(utilizationIngress); +K_FIFO_DEFINE(utilizationEggress); + +fifo_queue utilizationIngressQueue{utilizationIngress}; +fifo_queue utilizationEgressQueue{utilizationEggress}; + +struct StatsAccumulator { + uint32_t total_cpu = 0; + uint32_t total_threads = 0; + uint32_t max_stack_percent = 0; + uint32_t threads_over_stack_threshold = 0; + uint32_t idle_thread_cpu = 0; + uint32_t heap_free_percent = 0; // Optional, left as placeholder +}; + +// Threshold for stack usage warning (e.g. 80%) +constexpr uint32_t STACK_WARN_THRESHOLD = 80; + +// We'll store intermediate values here +static StatsAccumulator stats{}; + +// Simple utility to calculate percent with safety +static uint32_t percent(uint32_t used, uint32_t total) { + if (total == 0) return 0; + return (used * 100) / total; +} + +// Called once per thread by thread_analyzer_run() +extern "C" void mythread_analyzer_cb(struct thread_analyzer_info *info) { + if (!info || info->stack_size == 0) return; + + if (strcmp(info->name, "idle") == 0) { + stats.idle_thread_cpu = info->utilization; + return; + } + + stats.total_threads++; + + uint32_t stack_used = info->stack_size - info->stack_used; + uint32_t stack_used_percent = percent(stack_used, info->stack_size); + + if (stack_used_percent > stats.max_stack_percent) { + stats.max_stack_percent = stack_used_percent; + } + + if (stack_used_percent >= STACK_WARN_THRESHOLD) { + stats.threads_over_stack_threshold++; + } + stats.total_cpu += info->utilization; +} + +// Call after `thread_analyzer_run(mythread_analyzer_cb)` +void finalize_utilization(utilization_UtilizationSummary &summary, utilization_SystemStats &sysstats) { + summary.avg_cpu_percent = percent(stats.total_cpu, stats.total_threads * 100); + summary.max_stack_percent = stats.max_stack_percent; + summary.thread_count = stats.total_threads; + summary.cpu_idle = stats.idle_thread_cpu; + + sysstats.cpu_utilization_percent = stats.total_cpu; + sysstats.max_stack_utilization_percent = stats.max_stack_percent; + sysstats.heap_free_percent = stats.heap_free_percent; // Fill from heap stats if available + sysstats.num_threads_over_threshold = stats.threads_over_stack_threshold; + + // Reset accumulator if reused + stats = StatsAccumulator{}; +} + +// void mythread_analyzer_cb(struct thread_analyzer_info *info) { +// ULOG_DEBUG( +// "thread name, memfree, cpu : %s, %d, %d", +// info->name, +// info->stack_size - info->stack_used, +// info->utilization // cpu usage in % +// ); +// } + +void run_stats_collection() { + thread_analyzer_run(mythread_analyzer_cb, 0); + + utilization_UtilizationSummary summary{}; + utilization_SystemStats sysstats{}; + + finalize_utilization(summary, sysstats); + + // Now encode with nanopb and send + // pb_encode(..., &summary); + // pb_encode(..., &sysstats); } void ThreadAnalyzer::loop() { diff --git a/rims_app/src/utilization.hpp b/rims_app/src/utilization.hpp index 50d7bd110ef..4540978c43f 100644 --- a/rims_app/src/utilization.hpp +++ b/rims_app/src/utilization.hpp @@ -3,8 +3,14 @@ #include "common.hpp" #include +#include "fifo_queue.hpp" +#include "utilization_pb_helpers.hpp" + namespace rims { +extern fifo_queue utilizationIngressQueue; +extern fifo_queue utilizationEgressQueue; + class ThreadAnalyzer { public: ThreadAnalyzer() = default; diff --git a/rims_app/src/utilization_pb_helpers.hpp b/rims_app/src/utilization_pb_helpers.hpp new file mode 100644 index 00000000000..10cace2d4a6 --- /dev/null +++ b/rims_app/src/utilization_pb_helpers.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "message_type_id.hpp" +#include "traits_helpers.hpp" +#include + +namespace rims { + +template <> constexpr int tag() { + return utilization_IngressMessage_utilization_broadcast_config_request_tag; +} + +template <> constexpr int tag() { + return utilization_IngressMessage_utilization_summary_request_tag; +} + + +template <> constexpr int tag() { + return utilization_EgressMessage_utilization_broadcast_config_response_tag; +} + +template <> constexpr int tag() { + return utilization_EgressMessage_utilization_summary_response_tag; +} + +template <> constexpr int tag() { + return utilization_EgressMessage_utilization_broadcast_config_response_tag; +} + +template +constexpr bool is_utilization_ingress_msg_v = is_any_of_v< + MsgT, // + utilization_UtilizationBroadcastConfigRequest, + utilization_UtilizationSummaryRequest>; + +template +constexpr bool is_utilization_egress_msg_v = is_any_of_v< + MsgT, // + utilization_UtilizationBroadcastConfigResponse, + utilization_UtilizationSummaryResponse, + utilization_UtilizationBroadcast>; + +template <> struct ResponseSelector { + using response_t = utilization_UtilizationBroadcastConfigResponse; +}; + +template <> struct ResponseSelector { + using response_t = utilization_UtilizationSummaryResponse; +}; + +template <> struct EgressSelector { + using egress_t = utilization_EgressMessage; +}; + +} // namespace rims