khoih-prog / Portenta_H7_AsyncWebServer

Asynchronous WebServer Library for STM32H7-based Portenta_H7 using mbed_portenta core. This library, which is relied on Portenta_H7_AsyncTCP, is part of a series of advanced Async libraries, such as AsyncTCP, AsyncUDP, AsyncWebSockets, AsyncHTTPRequest, AsyncHTTPSRequest, etc. Now supporting using CString in optional SDRAM to save heap to send very large data
GNU Lesser General Public License v3.0
12 stars 3 forks source link

request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #6

Closed khoih-prog closed 2 years ago

khoih-prog commented 2 years ago

Posted by @salasidis as request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #2 in Portenta_H7_AsyncTCP library


Is your feature request related to a problem? Please describe.

I have a relatively web page - about 100kBytes. In addition, I need to send large amounts of Json data - about 1Mbyte sensor data via the async web interface.

I save all the data in the SDRAM (the web page as well as the Json data string (created from SD Card logged data).

When responding to the request via request->send(200, textPlainStr, jsonChartDataCharStr); // jsonChartDataCharStr is a C type char * string

The library converts the (char *) array (JSon or the web page) into a String class variable before sending. This results in large amounts of heap use, and basically I cannot send more than 4 months of telemetry data (whereas the design calls for at least 2 years).

Describe the solution you'd like

I would like to have a request->send call that looks at the variable passed, and if it is a char * leaves it as such, without changing it to a String. Not only will this be faster, but it will also use a LOT less memory

Describe alternatives you've considered

My alternatives of making many calls to retrieve the data in small batches would add needless complexity, and also require multiple accesses to the SD card, which I want to avoid

Additional context

This would not be an issue if heap space was not so critical, but the portenta only has a small finite amount, and this library consumes about 80+% of it in my application in a single function call.


It would be nice if any mallocs could be limited, or at least made to optionally use the SDRAM, and not the main RAM.

Also any string manipulations (adding a header etc) could be done in place - just having to ensure that the passed string is at least a minimum size larger than the contents it holds.


On the default program, I added some code to print the heap size, and I increased the graph array from 50 to 500 points.

On the modified program, I created a C string instead of an Arduino String, and sent the same 500 line graph svg file

There is a word file that shows the outputs, and the outputs demonstrate that if sending an Arduino String, the software requires 2x extra heap space on top of the the actual string size.

If sending a C string, it takes 3x.

My proposal is to have an option (separate call, or modify the current ones), that can use the c string as is, and simply pre and post append to it any headers and footers required, without creating new Arduino Strings in the library


File Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino

/****************************************************************************************************************************
  Async_AdvancedWebServer.ino - Dead simple AsyncWebServer for STM32 LAN8720 or built-in LAN8742A Ethernet

  For Portenta_H7 (STM32H7) with Vision-Shield Ethernet

  Portenta_H7_AsyncWebServer is a library for the Portenta_H7 with with Vision-Shield Ethernet

  Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer)
  Built by Khoi Hoang https://github.com/khoih-prog/Portenta_H7_AsyncWebServer
  Licensed under GPLv3 license

  Copyright (c) 2015, Majenko Technologies
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

  Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.

  Neither the name of Majenko Technologies nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************************************************************/

#if !( defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) )
  #error For Portenta_H7 only
#endif

#define _PORTENTA_H7_ATCP_LOGLEVEL_     1
#define _PORTENTA_H7_AWS_LOGLEVEL_      1

#define USE_ETHERNET_PORTENTA_H7        true

#include <Portenta_Ethernet.h>
#include <Ethernet.h>
#warning Using Portenta_Ethernet lib for Portenta_H7.

#include <Portenta_H7_AsyncWebServer.h>

// Enter a MAC address and IP address for your controller below.
#define NUMBER_OF_MAC      20

byte mac[][NUMBER_OF_MAC] =
{
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x01 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x02 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x03 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x04 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x05 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x06 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x07 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x08 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x09 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0A },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0B },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0C },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0D },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0E },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0F },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x10 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x11 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x12 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x13 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x14 },
};
// Select the IP address according to your local network
IPAddress ip(192, 168, 2, 232);

AsyncWebServer    server(80);

int reqCount = 0;                // number of requests received

#define LED_OFF             HIGH
#define LED_ON              LOW

#define BUFFER_SIZE         512
char temp[BUFFER_SIZE];

void handleRoot(AsyncWebServerRequest *request)
{
  digitalWrite(LED_BUILTIN, LED_ON);

  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
  int day = hr / 24;

  snprintf(temp, BUFFER_SIZE - 1,
           "<html>\
<head>\
<meta http-equiv='refresh' content='5'/>\
<title>AsyncWebServer-%s</title>\
<style>\
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
</style>\
</head>\
<body>\
<h2>AsyncWebServer_Portenta_H7!</h2>\
<h3>running on %s</h3>\
<p>Uptime: %d d %02d:%02d:%02d</p>\
<img src=\"/test.svg\" />\
</body>\
</html>", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60);

  request->send(200, "text/html", temp);

  digitalWrite(LED_BUILTIN, LED_OFF);
}

void handleNotFound(AsyncWebServerRequest *request)
{
  digitalWrite(LED_BUILTIN, LED_ON);
  String message = "File Not Found\n\n";

  message += "URI: ";
  message += request->url();
  message += "\nMethod: ";
  message += (request->method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += request->args();
  message += "\n";

  for (uint8_t i = 0; i < request->args(); i++)
  {
    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
  }

  request->send(404, "text/plain", message);
  digitalWrite(LED_BUILTIN, LED_OFF);
}

void PrintHeapData(String hIn){
  mbed_stats_heap_t heap_stats;

  Serial.print("HEAP DATA - ");
  Serial.print(hIn);

  mbed_stats_heap_get(&heap_stats);
  Serial.print("  Cur heap: ");
  Serial.print(heap_stats.current_size);
  Serial.print("  Res Size: ");
  Serial.print(heap_stats.reserved_size);
  Serial.print("  Max heap: ");
  Serial.println(heap_stats.max_size);
}

void drawGraph(AsyncWebServerRequest *request)
{
  String out;

  out.reserve(40000);
  char temp[70];

  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"310\" height=\"150\">\n";
  out += "<rect width=\"310\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"2\" stroke=\"rgb(0, 0, 0)\" />\n";
  out += "<g stroke=\"blue\">\n";
  int y = rand() % 130;

  for (int x = 10; x < 5000; x += 10)
  {
    int y2 = rand() % 130;
    sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"2\" />\n", x, 140 - y, x + 10, 140 - y2);
    out += temp;
    y = y2;
  }
  out += "</g>\n</svg>\n";

  PrintHeapData("Pre Send");

  Serial.print("Out String Length=");
  Serial.println(out.length());

  request->send(200, "image/svg+xml", out);

  PrintHeapData("Post Send");
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LED_OFF);

  Serial.begin(115200);
  while (!Serial);

  delay(200);

  Serial.print("\nStart Async_AdvancedWebServer on "); Serial.print(BOARD_NAME);
  Serial.print(" with "); Serial.println(SHIELD_TYPE);
  Serial.println(PORTENTA_H7_ASYNC_TCP_VERSION);
  Serial.println(PORTENTA_H7_ASYNC_WEBSERVER_VERSION);

  ///////////////////////////////////

  // start the ethernet connection and the server
  // Use random mac
  uint16_t index = millis() % NUMBER_OF_MAC;

  // Use Static IP
  //Ethernet.begin(mac[index], ip);
  // Use DHCP dynamic IP and random mac
  Ethernet.begin(mac[index]);

  if (Ethernet.hardwareStatus() == EthernetNoHardware)
  {
    Serial.println("No Ethernet found. Stay here forever");

    while (true)
    {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }

  if (Ethernet.linkStatus() == LinkOFF)
  {
    Serial.println("Not connected Ethernet cable");
  }

  Serial.print(F("Using mac index = "));
  Serial.println(index);

  Serial.print(F("Connected! IP address: "));
  Serial.println(Ethernet.localIP());

  ///////////////////////////////////

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    handleRoot(request);
  });

  server.on("/test.svg", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    drawGraph(request);
  });

  server.on("/inline", [](AsyncWebServerRequest * request)
  {
    request->send(200, "text/plain", "This works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();

  Serial.print(F("HTTP EthernetWebServer is @ IP : "));
  Serial.println(Ethernet.localIP());

  PrintHeapData("Pre Create Arduino String");

}

void heartBeatPrint()
{
  static int num = 1;

  Serial.print(F("."));

  if (num == 80)
  {
    Serial.println();
    num = 1;
  }
  else if (num++ % 10 == 0)
  {
    Serial.print(F(" "));
  }
}

void check_status()
{
  static unsigned long checkstatus_timeout = 0;

#define STATUS_CHECK_INTERVAL     10000L

  // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change.
  if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0))
  {
    heartBeatPrint();
    checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL;
  }
}

void loop()
{
  check_status();
}

File Async_AdvancedWebServer_MemoryIssues_Send_CString.ino

/****************************************************************************************************************************
  Async_AdvancedWebServer.ino - Dead simple AsyncWebServer for STM32 LAN8720 or built-in LAN8742A Ethernet

  For Portenta_H7 (STM32H7) with Vision-Shield Ethernet

  Portenta_H7_AsyncWebServer is a library for the Portenta_H7 with with Vision-Shield Ethernet

  Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer)
  Built by Khoi Hoang https://github.com/khoih-prog/Portenta_H7_AsyncWebServer
  Licensed under GPLv3 license

  Copyright (c) 2015, Majenko Technologies
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

  Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.

  Neither the name of Majenko Technologies nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************************************************************/

#if !( defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) )
  #error For Portenta_H7 only
#endif

#define _PORTENTA_H7_ATCP_LOGLEVEL_     1
#define _PORTENTA_H7_AWS_LOGLEVEL_      1

#define USE_ETHERNET_PORTENTA_H7        true

#include <Portenta_Ethernet.h>
#include <Ethernet.h>
#warning Using Portenta_Ethernet lib for Portenta_H7.

#include <Portenta_H7_AsyncWebServer.h>

#include "SDRAM.h"

// Enter a MAC address and IP address for your controller below.
#define NUMBER_OF_MAC      20

byte mac[][NUMBER_OF_MAC] =
{
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x01 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x02 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x03 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x04 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x05 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x06 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x07 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x08 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x09 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0A },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0B },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0C },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0D },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0E },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0F },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x10 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x11 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x12 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x13 },
  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x14 },
};
// Select the IP address according to your local network
IPAddress ip(192, 168, 2, 232);

AsyncWebServer    server(80);

int reqCount = 0;                // number of requests received

#define LED_OFF             HIGH
#define LED_ON              LOW

#define BUFFER_SIZE         512
char temp[BUFFER_SIZE];

void handleRoot(AsyncWebServerRequest *request)
{
  digitalWrite(LED_BUILTIN, LED_ON);

  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
  int day = hr / 24;

  snprintf(temp, BUFFER_SIZE - 1,
           "<html>\
<head>\
<title>AsyncWebServer-%s</title>\
<style>\
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
</style>\
</head>\
<body>\
<h2>AsyncWebServer_Portenta_H7!</h2>\
<h3>running on %s</h3>\
<p>Uptime: %d d %02d:%02d:%02d</p>\
<img src=\"/test.svg\" />\
</body>\
</html>", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60);

  request->send(200, "text/html", temp);

  digitalWrite(LED_BUILTIN, LED_OFF);
}

void handleNotFound(AsyncWebServerRequest *request)
{
  digitalWrite(LED_BUILTIN, LED_ON);
  String message = "File Not Found\n\n";

  message += "URI: ";
  message += request->url();
  message += "\nMethod: ";
  message += (request->method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += request->args();
  message += "\n";

  for (uint8_t i = 0; i < request->args(); i++)
  {
    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
  }

  request->send(404, "text/plain", message);
  digitalWrite(LED_BUILTIN, LED_OFF);
}

void PrintHeapData(String hIn){
  mbed_stats_heap_t heap_stats;

  Serial.print("HEAP DATA - ");
  Serial.print(hIn);

  mbed_stats_heap_get(&heap_stats);
  Serial.print("  Cur heap: ");
  Serial.print(heap_stats.current_size);
  Serial.print("  Res Size: ");
  Serial.print(heap_stats.reserved_size);
  Serial.print("  Max heap: ");
  Serial.println(heap_stats.max_size);
}

char *cStr;

void drawGraph(AsyncWebServerRequest *request) {
  char temp[80];
  String out;

  out.reserve(4000);

  cStr[0] = '\0';

  strcat(cStr, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"310\" height=\"150\">\n");
  strcat(cStr, "<rect width=\"310\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"2\" stroke=\"rgb(0, 0, 0)\" />\n");
  strcat(cStr, "<g stroke=\"blue\">\n");
  int y = rand() % 130;

  for (int x = 10; x < 5000; x += 10)
  {
    int y2 = rand() % 130;
    sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"2\" />\n", x, 140 - y, x + 10, 140 - y2);
    strcat(cStr, temp);
    y = y2;
  }
  strcat(cStr, "</g>\n</svg>\n");

  PrintHeapData("Pre Send");

  Serial.print("Out String Length=");
  Serial.println(strlen(cStr));

  request->send(200, "image/svg+xml", cStr);

  PrintHeapData("Post Send");
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LED_OFF);

  Serial.begin(115200);
  while (!Serial);

  delay(200);

  Serial.print("\nStart Async_AdvancedWebServer on "); Serial.print(BOARD_NAME);
  Serial.print(" with "); Serial.println(SHIELD_TYPE);
  Serial.println(PORTENTA_H7_ASYNC_TCP_VERSION);
  Serial.println(PORTENTA_H7_ASYNC_WEBSERVER_VERSION);

  SDRAM.begin();

  cStr = (char *)SDRAM.malloc(100000);    // make a little larger than required

  if (cStr == NULL) {
    Serial.println("Unable top Allocate RAM");
    for(;;);
  }

  ///////////////////////////////////

  // start the ethernet connection and the server
  // Use random mac
  uint16_t index = millis() % NUMBER_OF_MAC;

  // Use Static IP
  //Ethernet.begin(mac[index], ip);
  // Use DHCP dynamic IP and random mac
  Ethernet.begin(mac[index]);

  if (Ethernet.hardwareStatus() == EthernetNoHardware)
  {
    Serial.println("No Ethernet found. Stay here forever");

    while (true)
    {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }

  if (Ethernet.linkStatus() == LinkOFF)
  {
    Serial.println("Not connected Ethernet cable");
  }

  Serial.print(F("Using mac index = "));
  Serial.println(index);

  Serial.print(F("Connected! IP address: "));
  Serial.println(Ethernet.localIP());

  ///////////////////////////////////

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    handleRoot(request);
  });

  server.on("/test.svg", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    drawGraph(request);
  });

  server.on("/inline", [](AsyncWebServerRequest * request)
  {
    request->send(200, "text/plain", "This works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();

  Serial.print(F("HTTP EthernetWebServer is @ IP : "));
  Serial.println(Ethernet.localIP());

  PrintHeapData("Pre Create Arduino String");

}

void heartBeatPrint()
{
  static int num = 1;

  Serial.print(F("."));

  if (num == 80)
  {
    Serial.println();
    num = 1;
  }
  else if (num++ % 10 == 0)
  {
    Serial.print(F(" "));
  }
}

void check_status()
{
  static unsigned long checkstatus_timeout = 0;

#define STATUS_CHECK_INTERVAL     10000L

  // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change.
  if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0))
  {
    heartBeatPrint();
    checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL;
  }
}

void loop()
{
  check_status();
}

Heap Data when Sending a 500 line string

Heap Data when Sending a 500 line string (string is 31.2k long). Max Heap = 7.4k – before executing graph routine Max Heap = 48.5k – after reserving 40k for out string Max Heap = 111.2k – after send When sending am Arduino String, the library requires 2x the String length in extra heap.

Using mac index = 14
Connected! IP address: 192.168.0.72
HTTP EthernetWebServer is @ IP : 192.168.0.72
HEAP DATA - Pre Create Arduino String  Cur heap: 7434  Res Size: 452112  Max heap: 7448
.HEAP DATA - Pre Send  Cur heap: 48566  Res Size: 452112  Max heap: 48566
Out String Length=31257
HEAP DATA - Post Send  Cur heap: 78958  Res Size: 452112  Max heap: 111294
HEAP DATA - Pre Send  Cur heap: 48566  Res Size: 452112  Max heap: 111294

The second program modifies the source code, so that instead of creating the out String, I create a C string called cStr. This has a buffer allocated in SDRAM. I then create the cStr in a similar fashion as before, except using strcat etc. (I remove the meta .. refresh – just to make the output cleaner)

When running the code – a 500 line graph string is sent here as well – the string is again 31.2k long Max Heap = 8.4k – before executing graph routine Max Heap = 13.5k after creating the string, but before send *** note that there is no heap increase as before Max Heap = 107.5k – after send

When sending a c string, the web server uses 3x the c string size iin heap space (1x more than before, as it makes a String out of the c string before proceeding

Start Async_AdvancedWebServer on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
Portenta_H7_AsyncTCP v1.4.0
Portenta_H7_AsyncWebServer v1.3.0
Using mac index = 6
Connected! IP address: 192.168.0.72
HTTP EthernetWebServer is @ IP : 192.168.0.72
HEAP DATA - Pre Create Arduino String  Cur heap: 8458  Res Size: 452000  Max heap: 8472
.HEAP DATA - Pre Send  Cur heap: 13590  Res Size: 452000  Max heap: 13590
Out String Length=31257
HEAP DATA - Post Send  Cur heap: 43982  Res Size: 452000  Max heap: 107576
khoih-prog commented 2 years ago

Hi @salasidis

I don't know if you're using something different, but as I test here, the HEAP is not x2 as you experienced

Using Arduino String

Start Async_AdvancedWebServer on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
Portenta_H7_AsyncTCP v1.4.0
Portenta_H7_AsyncWebServer v1.3.0
Using mac index = 13
Connected! IP address: 192.168.2.123
HTTP EthernetWebServer is @ IP : 192.168.2.123
HEAP DATA - Pre Create Arduino String  Cur heap: 7434  Res Size: 452048  Max heap: 7448
..HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 48611
Out String Length=31257
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82115
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82115
Out String Length=31277
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31229
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31237
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31257
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31247
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31198
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82135
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82135
Out String Length=31285
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82143
HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82143
Out String Length=31224
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 82143
.HEAP DATA - Pre Send  Cur heap: 50426  Res Size: 452048  Max heap: 82143
Out String Length=31269
HEAP DATA - Post Send  Cur heap: 50521  Res Size: 452048  Max heap: 83942
HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 83942
Out String Length=31261
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 83942
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 83942
Out String Length=31237
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap: 83942

The current code is already using passing-by-reference to avoid unnecessary copy. The copy must take place once, to finally store the String to local var, to be sure the to-be-sent data is still there after the original String has been destroyed.

If you're interested, I'll post the full process, so that you know the data flow (without unnecessary copy).

salasidis commented 2 years ago

If you look at the top of your example

Start Async_AdvancedWebServer on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
Portenta_H7_AsyncTCP v1.4.0
Portenta_H7_AsyncWebServer v1.3.0
Using mac index = 13
Connected! IP address: 192.168.2.123
HTTP EthernetWebServer is @ IP : 192.168.2.123
HEAP DATA - Pre Create Arduino String  Cur heap:  `7434`  Res Size: 452048  Max heap: `7448`
..HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap  `48611`
Out String Length=31257
HEAP DATA - Post Send  Cur heap: 48706  Res Size: 452048  Max heap:  `82115`
.HEAP DATA - Pre Send  Cur heap: 48611  Res Size: 452048  Max heap: 82115

You can see that the max heap starts at 7.4k

Goes to 48k with the String reserve

Then goes to 82k when the send occurs.

So the max heap is increasing. I noticed sometimes it is only 2x and not 3x, but did not uncover when that is the case.

The heap is recovered after the code runs, but the problem is that the program will crash if a large enough string is passed that the max heap hits the 430-450k mark.

In my other example, If passing a c string, then the string has to first be created, so that uses Heap RAM (same as having created the string in the first place).

In my case, I am sending sensor data, and at 3 months worth, my heap grows to a max of 374k (basically another 60k, and I would expect crashes). However, the program is only using about 60-80k of total heap space when not sending data via the library.

My goal is to be able to send a 2Mb string (2 yrs of data) that is stored in SDRAM. In order to be able to do that, any string manipulations would have to avoid arduino strings, and do all the concatenating etc in place on the passed string (as mentioned though, the string buffer would have to be oversized enough to be able to take the added data)

khoih-prog commented 2 years ago

Ah, I see your point now. The HEAP must expand because of the mandatory copy as mentioned here

The copy must take place once, to finally store the String to local var, to be sure the to-be-sent data is still there after the original String has been destroyed.


Private var to store the String

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/blob/8297aa2ced8cdb6962541b9b455928d05bc1ad77/src/Portenta_H7_AsyncWebResponseImpl.h#L45

The copy is here, to store to private var

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/blob/8297aa2ced8cdb6962541b9b455928d05bc1ad77/src/Portenta_H7_AsyncWebResponses.cpp#L255-L258


request->send(200, "image/svg+xml", out);

We could use other way to avoid copy (such as pointer, reference, etc), but we can not know if the original String out is still there when needed !!! Therefore, copy to be sure.

khoih-prog commented 2 years ago

If you can be sure the original String out can be preserved, we can create another function to avoid the copy.

For example

request->sendConstantString(200, "image/svg+xml", `out`);
khoih-prog commented 2 years ago

My goal is to be able to send a 2Mb string (2 yrs of data) that is stored in SDRAM. In order to be able to do that, any string manipulations would have to avoid arduino strings, and do all the concatenating etc in place on the passed string (as mentioned though, the string buffer would have to be oversized enough to be able to take the added data)

A little bit odd here, why you have to send 2MBytes (2 yrs of data) in one String ??? Why can't you divide it to many smaller pieces, or to send it more frequently ???

MPUs, with very limited resource, have to be used / treated very carefully and differently from normal PCs.

salasidis commented 2 years ago

It would be best if there was a second function that could take a C string - that way the string can be sent straight from SDRAM. It would save a lot of memory, both for the sending of data, as well as the loading of the original web page (my web page is 70k long - after offloading a bunch of stuff to CDN).

I create the web page in an SDRAM string, and after my web page load, the heap goes to 200k in my case. I will unload some of the constant scripts to CDN as well - so this one is manageable, but for others where an even larger web page is created, or if CDN is not possible, then the proposed mod would help them as well.

In the data example, the sensor data is stored in the SD card. There are many sensors, and each one may have 2Mb of data. The web page received the data for making custom graphs. It is possible to break up the data into 8 chunks, but that would require 8 separate accesses to the SD card, with 8 file seeks to the correct positions ... Then the data has to be sent to the graphing widget piecemeal, which also takes time. It would all result in creating a graph that is likely 3-4x slower than doing it in one shot.

I tried to follow the sequence of the library from the send on out, and I think a c string would be possible, and the only manipulation that was required was adding I believe a header? and a footer (? terminology). Both of those would be done with an in place shift copy of the original string, then copy the header (without the trailing '\0', followed by a strcat of the footer. I think it would be reasonably fast, and would definitely not take any extra memory, except for the small header and footer strings.

What do you think?

Robert

khoih-prog commented 2 years ago

I tried to follow the sequence of the library from the send on out, and I think a c string would be possible, and the only manipulation that was required was adding I believe a header? and a footer (? terminology).

This is where the real problem is. The response must have, besides String out, all the baggage such as header, footer, etc.

So it's complex, but not impossible, to use original String out or cString as you have to concatenate the baggage + String/cString into one final String.

You also have to know ahead the header, footer, etc. lengths and reserve the continuous space (right before / after) the original String/cString.

I don't know if it's worth the time to do this, while the simpler solution is to shorten the original String by well planning.

khoih-prog commented 2 years ago

Another possible way to do is to create a function sendRawString(), where you can

  1. Get all the header, footer, etc. by calling newly created functions getHeader(), getFooter(), etc.
  2. Concatenate them into your String/cString i n your space SDRAM, etc. You're responsible for the concat
  3. Call request->sendRaw(200, "image/svg+xml", out);

The function just keep the original String/cString and keep it to be sent unconditionally, no question asked.

khoih-prog commented 2 years ago

Hi @salasidis

After some more serious thinking, I'm be sure this can be done, but I'm sorry I don't have the time and resource to do this for a special use-case.

I'd appreciate if you can modify the library to adapt it to your use-case, then make a PR.

I'm closing the issue now as won't fix, and waiting for your PR.

Good Luck,

salasidis commented 2 years ago

Thanks for looking into it. (not sure if this is viewed after the issue has been closed - but here goes)

I tried single stepping though the library using a j-Link debugger, but ? because of compiler code optimizations ? it does not seem to follow a pattern that I would have expected.

As I have no experience writing, or modifying libraries, would it be possible to provide some pointers as to what parts of the library would need to be modified.

My best guess as to how to approach this would be to overload the send (and the functions that are called by it) to accept a c string.

In AsyncBasicResponse::_respond and AsyncBasicResponse::AsyncBasicResponse a new copy of the string content is created as _content (this is the main source of the problem I think). I would simply change content to a pointer to the c string. I would leave all the the header etc info as is, as the sizes of those strings are minimal. Then everywhere where the content is concatenated or manipulated, I would simply change it to C string manipulations. All the writes etc are done as a c_str() in any case, so those would be left as is.

Am I way off base, that the only changes required would be in the 2 functions above?

Any help would be appreciated. Thanks again.

khoih-prog commented 2 years ago

In AsyncBasicResponse::_respond and AsyncBasicResponse::AsyncBasicResponse a new copy of the string content is created as _content (this is the main source of the problem I think). I would simply change content to a pointer to the c string. I would leave all the the header etc info as is, as the sizes of those strings are minimal. Then everywhere where the content is concatenated or manipulated, I would simply change it to C string manipulations. All the writes etc are done as a c_str() in any case, so those would be left as is.

Am I way off base, that the only changes required would be in the 2 functions above?

Basically correct. You have to try and modify step-by-step, then you'll be OK. You can create a new class, similar to AsyncBasicResponse, to deal only with the cString, certainly with all the functions / vars to support.

I certainly can help on the way, if necessary.

I'll be happy to have a new co-author of this library, as well as many other AsyncWebServer libraries which can benefit by the similar enhancement. Looking forward to receiving your PRs.

salasidis commented 2 years ago

Thanks for the support.

I will go the easiest route I know, which is to add a variable in the AsyncBasicResponse class called char * _clientCstr. When a send is called, that has a String as a argument, it will set this to NULL, and if the send that accepts a C string is called, it will set it to the string address.

In the routines mentioned above, I will simply check if _clientCstr is null or not, and use it, or the regular _client arduino String. Should require the most minimum of extra code hopefully.

I will keep you posted.

Robert

salasidis commented 2 years ago

Tried to create a pull request (first time), but it created a fork - not sure if that is what I was supposed to do.

In any case, the finished code is here

https://github.com/salasidis/Portenta_H7_AsyncWebServer/blob/main/README.md https://github.com/salasidis/Portenta_H7_AsyncWebServer

As per the Readme file - I am getting 66k instead of 374k max heap space in my application. I should havbe no problem sending 2M files, or web pages larger than the main RAM

Since I create the web page once, and reuse, I make a second copy of the web page, so that when the send happens (which messes wit the actual string), I simply recopy C string C string for the next send - fast, easy, and almost no code changes required (none if you don't wish to use the features 0 fully backwards compatible I think)

request->send(200, "text/html", html_out, false);
strcpy(html_out, html_out_bak);
khoih-prog commented 2 years ago

I temporarily created a PR to have an easier look of the files.

LGTM. You've written very good code.

Can you make some minor changes to match with the current styles

  1. Using AWS_LOGDEBUGx instead of Serial.printxx
  2. Using tab = 2 spaces for correct indentation
  3. Modify in README.md correctly by adding a note how to use the new feature / function

BTW, to create a PR, after you've forked the library and have done all the mods, just press Pull Requests (upper left corner, next to Code, and proceed. Just so simple for you now.

I'm deleting the PR and let you do it.

khoih-prog commented 2 years ago

Check the RSMOD branch for the modified code. Test / modify as necessary. Then create a new PR

salasidis commented 2 years ago

I see you made all the changes already, thanks.

I had a question about the original software.

If you look at line 447 and 450 in the AsyncWebResponses.cpp file. The original program sets the _content String to String().

I had decided to do the same with the Cstring, by doing a _contentCstr = '\0';

What that did was that on the last sent packet, there was a '\0' on the string being sent embedded into the file. When I commented it out, the code worked OK (that is why I added all the debug serial prints - to try to see if that was the source of the error.

The question is, is the same thing happening in the original code, except the string has not been reclaimed by the heap yet, so the error does not show up???

Does it need to be there ??

khoih-prog commented 2 years ago

If you look at line 447 and 450 in the AsyncWebResponses.cpp file. The original program sets the _content String to String().

Don't see that line.

Do you mean Portenta_H7_AsyncWebRequest.cpp ?

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/blob/8297aa2ced8cdb6962541b9b455928d05bc1ad77/src/Portenta_H7_AsyncWebRequest.cpp#L447-L450

khoih-prog commented 2 years ago

Could you also add examples to demo the new functions and better HEAP benefits ? Can be based on those 2 original examples you posted, Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino and Async_AdvancedWebServer_MemoryIssues_Send_CString.ino

salasidis commented 2 years ago

No should be in Portenta_H7_AsyncWebResponses.cpp

In the mod file it is the following

Using permalink

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/blob/3f11c768eaa94f614afbdb4ddb9def420267f858/src/Portenta_H7_AsyncWebResponses.cpp#L447-L448

Using code tag


size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time)
{
...
...
...
      if (_contentCstr)
      {
        _writtenLength += request->client()->write(_contentCstr, available);
        //_contentCstr[0] = '\0';  // <----- this gave error described until commented out
      }
      else
      {
        _writtenLength += request->client()->write(_content.c_str(), available);
        _content = String();  // I<---- had placed it because of this - should this be there ?
      }
      _state = RESPONSE_WAIT_ACK;

      return available;
    }
`
salasidis commented 2 years ago

Will try to add the files to the RSMOD examples - will run them first to make sure

salasidis commented 2 years ago

Worked nicely -= went from 111k to 12.7k max heap

I uploaded them to my fork, as it sad I had no push access to add them to RSMOD

Can you take them from there?

https://github.com/salasidis/Portenta_H7_AsyncWebServer/tree/main/examples/Ethernet

khoih-prog commented 2 years ago

I uploaded them to my fork, as it said I had no push access to add them to RSMOD

That's the way it must be. Only author / collaborator can modify any branch. You can only make PR.

Can you take them from there? https://github.com/salasidis/Portenta_H7_AsyncWebServer/tree/main/examples/Ethernet

I can. But it's better you make a PR for everything as you'll use the PR many more times.

salasidis commented 2 years ago

I did a pull request, but I don't know how to merge your changes on my end (it says conflicts - and no idea how to resolve them). So, it did a pull request for the files you already fixed up as well.

khoih-prog commented 2 years ago

You just either

  1. Replace the files on your local repo dirrectory. Then upload to your github. Or
  2. Go to the PR, press Resolve conflicts at the end of PR then go thru every conflict, delete all the markers and to-be-deleted code, then pressMarked as solved for every conflicting file.

For example, src/Portenta_H7_AsyncWebRequest.cpp, it'll show

<<<<<<< RSMOD           <============= to be deleted
  if (nonDetructiveSend)
  {
    send(beginResponse(code, contentType, String(content)));  // for backwards compatibility
  }
  else
  {
    send(beginResponse(code, contentType, content));
  }
}
=======           <============= to be deleted until >>>>>>> main
  if (nonDetructiveSend == true) {
    send(beginResponse(code, contentType, String(content)));    // for backwards compatibility
  } else {
    send(beginResponse(code, contentType, content));
  }
 }
>>>>>>> main

then you delete all the markers (<<<<<<< RSMOD, =======, >>>>>>> main) and the code between ======= and >>>>>>> main within to make it as follows

  if (nonDetructiveSend)
  {
    send(beginResponse(code, contentType, String(content)));  // for backwards compatibility
  }
  else
  {
    send(beginResponse(code, contentType, content));
  }
}

until all markers are deleted, then press Marked as resolved


Repeat for all 4 files. Then you'll be ready to merge to your GitHub

salasidis commented 2 years ago

Pull Requested the NO SDRAM file.

Also, have you had a chance to look at the code below - should I try to see how it works eliminating the line ****


       _writtenLength += request->client()->write(_contentCstr, available);
        //_contentCstr[0] = '\0';  // <----- this gave error described until commented out
      }
      else
      {
        _writtenLength += request->client()->write(_content.c_str(), available);

        _content = String();  // I<---- had placed it because of this - should this be there ?  ****

      }
`
khoih-prog commented 2 years ago

Closed as completed via #8

khoih-prog commented 2 years ago

As you mentioned in Arduino Forum AsyncWebServer Library - heap memory use

If the string is to be used again, rather than recreating it, I check if the first 10-20 characters have changed, if not, the string is still good, otherwise - you have to recreate it.

Do you think we can reuse the cString by just changing the address, such as creating a new private pointer and update it according to the change ?

For example, change

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/blob/19f25466d0caafb4e10b46d6df9983c195e7d46d/src/Portenta_H7_AsyncWebResponseImpl.h#L47

to

char *_contentCstr;      // RSMOD
char *new_contentCstr;

and in AsyncBasicResponse::_respond(AsyncWebServerRequest *request), update the pointer by, e.g., outLen is the final shift, then update the pointer at the end of the function

new_contentCstr = cStr + outLen;

Just an idea, I haven't tested and verified.

khoih-prog commented 2 years ago

BTW, I just ported your PR into AsyncWebServer_RP2040W library for RP2040W (WiFi) and OK now with the heap

The preliminary results are

Async_AdvancedWebServer_MemoryIssues_Send_CString on RASPBERRY_PI_PICO_W with RP2040W CYW43439 WiFi

Use CString => Max Heap = 43,976 bytes
Start Async_AdvancedWebServer_MemoryIssues_Send_CString on RASPBERRY_PI_PICO_W with RP2040W CYW43439 WiFi
AsyncTCP_RP2040W v1.1.0
AsyncWebServer_RP2040W v1.1.2
Connecting to SSID: HueNet1
SSID: HueNet1
Local IP Address: 192.168.2.74
Country code: XX
HTTP EthernetWebServer is @ IP : 192.168.2.74

HEAP DATA - Pre Create Arduino String  Cur heap: 193000  Res Size: 150928  Max heap: 42072
..
HEAP DATA - Pre Send  Cur heap: 193000  Res Size: 149176  Max heap: 43824

HEAP DATA - Post Send  Cur heap: 193000  Res Size: 149048  Max heap: 43952

HEAP DATA - Post Send  Cur heap: 193000  Res Size: 149032  Max heap: 43968
........ .
HEAP DATA - Post Send  Cur heap: 193000  Res Size: 149024  Max heap: 43976
......... .......... .......... 
Use String => Max Heap = 75,240 bytes
Start Async_AdvancedWebServer_MemoryIssues_SendArduinoString on RASPBERRY_PI_PICO_W with RP2040W CYW43439 WiFi
AsyncTCP_RP2040W v1.1.0
AsyncWebServer_RP2040W v1.1.2
Connecting to SSID: HueNet1
SSID: HueNet1
Local IP Address: 192.168.2.74
Country code: XX
HTTP EthernetWebServer is @ IP : 192.168.2.74

HEAP DATA - Pre Create Arduino String  Cur heap: 193256  Res Size: 191192  Max heap: 2064
.
HEAP DATA - Pre Send  Cur heap: 193256  Res Size: 149432  Max heap: 43824

HEAP DATA - Post Send  Cur heap: 193256  Res Size: 118056  Max heap: 75200

HEAP DATA - Post Send  Cur heap: 193256  Res Size: 118024  Max heap: 75232
...
HEAP DATA - Post Send  Cur heap: 193256  Res Size: 118016  Max heap: 75240
..

Selection_101

salasidis commented 2 years ago

I thought about it on my end as well.

I am not sure how often the header is larger than the first packet, but, since that is the only time.

Can create a second string that has the remnant header (could be an arduinovstring, as likelv<1k.

Then when relooping into code, check if the remnant header is empty, and if not, make it the new header, and zero length the remnant string.

Then can continue running the code as before.

Would take only maybe 3-4 lines new code, and no rewrites in the main body.

We would then never need to care about having space in the larger string, and would allow the elimination of all the memmove calls.

salasidis commented 2 years ago

Looking at the code, I think we need a combination.

Going in order,

Section 1 - no memmoves - content is 0 length - no change

Section 2 - the content and header all fit in 1 packet- we can just create an Arduino string and send it (the old way) - we lose a packet worth of heap, so not much

In Section 3 - the content length is unknown, but the the header is long, so we can do what I mentioned, send the partial header, and create a remnant header string to send in the RESPONSE_CONTENT loop

In Section 4 - Header is small, but content could be large - Leave as is - as the header is sent with a little added buffer

Section 5 - default - not sure why it would ever get here - to me the other instances seem to cover all the bases. But we would have to create a new header from the old, and proceed as in 3

In RESPONSE_CONTENT

before entering if/else statements

I can try all this later this week and let you know

khoih-prog commented 2 years ago

Sounds good. It's worth a try. Waiting for results form your mods and tests.

salasidis commented 2 years ago

Send a pull request with the mods - not fully tested yet, but works on my main application

Non destructive, no extra space needed

salasidis commented 2 years ago

I ran the Cstr example, and it works.

Max Heap 12795 - and seems to stay stable

khoih-prog commented 2 years ago

Your PR is very contagious ;=)), has been spreading to AsyncWebServer_WT32_ETH01 library for WT32_ETH01 (ESP32 + LAN8720A) and helps a big deal to save the heap.

The current results are


Async_AdvancedWebServer_MemoryIssues_Send_CString on WT32-ETH01

Using CString => heap = 120,876 bytes
Start Async_AdvancedWebServer_MemoryIssues_Send_CString on WT32-ETH01 with ETH_PHY_LAN8720
AsyncWebServer_WT32_ETH01 v1.6.0 for core v2.0.0+

ETH Started
ETH Connected
ETH MAC: A8:48:FA:08:4B:FF, IPv4: 192.168.2.76
FULL_DUPLEX, 100Mbps
HTTP EthernetWebServer is @ IP : 192.168.2.232

HEAP DATA - Pre Create Arduino String  Max heap: 326680  Free heap: 216212  Used heap: 110468
.
HEAP DATA - Pre Send  Max heap: 326680  Free heap: 212292  Used heap: 114388

HEAP DATA - Post Send  Max heap: 326680  Free heap: 205848  Used heap: 120832
.
HEAP DATA - Post Send  Max heap: 326680  Free heap: 205816  Used heap: 120864
..
HEAP DATA - Post Send  Max heap: 326680  Free heap: 205812  Used heap: 120868
...... ..
HEAP DATA - Post Send  Max heap: 326680  Free heap: 205804  Used heap: 120876
........ .......... .......... .......... .......... .......... ..........
Out String Length=31282
.......... .

Using String => heap = 152,088 bytes, around one time data buffer (~31,220 bytes) larger.
Start Async_AdvancedWebServer_MemoryIssues_SendArduinoString on WT32-ETH01 with ETH_PHY_LAN8720
AsyncWebServer_WT32_ETH01 v1.6.0 for core v2.0.0+

ETH Started
ETH Connected
ETH MAC: A8:48:FA:08:4B:FF, IPv4: 192.168.2.76
FULL_DUPLEX, 100Mbps
HTTP EthernetWebServer is @ IP : 192.168.2.232

HEAP DATA - Pre Create Arduino String  Max heap: 326952  Free heap: 256484  Used heap: 70468
.
HEAP DATA - Pre Send  Max heap: 326952  Free heap: 212600  Used heap: 114352

HEAP DATA - Post Send  Max heap: 326952  Free heap: 174864  Used heap: 152088
..

Async_AdvancedWebServer_MemoryIssues_Send_CString

khoih-prog commented 2 years ago

Send a pull request with the mods - not fully tested yet, but works on my main application

Haven't seen anything yet. PR forgotten ???

salasidis commented 2 years ago

Sent it again, if you did not get it, I am doing it wrong :-(

Only 2 files changed

https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11

khoih-prog commented 2 years ago

Great. Got it now. Will have a look later today.

khoih-prog commented 2 years ago

Closed as done in Portenta_H7_AsyncWebServer v1.4.1

Looking forward to receiving many more of your PRs, issue reports, enhancement requests, etc.

Best Regards,

khoih-prog commented 2 years ago

Hi @salasidis

Your PRs has just been spreading to AsyncWebServer_Ethernet library for ESP8266 and W5x00 / EMC28J60 Ethernet and helps a big deal to sned bigger data because of heap saving. ESP8266's heap space is much smaller than ESP32's