ObsidianDragon - DragonX ImGui Wallet
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)
This commit is contained in:
199
src/util/perf_log.h
Normal file
199
src/util/perf_log.h
Normal file
@@ -0,0 +1,199 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user