esp_http_client extremely slow upload speed compared to iperf

try_except
Posts: 5
Joined: Thu Jan 23, 2025 12:50 pm

esp_http_client extremely slow upload speed compared to iperf

Postby try_except » Tue Mar 04, 2025 6:20 pm

Hi guys,
I'm currently working on a project using ESP32-CAM to capture AVI files and upload them to an S3 bucket. Right now everything works, but the upload speed to my S3 bucket is ridiculously slow: I'm currently getting 25 kB/s!!

I tested my board using the iperf example and was able to achieve an average speed of 10 Mbit/sec (around 1.3 MB/sec), so the problem is not the board. Also, since I'm reading the video files from and SD card, I tested the read speed and got around 803 kB/sec using 512 kB chunks, so no issue there.

I'm currently adapting the esp_http_client example (https://github.com/espressif/esp-idf/tr ... ttp_client) so I can test if the bottleneck comes from my own code or the library, but I still think 25 kB/s is simply too slow for anything practical.

A code snippet so you can understand what I'm trying to do:

Code: Select all

#include "esp_log.h"
#include "nvs_flash.h"
#include <string.h>
#include <stdio.h>

#define BUFF_SIZE 524288

/* Anonymous namespace */
namespace {
  const constexpr char *TAG{"CAM Initial Setup"};
  EXT_RAM_BSS_ATTR static char buffer[BUFF_SIZE] = {0};
  char response_buffer[CONFIG_MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
}

extern "C" void app_main(void){
  /************************
   * Initialize NVS flash *
   *************************/
  esp_err_t ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
  }
  ESP_ERROR_CHECK(ret);
  
  /**********
   * Tests  *
   **********/
  setvbuf(stdout, NULL, _IONBF, 0);	// Don't buffer stdout
  std::fstream file;
  size_t filesize;
  // (declaration of this functions is not shown, but they're not important for this topic)
  storage.open_video_file("333565712.avi", &file); 		// Wrapper function for opening a file
  storage.get_video_size("333565712.avi", &filesize);		// Wrapper function for querying file size
  
  // 1) Reading from SD Card
  ESP_LOGI(TAG, "%d/%d = %d", filesize, BUFF_SIZE, filesize/BUFF_SIZE);
  uint32_t start = (uint32_t)time(NULL);
  for (int i=0; i<filesize; i+=BUFF_SIZE) {
    file.read(buffer, BUFF_SIZE);
    printf("%03d/%03d\r", i/BUFF_SIZE, filesize/BUFF_SIZE);
  }
  printf("\n");
  uint32_t end = (uint32_t)time(NULL);
  ESP_LOGI(TAG, "Read %d Bytes in %lus --> %lu B/s ~ %lu kB/s", filesize, end-start, filesize/(end-start), (filesize/(end-start)) >> 10);

  // 2) Writing to S3 bucket (WiFi is connected elsewhere, not important for this topic i think)
  file.close();									// Ensure file handle is "clean"
  
  esp_http_client_handle_t http_client;
  /*==========================================================================
   Here I obtain a presigned URL. This is done by using esp_http_client_perform and
   querying a custom endpoint. This is also not important for this topic, but I thought it
   was worth mentioning that my esp_http_client instance is used in a GET request before
   starting the upload. Assume the result is stored in a cJSON object:
   ==========================================================================*/
   std::string s3_bucket_url = cJSON_GetObjectItem(json, "url")->valuestring;
   
  esp_http_client_set_url(http_client, s3_bucket_url.c_str());
  esp_http_client_set_method( http_client, HTTP_METHOD_PUT);
  esp_http_client_set_header(http_client, "Content-Length", std::to_string(filesize).c_str());
   
  int bytes_sent = 0;
  storage.open_video_file("333565712.avi", &file); 
  start = (uint32_t)time(NULL);
  uint32_t then = start;
  uint32_t now;
  esp_http_client_open(http_client, filesize);
  ESP_LOGI(TAG, "filesize = %d", filesize);
  for (int i=0; i<filesize; i+=BUFF_SIZE) {
    file.read(buffer, BUFF_SIZE);
    bytes_sent = esp_http_client_write(http_client, buffer, file.gcount());
    now = (uint32_t)time(NULL);
    printf("%03d/%03d\t%ld kB/s\t%lds between chunks\r", 
    	i/BUFF_SIZE, filesize/BUFF_SIZE, (bytes_sent >> 10)/(now - then), now - then);
    then = now;
  }
  end = (uint32_t)time(NULL);
  printf("\n");
  ESP_LOGI(TAG, "Sent %d Bytes in %lus --> %lu B/s ~ %lu kB/s", filesize, end-start, filesize/(end-start), (filesize/(end-start)) >> 10);
  esp_http_client_fetch_headers(http_client);
  ESP_LOGI(TAG, "Got status code %d", esp_http_client_get_status_code(http_client));
  esp_http_client_close(http_client);
  esp_http_client_cleanup(http_client);
}
Output shows 773 kB/s read from SD card and 25 kB/s upload, with 20-23s between chunks.

Any help would be greatly appreciated, so thanks in advance :)

try_except
Posts: 5
Joined: Thu Jan 23, 2025 12:50 pm

Re: esp_http_client extremely slow upload speed compared to iperf

Postby try_except » Thu Mar 06, 2025 2:25 am

I found this topic: https://esp32.com/viewtopic.php?t=5844 and it seems to be related to my problem. Using wireshark I can see very small bursts of data coming from the ESP32, then some ACKs from the server, then again another small burst of data, etc:
Image
Is there any way to disable Nagle's algorithm for esp_http_client?

try_except
Posts: 5
Joined: Thu Jan 23, 2025 12:50 pm

Re: esp_http_client extremely slow upload speed compared to iperf

Postby try_except » Thu Mar 06, 2025 1:58 pm

Ok so I searched around menuconfig and found the option CONFIG_LWIP_TCP_SND_BUF_DEFAULT. Bumping this to the maximum possible value (65535) yielded 184 kB/s. Better, but not quite what I expected. Tried bumping CONFIG_LWIP_TCP_WND_DEFAULT to 65535 also, but it didn't have any impact on transfer speed. This leads me to think that it's not really Nagle's algorithm that's holding me back, but the way I have set my buffers. Is there any way to call esp_http_client_write over the whole file instead of using a buffer to read chunks?

Who is online

Users browsing this forum: No registered users and 83 guests