perf: stop per-frame SQLite writes and console spam, make perf.log opt-in

- Explorer: the tip check runs every frame the Explorer tab is open,
  and it read both tip rows and rewrote them each time (two autocommit
  WAL writes per frame). A steady tip is now kept in memory and skips
  SQLite. A new tip writes both rows in one savepoint, so a crash
  between them can't pair a new height with the old hash. A test
  checks that 100 steady checks write nothing.
- Console: while the daemon was in the Error state the Console tab
  added its error line on every poll, i.e. every frame, re-laying out
  the whole buffer each time. It prints once per error now: on entering
  Error, when the message changes, or after another crash
  (EmbeddedDaemon::getUnexpectedExitCount(), never reset). setState
  writes the message before the state, so a reader that sees Error sees
  that error's message. The stray " ===" suffix is gone.
- perf.log: the profiler appended a block every 300 frames on every
  run and never rotated the file (tens of MB a day for a wallet left
  open). It is now off unless the app starts with DRAGONX_PERFLOG=1;
  otherwise its zone hooks are near no-ops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:52:09 -05:00
co-authored by Claude Opus 5.5
parent 2d29a04981
commit 16e0ee9947
11 changed files with 116 additions and 17 deletions
+45 -11
View File
@@ -229,7 +229,16 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation(
int currentHeight, const std::string& currentBestHash)
{
SavedTipValidation validation;
if (currentHeight <= 0 || !ensureOpen()) return validation;
if (currentHeight <= 0) return validation;
// Steady state: the tip hasn't moved since it was saved. This runs every frame the Explorer is
// open, and used to read both meta rows and rewrite them each time (two autocommit WAL writes per
// frame). Only an exact match takes the shortcut; anything else goes through the checks below.
if (tip_known_ && !currentBestHash.empty() &&
currentHeight == tip_height_ && currentBestHash == tip_hash_) {
return validation;
}
if (!ensureOpen()) return validation;
int savedHeight = getMetaInt("tip_height", 0);
std::string savedHash = getMetaValue("tip_hash");
@@ -248,8 +257,15 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation(
if (currentHeight == savedHeight) {
if (currentBestHash.empty()) return validation;
if (currentBestHash != savedHash) clearBlocks();
updateTip(currentHeight, currentBestHash);
if (currentBestHash != savedHash) {
clearBlocks(); // same height, different block: a reorg
updateTip(currentHeight, currentBestHash);
} else {
// Already saved: remember it so later frames skip SQLite (nothing to write).
tip_known_ = true;
tip_height_ = savedHeight;
tip_hash_ = savedHash;
}
return validation;
}
@@ -280,9 +296,26 @@ void ExplorerBlockCache::applySavedTipValidation(const SavedTipValidation& valid
void ExplorerBlockCache::updateTip(int height, const std::string& hash)
{
tip_known_ = false;
if (!ensureOpen()) return;
setMetaValue("tip_height", std::to_string(std::max(0, height)));
setMetaValue("tip_hash", hash);
// Both rows in one transaction, so a crash between them can't save a new height with the old hash
// (which would wipe the cache for nothing on the next run). A savepoint, not BEGIN, so it nests
// inside a caller's transaction instead of failing.
if (!exec("SAVEPOINT explorer_tip")) return;
const bool written = setMetaValue("tip_height", std::to_string(std::max(0, height))) &&
setMetaValue("tip_hash", hash);
if (!written) exec("ROLLBACK TO explorer_tip");
const bool released = exec("RELEASE explorer_tip");
if (written && released && height > 0 && !hash.empty()) {
tip_known_ = true;
tip_height_ = height;
tip_hash_ = hash;
}
}
int ExplorerBlockCache::totalChanges() const
{
return db_ ? sqlite3_total_changes(db_) : 0;
}
bool ExplorerBlockCache::exec(const char* sql)
@@ -322,16 +355,16 @@ int ExplorerBlockCache::getMetaInt(const std::string& key, int fallback)
}
}
void ExplorerBlockCache::setMetaValue(const std::string& key, const std::string& value)
bool ExplorerBlockCache::setMetaValue(const std::string& key, const std::string& value)
{
if (!ensureOpen()) return;
if (!ensureOpen()) return false;
Statement statement(db_,
"INSERT OR REPLACE INTO explorer_cache_meta (key, value) VALUES (?, ?)");
if (!statement.handle) return;
if (!bindText(statement.handle, 1, key)) return;
if (!bindText(statement.handle, 2, value)) return;
sqlite3_step(statement.handle);
if (!statement.handle) return false;
if (!bindText(statement.handle, 1, key)) return false;
if (!bindText(statement.handle, 2, value)) return false;
return sqlite3_step(statement.handle) == SQLITE_DONE;
}
bool ExplorerBlockCache::createSchema()
@@ -392,6 +425,7 @@ void ExplorerBlockCache::migrateLegacyJsonIfNeeded()
void ExplorerBlockCache::close()
{
tip_known_ = false;
if (!db_) return;
sqlite3_close(db_);
db_ = nullptr;