diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 76ae545511..c68156b34f 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -220,7 +220,7 @@ proc newModule*[T]( result, events, accountsService, settingsService, stickersService, profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService, devicesService, mailserversService, chatService, ensService, walletAccountService, generalService, communityService, - networkService, keycardService, keychainService, tokenService + networkService, keycardService, keychainService, tokenService, nodeService ) result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService, networkService, tokenService, keycardService) diff --git a/src/app/modules/main/profile_section/module.nim b/src/app/modules/main/profile_section/module.nim index 35f62527cb..a2d5e93832 100644 --- a/src/app/modules/main/profile_section/module.nim +++ b/src/app/modules/main/profile_section/module.nim @@ -23,6 +23,7 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/keycard/service as keycard_service import ../../../../app_service/service/keychain/service as keychain_service import ../../../../app_service/service/token/service as token_service +import ../../../../app_service/service/node/service as node_service import ./profile/module as profile_module import ./contacts/module as contacts_module @@ -85,7 +86,8 @@ proc newModule*(delegate: delegate_interface.AccessInterface, networkService: network_service.Service, keycardService: keycard_service.Service, keychainService: keychain_service.Service, - tokenService: token_service.Service + tokenService: token_service.Service, + nodeService: node_service.Service ): Module = result = Module() result.delegate = delegate @@ -112,7 +114,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, privacyService, accountsService, walletAccountService, keychainService) result.walletModule = wallet_module.newModule(result, events, accountsService, walletAccountService, settingsService, - networkService, devicesService) + networkService, devicesService, nodeService) singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant) diff --git a/src/app/modules/main/profile_section/wallet/controller.nim b/src/app/modules/main/profile_section/wallet/controller.nim index 4ff74ca7bf..2aec234407 100644 --- a/src/app/modules/main/profile_section/wallet/controller.nim +++ b/src/app/modules/main/profile_section/wallet/controller.nim @@ -2,22 +2,26 @@ import io_interface import app/core/eventemitter import app_service/service/wallet_account/service as wallet_account_service import app_service/service/devices/service as devices_service +import app_service/service/node/service as node_service type Controller* = ref object of RootObj delegate: io_interface.AccessInterface events: EventEmitter walletAccountService: wallet_account_service.Service + nodeService: node_service.Service proc newController*( delegate: io_interface.AccessInterface, events: EventEmitter, walletAccountService: wallet_account_service.Service, + nodeService: node_service.Service ): Controller = result = Controller() result.delegate = delegate result.events = events result.walletAccountService = walletAccountService + result.nodeService = nodeService proc delete*(self: Controller) = discard @@ -28,4 +32,10 @@ proc init*(self: Controller) = self.delegate.onLocalPairingStatusUpdate(data) proc hasPairedDevices*(self: Controller): bool = - return self.walletAccountService.hasPairedDevices() \ No newline at end of file + return self.walletAccountService.hasPairedDevices() + +proc getRpcStats*(self: Controller): string = + return self.nodeService.getRpcStats() + +proc resetRpcStats*(self: Controller) = + self.nodeService.resetRpcStats() diff --git a/src/app/modules/main/profile_section/wallet/io_interface.nim b/src/app/modules/main/profile_section/wallet/io_interface.nim index dc637db09a..f3e552220f 100644 --- a/src/app/modules/main/profile_section/wallet/io_interface.nim +++ b/src/app/modules/main/profile_section/wallet/io_interface.nim @@ -57,3 +57,9 @@ method hasPairedDevices*(self: AccessInterface): bool {.base.} = method onLocalPairingStatusUpdate*(self: AccessInterface, data: LocalPairingStatus) {.base.} = raise newException(ValueError, "No implementation available") + +method getRpcStats*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method resetRpcStats*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/wallet/module.nim b/src/app/modules/main/profile_section/wallet/module.nim index f5b8f90404..318836dce9 100644 --- a/src/app/modules/main/profile_section/wallet/module.nim +++ b/src/app/modules/main/profile_section/wallet/module.nim @@ -15,6 +15,7 @@ import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service import app_service/service/settings/service as settings_service import app_service/service/devices/service as devices_service +import app_service/service/node/service as node_service logScope: topics = "profile-section-wallet-module" @@ -33,6 +34,7 @@ type accountsService: accounts_service.Service walletAccountService: wallet_account_service.Service devicesService: devices_service.Service + nodeService: node_service.Service accountsModule: accounts_module.AccessInterface networksModule: networks_module.AccessInterface keypairImportModule: keypair_import_module.AccessInterface @@ -44,11 +46,12 @@ proc newModule*( walletAccountService: wallet_account_service.Service, settingsService: settings_service.Service, networkService: network_service.Service, - devicesService: devices_service.Service + devicesService: devices_service.Service, + nodeService: node_service.Service ): Module = result = Module() result.delegate = delegate - result.controller = controller.newController(result, events, walletAccountService) + result.controller = controller.newController(result, events, walletAccountService, nodeService) result.view = newView(result) result.viewVariant = newQVariant(result.view) result.events = events @@ -132,4 +135,10 @@ method hasPairedDevices*(self: Module): bool = method onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) = if data.state == LocalPairingState.Finished: - self.view.emitHasPairedDevicesChangedSignal() \ No newline at end of file + self.view.emitHasPairedDevicesChangedSignal() + +method getRpcStats*(self: Module): string = + return self.controller.getRpcStats() + +method resetRpcStats*(self: Module) = + self.controller.resetRpcStats() diff --git a/src/app/modules/main/profile_section/wallet/view.nim b/src/app/modules/main/profile_section/wallet/view.nim index 8eb44f5593..2549fe0e7f 100644 --- a/src/app/modules/main/profile_section/wallet/view.nim +++ b/src/app/modules/main/profile_section/wallet/view.nim @@ -54,4 +54,9 @@ QtObject: return self.delegate.hasPairedDevices() QtProperty[bool] hasPairedDevices: read = getHasPairedDevices - notify = hasPairedDevicesChanged \ No newline at end of file + notify = hasPairedDevicesChanged + + proc getRpcStats(self: View): string {.slot.} = + return self.delegate.getRpcStats() + proc resetRpcStats(self: View) {.slot.} = + self.delegate.resetRpcStats() diff --git a/src/app/modules/main/wallet_section/io_interface.nim b/src/app/modules/main/wallet_section/io_interface.nim index 54e39020de..4944c49caf 100644 --- a/src/app/modules/main/wallet_section/io_interface.nim +++ b/src/app/modules/main/wallet_section/io_interface.nim @@ -114,3 +114,9 @@ method destroyKeypairImportPopup*(self: AccessInterface) {.base.} = method hasPairedDevices*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") + +method getRpcStats*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method resetRpcStats*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index 8e55cbc588..9b5322aa4e 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -445,3 +445,9 @@ method hasPairedDevices*(self: Module): bool = proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) = if data.state == LocalPairingState.Finished: self.view.emitHasPairedDevicesChangedSignal() + +method getRpcStats*(self: Module): string = + return self.view.getRpcStats() + +method resetRpcStats*(self: Module) = + self.view.resetRpcStats() diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index a4cd0e2487..4cb2f73d51 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -233,3 +233,8 @@ QtObject: QtProperty[bool] walletReady: read = getWalletReady notify = walletReadyChanged + + proc getRpcStats*(self: View): string {.slot.} = + return self.delegate.getRpcStats() + proc resetRpcStats*(self: View) {.slot.} = + self.delegate.resetRpcStats() diff --git a/src/app_service/service/node/service.nim b/src/app_service/service/node/service.nim index aa2afa2f6c..07235a52ce 100644 --- a/src/app_service/service/node/service.nim +++ b/src/app_service/service/node/service.nim @@ -68,4 +68,18 @@ QtObject: proc peerCount*(self: Service): int = self.peers.len - proc isConnected*(self: Service): bool = self.connected \ No newline at end of file + proc isConnected*(self: Service): bool = self.connected + + proc getRpcStats*(self: Service): string = + try: + return status_node.getRpcStats() + except Exception as e: + let errDescription = e.msg + error "error: ", errDescription + + proc resetRpcStats*(self: Service) = + try: + status_node.resetRpcStats() + except Exception as e: + let errDescription = e.msg + error "error: ", errDescription diff --git a/src/app_service/service/wallet_account/service_token.nim b/src/app_service/service/wallet_account/service_token.nim index 921649de91..52ae188106 100644 --- a/src/app_service/service/wallet_account/service_token.nim +++ b/src/app_service/service/wallet_account/service_token.nim @@ -114,7 +114,6 @@ proc hasGas*(self: Service, accountAddress: string, chainId: int, nativeGasSymbo if balance.account == accountAddress and balance.chainId == chainId: if(self.currencyService.parseCurrencyValue(nativeGasSymbol, balance.balance) >= requiredGas): return true - break return false proc getCurrency*(self: Service): string = diff --git a/src/backend/core.nim b/src/backend/core.nim index 2fb254522a..fa5321243f 100644 --- a/src/backend/core.nim +++ b/src/backend/core.nim @@ -40,6 +40,20 @@ proc makePrivateRpcCall*( error "error doing rpc request", methodName = methodName, exception=e.msg raise newException(RpcException, e.msg) +proc makePrivateRpcCallNoDecode*( + methodName: string, inputJSON: JsonNode +): string {.raises: [RpcException].} = + if DB_BLOCKED_DUE_TO_PROFILE_MIGRATION: + debug "DB blocked due to profile migration, unable to proceed with the rpc call", rpc_method=methodName + raise newException(RpcException, "db closed due to profile migration") + try: + debug "NewBE_callPrivateRPCNoDecode", rpc_method=methodName + result = status_go.callPrivateRPC($inputJSON) + + except Exception as e: + error "error doing rpc request", methodName = methodName, exception=e.msg + raise newException(RpcException, e.msg) + proc callPrivateRPCWithChainId*( methodName: string, chainId: int, payload = %* [] ): RpcResponse[JsonNode] {.raises: [RpcException, ValueError, Defect, SerializationError].} = @@ -61,6 +75,16 @@ proc callPrivateRPC*( } return makePrivateRpcCall(methodName, inputJSON) +proc callPrivateRPCNoDecode*( + methodName: string, payload = %* [] +): string {.raises: [RpcException].} = + let inputJSON = %* { + "jsonrpc": "2.0", + "method": methodName, + "params": %payload + } + return makePrivateRpcCallNoDecode(methodName, inputJSON) + proc migrateKeyStoreDir*(account: string, hashedPassword: string, oldKeystoreDir: string, multiaccountKeystoreDir: string) {.raises: [RpcException, ValueError, Defect, SerializationError].} = try: diff --git a/src/backend/node.nim b/src/backend/node.nim index 16b77895f1..70bf271bab 100644 --- a/src/backend/node.nim +++ b/src/backend/node.nim @@ -14,3 +14,9 @@ proc wakuV2Peers*(): RpcResponse[JsonNode] {.raises: [Exception].} = proc sendRPCMessageRaw*(inputJSON: string): string {.raises: [Exception].} = result = callPrivateRPCRaw(inputJSON) + +proc getRpcStats*(): string {.raises: [Exception].} = + result = callPrivateRPCNoDecode("rpcstats_getStats") + +proc resetRpcStats*() {.raises: [Exception].} = + discard callPrivateRPCNoDecode("rpcstats_reset") diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index a16ae31bcf..9b9488ed23 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -293,6 +293,7 @@ StatusSectionLayout { messagingStore: root.store.messagingStore advancedStore: root.store.advancedStore + walletStore: root.store.walletStore sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.advanced) contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/stores/WalletStore.qml b/ui/app/AppLayouts/Profile/stores/WalletStore.qml index 40ac9c4e00..f4f12494c0 100644 --- a/ui/app/AppLayouts/Profile/stores/WalletStore.qml +++ b/ui/app/AppLayouts/Profile/stores/WalletStore.qml @@ -148,4 +148,12 @@ QtObject { function updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) { accountsModule.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) } + + function getRpcStats() { + return root.walletModule.getRpcStats() + } + + function resetRpcStats() { + root.walletModule.resetRpcStats() + } } diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index 4781c11b58..fa13022b99 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -34,6 +34,7 @@ SettingsContentBase { property MessagingStore messagingStore property AdvancedStore advancedStore + property WalletStore walletStore Item { id: advancedContainer @@ -430,6 +431,14 @@ SettingsContentBase { Global.openPopup(changeNumberOfLogsArchived) } } + + StatusSettingsLineButton { + id: rpcStatsButton + anchors.leftMargin: 0 + anchors.rightMargin: 0 + text: qsTr("RPC statistics") + onClicked: rpcStatsModal.open() + } } FleetsModal { @@ -618,5 +627,12 @@ SettingsContentBase { onDecelerationChanged: root.advancedStore.setScrollDeceleration(value) onCustomScrollingChanged: root.advancedStore.setCustomScrollingEnabled(enabled) } + + RPCStatsModal { + id: rpcStatsModal + + walletStore: root.walletStore + title: rpcStatsButton.text + } } } diff --git a/ui/app/AppLayouts/Profile/views/RPCStatsModal.qml b/ui/app/AppLayouts/Profile/views/RPCStatsModal.qml new file mode 100644 index 0000000000..09d88dadc5 --- /dev/null +++ b/ui/app/AppLayouts/Profile/views/RPCStatsModal.qml @@ -0,0 +1,163 @@ +import QtQuick 2.15 +import QtQml 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups.Dialog 0.1 + +import shared.controls 1.0 +import utils 1.0 + +import SortFilterProxyModel 0.2 + +import "../stores" + + +StatusDialog { + id: root + + implicitHeight: 610 + + property WalletStore walletStore + + QtObject { + id: d + + property int totalCalls + property int totalFilteredCalls: countAggregator.value + + function updateModel() { + sourceModel.clear() + + const jsonStatsRaw = root.walletStore.getRpcStats() + const jsonStats = JSON.parse(jsonStatsRaw) + if (jsonStats.result && jsonStats.result.methods) { + const methods = jsonStats.result.methods + d.totalCalls = jsonStats.result.total + + for (const method in methods) { + sourceModel.append({ method: method, count: methods[method] }) + } + } + } + + function resetStats() { + root.walletStore.resetRpcStats() + updateModel() + } + } + + ColumnLayout { + id: contentColumn + + anchors.fill: parent + + SearchBox { + id: searchBox + + Layout.fillWidth: true + Layout.bottomMargin: 16 + } + + StatusListView { + id: resultsListView + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 2 + + ListModel { + id: sourceModel + } + + header: StatusListItem { + title: qsTr("Total") + statusListItemTitle.customColor: Theme.palette.directColor1 + statusListItemLabel.customColor: Theme.palette.directColor1 + label: qsTr("%1 of %2").arg(d.totalFilteredCalls).arg(d.totalCalls) + enabled: false + z: 3 // Above delegate (z=1) and above section.delegate (z = 2) + } + headerPositioning: ListView.OverlayHeader + + model: SortFilterProxyModel { + sourceModel: sourceModel + sorters: RoleSorter { + roleName: "count" + sortOrder: Qt.DescendingOrder + } + + filters: FastExpressionFilter { + function spellingTolerantSearch(data, searchKeyword) { + const regex = new RegExp(searchKeyword.split('').join('.{0,1}'), 'i') + return regex.test(data) + } + + enabled: !!searchBox.text + expression: { + searchBox.text + let keyword = searchBox.text.trim().toUpperCase() + return spellingTolerantSearch(model.method, keyword) + } + expectedRoles: ["method"] + } + } + + SumAggregator { + id: countAggregator + + model: resultsListView.model + roleName: "count" + } + + delegate: StatusListItem { + title: model.method + label: model.count + enabled: false + } + + Component.onCompleted: { + d.updateModel() + } + } + } + + footer: StatusDialogFooter { + leftButtons: ObjectModel { + StatusButton { + text: qsTr("Refresh") + onClicked: { + d.updateModel() + } + } + StatusButton { + text: qsTr("Reset") + onClicked: { + root.walletStore.resetRpcStats() + d.updateModel() + } + } + } + + rightButtons: ObjectModel { + CopyToClipBoardButton { + id: copyToClipboardButton + + onCopyClicked: root.walletStore.copyToClipboard(textToCopy) + onPressed: function() { + let copiedText = "Total" + '\t' + d.totalFilteredCalls + " of " + d.totalCalls + '\n' + '\n' + for (let i = 0; i < resultsListView.model.count; i++) { + const item = resultsListView.model.get(i) + copiedText += item.method + '\t' + item.count + '\n' + } + textToCopy = copiedText + } + } + } + } +}