mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-22 04:24:05 +00:00
Initial version of status bar
This commit is contained in:
parent
d3f88929da
commit
f72a58595f
@ -14,7 +14,7 @@ import
|
||||
conf, time, state_transition, fork_choice, ssz, beacon_chain_db,
|
||||
validator_pool, extras, attestation_pool, block_pool, eth2_network,
|
||||
beacon_node_types, mainchain_monitor, trusted_state_snapshots, version,
|
||||
sync_protocol, request_manager, validator_keygen, interop
|
||||
sync_protocol, request_manager, validator_keygen, interop, statusbar
|
||||
|
||||
const
|
||||
dataDirValidators = "validators"
|
||||
@ -287,7 +287,7 @@ proc addLocalValidator(
|
||||
if idx == -1:
|
||||
warn "Validator not in registry", pubKey
|
||||
else:
|
||||
node.attachedValidators.addLocalValidator(pubKey, privKey)
|
||||
node.attachedValidators.addLocalValidator(ValidatorIndex(idx), pubKey, privKey)
|
||||
|
||||
proc addLocalValidators(node: BeaconNode, state: BeaconState) =
|
||||
for validatorKeyFile in node.config.validators:
|
||||
@ -831,6 +831,21 @@ proc start(node: BeaconNode, headState: BeaconState) =
|
||||
node.addLocalValidators(headState)
|
||||
node.run()
|
||||
|
||||
func formatGwei(amount: uint64): string =
|
||||
# TODO This is implemented in a quite a silly way.
|
||||
# Better routines for formatting decimal numbers
|
||||
# should exists somewhere else.
|
||||
let
|
||||
eth = amount div 1000000000
|
||||
remainder = amount mod 1000000000
|
||||
|
||||
result = $eth
|
||||
if remainder != 0:
|
||||
result.add '.'
|
||||
result.add $remainder
|
||||
while result[^1] == '0':
|
||||
result.setLen(result.len - 1)
|
||||
|
||||
when hasPrompt:
|
||||
from unicode import Rune
|
||||
import terminal, prompt
|
||||
@ -841,26 +856,90 @@ when hasPrompt:
|
||||
# parsing API of Confutils
|
||||
result = @[]
|
||||
|
||||
proc processPromptCommands(p: ptr Prompt) {.thread.} =
|
||||
while true:
|
||||
var cmd = p[].readLine()
|
||||
case cmd
|
||||
of "quit":
|
||||
quit 0
|
||||
else:
|
||||
p[].writeLine("Unknown command: " & cmd)
|
||||
|
||||
proc initPrompt(node: BeaconNode) =
|
||||
doAssert defaultChroniclesStream.outputs.len > 0
|
||||
doAssert defaultChroniclesStream.output is DynamicOutput
|
||||
if isatty(stdout) and node.config.statusbar:
|
||||
enableTrueColors()
|
||||
|
||||
if isatty(stdout):
|
||||
var p = Prompt.init("nimbus > ", providePromptCompletions)
|
||||
p.useHistoryFile()
|
||||
# TODO: nim-prompt seems to have threading issues at the moment
|
||||
# which result in sporadic crashes. We should introduce a
|
||||
# lock that guards the access to the internal prompt line
|
||||
# variable.
|
||||
#
|
||||
# var p = Prompt.init("nimbus > ", providePromptCompletions)
|
||||
# p.useHistoryFile()
|
||||
|
||||
proc dataResolver(expr: string): string =
|
||||
# TODO:
|
||||
# We should introduce a general API for resolving dot expressions
|
||||
# such as `db.latest_block.slot` or `metrics.connected_peers`.
|
||||
# Such an API can be shared between the RPC back-end, CLI tools
|
||||
# such as ncli, a potential GraphQL back-end and so on.
|
||||
# The status bar feature would allow the user to specify an
|
||||
# arbitrary expression that is resolvable through this API.
|
||||
case expr.toLowerAscii
|
||||
of "connected_peers":
|
||||
$(sync_protocol.libp2p_peers.value.int)
|
||||
|
||||
of "last_finalized_epoch":
|
||||
var head = node.blockPool.finalizedHead
|
||||
# TODO: Should we display a state root instead?
|
||||
$(head.slot) & "(" & shortLog(head.blck.root) & ")"
|
||||
|
||||
of "attached_validators_balance":
|
||||
var balance = uint64(0)
|
||||
for _, validator in node.attachedValidators.validators:
|
||||
balance += node.stateCache.data.data.balances[validator.idx]
|
||||
formatGwei(balance)
|
||||
|
||||
else:
|
||||
# We ignore typos for now and just render the expression
|
||||
# as it was written. TODO: come up with a good way to show
|
||||
# an error message to the user.
|
||||
"$" & expr
|
||||
|
||||
var statusBar = StatusBarView.init(
|
||||
node.config.statusbarContents,
|
||||
dataResolver)
|
||||
|
||||
defaultChroniclesStream.output.writer =
|
||||
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} =
|
||||
p.writeLine(msg)
|
||||
else:
|
||||
defaultChroniclesStream.output.writer =
|
||||
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} =
|
||||
stdout.writeLine(msg)
|
||||
# p.hidePrompt
|
||||
erase statusBar
|
||||
# p.writeLine msg
|
||||
stdout.write msg
|
||||
render statusBar
|
||||
# p.showPrompt
|
||||
|
||||
proc statusBarUpdatesPollingLoop() {.async.} =
|
||||
while true:
|
||||
update statusBar
|
||||
await sleepAsync(1000)
|
||||
|
||||
traceAsyncErrors statusBarUpdatesPollingLoop()
|
||||
|
||||
# var t: Thread[ptr Prompt]
|
||||
# createThread(t, processPromptCommands, addr p)
|
||||
|
||||
when isMainModule:
|
||||
randomize()
|
||||
let config = BeaconNodeConf.load(version = fullVersionStr())
|
||||
|
||||
doAssert defaultChroniclesStream.outputs.len > 0
|
||||
doAssert defaultChroniclesStream.output is DynamicOutput
|
||||
|
||||
defaultChroniclesStream.output.writer =
|
||||
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} =
|
||||
stdout.write(msg)
|
||||
|
||||
if config.logLevel != LogLevel.NONE:
|
||||
setLogLevel(config.logLevel)
|
||||
|
||||
|
@ -229,6 +229,7 @@ type
|
||||
ValidatorConnection* = object
|
||||
|
||||
AttachedValidator* = ref object
|
||||
idx*: ValidatorIndex
|
||||
pubKey*: ValidatorPubKey
|
||||
|
||||
case kind*: ValidatorKind
|
||||
|
@ -22,54 +22,63 @@ type
|
||||
BeaconNodeConf* = object
|
||||
logLevel* {.
|
||||
desc: "Sets the log level",
|
||||
defaultValue: enabledLogLevel.}: LogLevel
|
||||
defaultValue: enabledLogLevel }: LogLevel
|
||||
|
||||
network* {.
|
||||
desc: "The network Nimbus should connect to. " &
|
||||
"Possible values: testnet0, testnet1, mainnet, custom-network.json"
|
||||
longform: "network"
|
||||
shortform: "n"
|
||||
defaultValue: DEFAULT_NETWORK .}: string
|
||||
defaultValue: DEFAULT_NETWORK }: string
|
||||
|
||||
quickStart* {.
|
||||
desc: "Run in quickstart mode",
|
||||
defaultValue: false.}: bool
|
||||
defaultValue: false }: bool
|
||||
|
||||
dataDir* {.
|
||||
desc: "The directory where nimbus will store all blockchain data."
|
||||
shortform: "d"
|
||||
defaultValue: config.defaultDataDir().}: OutDir
|
||||
defaultValue: config.defaultDataDir() }: OutDir
|
||||
|
||||
depositWeb3Url* {.
|
||||
desc: "URL of the Web3 server to observe Eth1",
|
||||
defaultValue: ""}: string
|
||||
defaultValue: "" }: string
|
||||
|
||||
depositContractAddress* {.
|
||||
desc: "Address of the deposit contract",
|
||||
defaultValue: ""}: string
|
||||
defaultValue: "" }: string
|
||||
|
||||
statusbar* {.
|
||||
desc: "Display a status bar at the bottom of the terminal screen"
|
||||
defaultValue: true }: bool
|
||||
|
||||
statusbarContents* {.
|
||||
desc: ""
|
||||
defaultValue: "peers: $connected_peers; finalized epoch: $last_finalized_epoch |" &
|
||||
"ETH: $attached_validators_balance" }: string
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: noCommand.}: StartUpCommand
|
||||
defaultValue: noCommand }: StartUpCommand
|
||||
|
||||
of noCommand:
|
||||
bootstrapNodes* {.
|
||||
desc: "Specifies one or more bootstrap nodes to use when connecting to the network."
|
||||
longform: "bootstrapNode"
|
||||
shortform: "b".}: seq[string]
|
||||
shortform: "b" }: seq[string]
|
||||
|
||||
bootstrapNodesFile* {.
|
||||
desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses"
|
||||
shortform: "f"
|
||||
defaultValue: "".}: InputFile
|
||||
defaultValue: "" }: InputFile
|
||||
|
||||
tcpPort* {.
|
||||
desc: "TCP listening port"
|
||||
defaultValue: defaultPort(config) .}: int
|
||||
defaultValue: defaultPort(config) }: int
|
||||
|
||||
udpPort* {.
|
||||
desc: "UDP listening port",
|
||||
defaultValue: defaultPort(config) .}: int
|
||||
defaultValue: defaultPort(config) }: int
|
||||
|
||||
nat* {.
|
||||
desc: "Specify method to use for determining public address. Must be one of: any, none, upnp, pmp, extip:<IP>"
|
||||
|
111
beacon_chain/statusbar.nim
Normal file
111
beacon_chain/statusbar.nim
Normal file
@ -0,0 +1,111 @@
|
||||
import
|
||||
tables, strutils, parseutils, sequtils, terminal, colors
|
||||
|
||||
type
|
||||
ContentFragments = seq[tuple[kind: InterpolatedKind, value: string]]
|
||||
|
||||
StatusBarCell = object
|
||||
label, content: string
|
||||
contentFragments: ContentFragments
|
||||
|
||||
Layout = object
|
||||
cellsLeft: seq[StatusBarCell]
|
||||
cellsRight: seq[StatusBarCell]
|
||||
|
||||
DataItemResolver* = proc (dataItem: string): string
|
||||
|
||||
StatusBarView* = object
|
||||
model: DataItemResolver
|
||||
layout: Layout
|
||||
consumedLines: int
|
||||
|
||||
const
|
||||
sepLeft = "❯"
|
||||
sepRight = "❮"
|
||||
|
||||
# sepLeft = "|"
|
||||
# sepRight = "|"
|
||||
|
||||
backgroundColor = rgb(36, 36, 36)
|
||||
foregroundColor = colWhiteSmoke
|
||||
|
||||
func loadFragmentsLayout(contentLayout: string): ContentFragments =
|
||||
result = toSeq(interpolatedFragments(strip contentLayout))
|
||||
|
||||
func loadCellsLayout(cellsLayout: string): seq[StatusBarCell] =
|
||||
var cells = cellsLayout.split(';')
|
||||
for cell in cells:
|
||||
var columns = cell.split(':', maxSplit = 1)
|
||||
if columns.len == 2:
|
||||
result.add StatusBarCell(
|
||||
label: strip(columns[0]),
|
||||
contentFragments: loadFragmentsLayout(columns[1]))
|
||||
else:
|
||||
result.add StatusBarCell(
|
||||
contentFragments: loadFragmentsLayout(columns[0]))
|
||||
|
||||
func loadLayout(layout: string): Layout {.raises: [Defect, ValueError].} =
|
||||
var sections = layout.split('|', maxSplit = 1)
|
||||
result.cellsLeft = loadCellsLayout(sections[0])
|
||||
if sections.len == 2: result.cellsRight = loadCellsLayout(sections[1])
|
||||
|
||||
proc updateContent(cell: var StatusBarCell, model: DataItemResolver) =
|
||||
cell.content.setLen 0
|
||||
for fragment in cell.contentFragments:
|
||||
case fragment[0]
|
||||
of ikStr, ikDollar:
|
||||
cell.content.add fragment[1]
|
||||
of ikExpr, ikVar:
|
||||
cell.content.add model(fragment[1])
|
||||
|
||||
proc updateCells(cells: var seq[StatusBarCell], model: DataItemResolver) =
|
||||
for cell in mitems(cells):
|
||||
cell.updateContent(model)
|
||||
|
||||
proc update*(s: var StatusBarView) =
|
||||
updateCells s.layout.cellsLeft, s.model
|
||||
updateCells s.layout.cellsRight, s.model
|
||||
|
||||
func width(cell: StatusBarCell): int =
|
||||
cell.label.len + cell.content.len + 4 # separator + pading
|
||||
|
||||
func width(cells: seq[StatusBarCell]): int =
|
||||
result = max(0, cells.len - 1) # the number of separators
|
||||
for cell in cells: result += cell.width
|
||||
|
||||
proc renderCells(cells: seq[StatusBarCell], sep: string) =
|
||||
for i, cell in cells:
|
||||
if i > 0: stdout.write sep
|
||||
stdout.setStyle {styleDim}
|
||||
stdout.write " ", cell.label, ": "
|
||||
stdout.setStyle {styleBright}
|
||||
stdout.write cell.content, " "
|
||||
|
||||
proc render*(s: var StatusBarView) =
|
||||
doAssert s.consumedLines == 0
|
||||
|
||||
let
|
||||
termWidth = terminalWidth()
|
||||
allCellsWidth = s.layout.cellsLeft.width + s.layout.cellsRight.width
|
||||
centerPadding = max(0, termWidth - allCellsWidth)
|
||||
|
||||
stdout.setBackgroundColor backgroundColor
|
||||
stdout.setForegroundColor foregroundColor
|
||||
renderCells(s.layout.cellsLeft, sepLeft)
|
||||
stdout.write spaces(centerPadding)
|
||||
renderCells(s.layout.cellsRight, sepRight)
|
||||
stdout.resetAttributes
|
||||
stdout.flushFile
|
||||
|
||||
s.consumedLines = 1
|
||||
|
||||
proc erase*(s: var StatusBarView) =
|
||||
# cursorUp()
|
||||
for i in 0 ..< s.consumedLines: eraseLine()
|
||||
s.consumedLines = 0
|
||||
|
||||
func init*(T: type StatusBarView,
|
||||
layout: string,
|
||||
model: DataItemResolver): T {.raises: [Defect, ValueError].} =
|
||||
StatusBarView(model: model, consumedLines: 1, layout: loadLayout(layout))
|
||||
|
@ -8,7 +8,7 @@ when networkBackend == rlpxBackend:
|
||||
import eth/rlp/options as rlpOptions
|
||||
template libp2pProtocol*(name: string, version: int) {.pragma.}
|
||||
|
||||
declareGauge libp2p_peers, "Number of libp2p peers"
|
||||
declarePublicGauge libp2p_peers, "Number of libp2p peers"
|
||||
|
||||
type
|
||||
ValidatorSetDeltaFlags {.pure.} = enum
|
||||
|
@ -4,7 +4,6 @@ import
|
||||
spec/[datatypes, crypto, digest, helpers], ssz,
|
||||
beacon_node_types
|
||||
|
||||
|
||||
proc init*(T: type ValidatorPool): T =
|
||||
result.validators = initTable[ValidatorPubKey, AttachedValidator]()
|
||||
|
||||
@ -12,9 +11,11 @@ template count*(pool: ValidatorPool): int =
|
||||
pool.validators.len
|
||||
|
||||
proc addLocalValidator*(pool: var ValidatorPool,
|
||||
idx: ValidatorIndex,
|
||||
pubKey: ValidatorPubKey,
|
||||
privKey: ValidatorPrivKey) =
|
||||
let v = AttachedValidator(pubKey: pubKey,
|
||||
let v = AttachedValidator(idx: idx,
|
||||
pubKey: pubKey,
|
||||
kind: inProcess,
|
||||
privKey: privKey)
|
||||
pool.validators[pubKey] = v
|
||||
|
@ -34,6 +34,7 @@ set -x
|
||||
trap 'kill -9 -- -$$' SIGINT EXIT SIGTERM
|
||||
|
||||
./env.sh $BEACON_NODE_BIN \
|
||||
--statusbar:off \
|
||||
--network:$NETWORK_METADATA_FILE \
|
||||
--dataDir:$DATA_DIR \
|
||||
--nodename:0 \
|
||||
|
@ -2,21 +2,24 @@
|
||||
|
||||
set -eu
|
||||
|
||||
NODE_ID=${1}
|
||||
shift
|
||||
|
||||
source "$(dirname "$0")/vars.sh"
|
||||
cd "$GIT_ROOT"
|
||||
|
||||
DATA_DIR="${SIMULATION_DIR}/node-${1}"
|
||||
DATA_DIR="${SIMULATION_DIR}/node-$NODE_ID"
|
||||
|
||||
V_PREFIX="${VALIDATORS_DIR}/v$(printf '%06d' ${1})"
|
||||
PORT=$(printf '5%04d' ${1})
|
||||
V_PREFIX="${VALIDATORS_DIR}/v$(printf '%06d' $NODE_ID)"
|
||||
PORT=$(printf '5%04d' $NODE_ID)
|
||||
|
||||
NAT_FLAG="--nat:none"
|
||||
if [ "${NAT:-}" == "1" ]; then
|
||||
NAT_FLAG="--nat:any"
|
||||
fi
|
||||
|
||||
FIRST_VALIDATOR_IDX=$(( (NUM_VALIDATORS / ($NUM_NODES + $NUM_MISSING_NODES)) * $1 ))
|
||||
LAST_VALIDATOR_IDX=$(( (NUM_VALIDATORS / ($NUM_NODES + $NUM_MISSING_NODES)) * ($1 + 1) - 1 ))
|
||||
FIRST_VALIDATOR_IDX=$(( (NUM_VALIDATORS / ($NUM_NODES + $NUM_MISSING_NODES)) * $NODE_ID ))
|
||||
LAST_VALIDATOR_IDX=$(( (NUM_VALIDATORS / ($NUM_NODES + $NUM_MISSING_NODES)) * ($NODE_ID + 1) - 1 ))
|
||||
|
||||
mkdir -p $DATA_DIR/validators
|
||||
rm -f $DATA_DIR/validators/*
|
||||
@ -28,10 +31,12 @@ popd >/dev/null
|
||||
$BEACON_NODE_BIN \
|
||||
--network:$NETWORK_METADATA_FILE \
|
||||
--dataDir:$DATA_DIR \
|
||||
--nodename:${1} \
|
||||
--nodename:$NODE_ID \
|
||||
--tcpPort:$PORT \
|
||||
--udpPort:$PORT \
|
||||
$NAT_FLAG \
|
||||
--stateSnapshot:$SNAPSHOT_FILE \
|
||||
$DEPOSIT_WEB3_URL_ARG \
|
||||
--depositContractAddress=$DEPOSIT_CONTRACT_ADDRESS
|
||||
--depositContractAddress=$DEPOSIT_CONTRACT_ADDRESS \
|
||||
$*
|
||||
|
||||
|
@ -96,7 +96,7 @@ for i in $(seq 0 $LAST_NODE); do
|
||||
done
|
||||
fi
|
||||
|
||||
CMD="${SIM_ROOT}/run_node.sh $i"
|
||||
CMD="${SIM_ROOT}/run_node.sh $i --statusbar:off"
|
||||
|
||||
if [ "$USE_MULTITAIL" != "no" ]; then
|
||||
if [ "$i" = "0" ]; then
|
||||
|
@ -15,7 +15,7 @@ cd - &>/dev/null
|
||||
|
||||
NUM_VALIDATORS=${VALIDATORS:-1000}
|
||||
NUM_NODES=${NODES:-4}
|
||||
NUM_MISSING_NODES=${MISSING_NODES:-0}
|
||||
NUM_MISSING_NODES=${MISSING_NODES:-1}
|
||||
|
||||
SIMULATION_DIR="${SIM_ROOT}/data"
|
||||
VALIDATORS_DIR="${SIM_ROOT}/validators"
|
||||
|
Loading…
x
Reference in New Issue
Block a user