mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
fix: track the JSON length instead of recounting it
Every append found the end of the buffer with strlen(), up to four times per append and four appends per field, so building a response cost the square of its size. /discovery fills a 4 KB buffer and spent about 195 ms of its ~210 ms there (shades 126 ms, groups 69 ms), all on the async_tcp task that serves every socket and web request. Keep the length in the formatter and write every append at the end through one helper. The web response and socket event subclasses and ControllerChunker use the tracked length too. Output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
55b1db4508
commit
a8bb377957
5 changed files with 86 additions and 33 deletions
|
|
@ -7,13 +7,14 @@ void BufferedJsonFormatter::setBuffer(char *b, size_t sz) {
|
|||
this->buff = b;
|
||||
this->buffSize = sz;
|
||||
this->buff[0] = 0;
|
||||
this->_len = 0;
|
||||
this->_nocomma = true;
|
||||
this->_overflow = false;
|
||||
this->_objects = 0;
|
||||
this->_arrays = 0;
|
||||
}
|
||||
size_t BufferedJsonFormatter::length() const {
|
||||
return strlen(this->buff);
|
||||
return this->_len;
|
||||
}
|
||||
void JsonFormatter::beginObject(const char *name) {
|
||||
if(name && strlen(name) > 0) this->appendElem(name);
|
||||
|
|
@ -87,17 +88,29 @@ void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_nu
|
|||
void JsonFormatter::addElem(const char *name, bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(name); }
|
||||
|
||||
void JsonFormatter::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
const size_t vlen = escape ? this->calcEscapedLength(val) + 2 : strlen(val);
|
||||
if(this->_len + vlen >= this->buffSize) {
|
||||
if(!this->_overflow) ESP_LOGE(TAG, "JSON exceeded buffer size %u", (unsigned)this->buffSize);
|
||||
this->_overflow = true;
|
||||
return;
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
this->_appendAt(val, escape, vlen);
|
||||
}
|
||||
void JsonFormatter::_appendAt(const char *val, bool escape, size_t vlen) {
|
||||
char *p = this->buff + this->_len;
|
||||
if(escape) {
|
||||
*p++ = '"';
|
||||
*p = 0;
|
||||
this->escapeString(val, p);
|
||||
p += vlen - 2;
|
||||
*p++ = '"';
|
||||
}
|
||||
else {
|
||||
memcpy(p, val, vlen);
|
||||
p += vlen;
|
||||
}
|
||||
*p = 0;
|
||||
this->_len += vlen;
|
||||
}
|
||||
void JsonFormatter::_appendNumber(const char *name) { this->appendElem(name); this->_safecat(this->_numbuff); }
|
||||
size_t JsonFormatter::escapeChar(char c, char *out) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class JsonFormatter {
|
|||
protected:
|
||||
char *buff;
|
||||
size_t buffSize;
|
||||
// What is in buff, kept so appending never has to strlen() the buffer.
|
||||
size_t _len = 0;
|
||||
bool _headersSent = false;
|
||||
uint8_t _objects = 0;
|
||||
uint8_t _arrays = 0;
|
||||
|
|
@ -16,6 +18,9 @@ class JsonFormatter {
|
|||
bool _overflow = false;
|
||||
char _numbuff[25] = {0};
|
||||
virtual void _safecat(const char *val, bool escape = false);
|
||||
// Appends val at buff + _len. vlen is what it takes, including the quotes
|
||||
// when escaped; the caller has made sure it fits.
|
||||
void _appendAt(const char *val, bool escape, size_t vlen);
|
||||
void _appendNumber(const char *name);
|
||||
public:
|
||||
bool overflowed() const { return _overflow; }
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ void JsonSockEvent::beginEvent(AsyncWebSocket *server, const char *evt, char *bu
|
|||
this->_nocomma = true;
|
||||
this->_closed = false;
|
||||
this->_overflow = false;
|
||||
snprintf(this->buff, buffSize, "42[%s,", evt);
|
||||
const int n = snprintf(this->buff, buffSize, "42[%s,", evt);
|
||||
this->_len = n < 0 ? 0 : ((size_t)n < buffSize ? (size_t)n : buffSize - 1);
|
||||
}
|
||||
void JsonSockEvent::closeEvent() {
|
||||
if(!this->_closed) {
|
||||
// The ] and the terminator both have to fit.
|
||||
if(strlen(this->buff) + 1 < buffSize) strcat(this->buff, "]");
|
||||
if(this->_len + 1 < buffSize) this->_appendAt("]", false, 1);
|
||||
else this->_overflow = true;
|
||||
}
|
||||
this->_nocomma = true;
|
||||
|
|
@ -37,49 +38,47 @@ void JsonSockEvent::endEvent(uint32_t clientId) {
|
|||
const uint32_t us = timer.us();
|
||||
if(us < EMIT_SLOW_MS * 1000) return;
|
||||
char what[80];
|
||||
if(clientId == 0) snprintf(what, sizeof(what), "Socket emit %s (%u bytes) to all", this->_evt, (unsigned)strlen(this->buff));
|
||||
else snprintf(what, sizeof(what), "Socket emit %s (%u bytes) to %lu", this->_evt, (unsigned)strlen(this->buff), (unsigned long)clientId);
|
||||
if(clientId == 0) snprintf(what, sizeof(what), "Socket emit %s (%u bytes) to all", this->_evt, (unsigned)this->_len);
|
||||
else snprintf(what, sizeof(what), "Socket emit %s (%u bytes) to %lu", this->_evt, (unsigned)this->_len, (unsigned long)clientId);
|
||||
logElapsed(TAG, what, us);
|
||||
}
|
||||
void JsonSockEvent::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
const size_t vlen = escape ? this->calcEscapedLength(val) + 2 : strlen(val);
|
||||
if(this->_len + vlen >= this->buffSize) {
|
||||
this->_overflow = true;
|
||||
return;
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
this->_appendAt(val, escape, vlen);
|
||||
}
|
||||
void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize) {
|
||||
this->_request = request;
|
||||
this->buff = buff;
|
||||
this->buffSize = buffSize;
|
||||
this->buff[0] = 0x00;
|
||||
this->_len = 0;
|
||||
this->_nocomma = true;
|
||||
this->_headersSent = false;
|
||||
this->_overflow = false;
|
||||
this->_stream = request->beginResponseStream("application/json");
|
||||
}
|
||||
void AsyncJsonResp::endResponse() {
|
||||
if(strlen(this->buff)) this->flush();
|
||||
if(this->_len) this->flush();
|
||||
if(this->_request && this->_stream) {
|
||||
this->_request->send(this->_stream);
|
||||
}
|
||||
}
|
||||
void AsyncJsonResp::flush() {
|
||||
if(this->_stream && strlen(this->buff) > 0) {
|
||||
this->_stream->print(this->buff);
|
||||
if(this->_stream && this->_len > 0) {
|
||||
this->_stream->write((const uint8_t *)this->buff, this->_len);
|
||||
this->buff[0] = 0x00;
|
||||
this->_len = 0;
|
||||
}
|
||||
}
|
||||
void AsyncJsonResp::_safecat(const char *val, bool escape) {
|
||||
const size_t vlen = escape ? this->calcEscapedLength(val) + 2 : strlen(val);
|
||||
if(vlen + strlen(this->buff) >= this->buffSize) {
|
||||
if(vlen + this->_len >= this->buffSize) {
|
||||
this->flush();
|
||||
if(vlen + strlen(this->buff) >= this->buffSize) {
|
||||
if(vlen + this->_len >= this->buffSize) {
|
||||
// Bigger than the whole buffer so stream it straight out.
|
||||
if(!this->_stream) {
|
||||
this->_overflow = true;
|
||||
|
|
@ -99,8 +98,5 @@ void AsyncJsonResp::_safecat(const char *val, bool escape) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
this->_appendAt(val, escape, vlen);
|
||||
}
|
||||
|
|
|
|||
10
src/Web.cpp
10
src/Web.cpp
|
|
@ -555,7 +555,7 @@ private:
|
|||
serializeGitVersion(fmt);
|
||||
fmt.endObject();
|
||||
fmt.beginArray("rooms");
|
||||
unitLen = strlen(unit);
|
||||
unitLen = fmt.length();
|
||||
section = S_ROOMS;
|
||||
idx = 0;
|
||||
firstInArray = true;
|
||||
|
|
@ -577,7 +577,7 @@ private:
|
|||
fmt.beginObject();
|
||||
serializeRoom(&somfy.rooms[idx], fmt);
|
||||
fmt.endObject();
|
||||
unitLen = pos + strlen(unit + pos);
|
||||
unitLen = pos + fmt.length();
|
||||
idx++;
|
||||
firstInArray = false;
|
||||
return;
|
||||
|
|
@ -599,7 +599,7 @@ private:
|
|||
fmt.beginObject();
|
||||
serializeShade(&somfy.shades[idx], fmt);
|
||||
fmt.endObject();
|
||||
unitLen = pos + strlen(unit + pos);
|
||||
unitLen = pos + fmt.length();
|
||||
idx++;
|
||||
firstInArray = false;
|
||||
return;
|
||||
|
|
@ -622,7 +622,7 @@ private:
|
|||
fmt.beginObject();
|
||||
serializeGroupRef(g, fmt);
|
||||
fmt.beginArray("linkedShades");
|
||||
unitLen = pos + strlen(unit + pos);
|
||||
unitLen = pos + fmt.length();
|
||||
lsIdx = 0;
|
||||
firstInArray = false;
|
||||
firstInLSArray = true;
|
||||
|
|
@ -650,7 +650,7 @@ private:
|
|||
fmt.beginObject();
|
||||
serializeShadeRef(lshade, fmt);
|
||||
fmt.endObject();
|
||||
unitLen = pos + strlen(unit + pos);
|
||||
unitLen = pos + fmt.length();
|
||||
lsIdx++;
|
||||
firstInLSArray = false;
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue