Memory allocation and reporting

I’m using LVGL 9.2 on ESP32 (FreeRTOS/Arduino).
My project runs out of memory within a few minutes.
I’m using LV_STDLIB_CLIB because I saw a recommendation for this with ESP32, but it seems that it precludes use of lv_mem_monitor().
I’ve also set #define LV_USE_OS LV_OS_FREERTOS
My code reports lots of free heap space when the errors start:

Free heap: 95888 bytes
~[Warn]   (14.800, +14800)         lv_draw_buf_create_ex: No memory: 143x53, cf: 16, stride: 572, 30316Byte,  lv_draw_buf.c:287
[Warn]  (14.800, +14800)         lv_draw_buf_create_ex: No memory: 143x53, cf: 16, stride: 572, 30316Byte,  lv_draw_buf.c:287

[Warn]  (14.800, +0)     lv_draw_layer_alloc_buf: Allocating layer buffer failed. Try later lv_draw.c:408
[Warn]  (14.800, +0)     lv_draw_layer_alloc_buf: Allocating layer buffer failed. Try later lv_draw.c:408

I’m drawing some text, an arc and some tics, and moving a needle.
Not sure why this code would be consuming more memory each cycle. Any recommendations for either a) enabling memory usage monitoring or b) garbage collection/cleanup I can do manually?

Below is my GUI create code and my update code which is called once per second.

void lv_create_main_gui(void) {
  screen = lv_screen_active();
  lv_obj_set_style_bg_color(screen, lv_color_black(), 0);
  lv_style_init(&style);
  lv_style_set_text_color(&style, lv_color_hex(yellow));
  lv_style_init(&style_large_font);
  lv_style_set_text_font(&style_large_font, &lv_font_montserrat_16);  
  //lv_style_set_text_font(&style_large_font, &lv_font_unscii_16);  
  data_label = lv_label_create(screen);
  lv_obj_add_style(data_label, &style, 0);
  lv_obj_add_style(data_label, &style_large_font, 0);
  lv_label_set_long_mode(data_label, LV_LABEL_LONG_WRAP);
  lv_obj_set_width(data_label, SCREEN_WIDTH);

  // create a "scale""
  lv_obj_t *scale_line = lv_scale_create(lv_screen_active());
  lv_obj_set_size(scale_line, SCREEN_WIDTH*6/10, SCREEN_WIDTH*6/10);
  lv_scale_set_mode(scale_line, LV_SCALE_MODE_ROUND_OUTER);
  lv_obj_set_style_bg_opa(scale_line, LV_OPA_TRANSP, 0);
  //lv_obj_set_style_bg_opa(scale_line, LV_OPA_50, 0); // uncomment to see whole circle

  lv_obj_set_style_arc_color(scale_line, lv_color_hex(yellow), LV_PART_MAIN);
  lv_obj_set_style_arc_width(scale_line, 4, LV_PART_MAIN);
  lv_obj_set_style_line_color(scale_line, lv_color_hex(yellow), LV_PART_MAIN);
  lv_obj_set_style_line_color(scale_line, lv_color_hex(yellow), LV_PART_ITEMS);
  lv_obj_set_style_text_color(scale_line, lv_color_hex(yellow), 0);

  lv_obj_set_style_radius(scale_line, LV_RADIUS_CIRCLE, 0);
  lv_obj_set_style_clip_corner(scale_line, true, 0);
  //lv_obj_align(scale_line, LV_ALIGN_BOTTOM_MID, LV_PCT(2), 0);
  lv_obj_align(scale_line, LV_ALIGN_BOTTOM_MID, 0, -SCALE_BOT_OFFSET);
  lv_scale_set_label_show(scale_line, false);
  lv_obj_set_style_text_color(scale_line, lv_color_hex(yellow), 0);
  lv_obj_set_style_line_color(scale_line, lv_color_hex(yellow), LV_PART_MAIN);
  lv_obj_set_style_line_color(scale_line, lv_color_hex(yellow), LV_PART_ITEMS);
  lv_obj_set_style_line_color(scale_line, lv_color_hex(yellow), LV_PART_INDICATOR);
  lv_obj_set_style_length(scale_line, 5, LV_PART_ITEMS);
  lv_obj_set_style_length(scale_line, 10, LV_PART_INDICATOR);
  lv_scale_set_range(scale_line, 90, -90);

  lv_scale_set_angle_range(scale_line, 180);
  lv_scale_set_rotation(scale_line, 0);

  // create label for scale (rotation)
  rotation_label = lv_label_create(lv_screen_active());
  lv_label_set_text(rotation_label, "0");
  lv_obj_set_style_text_color(rotation_label, lv_color_hex(yellow), 0);
  lv_obj_align_to(rotation_label, scale_line, LV_ALIGN_BOTTOM_MID, -10, SCALE_BOT_OFFSET);  

  // Create the needle line
  needle_line = lv_line_create(scale_line);
  lv_obj_set_style_line_color(needle_line, lv_color_hex(yellow), LV_PART_MAIN);
  lv_obj_set_style_line_width(needle_line, 3, LV_PART_MAIN);  // thinner
  lv_obj_set_style_line_rounded(needle_line, true, LV_PART_MAIN);

  update_needle_position(0);
}

char bufB[128], bufS[64];
unsigned long uptime;
//lv_mem_monitor_t mon;

void LVGLdataWindDebug() {
    uptime = millis() / 1000;
    sprintf(bufB, "Uptime: %lu\n", uptime % 10000);
    sprintf(bufS, "Wifi: %s\n", (WiFi.status() == WL_CONNECTED) ? WiFi.SSID().substring(0,7).c_str() : "----");
    strcat(bufB, bufS);
#ifdef N2K
    sprintf(bufS, "N2K: %d:%d\n", n2k::num_n2k_messages % 10000, 0);
    strcat(bufB, bufS);
    sprintf(bufS, "Wind: %d:%d\n", n2k::num_wind_messages % 10000, 0);
    strcat(bufB, bufS);
    sprintf(bufS, "AWS:%2.1f\n", n2k::windSpeedKnots);
    strcat(bufB, bufS);
    sprintf(bufS, "AWA:%2.0f\n", n2k::windAngleDegrees);
    strcat(bufB, bufS);
#endif
    lv_label_set_text(data_label, bufB);

#ifdef N2K
    // update label at bottom of gauge
    snprintf(bufB, sizeof(bufB), "%0.2f", n2k::rotateout);
    lv_label_set_text(rotation_label, bufB);
    update_needle_position(n2k::rotateout);
#endif
    if (uptime % 5 == 1) {
      //lv_mem_monitor(&mon);
      //Serial.printf("Total: %d bytes, Free: %d bytes, Max used: %d bytes, Used percentage: %d%%\n", mon.total_size, mon.free_size, mon.max_used, mon.used_pct);
      Serial.println("Free heap: " + String(ESP.getFreeHeap()) + " bytes");    
      }
}

It’s possible this is not quite out-of-memory, but perhaps heap-fragmentation? If so, you could try configuring LVGL to draw in smaller chunks, or increasing the heap size to increase the likelihood of a contiguous 30316B buffer. You could try printing the heap stats out after this failure to try and diagnose further. Check lv_mem_monitor_t.free_biggest_size - it’s the largest contiguous allocation available.

Thank you. The memory monitoring functions are not available when using stdlib “malloc” but once I switched to LVGL memory allocation (and got my code to compile with that enabled) I am not having the same issues. Apparently the library isn’t freeing memory for drawing the needle when it uses malloc().