diff --git a/app/src/lib/stores/analytics.ts b/app/src/lib/stores/analytics.ts index d8fdb17..d931276 100644 --- a/app/src/lib/stores/analytics.ts +++ b/app/src/lib/stores/analytics.ts @@ -9,7 +9,10 @@ let analytics_data = { max_alloc_heap: [], fs_used: [], fs_total: [], - core_temp: [] + core_temp: [], + cpu0_usage: [], + cpu1_usage: [], + cpu_usage: [] }; const maxAnalyticsData = 1000; // roughly 33 Minutes of data at 1 update per 2 seconds @@ -35,7 +38,10 @@ function createAnalytics() { ), fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(-maxAnalyticsData), fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(-maxAnalyticsData), - core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData) + core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData), + cpu0_usage: [...analytics_data.cpu0_usage, content.cpu0_usage].slice(-maxAnalyticsData), + cpu1_usage: [...analytics_data.cpu1_usage, content.cpu1_usage].slice(-maxAnalyticsData), + cpu_usage: [...analytics_data.cpu_usage, content.cpu_usage].slice(-maxAnalyticsData) })); } }; diff --git a/app/src/lib/types/models.ts b/app/src/lib/types/models.ts index 7f95206..b3ab843 100644 --- a/app/src/lib/types/models.ts +++ b/app/src/lib/types/models.ts @@ -108,6 +108,9 @@ export type Analytics = { fs_total: number; fs_used: number; uptime: number; + cpu0_usage: number; + cpu1_usage: number; + cpu_usage: number; }; export type Rssi = { diff --git a/app/src/routes/system/metrics/SystemMetrics.svelte b/app/src/routes/system/metrics/SystemMetrics.svelte index 5aef9ec..1a5b85c 100644 --- a/app/src/routes/system/metrics/SystemMetrics.svelte +++ b/app/src/routes/system/metrics/SystemMetrics.svelte @@ -11,6 +11,9 @@ Chart.register(...registerables); + let cpuChartElement: HTMLCanvasElement; + let cpuChart: Chart; + let heapChartElement: HTMLCanvasElement; let heapChart: Chart; @@ -21,6 +24,87 @@ let temperatureChart: Chart; onMount(() => { + cpuChart = new Chart(cpuChartElement, { + type: 'line', + data: { + labels: $analytics.cpu_usage, + datasets: [ + { + label: 'Cpu usage core 0', + borderColor: daisyColor('--p'), + backgroundColor: daisyColor('--p', 50), + borderWidth: 2, + data: $analytics.cpu0_usage, + yAxisID: 'y' + }, + { + label: 'Cpu usage core 1', + borderColor: daisyColor('--p'), + backgroundColor: daisyColor('--p', 50), + borderWidth: 2, + data: $analytics.cpu1_usage, + yAxisID: 'y' + }, + { + label: 'Cpu usage total', + borderColor: daisyColor('--s'), + backgroundColor: daisyColor('--s', 50), + borderWidth: 2, + data: $analytics.cpu_usage, + yAxisID: 'y' + }, + ] + }, + options: { + maintainAspectRatio: false, + responsive: true, + plugins: { + legend: { + display: true + }, + tooltip: { + mode: 'index', + intersect: false + } + }, + elements: { + point: { + radius: 1 + } + }, + scales: { + x: { + grid: { + color: daisyColor('--bc', 10) + }, + ticks: { + color: daisyColor('--bc') + }, + display: false + }, + y: { + type: 'linear', + title: { + display: true, + text: 'Cpu usage [%]', + color: daisyColor('--bc'), + font: { + size: 16, + weight: 'bold' + } + }, + position: 'left', + min: 0, + max: Math.round($analytics.total_heap[0]), + grid: { color: daisyColor('--bc', 10) }, + ticks: { + color: daisyColor('--bc') + }, + border: { color: daisyColor('--bc', 10) } + } + } + } + }); heapChart = new Chart(heapChartElement, { type: 'line', data: { @@ -230,6 +314,12 @@ }); function updateData() { + cpuChart.data.labels = $analytics.cpu_usage; + cpuChart.data.datasets[0].data = $analytics.cpu0_usage; + cpuChart.data.datasets[1].data = $analytics.cpu1_usage; + cpuChart.data.datasets[2].data = $analytics.cpu_usage; + cpuChart.update('none'); + heapChart.data.labels = $analytics.uptime; heapChart.data.datasets[0].data = $analytics.free_heap; heapChart.data.datasets[1].data = $analytics.max_alloc_heap; @@ -276,6 +366,15 @@ System Metrics +
+
+ +
+
+
#include #include #include +#include +#include -#define MAX_ESP_ANALYTICS_SIZE 1024 +#define MAX_ESP_ANALYTICS_SIZE 2024 #define EVENT_ANALYTICS "analytics" #define ANALYTICS_INTERVAL 2000 class AnalyticsService { public: - AnalyticsService(EventSocket *socket) : _socket(socket){}; + AnalyticsService(EventSocket *socket, TaskManager *taskManager) : _socket(socket), _taskManager(taskManager){}; - void begin() - { - _socket->registerEvent(EVENT_ANALYTICS); + void begin() + { + _socket->registerEvent(EVENT_ANALYTICS); - xTaskCreatePinnedToCore( - this->_loopImpl, // Function that should be called - "Analytics Service", // Name of the task (for debugging) - 5120, // Stack size (bytes) - this, // Pass reference to this class instance - (tskIDLE_PRIORITY), // task priority - NULL, // Task handle - ESP32SVELTEKIT_RUNNING_CORE // Pin to application core - ); - }; + _taskManager->createTask(&AnalyticsService::_loopImpl, "Analytics Service", 8120, this, tskIDLE_PRIORITY, nullptr, + ESP32SVELTEKIT_RUNNING_CORE); + }; protected: EventSocket *_socket; + TaskManager *_taskManager; static void _loopImpl(void *_this) { static_cast(_this)->_loop(); } void _loop() @@ -62,6 +57,19 @@ protected: doc["fs_used"] = ESPFS.usedBytes(); doc["fs_total"] = ESPFS.totalBytes(); doc["core_temp"] = temperatureRead(); + doc["cpu0_usage"] = _taskManager->getCpuUsage(0); + doc["cpu1_usage"] = _taskManager->getCpuUsage(1); + doc["cpu_usage"] = _taskManager->getCpuUsage(); + // Add _taskManager->getTaskNames() as a JSON array + JsonArray tasks = doc.createNestedArray("tasks"); + for (auto const &task : _taskManager->getTasks()) + { + JsonObject nested = tasks.createNestedObject(); + nested["name"] = task.name; + nested["stackSize"] = task.stackSize; + nested["priority"] = task.priority; + nested["coreId"] = task.coreId; + } serializeJson(doc, message); _socket->emit(EVENT_ANALYTICS, message); diff --git a/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.cpp b/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.cpp index 07a09ed..7a43453 100644 --- a/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.cpp +++ b/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.cpp @@ -107,9 +107,9 @@ void updateTask(void *param) DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, - EventSocket *socket) : _server(server), + EventSocket *socket, TaskManager *taskManager) : _server(server), _securityManager(securityManager), - _socket(socket) + _socket(socket), _taskManager(taskManager) { } @@ -148,9 +148,9 @@ esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonV _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); - if (xTaskCreatePinnedToCore( + if (_taskManager->createTask( &updateTask, // Function that should be called - "Update", // Name of the task (for debugging) + "Firmware download", // Name of the task (for debugging) OTA_TASK_STACK_SIZE, // Stack size (bytes) &downloadURL, // Pass reference to this class instance (configMAX_PRIORITIES - 1), // Pretty high task priority diff --git a/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.h b/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.h index b41f5cc..8143ae7 100644 --- a/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.h +++ b/esp32/lib/ESP32-sveltekit/DownloadFirmwareService.h @@ -24,6 +24,7 @@ #include #include +#include // #include #define GITHUB_FIRMWARE_PATH "/api/downloadUpdate" @@ -33,7 +34,7 @@ class DownloadFirmwareService { public: - DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, EventSocket *socket); + DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, EventSocket *socket, TaskManager *taskManager); void begin(); @@ -41,5 +42,6 @@ private: SecurityManager *_securityManager; PsychicHttpServer *_server; EventSocket *_socket; + TaskManager *_taskManager; esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json); }; diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp index a784a3e..e4f6969 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp @@ -16,7 +16,7 @@ #include ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEndpoints) - : _server(server), _numberEndpoints(numberEndpoints), + : _server(server), _numberEndpoints(numberEndpoints), _taskManager(), _featureService(server), _securitySettingsService(server, &ESPFS), _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), _wifiScanner(server, &_securitySettingsService), @@ -32,7 +32,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _uploadFirmwareService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_DOWNLOAD_FIRMWARE) - _downloadFirmwareService(server, &_securitySettingsService, &_socket), + _downloadFirmwareService(server, &_securitySettingsService, &_socket, &_taskManager), #endif #if FT_ENABLED(FT_MQTT) _mqttSettingsService(server, &ESPFS, &_securitySettingsService), @@ -48,7 +48,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _batteryService(&_socket), #endif #if FT_ENABLED(FT_ANALYTICS) - _analyticsService(&_socket), + _analyticsService(&_socket, &_taskManager), #endif _restartService(server, &_securitySettingsService), _factoryResetService(server, &ESPFS, &_securitySettingsService), @@ -69,7 +69,7 @@ void ESP32SvelteKit::begin() { startServices(); ESP_LOGV("ESP32SvelteKit", "Starting loop task"); - xTaskCreatePinnedToCore(this->_loopImpl, "ESP32 SvelteKit Loop", 4096, this, + _taskManager.createTask(this->_loopImpl, "Spot main", 4096, this, (tskIDLE_PRIORITY + 1), NULL, ESP32SVELTEKIT_RUNNING_CORE); } @@ -178,6 +178,8 @@ void ESP32SvelteKit::startServices() { #if FT_ENABLED(FT_BATTERY) _batteryService.begin(); #endif + + _taskManager.begin(); } void ESP32SvelteKit::_loop() { diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h index 7d3fc3e..2e4e746 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h @@ -148,6 +148,11 @@ public: return &_featureService; } + TaskManager *getTaskManager() + { + return &_taskManager; + } + void factoryReset() { _factoryResetService.factoryReset(); @@ -203,6 +208,7 @@ private: RestartService _restartService; FactoryResetService _factoryResetService; SystemStatus _systemStatus; + TaskManager _taskManager; String _appName = APP_NAME; diff --git a/esp32/lib/ESP32-sveltekit/TaskManager.h b/esp32/lib/ESP32-sveltekit/TaskManager.h index f13a2a0..a764b70 100644 --- a/esp32/lib/ESP32-sveltekit/TaskManager.h +++ b/esp32/lib/ESP32-sveltekit/TaskManager.h @@ -3,77 +3,179 @@ #include #include #include +#include #define IDLE_STACK_SIZE 2048 #define DEFAULT_STACK_SIZE 2048+512 #define DELETE_TASK(handle) if (handle != nullptr) vTaskDelete(handle) +struct task_t +{ + String name; + TaskHandle_t handle; + uint32_t stackSize; + UBaseType_t priority; + BaseType_t coreId; + bool pinned; + bool active; // blocked ('B'), ready ('R'), deleted ('D') or suspended ('S'). +}; + +class IdleTask +{ + private: + float _idleRatio = 0; + unsigned long _lastMeasurement; + + const int kMillisPerLoop = 1; + const int kMillisPerCalc = 1000; + + unsigned long counter = 0; + + public: + void ProcessIdleTime() + { + _lastMeasurement = millis(); + counter = 0; + + for(;;) + { + int delta = millis() - _lastMeasurement; + if (delta >= kMillisPerCalc) + { + _idleRatio = static_cast(counter) / delta; + _lastMeasurement = millis(); + counter = 0; + } + else + { + esp_task_wdt_reset(); + delayMicroseconds(kMillisPerLoop * 1000); + counter += kMillisPerLoop; + } + } + } + + IdleTask() : _lastMeasurement(millis()) + { + } + + float GetCPUUsage() const + { + if (millis() - _lastMeasurement > kMillisPerCalc) + return 100.0f; + + return 100.0f - 100 * _idleRatio; + } + + static void IdleTaskEntry(void *that) + { + static_cast(that)->ProcessIdleTime(); + } +}; + class TaskManager { private: - std::map tasks; + std::map _tasks; + IdleTask _taskIdle0; + IdleTask _taskIdle1; - static void idleTask(void *pvParameters) - { - while (true) - { - vTaskDelay(pdMS_TO_TICKS(1000)); // Delay to simulate workload - } - } + TaskHandle_t _hIdle0; + TaskHandle_t _hIdle1; public: TaskManager() { - xTaskCreatePinnedToCore(idleTask, "IdleTaskCore0", 1024, nullptr, 0, nullptr, 0); - xTaskCreatePinnedToCore(idleTask, "IdleTaskCore1", 1024, nullptr, 0, nullptr, 1); } - void createTask(const std::string &name, void (*taskFunction)(void *), void *params = nullptr, - uint32_t stackSize = 2048, UBaseType_t priority = 1) + void begin() { - TaskHandle_t handle; - xTaskCreate(taskFunction, name.c_str(), stackSize, params, priority + 1, &handle); - tasks[name] = handle; + createTask(IdleTask::IdleTaskEntry, "Idle Core 0", IDLE_STACK_SIZE, &_taskIdle0, tskIDLE_PRIORITY-1, &_hIdle0, 0); + createTask(IdleTask::IdleTaskEntry, "Idle Core 1", IDLE_STACK_SIZE, &_taskIdle1, tskIDLE_PRIORITY-1, &_hIdle1, 1); + esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); + esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(1)); + esp_task_wdt_add(_hIdle0); + esp_task_wdt_add(_hIdle1); } - void suspendTask(const std::string &name) + std::vector getTasks() { - if (tasks.find(name) != tasks.end()) + std::vector tasks; + for (auto const &task : _tasks) + tasks.push_back(task.second); + return tasks; + } + + int getTaskCount() const + { + return _tasks.size(); + } + + int getKernelTaskCount() const + { + return uxTaskGetNumberOfTasks(); + } + + void update() + { + for (auto const &task : _tasks) { - vTaskSuspend(tasks[name]); + _tasks[task.first].priority = uxTaskPriorityGet(task.second.handle); + _tasks[task.first].coreId = xTaskGetAffinity(task.second.handle); } } - void resumeTask(const std::string &name) + float getCpuUsage(int iCore = -1) const { - if (tasks.find(name) != tasks.end()) + if (iCore == 0) return _taskIdle0.GetCPUUsage(); + else if (iCore == 1) return _taskIdle1.GetCPUUsage(); + return (_taskIdle0.GetCPUUsage() + _taskIdle1.GetCPUUsage()) / 2; + } + + BaseType_t createTask(void (*taskFunction)(void *), const char * name, uint32_t stackSize = 2048, + void *params = nullptr, UBaseType_t priority = tskIDLE_PRIORITY + 1, TaskHandle_t* handle = nullptr, + BaseType_t coreId = -1) + { + BaseType_t res = coreId == -1 + ? xTaskCreate(taskFunction, name, stackSize, params, priority + 1, handle) + : xTaskCreatePinnedToCore(taskFunction, name, stackSize, params, priority + 1, handle, coreId); + task_t task = {name, handle, stackSize, priority + 1, coreId, coreId != -1, true}; + if (res == pdPASS) + _tasks[name] = task; + return res; + } + + void suspendTask(const char * name) + { + if (_tasks.find(name) != _tasks.end()) { - vTaskResume(tasks[name]); + vTaskSuspend(_tasks[name].handle); + _tasks[name].active = false; } } - void notifyTask(const std::string &name, uint32_t notificationValue, eNotifyAction action = eSetValueWithOverwrite) + void resumeTask(const char * name) { - if (tasks.find(name) != tasks.end()) + if (_tasks.find(name) != _tasks.end()) { - xTaskNotify(tasks[name], notificationValue, action); + vTaskResume(_tasks[name].handle); + _tasks[name].active = true; } } - void deleteTask(const std::string &name) + void notifyTask(const char * name, uint32_t notificationValue, eNotifyAction action = eSetValueWithOverwrite) { - if (tasks.find(name) != tasks.end()) - { - vTaskDelete(tasks[name]); - tasks.erase(name); - } + if (_tasks.find(name) != _tasks.end()) + xTaskNotify(_tasks[name].handle, notificationValue, action); } - float getCpuUsage(uint8_t coreId) + void deleteTask(const char * name) { - if (coreId > 1) - return 0.0; // ESP32 has only core 0 and 1 - return uxTaskGetSystemState(nullptr, 0, nullptr) * 100.0 / configTICK_RATE_HZ; + if (_tasks.find(name) != _tasks.end()) + { + vTaskDelete(_tasks[name].handle); + _tasks.erase(name); + } } }; \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp b/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp index 074f096..992b4e2 100644 --- a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp +++ b/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp @@ -215,8 +215,8 @@ void WiFiSettingsService::configureNetwork(wifi_settings_t &network) void WiFiSettingsService::updateRSSI() { - char buffer[16]; - snprintf(buffer, sizeof(buffer), WiFi.isConnected() ? "%d" : "disconnected", WiFi.RSSI()); + char buffer[4]; + snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); _socket->emit(EVENT_RSSI, buffer); } diff --git a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h b/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h index 92b5622..54feaeb 100644 --- a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h +++ b/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h @@ -48,7 +48,7 @@ #define WIFI_SETTINGS_SERVICE_PATH "/api/wifiSettings" #define WIFI_RECONNECTION_DELAY 1000 * 30 -#define RSSI_EVENT_DELAY 200 +#define RSSI_EVENT_DELAY 500 #define WIFI_SETTINGS_BUFFER_SIZE 2048