feat: log timings for slow work, socket clients and boot crashes

Add Timing.h and use it to log what holds up the main loop, how long a
new socket client's setup takes, slow socket sends, and how long each
web request took and from which client. Socket connects and disconnects
are logged with how long the client stayed, and late pings as warnings.
Routine sends and pings are not logged, as each line costs several ms
on a 115200 baud console.

Report a stored crash once with the crashed and running build IDs, then
erase it, since it was reported on every boot and its backtrace only
decodes against the build that crashed. Mount the file system once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-11 20:35:43 +02:00
parent 05c3d309a6
commit dd47c04905
6 changed files with 157 additions and 18 deletions

50
src/Timing.h Normal file
View file

@ -0,0 +1,50 @@
#ifndef timing_h
#define timing_h
#include <Arduino.h>
#include "esp_log.h"
// Timings shorter than this are not logged. Every line costs several ms on a
// 115200 baud console so raise it (e.g. -DTIMING_LOG_MIN_MS=50) to see only
// the calls worth looking at.
#ifndef TIMING_LOG_MIN_MS
#define TIMING_LOG_MIN_MS 0
#endif
// Anything that takes at least this long is logged as a warning so the slow
// calls stand out from the routine timings.
#ifndef TIMING_SLOW_MS
#define TIMING_SLOW_MS 1000
#endif
// Measures elapsed time in microseconds. micros() wraps every ~71 minutes but
// the unsigned subtraction keeps any span shorter than that correct.
class ElapsedTimer {
uint32_t _start;
public:
ElapsedTimer() : _start(micros()) {}
void restart() { _start = micros(); }
uint32_t us() const { return micros() - _start; }
};
// Logs how long something took as milliseconds with microsecond precision.
static inline void logElapsed(const char *tag, const char *what, uint32_t us) {
const unsigned long ms = us / 1000, frac = us % 1000;
if(ms < TIMING_LOG_MIN_MS) return;
if(ms >= TIMING_SLOW_MS) ESP_LOGW(tag, "%s took %lu.%03lums (slow)", what, ms, frac);
else ESP_LOGI(tag, "%s took %lu.%03lums", what, ms, frac);
}
// For calls that run constantly, like loop(), where logging every pass would
// flood the console. Only logs once the time reaches minMs.
static inline void logIfSlow(const char *tag, const char *what, uint32_t us, uint32_t minMs) {
if(us >= minMs * 1000) logElapsed(tag, what, us);
}
// Logs the time the enclosing scope took when it ends.
class ScopeTimer {
const char *_tag;
const char *_what;
ElapsedTimer _timer;
public:
ScopeTimer(const char *tag, const char *what) : _tag(tag), _what(what) {}
~ScopeTimer() { logElapsed(_tag, _what, _timer.us()); }
};
#endif