Files
ObsidianDragon/src/ui/windows/console_command_executor.h
T
DanSandClaude Opus 5.5 16e0ee9947 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>
2026-10-03 19:52:09 -05:00

148 lines
7.0 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// ConsoleCommandExecutor — the pluggable backend behind the shared ConsoleTab UI. It
// abstracts the only real differences between the full-node console (dragonxd log + RPC
// command execution) and the lite console (lite diagnostics ring + backend command),
// so both variants use one rich terminal widget instead of two implementations.
#pragma once
#include "console_channel.h"
#include "imgui.h"
#include <atomic>
#include <cstdint>
#include <deque>
#include <functional>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
namespace dragonx {
class App;
namespace ui {
struct ConsoleCommandCategory; // console_command_reference.h — command-reference table
using ConsoleAddLineFn = std::function<void(const std::string&, ConsoleChannel)>;
struct ConsoleStatusLine {
std::string text;
ImU32 color = IM_COL32(200, 200, 200, 255);
bool pulse = false; // animate the toolbar dot (starting/connecting)
};
// Which log-filter toggles the toolbar should show for a given backend. The full node has a
// daemon/xmrig log + an RPC trace; the lite backend has neither (its diagnostics ring maps to
// the App/Error channels), so it only wants the errors-only + app-messages toggles.
struct ConsoleLogFilterCaps {
bool daemon = false; // daemon + xmrig output toggle
bool errorsOnly = false; // errors-only toggle
bool rpcTrace = false; // RPC method/source trace toggle
bool appMessages = false; // app / diagnostics log toggle
bool any() const { return daemon || errorsOnly || rpcTrace || appMessages; }
};
class ConsoleCommandExecutor {
public:
virtual ~ConsoleCommandExecutor() = default;
// True when commands can be run (RPC connected / lite wallet open).
virtual bool isReady() const = 0;
// A command is currently in flight (input is disabled while true).
virtual bool busy() const { return false; }
// Submit a user command for async execution. The UI has already echoed "> cmd" and
// handled the built-ins (clear/help/quit). Results arrive later via pollResult().
virtual void submit(const std::string& cmd) = 0;
// Pop one completed command result (raw text + error flag) if available; the UI
// formats/colors it via FormatConsoleRpcResultLines. Returns false if none pending.
virtual bool pollResult(std::string& result, bool& isError) = 0;
// Pull any new passive log lines (daemon/xmrig output, or the lite diagnostics ring)
// and hand each to `add`. Called once per frame.
virtual void pollLogLines(const ConsoleAddLineFn& add) { (void)add; }
// UI-chrome capabilities.
// True when this backend speaks JSON-RPC to a daemon (the full node). Gates daemon-specific
// console behavior (the 'stop' shutdown confirm, "not connected to daemon" wording) and the
// command-reference modal's JSON string-arg quoting — the lite backend takes bare tokens.
virtual bool hasRpcReference() const { return false; }
// The command-reference table this backend offers (browsed by the console's reference modal),
// or nullptr for none. Full node = the JSON-RPC reference; lite = its own backend verbs.
virtual const std::vector<ConsoleCommandCategory>* commandReference() const { return nullptr; }
// Which log-filter toggles the toolbar should show (default: none).
virtual ConsoleLogFilterCaps logFilterCaps() const { return {}; }
// Print the backend-appropriate `help` output via `add`.
virtual void printHelp(const ConsoleAddLineFn& add) = 0;
// Extra status lines drawn above the toolbar (lite shows sync + last error).
virtual std::vector<ConsoleStatusLine> statusLines() const { return {}; }
// Short status shown in the toolbar (daemon state / lite connection). Empty text =>
// the toolbar shows its generic "no daemon" label.
virtual ConsoleStatusLine toolbarStatus() const { return {}; }
};
// ── Full-node: dragonxd log ingestion + RPC command execution ────────────────
class FullNodeConsoleExecutor : public ConsoleCommandExecutor {
public:
explicit FullNodeConsoleExecutor(App* app) : app_(app) {}
bool isReady() const override;
void submit(const std::string& cmd) override;
bool pollResult(std::string& result, bool& isError) override;
void pollLogLines(const ConsoleAddLineFn& add) override;
bool hasRpcReference() const override { return true; }
const std::vector<ConsoleCommandCategory>* commandReference() const override;
// Full node: daemon/xmrig log, errors-only, RPC trace, and app messages.
ConsoleLogFilterCaps logFilterCaps() const override { return {true, true, true, true}; }
void printHelp(const ConsoleAddLineFn& add) override;
ConsoleStatusLine toolbarStatus() const override;
// A command is in flight on the RPC worker — lets the UI disable the input (no queued pile-up).
bool busy() const override { return in_flight_.load() > 0; }
private:
App* app_;
std::atomic<int> in_flight_{0};
size_t last_daemon_output_size_ = 0;
size_t last_xmrig_output_size_ = 0;
int last_daemon_state_ = -1; // daemon::EmbeddedDaemon::State as int
std::string last_daemon_error_; // error text last printed (main thread only)
std::uint64_t last_printed_exit_count_ = 0; // daemon unexpected-exit count at that print
bool last_rpc_connected_ = false;
std::deque<std::pair<std::string, bool>> results_; // {text, isError}
std::mutex results_mutex_;
// Chat diagnostics ring cursor: chat code logs via wallet::liteLog on BOTH variants, so the full-node
// console surfaces those lines (as App messages) too — mirrors LiteConsoleExecutor's diag drain.
std::uint64_t diag_gen_ = static_cast<std::uint64_t>(-1);
};
// ── Lite: diagnostics ring + backend console command ─────────────────────────
class LiteConsoleExecutor : public ConsoleCommandExecutor {
public:
explicit LiteConsoleExecutor(App* app) : app_(app) {}
bool isReady() const override;
bool busy() const override;
void submit(const std::string& cmd) override;
bool pollResult(std::string& result, bool& isError) override;
void pollLogLines(const ConsoleAddLineFn& add) override;
// Lite: no daemon log / RPC trace — its diagnostics ring maps to the App + Error
// channels, so offer errors-only + app-messages (plus the always-shown text filter).
ConsoleLogFilterCaps logFilterCaps() const override { return {false, true, false, true}; }
const std::vector<ConsoleCommandCategory>* commandReference() const override;
void printHelp(const ConsoleAddLineFn& add) override;
std::vector<ConsoleStatusLine> statusLines() const override;
ConsoleStatusLine toolbarStatus() const override;
private:
App* app_;
std::uint64_t diag_gen_ = static_cast<std::uint64_t>(-1); // last consumed diagnostics generation
};
} // namespace ui
} // namespace dragonx