diff --git a/tools/debug-ui/src/CurrentPeersView.tsx b/tools/debug-ui/src/CurrentPeersView.tsx index 2d6183b9d68..ad4bcc1d503 100644 --- a/tools/debug-ui/src/CurrentPeersView.tsx +++ b/tools/debug-ui/src/CurrentPeersView.tsx @@ -1,6 +1,7 @@ import { MouseEvent, ReactElement, useCallback, useMemo, useState } from "react"; import { useQuery } from "react-query"; import { fetchEpochInfo, fetchFullStatus, PeerInfoView } from "./api"; +import { formatDurationInMillis, formatTraffic, addDebugPortLink } from "./utils"; import "./CurrentPeersView.scss"; type NetworkInfoViewProps = { @@ -14,56 +15,6 @@ type PeerInfo = { statusClassName: string, } -function formatDurationInMillis(millis: number): string { - if (millis == null) { - return '(null)'; - } - let total_seconds = Math.floor(millis / 1000); - let hours = Math.floor(total_seconds / 3600) - let minutes = Math.floor((total_seconds - (hours * 3600)) / 60) - let seconds = total_seconds - (hours * 3600) - (minutes * 60) - if (hours > 0) { - if (minutes > 0) { - return `${hours}h ${minutes}m ${seconds}s` - } else { - return `${hours}h ${seconds}s` - } - } - if (minutes > 0) { - return `${minutes}m ${seconds}s` - } - return `${seconds}s` -} - -function formatBytesPerSecond(bytes_per_second: number): string { - if (bytes_per_second == null) { - return '-'; - } - if (bytes_per_second < 3000) { - return `${bytes_per_second} bps`; - } - let kilobytes_per_second = bytes_per_second / 1024; - if (kilobytes_per_second < 3000) { - return `${kilobytes_per_second.toFixed(1)} Kbps`; - } - let megabytes_per_second = kilobytes_per_second / 1024; - return `${megabytes_per_second.toFixed(1)} Mbps`; -} - -function formatTraffic(bytes_received: number, bytes_sent: number): ReactElement { - return
-
{"⬇ " + formatBytesPerSecond(bytes_received)}
-
{"⬆ " + formatBytesPerSecond(bytes_sent)}
-
; -} - -function addDebugPortLink(peer_addr: string): ReactElement { - return - {peer_addr} - ; -} - function peerClass(current_height: number, peer_height: number): string { if (peer_height > current_height + 5) { return 'peer_ahead_alot'; diff --git a/tools/debug-ui/src/NetworkInfoView.tsx b/tools/debug-ui/src/NetworkInfoView.tsx index 47adfa79f88..5e78a4f6b68 100644 --- a/tools/debug-ui/src/NetworkInfoView.tsx +++ b/tools/debug-ui/src/NetworkInfoView.tsx @@ -4,6 +4,7 @@ import { CurrentPeersView } from "./CurrentPeersView"; import './NetworkInfoView.scss'; import { PeerStorageView } from "./PeerStorageView"; import { ConnectionStorageView } from "./ConnectionStorageView"; +import { Tier1View } from "./Tier1View"; type NetworkInfoViewProps = { addr: string, @@ -15,6 +16,7 @@ export const NetworkInfoView = ({ addr }: NetworkInfoViewProps) => { Current Peers Detailed Peer Storage Connection Storage + TIER1 @@ -22,6 +24,7 @@ export const NetworkInfoView = ({ addr }: NetworkInfoViewProps) => { } /> } /> } /> + } /> ; }; diff --git a/tools/debug-ui/src/Tier1View.scss b/tools/debug-ui/src/Tier1View.scss new file mode 100644 index 00000000000..b7959cdf148 --- /dev/null +++ b/tools/debug-ui/src/Tier1View.scss @@ -0,0 +1,3 @@ +.tier1-peers-view { + padding: 10px; +} diff --git a/tools/debug-ui/src/Tier1View.tsx b/tools/debug-ui/src/Tier1View.tsx new file mode 100644 index 00000000000..ec36dcaa923 --- /dev/null +++ b/tools/debug-ui/src/Tier1View.tsx @@ -0,0 +1,186 @@ +import { MouseEvent, ReactElement, useCallback, useMemo, useState } from "react"; +import { useQuery } from "react-query"; +import { fetchEpochInfo, fetchFullStatus, PeerInfoView, PeerAddr } from "./api"; +import { formatDurationInMillis, formatTraffic, addDebugPortLink } from "./utils"; +import "./Tier1View.scss"; + +type Tier1ViewProps = { + addr: string, +}; + +type PeerInfo = { + peer: PeerInfoView, + validator: string[], + routedValidator: string[], + statusClassName: string, +} + +export const Tier1View = ({ addr }: Tier1ViewProps) => { + const { data: fullStatus, error: fullStatusError, isLoading: fullStatusLoading } = + useQuery(['fullStatus', addr], () => fetchFullStatus(addr)); + + if (fullStatusLoading) { + return
Loading...
; + } + if (fullStatusError) { + return
+
+ {(fullStatusError as Error).stack} +
+
; + } + if (!fullStatus) { + return
+
No Data
+
; + } + + const networkInfo = fullStatus.detailed_debug_status!.network_info; + + const rendered = new Set(); + const rows = new Array(); + + // tier1_connections contains TIER1 nodes we are currently connected to + for (const peer of networkInfo.tier1_connections!) { + let accountKey = ""; + let proxies:PeerAddr[] = []; + + networkInfo.tier1_accounts_data!.forEach(account => { + if (account.peer_id == peer.peer_id) { + accountKey = account.account_key; + proxies = account.proxies; + } + }); + + rendered.add(accountKey); + + let accountId = ""; + networkInfo.known_producers.forEach(producer => { + if (producer.peer_id == peer.peer_id) { + accountId = producer.account_id; + } + }); + + let lastPing = formatDurationInMillis(peer.last_time_received_message_millis); + let lastPingClass = ""; + if (fullStatus.node_public_key == accountKey) { + lastPing = "n/a" + } else if (peer.last_time_received_message_millis > 60 * 1000) { + lastPingClass = "peer_far_behind"; + } + + rows.push( + {addDebugPortLink(peer.addr)} + {accountKey.substring(8, 14)}... + {accountId} + + {peer.peer_id.substring(8, 14)}... + {lastPing} + {JSON.stringify(peer.tracked_shards)} + {JSON.stringify(peer.archival)} + {peer.is_outbound_peer ? 'OUT' : 'IN'} + {formatDurationInMillis(peer.connection_established_time_millis)} + {formatTraffic(peer.received_bytes_per_sec, peer.sent_bytes_per_sec)} + ); + } + + // tier1_accounts_data contains data about TIER1 nodes we would like to connect to + for (const account of networkInfo.tier1_accounts_data!) { + let accountKey = account.account_key; + + if (rendered.has(accountKey)) { + continue; + } + rendered.add(accountKey); + + let accountId = ""; + networkInfo.known_producers.forEach(producer => { + if (producer.peer_id == account.peer_id) { + accountId = producer.account_id; + } + }); + + rows.push( + + {accountKey.substring(8, 14)}... + {accountId} + + {account.peer_id.substring(8, 14)}... + + + + + + + ); + } + + // tier1_accounts_keys contains accounts whose data we would like to collect + for (const accountKey of networkInfo.tier1_accounts_keys!) { + if (rendered.has(accountKey)) { + continue; + } + rendered.add(accountKey); + + rows.push( + + {accountKey.substring(8, 14)}... + + + + + + + + + + ); + } + + return
+ + + + + + + + + + + + + + + + + + {rows} + +
AddressAccountKeyAccount IDProxiesPeerIdLast pingTracked ShardsArchivalConnection typeFirst connectionTraffic (last minute)
+
; +}; + +const CollapsibleProxyList = ({ proxies }: { proxies: PeerAddr[] }) => { + const [showAll, setShowAll] = useState(false); + const callback = useCallback((e: MouseEvent) => { + e.preventDefault(); + setShowAll((show) => !show); + }, []); + const MAX_TO_SHOW = 2; + + const formatted = proxies.map(proxy => { + return proxy.peer_id.substring(8, 14) + "...@" + proxy.addr; + }); + + if (formatted.length <= MAX_TO_SHOW) { + return <>{formatted.join(', ')}; + } + if (showAll) { + return <>{formatted.join(', ')} Show fewer; + } + return <> + {formatted.slice(0, MAX_TO_SHOW).join(', ')}, ...
+ Show all {formatted.length} + ; +} diff --git a/tools/debug-ui/src/api.tsx b/tools/debug-ui/src/api.tsx index 47d36a32475..9ac8dd351aa 100644 --- a/tools/debug-ui/src/api.tsx +++ b/tools/debug-ui/src/api.tsx @@ -53,6 +53,7 @@ export interface NetworkInfoView { num_connected_peers: number, connected_peers: PeerInfoView[], known_producers: KnownProducerView[], + tier1_accounts_keys?: string[], tier1_accounts_data?: AccountData[], tier1_connections?: PeerInfoView[], } diff --git a/tools/debug-ui/src/utils.js b/tools/debug-ui/src/utils.js index 71c7d156622..cc15fc73e3c 100644 --- a/tools/debug-ui/src/utils.js +++ b/tools/debug-ui/src/utils.js @@ -1,3 +1,53 @@ +export function formatDurationInMillis(millis: number): string { + if (millis == null) { + return '(null)'; + } + let total_seconds = Math.floor(millis / 1000); + let hours = Math.floor(total_seconds / 3600) + let minutes = Math.floor((total_seconds - (hours * 3600)) / 60) + let seconds = total_seconds - (hours * 3600) - (minutes * 60) + if (hours > 0) { + if (minutes > 0) { + return `${hours}h ${minutes}m ${seconds}s` + } else { + return `${hours}h ${seconds}s` + } + } + if (minutes > 0) { + return `${minutes}m ${seconds}s` + } + return `${seconds}s` +} + +function formatBytesPerSecond(bytes_per_second: number): string { + if (bytes_per_second == null) { + return '-'; + } + if (bytes_per_second < 3000) { + return `${bytes_per_second} bps`; + } + let kilobytes_per_second = bytes_per_second / 1024; + if (kilobytes_per_second < 3000) { + return `${kilobytes_per_second.toFixed(1)} Kbps`; + } + let megabytes_per_second = kilobytes_per_second / 1024; + return `${megabytes_per_second.toFixed(1)} Mbps`; +} + +export function formatTraffic(bytes_received: number, bytes_sent: number): ReactElement { + return
+
{"⬇ " + formatBytesPerSecond(bytes_received)}
+
{"⬆ " + formatBytesPerSecond(bytes_sent)}
+
; +} + +export function addDebugPortLink(peer_addr: string): ReactElement { + return + {peer_addr} + ; +} + export function toHumanTime(seconds: number): string { let result = ""; if (seconds >= 60) {