Initial version of status bar

This commit is contained in:
Zahary Karadjov 2019-10-03 04:51:44 +03:00 committed by zah
parent d3f88929da
commit f72a58595f
10 changed files with 242 additions and 35 deletions

View File

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

View File

@ -229,6 +229,7 @@ type
ValidatorConnection* = object
AttachedValidator* = ref object
idx*: ValidatorIndex
pubKey*: ValidatorPubKey
case kind*: ValidatorKind

View File

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

View File

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

View File

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

View File

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

View File

@ -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 \
$*

View File

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

View File

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