Skip to content

Firmware JSON document memory management#117

Open
brian-r-calder wants to merge 17 commits into
mainfrom
development
Open

Firmware JSON document memory management#117
brian-r-calder wants to merge 17 commits into
mainfrom
development

Conversation

@brian-r-calder
Copy link
Copy Markdown
Collaborator

This PR includes the code contributed in PR #108 from Spatialnetics, which will close issue #107 when merged.

The problem here is that the JSON documents being constructed for status information can become large if there are large numbers of files on the logger for any reason, and the firmware can either run out of memory to hold the whole document, or might eat up enough of the limited heap space that you can't get a block of memory to generate the SSL data for a secure upload connection. It appears that this was also the thing that was slowing down the web server implementation (or, at least, part of it, since it's much snappier afterwards).

The modifications here beyond PR #108 are to bump the firmware version number, and to fix up the parsing of the new status message at the upload server end of the connection so that check-ins still work as expected.

parkercoates and others added 15 commits May 12, 2026 14:22
We ran into issues with allocation failures when attempting to set up
SSL connections. After a lot of searching and trial-and-error, we
realized that as the number of log files on the SD card grew, the size
of the JSON document returned by GenerateFilelist() grew to consume a
significant chunk of heap memory. This document was resident while the
upload loop ran, which is the exact time we needed the memory most.

Once this was realized, we almost immediately notices that we didn't
need this JSON at all, as the only thing we were pulling from it was
the file numbers, something we can easily get from a CountLogFiles()
call.
While dealing with heap shortages, I realized that we were always
holding onto 4 KiB array, even if only the first few entries were
actually populated. Four kibibytes isn't a massive amount, but when
running near capacity, it might be enough to push us over the edge
into SSL allocation failures.

To do this shrink-to-fit operation consistently, I replaced one
overload of logging::Manager::CountLogFiles() with a new
GetLogFileNumbers() method that returns a vector, which provides much
nicer ergonomics to the caller.
Previously any missing field would cause the whole row to be dropped.
Given that MD5 hashes aren't guaranteed to be present, that felt like
overkill.
My goal is to pull the file list out of the status JSON document, which
will require calculating the total file count and size separately from
the full file list.
I'm running into situations where a large JSON document is being
successfully generated, but then allocation fails when trying to copy
it into ESP32WiFiAdapter::m_messages.

Move semantics to the rescue!
Keeping the document itself on the heap only introduced complexity
which we didn't really need.

Also store the default buffer size in a named constant.
This document's capacity increased every time a large message was
sent, but that memory was never recovered. So if we succeeded in
sending a giant file list, ESP32WiFiAdapter would hold onto that memory
forever.
…ument

This is a subtle dance that was repeated a few times. All of those
copies were missing a std::move() call that significantly reduces the
peak memory usage of the operation.

To me that justifies abstracting it out.
Given that our large JSON documents can grow to fill temporarily fill
all available memory, a more conservate growth factor seems
appropriate.

1. Less aggressive growth means we are more likely to "just" fit in
memory.
2. A growth factor less than the golden ratio allows the possibility
of reusing memory blocks the document previously occupied. A growth
factor of 2 prevents this.
There was a lot of unnecessary repetition between commands. Other
improvements:

* Serializing directly to Serial instead of allocating a large
temporary string, like some jobs were doing.
* Using Serial.println() to print the trailing new line instead of
appending a single character to a large string, potentially resulting
in another large allocation and copy.
* Avoid some unnecessary JsonDocument->String->JsonDocument
round-trips.
Just include the total file count and total file size.

The file list JSON just isn't guaranteed to fit in memory, so it
doesn't make sense to try to cram it inside the status JSON. A failed
allocation means no status is sent, which is much more problematic
than an incomplete file list.

Moving "catalog" out into its own command means the WIBL can serve the
status, release memory, then serve the file list. This approach also
lets us handle a partial file list by appending a row of ellipses to
the file table.
This merges a PR to address memory usage requirements in the logger firmware which can cause it to hold on to too much memory for JSON documents (and messages being sent back to the WiFi client), which has the knock-on effect of stopping the system from setting up SSL blocks for upload.  This address issue #107
@brian-r-calder brian-r-calder requested a review from selimnairb May 22, 2026 22:12
@brian-r-calder brian-r-calder self-assigned this May 22, 2026
@brian-r-calder brian-r-calder added bug Something isn't working enhancement New feature or request major logger Discussions of topics that definitely do or may pertain to WIBL logger hardware. firmware Discussions of topics that definitely do or may pertain to WIBL logger firmware. labels May 22, 2026
Copy link
Copy Markdown
Member

@selimnairb selimnairb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor changes in the firmware code. I have some concerns about the changes to the UploadServer README, as detailed in my comments.

Comment thread LoggerFirmware/include/JSONUtilities.h Outdated
Comment thread LoggerFirmware/include/LogManager.h Outdated
Comment thread LoggerFirmware/include/LogManager.h Outdated
Comment thread LoggerFirmware/src/LogManager.cpp Outdated
Comment thread LoggerFirmware/src/SerialCommand.cpp
Comment thread LoggerFirmware/src/WiFiAdapter.cpp
Comment thread UploadServer/README.md Outdated
Comment thread UploadServer/README.md Outdated
@brian-r-calder brian-r-calder requested a review from selimnairb May 27, 2026 19:08
Copy link
Copy Markdown
Member

@selimnairb selimnairb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor change leftover from the last round, else I think this is ready.

uint32_t CountLogFiles(uint32_t filenumbers[MaxLogFiles]);
/// \brief Count the number of log files on the system
uint32_t CountLogFiles(void);
uint32_t CountLogFiles(uint64_t * fileSize = nullptr);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be *fileSize instead of * fileSize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request firmware Discussions of topics that definitely do or may pertain to WIBL logger firmware. logger Discussions of topics that definitely do or may pertain to WIBL logger hardware. major

Projects

None yet

Development

Successfully merging this pull request may close these issues.

As the number of log files on the SD card grows, the JSON file list no longer fits easily in memory causing numerous bugs

3 participants