diff --git a/src/app.cpp b/src/app.cpp index 0143d71..45a8658 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1271,8 +1271,10 @@ void App::update() // Record hashrate sample for the chart ps.hashrate_history.push_back(ps.hashrate_10s); + ps.hashrate_history_t.push_back(std::time(nullptr)); if (ps.hashrate_history.size() > PoolMiningState::MAX_HISTORY) { ps.hashrate_history.erase(ps.hashrate_history.begin()); + ps.hashrate_history_t.erase(ps.hashrate_history_t.begin()); } } else if (xmrig_manager_ && !xmrig_manager_->isRunning()) { state_.pool_mining.xmrig_running = false; diff --git a/src/data/wallet_state.h b/src/data/wallet_state.h index 79f20d9..4f87016 100644 --- a/src/data/wallet_state.h +++ b/src/data/wallet_state.h @@ -146,7 +146,8 @@ struct MiningInfo { // History for chart std::vector hashrate_history; // Last N samples - static constexpr int MAX_HISTORY = 300; // 5 minutes at 1s intervals + std::vector hashrate_history_t; // wall-clock stamp per sample (lockstep with hashrate_history) + static constexpr int MAX_HISTORY = 300; // 5 minutes at ~1s intervals // Recent daemon log lines for the mining log panel std::vector log_lines; @@ -263,7 +264,8 @@ struct PoolMiningState { // Hashrate history for chart (mirrors MiningInfo::hashrate_history) std::vector hashrate_history; - static constexpr int MAX_HISTORY = 60; // 5 minutes at ~5s intervals + std::vector hashrate_history_t; // wall-clock stamp per sample (lockstep with hashrate_history) + static constexpr int MAX_HISTORY = 60; // ~2 min at the ~2s pool poll // Recent log lines for the log panel std::vector log_lines; diff --git a/src/services/network_refresh_service.cpp b/src/services/network_refresh_service.cpp index c5a4068..8b235a9 100644 --- a/src/services/network_refresh_service.cpp +++ b/src/services/network_refresh_service.cpp @@ -1844,8 +1844,10 @@ void NetworkRefreshService::applyMiningRefreshResult(WalletState& state, if (result.localHashrate) { state.mining.localHashrate = *result.localHashrate; state.mining.hashrate_history.push_back(state.mining.localHashrate); + state.mining.hashrate_history_t.push_back(updatedAt); if (state.mining.hashrate_history.size() > MiningInfo::MAX_HISTORY) { state.mining.hashrate_history.erase(state.mining.hashrate_history.begin()); + state.mining.hashrate_history_t.erase(state.mining.hashrate_history_t.begin()); } } diff --git a/src/ui/windows/mining_stats.cpp b/src/ui/windows/mining_stats.cpp index 38f75a7..1987a55 100644 --- a/src/ui/windows/mining_stats.cpp +++ b/src/ui/windows/mining_stats.cpp @@ -9,6 +9,7 @@ #include "../../config/settings.h" #include "../../util/i18n.h" #include "../../util/pool_registry.h" +#include "../../util/text_format.h" #include "../schema/ui_schema.h" #include "../material/type.h" #include "../material/draw_helpers.h" @@ -40,6 +41,7 @@ static bool s_show_solo_log = false; // --------------------------------------------------------------------------- static void DrawHashrateSparkline(ImDrawList* dl, ImVec2 regionMin, ImVec2 regionMax, const std::vector& chartHistory, + std::time_t oldestSampleT, // 0 when unknown ImFont* capFont, float dp) { if (chartHistory.size() < 2) return; @@ -116,10 +118,11 @@ static void DrawHashrateSparkline(ImDrawList* dl, ImVec2 regionMin, ImVec2 regio dl->AddText(capFont, capFont->LegacySize, ImVec2(plotLeft + 2 * dp, plotBottom + 4 * dp), OnSurfaceDisabled(), yMinStr.c_str()); // X-axis labels + std::string agoLbl = (oldestSampleT > 0) ? dragonx::util::formatTimeAgoShort(oldestSampleT) + : std::string(TR("mining_chart_start")); dl->AddText(capFont, capFont->LegacySize, ImVec2(plotLeft, chartBot - capFont->LegacySize - 2 * dp), OnSurfaceDisabled(), - chartHistory.size() >= 300 ? TR("mining_chart_5m_ago") : - chartHistory.size() >= 60 ? TR("mining_chart_1m_ago") : TR("mining_chart_start")); + agoLbl.c_str()); std::string nowLbl = TR("mining_chart_now"); ImVec2 nowSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, nowLbl.c_str()); dl->AddText(capFont, capFont->LegacySize, @@ -374,6 +377,11 @@ void RenderMiningStats(const WalletState& state, const MiningInfo& mining, const std::vector& chartHistory = s_pool_mode ? state.pool_mining.hashrate_history : mining.hashrate_history; + const std::vector& chartHistoryT = s_pool_mode + ? state.pool_mining.hashrate_history_t + : mining.hashrate_history_t; + const std::time_t oldestT = (chartHistoryT.size() == chartHistory.size() && !chartHistoryT.empty()) + ? chartHistoryT.front() : 0; bool hasChartContent = chartHistory.size() >= 2; float statRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + Layout::spacingSm(); @@ -432,7 +440,7 @@ void RenderMiningStats(const WalletState& state, const MiningInfo& mining, DrawHashrateSparkline(dl, ImVec2(chartBandX, rightMin.y + statRowH * 0.5f), ImVec2(chartBandX + chartBandW, rightMax.y), - chartHistory, capFont, dp); + chartHistory, oldestT, capFont, dp); } else { const char* hint = TR("mining_chart_start"); ImVec2 hs = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, hint); @@ -542,7 +550,7 @@ void RenderMiningStats(const WalletState& state, const MiningInfo& mining, DrawHashrateSparkline(dl, ImVec2(cardMin.x + pad, cardMin.y + statRowH), ImVec2(cardMax.x - pad, cardMax.y), - chartHistory, capFont, dp); + chartHistory, oldestT, capFont, dp); } ImGui::Dummy(ImVec2(availWidth, totalCardH)); } diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index e093b35..e49e5c5 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -2612,6 +2612,16 @@ void testNetworkRefreshResultModels() EXPECT_EQ(state.mining.genproclimit, 4); EXPECT_NEAR(state.mining.localHashrate, 45.0, 0.0001); EXPECT_EQ(state.mining.hashrate_history.size(), static_cast(1)); + EXPECT_EQ(state.mining.hashrate_history_t.size(), static_cast(1)); + EXPECT_EQ(state.mining.hashrate_history_t[0], static_cast(2345)); + { + auto capState = state; + for (int i = 0; i < dragonx::MiningInfo::MAX_HISTORY + 20; ++i) { + Refresh::applyMiningRefreshResult(capState, mining, 3000 + i); + } + EXPECT_EQ(capState.mining.hashrate_history.size(), static_cast(dragonx::MiningInfo::MAX_HISTORY)); + EXPECT_EQ(capState.mining.hashrate_history_t.size(), capState.mining.hashrate_history.size()); + } EXPECT_NEAR(state.mining.networkHashrate, 900.0, 0.0001); EXPECT_NEAR(state.mining.daemon_memory_mb, 64.0, 0.0001); EXPECT_EQ(state.last_mining_update, static_cast(2345));