Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Debug UI] Migrate TIER1 debug page #8586

Merged
merged 2 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 1 addition & 50 deletions tools/debug-ui/src/CurrentPeersView.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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 <div>
<div>{"⬇ " + formatBytesPerSecond(bytes_received)}</div>
<div>{"⬆ " + formatBytesPerSecond(bytes_sent)}</div>
</div>;
}

function addDebugPortLink(peer_addr: string): ReactElement {
return <a
href={"/" + peer_addr.replace(/:.*/, "/network_info/current")}>
{peer_addr}
</a>;
}

function peerClass(current_height: number, peer_height: number): string {
if (peer_height > current_height + 5) {
return 'peer_ahead_alot';
Expand Down
3 changes: 3 additions & 0 deletions tools/debug-ui/src/NetworkInfoView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -15,13 +16,15 @@ export const NetworkInfoView = ({ addr }: NetworkInfoViewProps) => {
<NavLink to="current" className={navLinkClassName}>Current Peers</NavLink>
<NavLink to="peer_storage" className={navLinkClassName}>Detailed Peer Storage</NavLink>
<NavLink to="connection_storage" className={navLinkClassName}>Connection Storage</NavLink>
<NavLink to="tier1" className={navLinkClassName}>TIER1</NavLink>

</div>
<Routes>
<Route path="" element={<Navigate to="current" />} />
<Route path="current" element={<CurrentPeersView addr={addr} />} />
<Route path="peer_storage" element={<PeerStorageView addr={addr} />} />
<Route path="connection_storage" element={<ConnectionStorageView addr={addr} />} />
<Route path="tier1" element={<Tier1View addr={addr} />} />
</Routes>
</div>;
};
Expand Down
3 changes: 3 additions & 0 deletions tools/debug-ui/src/Tier1View.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tier1-peers-view {
padding: 10px;
}
186 changes: 186 additions & 0 deletions tools/debug-ui/src/Tier1View.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>Loading...</div>;
}
if (fullStatusError) {
return <div className="network-info-view">
<div className="error">
{(fullStatusError as Error).stack}
</div>
</div>;
}
if (!fullStatus) {
return <div className="network-info-view">
<div className="error">No Data</div>
</div>;
}

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(<tr key={accountKey}>
<td>{addDebugPortLink(peer.addr)}</td>
<td>{accountKey.substring(8, 14)}...</td>
<td>{accountId}</td>
<td><CollapsibleProxyList proxies={proxies} /></td>
<td>{peer.peer_id.substring(8, 14)}...</td>
<td className={lastPingClass}>{lastPing}</td>
<td>{JSON.stringify(peer.tracked_shards)}</td>
<td>{JSON.stringify(peer.archival)}</td>
<td>{peer.is_outbound_peer ? 'OUT' : 'IN'}</td>
<td>{formatDurationInMillis(peer.connection_established_time_millis)}</td>
<td>{formatTraffic(peer.received_bytes_per_sec, peer.sent_bytes_per_sec)}</td>
</tr>);
}

// 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(<tr key={accountKey}>
<td></td>
<td>{accountKey.substring(8, 14)}...</td>
<td>{accountId}</td>
<td><CollapsibleProxyList proxies={account.proxies} /></td>
<td>{account.peer_id.substring(8, 14)}...</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>);
}

// 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(<tr key={accountKey}>
<td></td>
<td>{accountKey.substring(8, 14)}...</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>);
}

return <div className="tier1-peers-view">
<table className="peers">
<thead>
<tr>
<th>Address</th>
<th>AccountKey</th>
<th>Account ID</th>
<th>Proxies</th>
<th>PeerId</th>
<th>Last ping</th>
<th>Tracked Shards</th>
<th>Archival</th>
<th>Connection type</th>
<th>First connection</th>
<th>Traffic (last minute)</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>;
};

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(', ')} <a href="#" onClick={callback}>Show fewer</a></>;
}
return <>
{formatted.slice(0, MAX_TO_SHOW).join(', ')}, ...<br />
<a href="#" onClick={callback}>Show all {formatted.length}</a>
</>;
}
1 change: 1 addition & 0 deletions tools/debug-ui/src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[],
}
Expand Down
50 changes: 50 additions & 0 deletions tools/debug-ui/src/utils.js
Original file line number Diff line number Diff line change
@@ -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 <div>
<div>{"⬇ " + formatBytesPerSecond(bytes_received)}</div>
<div>{"⬆ " + formatBytesPerSecond(bytes_sent)}</div>
</div>;
}

export function addDebugPortLink(peer_addr: string): ReactElement {
return <a
href={"http://localhost:3000/" + peer_addr.replace(/:.*/, "/")}>
{peer_addr}
</a>;
}

export function toHumanTime(seconds: number): string {
let result = "";
if (seconds >= 60) {
Expand Down