diff --git a/pages/Advanced.qml b/pages/Advanced.qml index d9c868aec4..ae815f4aa5 100644 --- a/pages/Advanced.qml +++ b/pages/Advanced.qml @@ -78,6 +78,7 @@ ColumnLayout { property Item currentView property Item previousView property Mining miningView: Mining { } + property MiningStats miningStatsView: MiningStats { } property TxKey prooveView: TxKey { } property SharedRingDB sharedRingDBView: SharedRingDB { } property Sign signView: Sign { } @@ -106,6 +107,10 @@ ColumnLayout { name: "Mining" PropertyChanges { target: stateView; currentView: stateView.miningView } PropertyChanges { target: root; panelHeight: stateView.miningView.miningHeight + 140 } + }, State { + name: "MiningStats" + PropertyChanges { target: stateView; currentView: stateView.miningStatsView } + PropertyChanges { target: root; panelHeight: stateView.miningStatsView.miningStatsHeight + 140 } }, State { name: "Prove" PropertyChanges { target: stateView; currentView: stateView.prooveView } diff --git a/pages/Mining.qml b/pages/Mining.qml index e0f220aad8..17c02acd65 100644 --- a/pages/Mining.qml +++ b/pages/Mining.qml @@ -379,6 +379,37 @@ Rectangle { } } + ColumnLayout { + Layout.fillWidth: true + Layout.alignment : Qt.AlignTop | Qt.AlignLeft + + MoneroComponents.Label { + id: statisticsLabel + visible: persistentSettings.allow_p2pool_mining + color: MoneroComponents.Style.defaultFontColor + text: qsTr("Statistics") + translationManager.emptyString + fontSize: 16 + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 16 + + MoneroComponents.StandardButton { + id: miningStatsButton + small: true + primary: false + text: qsTr("Open mining statistics") + translationManager.emptyString + enabled: appWindow.isMining + visible: persistentSettings.allow_p2pool_mining + onClicked: { + p2poolManager.getStats(); + stateView.state = "MiningStats"; + } + } + } + ListModel { id: chainModel diff --git a/pages/MiningStats.qml b/pages/MiningStats.qml new file mode 100644 index 0000000000..7d13614f54 --- /dev/null +++ b/pages/MiningStats.qml @@ -0,0 +1,203 @@ +// Copyright (c) 2014-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQml.Models 2.2 +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import "../components" as MoneroComponents +import moneroComponents.P2PoolManager 1.0 + +Rectangle { + id: root + color: "transparent" + property alias miningStatsHeight: miningStatsContentColumn.height + property int entryHeight: 24 + property int entryPixelSize: 14 + property bool showFullStats: false + + Timer { + id: timer + interval: 5000 + running: stateView.state === "MiningStats"; + repeat: true + onTriggered: update() + } + + function update() + { + if (persistentSettings.allow_p2pool_mining) { + p2poolManager.getStats(); + } + } + + function recursivelyFindPairs(statsMap, depth) + { + for (var key in statsMap) + { + var value = statsMap[key]; + if (typeof value === 'object') { + miningStatsModel.append({"key": key + ":", "value": "", "depth": depth}); + recursivelyFindPairs(value, depth + 1); + } + else { + miningStatsModel.append({"key": key + ":", "value": value.toString(), "depth": depth}); + } + } + } + + function updateSimpleMiningStats(statsMap) + { + var pool_statistics = statsMap["pool_statistics"]; + + if (pool_statistics != null) + { + var statsData = [ + {"key": "Your hashrate:", "value": statsMap["current_hashrate"] + " H/s"}, + + {"key": "Main chain height:", "value": statsMap["height"]}, + {"key": "Side chain height:", "value": pool_statistics["sidechainHeight"]}, + {"key": "PPLNS window:", "value": pool_statistics["pplnsWindowSize"] + " blocks"}, + {"key": "Your shares:", "value": statsMap["shares_found"] + " blocks"}, + {"key": "Block reward share:", "value": statsMap["block_reward_share_percent"] + "%"}, + + {"key": "Connections: ", "value": statsMap["connections"] + " (" + statsMap["incoming_connections"] + " incoming)"}, + {"key": "Peer list size: ", "value": statsMap["peer_list_size"]}, + {"key": "Uptime: ", "value": statsMap["uptime"] + "s"} + ]; + + for (var element of statsData) + { + if (element.value != null) + { + simpleMiningStatsModel.append({"key": element["key"], "value": element["value"].toString(), "depth": 0}); + } + } + } + } + + function updateMiningStats(statsMap) + { + simpleMiningStatsModel.clear(); + updateSimpleMiningStats(statsMap); + + miningStatsModel.clear(); + recursivelyFindPairs(statsMap, 0); + } + + ListModel { id: simpleMiningStatsModel } + ListModel { id: miningStatsModel } + + Column { + id: miningStatsContentColumn + spacing: entryHeight + + MoneroComponents.StandardButton { + id: backButton + text: qsTr("Back") + translationManager.emptyString; + width: 100 + primary: false + onClicked: { + stateView.state = "Mining"; + } + } + + ListView { + id: simpleMiningStatsView + width: root.width + height: count * entryHeight + entryHeight + model: simpleMiningStatsModel + interactive: false + + delegate: Rectangle { + id: miningStatsDelegate + color: "transparent" + height: entryHeight + Layout.fillWidth: true + + RowLayout { + MoneroComponents.TextBlock { + Layout.fillWidth: true + Layout.leftMargin: depth * entryPixelSize + font.pixelSize: entryPixelSize + text: key + translationManager.emptyString + } + + MoneroComponents.TextBlock { + Layout.fillWidth: true + color: MoneroComponents.Style.dimmedFontColor + font.pixelSize: entryPixelSize + text: value + translationManager.emptyString + } + } + } + } + + MoneroComponents.CheckBox2 { + id: showFullStatsCheckbox + checked: showFullStats + onClicked: showFullStats = !showFullStats + text: qsTr("Show full statistics") + translationManager.emptyString + } + + ListView { + visible: showFullStats + id: miningStatsView + width: root.width + height: count * entryHeight + entryHeight + model: miningStatsModel + interactive: false + + delegate: Rectangle { + id: miningStatsDelegate + color: "transparent" + height: entryHeight + Layout.fillWidth: true + + RowLayout { + MoneroComponents.TextBlock { + Layout.fillWidth: true + Layout.leftMargin: depth * entryPixelSize + font.pixelSize: entryPixelSize + text: key + translationManager.emptyString + } + + MoneroComponents.TextBlock { + Layout.fillWidth: true + color: MoneroComponents.Style.dimmedFontColor + font.pixelSize: entryPixelSize + text: value + translationManager.emptyString + } + } + } + } + } + + Component.onCompleted: { + p2poolManager.p2poolStats.connect(updateMiningStats); + } +} diff --git a/qml.qrc b/qml.qrc index 4915d4ae43..549723a860 100644 --- a/qml.qrc +++ b/qml.qrc @@ -26,6 +26,7 @@ pages/History.qml pages/AddressBook.qml pages/Mining.qml + pages/MiningStats.qml components/ContextMenu.qml components/ContextMenuItem.qml components/NetworkStatusItem.qml diff --git a/src/p2pool/P2PoolManager.cpp b/src/p2pool/P2PoolManager.cpp index 0a035d5a75..4519566b30 100644 --- a/src/p2pool/P2PoolManager.cpp +++ b/src/p2pool/P2PoolManager.cpp @@ -151,6 +151,24 @@ void P2PoolManager::getStatus() { return; } +void P2PoolManager::getStats() +{ + QVariantMap statsMap; + QString statsPath = m_p2poolPath + "/stats"; + QDirIterator it(statsPath, QDir::Filter::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + QFile statsFile(it.next()); + statsFile.open(QIODevice::ReadOnly); + QTextStream statsOut(&statsFile); + QByteArray data; + statsOut >> data; + statsFile.close(); + QVariantMap jsonMap = QJsonDocument::fromJson(data).object().toVariantMap(); + statsMap.insert(jsonMap); + } + emit p2poolStats(statsMap); +} + bool P2PoolManager::start(const QString &flags, const QString &address, const QString &chain, const QString &threads) { // prepare command line arguments and pass to p2pool diff --git a/src/p2pool/P2PoolManager.h b/src/p2pool/P2PoolManager.h index b813e042ab..b0c1ead84a 100644 --- a/src/p2pool/P2PoolManager.h +++ b/src/p2pool/P2PoolManager.h @@ -50,6 +50,7 @@ class P2PoolManager : public QObject Q_INVOKABLE void exit(); Q_INVOKABLE bool isInstalled(); Q_INVOKABLE void getStatus(); + Q_INVOKABLE void getStats(); Q_INVOKABLE void download(); enum DownloadError { @@ -68,6 +69,7 @@ class P2PoolManager : public QObject void p2poolStatus(bool isMining, int hashrate) const; void p2poolDownloadFailure(int errorCode) const; void p2poolDownloadSuccess() const; + void p2poolStats(QVariantMap statsMap) const; private: std::unique_ptr m_p2poold;