From 145e571411fe3bdf7da5f337fc9a8cf81cfff0f5 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Mon, 1 Jul 2024 21:43:20 +0200 Subject: [PATCH 1/2] Chart optimisations --- src/Chart.cpp | 12 ++-- src/Chart.h | 3 +- src/ESPDash.cpp | 161 +++++++++++++++++++++++++----------------------- 3 files changed, 91 insertions(+), 85 deletions(-) diff --git a/src/Chart.cpp b/src/Chart.cpp index 4088c25d..7397a85c 100644 --- a/src/Chart.cpp +++ b/src/Chart.cpp @@ -58,7 +58,7 @@ void Chart::updateX(int arr_x[], size_t x_size){ _x_axis_i_ptr = arr_x; _x_axis_ptr_size = x_size; #endif - _changed = true; + _x_changed = true; } void Chart::updateX(float arr_x[], size_t x_size){ @@ -73,7 +73,7 @@ void Chart::updateX(float arr_x[], size_t x_size){ _x_axis_f_ptr = arr_x; _x_axis_ptr_size = x_size; #endif - _changed = true; + _x_changed = true; } void Chart::updateX(String arr_x[], size_t x_size){ @@ -88,7 +88,7 @@ void Chart::updateX(String arr_x[], size_t x_size){ _x_axis_s_ptr = arr_x; _x_axis_ptr_size = x_size; #endif - _changed = true; + _x_changed = true; } void Chart::updateX(const char* arr_x[], size_t x_size){ @@ -103,7 +103,7 @@ void Chart::updateX(const char* arr_x[], size_t x_size){ _x_axis_char_ptr = arr_x; _x_axis_ptr_size = x_size; #endif - _changed = true; + _x_changed = true; } void Chart::updateY(int arr_y[], size_t y_size){ @@ -118,7 +118,7 @@ void Chart::updateY(int arr_y[], size_t y_size){ _y_axis_i_ptr = arr_y; _y_axis_ptr_size = y_size; #endif - _changed = true; + _y_changed = true; } void Chart::updateY(float arr_y[], size_t y_size){ @@ -133,7 +133,7 @@ void Chart::updateY(float arr_y[], size_t y_size){ _y_axis_f_ptr = arr_y; _y_axis_ptr_size = y_size; #endif - _changed = true; + _y_changed = true; } /* diff --git a/src/Chart.h b/src/Chart.h index 796b1a3d..e4bfb912 100644 --- a/src/Chart.h +++ b/src/Chart.h @@ -35,7 +35,8 @@ class Chart { uint32_t _id; const char *_name; int _type; - bool _changed; + bool _x_changed; + bool _y_changed; GraphAxisType _x_axis_type; GraphAxisType _y_axis_type; diff --git a/src/ESPDash.cpp b/src/ESPDash.cpp index 3a469990..f0b1a0e9 100644 --- a/src/ESPDash.cpp +++ b/src/ESPDash.cpp @@ -226,7 +226,7 @@ void ESPDash::generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only for (int i = 0; i < charts.Size(); i++) { Chart* c = charts[i]; if (changes_only) { - if (!c->_changed) { + if (!c->_x_changed && !c->_y_changed) { continue; } } @@ -249,7 +249,8 @@ void ESPDash::generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only // Clear change flags if (changes_only) { - c->_changed = false; + c->_x_changed = false; + c->_y_changed = false; } } @@ -402,84 +403,88 @@ void ESPDash::generateComponentJSON(JsonObject& doc, Chart* chart, bool change_o doc["t"] = chartTags[chart->_type].type; } - JsonArray xAxis = doc["x"].to(); - switch (chart->_x_axis_type) { - case GraphAxisType::INTEGER: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_x_axis_i.Size(); i++) - xAxis.add(chart->_x_axis_i[i]); - #else - if (chart->_x_axis_i_ptr != nullptr) { - for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) - xAxis.add(chart->_x_axis_i_ptr[i]); - } - #endif - break; - case GraphAxisType::FLOAT: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_x_axis_f.Size(); i++) - xAxis.add(chart->_x_axis_f[i]); - #else - if (chart->_x_axis_f_ptr != nullptr) { - for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) - xAxis.add(chart->_x_axis_f_ptr[i]); - } - #endif - break; - case GraphAxisType::CHAR: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_x_axis_s.Size(); i++) - xAxis.add(chart->_x_axis_s[i].c_str()); - #else - if (chart->_x_axis_char_ptr != nullptr) { - for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) - xAxis.add(chart->_x_axis_char_ptr[i]); - } - #endif - break; - case GraphAxisType::STRING: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_x_axis_s.Size(); i++) - xAxis.add(chart->_x_axis_s[i].c_str()); - #else - if (chart->_x_axis_s_ptr != nullptr) { - for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) - xAxis.add(chart->_x_axis_s_ptr[i]); - } - #endif - break; - default: - // blank value - break; + if(!change_only || chart->_x_changed) { + JsonArray xAxis = doc["x"].to(); + switch (chart->_x_axis_type) { + case GraphAxisType::INTEGER: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_i.Size(); i++) + xAxis.add(chart->_x_axis_i[i]); + #else + if (chart->_x_axis_i_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_i_ptr[i]); + } + #endif + break; + case GraphAxisType::FLOAT: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_f.Size(); i++) + xAxis.add(chart->_x_axis_f[i]); + #else + if (chart->_x_axis_f_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_f_ptr[i]); + } + #endif + break; + case GraphAxisType::CHAR: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_s.Size(); i++) + xAxis.add(chart->_x_axis_s[i].c_str()); + #else + if (chart->_x_axis_char_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_char_ptr[i]); + } + #endif + break; + case GraphAxisType::STRING: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_x_axis_s.Size(); i++) + xAxis.add(chart->_x_axis_s[i].c_str()); + #else + if (chart->_x_axis_s_ptr != nullptr) { + for(unsigned int i=0; i < chart->_x_axis_ptr_size; i++) + xAxis.add(chart->_x_axis_s_ptr[i]); + } + #endif + break; + default: + // blank value + break; + } } - JsonArray yAxis = doc["y"].to(); - switch (chart->_y_axis_type) { - case GraphAxisType::INTEGER: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_y_axis_i.Size(); i++) - yAxis.add(chart->_y_axis_i[i]); - #else - if (chart->_y_axis_i_ptr != nullptr) { - for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) - yAxis.add(chart->_y_axis_i_ptr[i]); - } - #endif - break; - case GraphAxisType::FLOAT: - #if DASH_USE_LEGACY_CHART_STORAGE == 1 - for(int i=0; i < chart->_y_axis_f.Size(); i++) - yAxis.add(chart->_y_axis_f[i]); - #else - if (chart->_y_axis_f_ptr != nullptr) { - for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) - yAxis.add(chart->_y_axis_f_ptr[i]); - } - #endif - break; - default: - // blank value - break; + if(!change_only || chart->_y_changed) { + JsonArray yAxis = doc["y"].to(); + switch (chart->_y_axis_type) { + case GraphAxisType::INTEGER: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_y_axis_i.Size(); i++) + yAxis.add(chart->_y_axis_i[i]); + #else + if (chart->_y_axis_i_ptr != nullptr) { + for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) + yAxis.add(chart->_y_axis_i_ptr[i]); + } + #endif + break; + case GraphAxisType::FLOAT: + #if DASH_USE_LEGACY_CHART_STORAGE == 1 + for(int i=0; i < chart->_y_axis_f.Size(); i++) + yAxis.add(chart->_y_axis_f[i]); + #else + if (chart->_y_axis_f_ptr != nullptr) { + for(unsigned int i=0; i < chart->_y_axis_ptr_size; i++) + yAxis.add(chart->_y_axis_f_ptr[i]); + } + #endif + break; + default: + // blank value + break; + } } } From f47b9df753fbb878783aca09e2ec094cf7ac4854 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Wed, 3 Jul 2024 12:28:34 +0200 Subject: [PATCH 2/2] Perf improvement and better memory management for ArduinoJson 7 and message fragmentation --- src/ESPDash.cpp | 7 +++++++ src/ESPDash.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/ESPDash.cpp b/src/ESPDash.cpp index f0b1a0e9..7160dfe2 100644 --- a/src/ESPDash.cpp +++ b/src/ESPDash.cpp @@ -343,6 +343,7 @@ void ESPDash::generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only void ESPDash::send(AsyncWebSocketClient* client, JsonDocument& doc) { const size_t len = measureJson(doc); + // ESP_LOGW("ESPDash", "Required Heap size to build WebSocket message: %d bytes. Free Heap: %" PRIu32 " bytes", len, ESP.getFreeHeap()); AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); assert(buffer); serializeJson(doc, buffer->get(), len); @@ -355,7 +356,13 @@ void ESPDash::send(AsyncWebSocketClient* client, JsonDocument& doc) { } bool ESPDash::overflowed(JsonDocument& doc) { +#if DASH_JSON_SIZE > 0 // ArduinoJson 6 (mandatory) or 7 return doc.overflowed() || measureJson(doc.as()) > DASH_JSON_SIZE; +#elif DASH_MIN_FREE_HEAP > 0 // ArduinoJson 7 only + return ESP.getFreeHeap() >= DASH_MIN_FREE_HEAP; +#else // ArduinoJson 7 only + return doc.overflowed(); +#endif } /* diff --git a/src/ESPDash.h b/src/ESPDash.h index 9ed17f29..785fd88b 100644 --- a/src/ESPDash.h +++ b/src/ESPDash.h @@ -45,11 +45,45 @@ Github URL: https://github.com/ayushsharma82/ESP-DASH #include "Chart.h" #include "Statistic.h" +// If DASH_JSON_SIZE is set to a value, ESP-DASH will frequently measure the Json payload to make sure it remains within this size. +// If the Json payload to send is larger, the payload will be split in several parts and sent in multiple messages. +// +// When this value is set: +// - it should not be too large to avoid sending a big message, which takes longer to send and to build because of the frequent json size measurements. 4096 and 8192 are good values for large dashboards. +// - it should not be too small to avoid sending too many messages, which can slow down the dashboard rendering and fill the websocket message queue. 2048 is a good minimum value. +// +// When using ArduinoJson 7, you can set this value to 0 (by default) to disable the websocket message fragmentation in smaller parts and to disable the measurements, to improve performance. +// This will speed up the rendering, at the expense of risking to exhaust the heap in the case of large dashboard. +// To workaround that, when using DASH_JSON_SIZE == 0 with ArduinoJson 7, you can also set DASH_MIN_FREE_HEAP to a value which is more than the size of the biggest payload for your dashboard. +// For example, if your app is big and has a payload sie of 12kb, then you can set DASH_MIN_FREE_HEAP to 16384 (16kb) to make sure the heap is never exhausted. +// When DASH_MIN_FREE_HEAP is set to a value, you instruct ESP-DASH to check the free heap to make sure there is enough heap to send the payload. +// +// In summary: +// +// - With ArduinoJson 6: DASH_JSON_SIZE should be set to a value greater than 0 and fragmentation is used. +// - With ArduinoJson 7: DASH_JSON_SIZE can be set to 0 to disable the fragmentation and the measurements, at the risk of going out of heap. This option gives the best performance but you need to make sure to have enough heap in case your application is large. +// - With ArduinoJson 7: if DASH_JSON_SIZE is set to 0 and DASH_MIN_FREE_HEAP is set to a value greater than 0, heap will be checked before sending the payload and fragmentation will trigger if not enough heap.. +// - DASH_MIN_FREE_HEAP will have no effect is DASH_JSON_SIZE is not set to 0 +// +// To help you decide, you can uncomment line 543 in the cpp which will display the free heap size and teh required heap size to build the websocket message. +// ESP_LOGW("ESPDash", "Required Heap size to build WebSocket message: %d bytes. Free Heap: %" PRIu32 " bytes", len, ESP.getFreeHeap()); #ifndef DASH_JSON_SIZE +#if ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION) #define DASH_JSON_SIZE 2048 +#else +#define DASH_JSON_SIZE 0 +#endif // ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION) +#endif // DASH_JSON_SIZE + +// Only for ArduinoJson 7 +#ifndef DASH_MIN_FREE_HEAP +#define DASH_MIN_FREE_HEAP 0 #endif #if ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION) +#if DASH_JSON_SIZE == 0 +#error "DASH_JSON_SIZE must be set to a value greater than 0 when using ArduinoJson 6" +#endif #define DASH_JSON_DOCUMENT_ALLOCATION DASH_JSON_SIZE * 3 #endif