Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
200 lines
6.1 KiB
C++
200 lines
6.1 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <mutex>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <cstdint>
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include "../util/logger.h"
|
|
|
|
namespace dragonx {
|
|
namespace util {
|
|
|
|
/**
|
|
* @brief Lightweight per-frame performance profiler that writes periodic
|
|
* summaries to a log file.
|
|
*
|
|
* Usage:
|
|
* PerfLog::instance().beginFrame();
|
|
* { PERF_SCOPE("EventPoll"); ... }
|
|
* { PERF_SCOPE("AppUpdate"); ... }
|
|
* { PERF_SCOPE("AppRender"); ... }
|
|
* PerfLog::instance().endFrame();
|
|
*
|
|
* Every N frames (default 300 ≈ 5s at 60fps) it appends a summary block
|
|
* to <ObsidianDragonDir>/perf.log with min/avg/max/p95 for each zone.
|
|
*/
|
|
class PerfLog {
|
|
public:
|
|
static PerfLog& instance() {
|
|
static PerfLog s;
|
|
return s;
|
|
}
|
|
|
|
/// Open (or re-open) the log file. Called once at startup.
|
|
bool init(const std::string& path, int flushIntervalFrames = 300) {
|
|
std::lock_guard<std::mutex> lk(mutex_);
|
|
flushInterval_ = flushIntervalFrames;
|
|
file_.open(path, std::ios::out | std::ios::app);
|
|
if (!file_.is_open()) {
|
|
DEBUG_LOGF("[PerfLog] Failed to open %s\n", path.c_str());
|
|
return false;
|
|
}
|
|
DEBUG_LOGF("[PerfLog] Logging to %s (flush every %d frames)\n",
|
|
path.c_str(), flushInterval_);
|
|
|
|
// Header
|
|
file_ << "\n========== PerfLog started ==========\n";
|
|
file_.flush();
|
|
ready_ = true;
|
|
return true;
|
|
}
|
|
|
|
/// Call at the very start of the frame.
|
|
void beginFrame() {
|
|
if (!ready_) return;
|
|
frameStart_ = Clock::now();
|
|
}
|
|
|
|
/// Call at the very end of the frame (after present/swap).
|
|
void endFrame() {
|
|
if (!ready_) return;
|
|
double totalUs = usFrom(frameStart_);
|
|
record("FRAME_TOTAL", totalUs);
|
|
frameCount_++;
|
|
if (frameCount_ >= flushInterval_) {
|
|
flush();
|
|
frameCount_ = 0;
|
|
}
|
|
}
|
|
|
|
/// Begin a named zone. Returns an opaque time point.
|
|
std::chrono::steady_clock::time_point beginZone() {
|
|
return Clock::now();
|
|
}
|
|
|
|
/// End a named zone. Pass the time point from beginZone().
|
|
void endZone(const char* name, std::chrono::steady_clock::time_point start) {
|
|
if (!ready_) return;
|
|
record(name, usFrom(start));
|
|
}
|
|
|
|
private:
|
|
using Clock = std::chrono::steady_clock;
|
|
using TimePoint = Clock::time_point;
|
|
|
|
PerfLog() = default;
|
|
~PerfLog() {
|
|
if (ready_ && frameCount_ > 0) flush();
|
|
}
|
|
|
|
PerfLog(const PerfLog&) = delete;
|
|
PerfLog& operator=(const PerfLog&) = delete;
|
|
|
|
static double usFrom(TimePoint start) {
|
|
return std::chrono::duration<double, std::micro>(Clock::now() - start).count();
|
|
}
|
|
|
|
/// Record a single timing sample (microseconds).
|
|
void record(const char* name, double us) {
|
|
auto& bucket = buckets_[name];
|
|
bucket.samples.push_back(us);
|
|
}
|
|
|
|
/// Write accumulated stats and reset.
|
|
void flush() {
|
|
std::lock_guard<std::mutex> lk(mutex_);
|
|
if (!file_.is_open()) return;
|
|
|
|
auto now = std::chrono::system_clock::now();
|
|
auto tt = std::chrono::system_clock::to_time_t(now);
|
|
char timeBuf[64];
|
|
std::strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", std::localtime(&tt));
|
|
|
|
file_ << "\n--- " << timeBuf << " (" << frameCount_ << " frames) ---\n";
|
|
|
|
// Sort zone names for consistent output
|
|
std::vector<std::string> names;
|
|
names.reserve(buckets_.size());
|
|
for (auto& kv : buckets_) names.push_back(kv.first);
|
|
std::sort(names.begin(), names.end());
|
|
|
|
for (auto& name : names) {
|
|
auto& b = buckets_[name];
|
|
if (b.samples.empty()) continue;
|
|
std::sort(b.samples.begin(), b.samples.end());
|
|
size_t n = b.samples.size();
|
|
double sum = 0;
|
|
for (double v : b.samples) sum += v;
|
|
double avg = sum / n;
|
|
double mn = b.samples.front();
|
|
double mx = b.samples.back();
|
|
double p95 = b.samples[std::min(n - 1, (size_t)(n * 0.95))];
|
|
double p99 = b.samples[std::min(n - 1, (size_t)(n * 0.99))];
|
|
|
|
char line[256];
|
|
std::snprintf(line, sizeof(line),
|
|
" %-28s min=%7.0f avg=%7.0f p95=%7.0f p99=%7.0f max=%7.0f us\n",
|
|
name.c_str(), mn, avg, p95, p99, mx);
|
|
file_ << line;
|
|
}
|
|
|
|
// FPS summary from FRAME_TOTAL
|
|
auto it = buckets_.find("FRAME_TOTAL");
|
|
if (it != buckets_.end() && !it->second.samples.empty()) {
|
|
auto& s = it->second.samples;
|
|
double avgFrame = 0;
|
|
for (double v : s) avgFrame += v;
|
|
avgFrame /= s.size();
|
|
double fps = (avgFrame > 0) ? 1000000.0 / avgFrame : 0;
|
|
char fpsLine[128];
|
|
std::snprintf(fpsLine, sizeof(fpsLine),
|
|
" FPS: %.1f (avg frame %.1f us = %.2f ms)\n",
|
|
fps, avgFrame, avgFrame / 1000.0);
|
|
file_ << fpsLine;
|
|
}
|
|
|
|
file_.flush();
|
|
|
|
// Reset all buckets
|
|
for (auto& kv : buckets_) kv.second.samples.clear();
|
|
}
|
|
|
|
struct Bucket {
|
|
std::vector<double> samples; // microseconds
|
|
};
|
|
|
|
std::mutex mutex_;
|
|
std::ofstream file_;
|
|
bool ready_ = false;
|
|
int flushInterval_ = 300;
|
|
int frameCount_ = 0;
|
|
TimePoint frameStart_;
|
|
std::unordered_map<std::string, Bucket> buckets_;
|
|
};
|
|
|
|
/// RAII scope timer — records duration under a named zone.
|
|
struct PerfScope {
|
|
const char* name;
|
|
std::chrono::steady_clock::time_point start;
|
|
PerfScope(const char* n) : name(n), start(PerfLog::instance().beginZone()) {}
|
|
~PerfScope() { PerfLog::instance().endZone(name, start); }
|
|
};
|
|
|
|
#define PERF_SCOPE(name) ::dragonx::util::PerfScope _perf_##__LINE__(name)
|
|
|
|
// Manual begin/end for zones that span across scopes
|
|
#define PERF_BEGIN(var) auto var = ::dragonx::util::PerfLog::instance().beginZone()
|
|
#define PERF_END(name, var) ::dragonx::util::PerfLog::instance().endZone(name, var)
|
|
|
|
} // namespace util
|
|
} // namespace dragonx
|