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,
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)

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/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)

View File

@ -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()
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.} =
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/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()
self.view.emitHasPairedDevicesChangedSignal()
method getRpcStats*(self: Module): string =
return self.controller.getRpcStats()
method resetRpcStats*(self: Module) =
self.controller.resetRpcStats()

View File

@ -54,4 +54,9 @@ QtObject:
return self.delegate.hasPairedDevices()
QtProperty[bool] hasPairedDevices:
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.} =
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) =
if data.state == LocalPairingState.Finished:
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:
read = getWalletReady
notify = walletReadyChanged
proc getRpcStats*(self: View): string {.slot.} =
return self.delegate.getRpcStats()
proc resetRpcStats*(self: View) {.slot.} =
self.delegate.resetRpcStats()

View File

@ -68,4 +68,18 @@ QtObject:
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(self.currencyService.parseCurrencyValue(nativeGasSymbol, balance.balance) >= requiredGas):
return true
break
return false
proc getCurrency*(self: Service): string =

View File

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

View File

@ -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")

View File

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

View File

@ -148,4 +148,12 @@ QtObject {
function 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 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
}
}
}

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