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 ;
+ }
+
+ 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
+
+
+
+ Address |
+ AccountKey |
+ Account ID |
+ Proxies |
+ PeerId |
+ Last ping |
+ Tracked Shards |
+ Archival |
+ Connection type |
+ First connection |
+ Traffic (last minute) |
+
+
+
+ {rows}
+
+
+
;
+};
+
+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) {