feat: Add RPC statistics screen to settings' advanced view

Closes #13264
This commit is contained in:
Ivan Belyakov 2024-02-07 14:47:28 +01:00 committed by IvanBelyakoff
parent b38138cc71
commit 1ac52f5d38
17 changed files with 290 additions and 10 deletions

View File

@ -220,7 +220,7 @@ proc newModule*[T](
result, events, accountsService, settingsService, stickersService, result, events, accountsService, settingsService, stickersService,
profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService, profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService,
devicesService, mailserversService, chatService, ensService, walletAccountService, generalService, communityService, 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, result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService,
networkService, tokenService, keycardService) networkService, tokenService, keycardService)

View File

@ -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/keycard/service as keycard_service
import ../../../../app_service/service/keychain/service as keychain_service import ../../../../app_service/service/keychain/service as keychain_service
import ../../../../app_service/service/token/service as token_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 ./profile/module as profile_module
import ./contacts/module as contacts_module import ./contacts/module as contacts_module
@ -85,7 +86,8 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
networkService: network_service.Service, networkService: network_service.Service,
keycardService: keycard_service.Service, keycardService: keycard_service.Service,
keychainService: keychain_service.Service, keychainService: keychain_service.Service,
tokenService: token_service.Service tokenService: token_service.Service,
nodeService: node_service.Service
): Module = ): Module =
result = Module() result = Module()
result.delegate = delegate result.delegate = delegate
@ -112,7 +114,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
privacyService, accountsService, walletAccountService, keychainService) privacyService, accountsService, walletAccountService, keychainService)
result.walletModule = wallet_module.newModule(result, events, accountsService, walletAccountService, settingsService, result.walletModule = wallet_module.newModule(result, events, accountsService, walletAccountService, settingsService,
networkService, devicesService) networkService, devicesService, nodeService)
singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant) singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant)

View File

@ -2,22 +2,26 @@ import io_interface
import app/core/eventemitter import app/core/eventemitter
import app_service/service/wallet_account/service as wallet_account_service import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/devices/service as devices_service import app_service/service/devices/service as devices_service
import app_service/service/node/service as node_service
type type
Controller* = ref object of RootObj Controller* = ref object of RootObj
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
events: EventEmitter events: EventEmitter
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
nodeService: node_service.Service
proc newController*( proc newController*(
delegate: io_interface.AccessInterface, delegate: io_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
walletAccountService: wallet_account_service.Service, walletAccountService: wallet_account_service.Service,
nodeService: node_service.Service
): Controller = ): Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.nodeService = nodeService
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard discard
@ -29,3 +33,9 @@ proc init*(self: Controller) =
proc hasPairedDevices*(self: Controller): bool = proc hasPairedDevices*(self: Controller): bool =
return self.walletAccountService.hasPairedDevices() return self.walletAccountService.hasPairedDevices()
proc getRpcStats*(self: Controller): string =
return self.nodeService.getRpcStats()
proc resetRpcStats*(self: Controller) =
self.nodeService.resetRpcStats()

View File

@ -57,3 +57,9 @@ method hasPairedDevices*(self: AccessInterface): bool {.base.} =
method onLocalPairingStatusUpdate*(self: AccessInterface, data: LocalPairingStatus) {.base.} = method onLocalPairingStatusUpdate*(self: AccessInterface, data: LocalPairingStatus) {.base.} =
raise newException(ValueError, "No implementation available") 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")

View File

@ -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/network/service as network_service
import app_service/service/settings/service as settings_service import app_service/service/settings/service as settings_service
import app_service/service/devices/service as devices_service import app_service/service/devices/service as devices_service
import app_service/service/node/service as node_service
logScope: logScope:
topics = "profile-section-wallet-module" topics = "profile-section-wallet-module"
@ -33,6 +34,7 @@ type
accountsService: accounts_service.Service accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
devicesService: devices_service.Service devicesService: devices_service.Service
nodeService: node_service.Service
accountsModule: accounts_module.AccessInterface accountsModule: accounts_module.AccessInterface
networksModule: networks_module.AccessInterface networksModule: networks_module.AccessInterface
keypairImportModule: keypair_import_module.AccessInterface keypairImportModule: keypair_import_module.AccessInterface
@ -44,11 +46,12 @@ proc newModule*(
walletAccountService: wallet_account_service.Service, walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service, settingsService: settings_service.Service,
networkService: network_service.Service, networkService: network_service.Service,
devicesService: devices_service.Service devicesService: devices_service.Service,
nodeService: node_service.Service
): Module = ): Module =
result = Module() result = Module()
result.delegate = delegate result.delegate = delegate
result.controller = controller.newController(result, events, walletAccountService) result.controller = controller.newController(result, events, walletAccountService, nodeService)
result.view = newView(result) result.view = newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.events = events result.events = events
@ -133,3 +136,9 @@ method hasPairedDevices*(self: Module): bool =
method onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) = method onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) =
if data.state == LocalPairingState.Finished: if data.state == LocalPairingState.Finished:
self.view.emitHasPairedDevicesChangedSignal() self.view.emitHasPairedDevicesChangedSignal()
method getRpcStats*(self: Module): string =
return self.controller.getRpcStats()
method resetRpcStats*(self: Module) =
self.controller.resetRpcStats()

View File

@ -55,3 +55,8 @@ QtObject:
QtProperty[bool] hasPairedDevices: QtProperty[bool] hasPairedDevices:
read = getHasPairedDevices read = getHasPairedDevices
notify = hasPairedDevicesChanged notify = hasPairedDevicesChanged
proc getRpcStats(self: View): string {.slot.} =
return self.delegate.getRpcStats()
proc resetRpcStats(self: View) {.slot.} =
self.delegate.resetRpcStats()

View File

@ -114,3 +114,9 @@ method destroyKeypairImportPopup*(self: AccessInterface) {.base.} =
method hasPairedDevices*(self: AccessInterface): bool {.base.} = method hasPairedDevices*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available") 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")

View File

@ -445,3 +445,9 @@ method hasPairedDevices*(self: Module): bool =
proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) = proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus) =
if data.state == LocalPairingState.Finished: if data.state == LocalPairingState.Finished:
self.view.emitHasPairedDevicesChangedSignal() self.view.emitHasPairedDevicesChangedSignal()
method getRpcStats*(self: Module): string =
return self.view.getRpcStats()
method resetRpcStats*(self: Module) =
self.view.resetRpcStats()

View File

@ -233,3 +233,8 @@ QtObject:
QtProperty[bool] walletReady: QtProperty[bool] walletReady:
read = getWalletReady read = getWalletReady
notify = walletReadyChanged notify = walletReadyChanged
proc getRpcStats*(self: View): string {.slot.} =
return self.delegate.getRpcStats()
proc resetRpcStats*(self: View) {.slot.} =
self.delegate.resetRpcStats()

View File

@ -69,3 +69,17 @@ QtObject:
proc peerCount*(self: Service): int = self.peers.len proc peerCount*(self: Service): int = self.peers.len
proc isConnected*(self: Service): bool = self.connected 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

View File

@ -114,7 +114,6 @@ proc hasGas*(self: Service, accountAddress: string, chainId: int, nativeGasSymbo
if balance.account == accountAddress and balance.chainId == chainId: if balance.account == accountAddress and balance.chainId == chainId:
if(self.currencyService.parseCurrencyValue(nativeGasSymbol, balance.balance) >= requiredGas): if(self.currencyService.parseCurrencyValue(nativeGasSymbol, balance.balance) >= requiredGas):
return true return true
break
return false return false
proc getCurrency*(self: Service): string = proc getCurrency*(self: Service): string =

View File

@ -40,6 +40,20 @@ proc makePrivateRpcCall*(
error "error doing rpc request", methodName = methodName, exception=e.msg error "error doing rpc request", methodName = methodName, exception=e.msg
raise newException(RpcException, 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*( proc callPrivateRPCWithChainId*(
methodName: string, chainId: int, payload = %* [] methodName: string, chainId: int, payload = %* []
): RpcResponse[JsonNode] {.raises: [RpcException, ValueError, Defect, SerializationError].} = ): RpcResponse[JsonNode] {.raises: [RpcException, ValueError, Defect, SerializationError].} =
@ -61,6 +75,16 @@ proc callPrivateRPC*(
} }
return makePrivateRpcCall(methodName, inputJSON) 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) proc migrateKeyStoreDir*(account: string, hashedPassword: string, oldKeystoreDir: string, multiaccountKeystoreDir: string)
{.raises: [RpcException, ValueError, Defect, SerializationError].} = {.raises: [RpcException, ValueError, Defect, SerializationError].} =
try: try:

View File

@ -14,3 +14,9 @@ proc wakuV2Peers*(): RpcResponse[JsonNode] {.raises: [Exception].} =
proc sendRPCMessageRaw*(inputJSON: string): string {.raises: [Exception].} = proc sendRPCMessageRaw*(inputJSON: string): string {.raises: [Exception].} =
result = callPrivateRPCRaw(inputJSON) result = callPrivateRPCRaw(inputJSON)
proc getRpcStats*(): string {.raises: [Exception].} =
result = callPrivateRPCNoDecode("rpcstats_getStats")
proc resetRpcStats*() {.raises: [Exception].} =
discard callPrivateRPCNoDecode("rpcstats_reset")

View File

@ -293,6 +293,7 @@ StatusSectionLayout {
messagingStore: root.store.messagingStore messagingStore: root.store.messagingStore
advancedStore: root.store.advancedStore advancedStore: root.store.advancedStore
walletStore: root.store.walletStore
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.advanced) sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.advanced)
contentWidth: d.contentWidth contentWidth: d.contentWidth
} }

View File

@ -148,4 +148,12 @@ QtObject {
function updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) { function updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) {
accountsModule.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) accountsModule.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance)
} }
function getRpcStats() {
return root.walletModule.getRpcStats()
}
function resetRpcStats() {
root.walletModule.resetRpcStats()
}
} }

View File

@ -34,6 +34,7 @@ SettingsContentBase {
property MessagingStore messagingStore property MessagingStore messagingStore
property AdvancedStore advancedStore property AdvancedStore advancedStore
property WalletStore walletStore
Item { Item {
id: advancedContainer id: advancedContainer
@ -430,6 +431,14 @@ SettingsContentBase {
Global.openPopup(changeNumberOfLogsArchived) Global.openPopup(changeNumberOfLogsArchived)
} }
} }
StatusSettingsLineButton {
id: rpcStatsButton
anchors.leftMargin: 0
anchors.rightMargin: 0
text: qsTr("RPC statistics")
onClicked: rpcStatsModal.open()
}
} }
FleetsModal { FleetsModal {
@ -618,5 +627,12 @@ SettingsContentBase {
onDecelerationChanged: root.advancedStore.setScrollDeceleration(value) onDecelerationChanged: root.advancedStore.setScrollDeceleration(value)
onCustomScrollingChanged: root.advancedStore.setCustomScrollingEnabled(enabled) onCustomScrollingChanged: root.advancedStore.setCustomScrollingEnabled(enabled)
} }
RPCStatsModal {
id: rpcStatsModal
walletStore: root.walletStore
title: rpcStatsButton.text
}
} }
} }

View File

@ -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
}
}
}
}
}