Skip to content

Commit

Permalink
Add cluster stats tab to explorer
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Aug 1, 2020
1 parent a5b6fd3 commit 767bc2b
Show file tree
Hide file tree
Showing 10 changed files with 651 additions and 30 deletions.
276 changes: 262 additions & 14 deletions explorer/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"@types/socket.io-client": "^1.4.33",
"bootstrap": "^4.5.0",
"bs58": "^4.0.1",
"humanize-duration-ts": "^2.1.1",
"node-sass": "^4.14.1",
"prettier": "^2.0.5",
"react": "^16.13.1",
"react-app-rewired": "^2.1.6",
"react-countup": "^4.3.3",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"socket.io-client": "^2.3.0",
"solana-sdk-wasm": "file:wasm/pkg",
"superstruct": "^0.10.12",
"typescript": "^3.9.7",
"wasm-loader": "^1.3.0"
},
Expand Down
11 changes: 6 additions & 5 deletions explorer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ACCOUNT_ALIASES, ACCOUNT_ALIASES_PLURAL } from "./providers/accounts";
import TabbedPage from "components/TabbedPage";
import TopAccountsCard from "components/TopAccountsCard";
import SupplyCard from "components/SupplyCard";
import StatsCard from "components/StatsCard";
import { pickCluster } from "utils/url";
import Banner from "components/Banner";

Expand Down Expand Up @@ -84,11 +85,11 @@ function App() {
<AccountsCard />
</TabbedPage>
</Route>
<Route
render={({ location }) => (
<Redirect to={{ ...location, pathname: "/transactions" }} />
)}
></Route>
<Route>
<TabbedPage tab="Stats">
<StatsCard />
</TabbedPage>
</Route>
</Switch>
</div>
</>
Expand Down
131 changes: 131 additions & 0 deletions explorer/src/components/StatsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from "react";
import CountUp from "react-countup";

import TableCardBody from "./common/TableCardBody";
import {
useDashboardInfo,
usePerformanceInfo,
useRootSlot,
PERF_UPDATE_SEC,
useSetActive,
} from "providers/stats/solanaBeach";
import { slotsToHumanString } from "utils";
import { useCluster, Cluster } from "providers/cluster";

export default function StatsCard() {
return (
<div className="card">
<div className="card-header">
<div className="row align-items-center">
<div className="col">
<h4 className="card-header-title">Live Cluster Info</h4>
</div>
</div>
</div>
<StatsCardBody />
</div>
);
}

function StatsCardBody() {
const rootSlot = useRootSlot();
const dashboardInfo = useDashboardInfo();
const performanceInfo = usePerformanceInfo();
const txTrackerRef = React.useRef({ old: 0, new: 0 });
const txTracker = txTrackerRef.current;
const setSocketActive = useSetActive();
const { cluster } = useCluster();

React.useEffect(() => {
setSocketActive(true);
return () => setSocketActive(false);
}, [setSocketActive, cluster]);

const statsAvailable =
cluster === Cluster.MainnetBeta || cluster === Cluster.Testnet;
if (!statsAvailable) {
return (
<div className="card-body text-center">
<div className="text-muted">
Stats are not available for this cluster
</div>
</div>
);
}

if (performanceInfo) {
const { totalTransactionCount: txCount, avgTPS } = performanceInfo;

// Track last tx count to initialize count up
if (txCount !== txTracker.new) {
// If this is the first tx count value, estimate the previous one
// in order to have a starting point for our animation
txTracker.old = txTracker.new || txCount - PERF_UPDATE_SEC * avgTPS;
txTracker.new = txCount;
}
} else {
txTrackerRef.current = { old: 0, new: 0 };
}

if (rootSlot === undefined || !dashboardInfo || !performanceInfo) {
return (
<div className="card-body text-center">
<span className="spinner-grow spinner-grow-sm mr-2"></span>
Loading
</div>
);
}

const currentBlock = rootSlot.toLocaleString("en-US");
const { avgBlockTime_1min, epochInfo } = dashboardInfo;
const averageBlockTime = Math.round(1000 * avgBlockTime_1min) + "ms";
const { slotIndex, slotsInEpoch } = epochInfo;
const currentEpoch = epochInfo.epoch.toString();
const epochProgress = ((100 * slotIndex) / slotsInEpoch).toFixed(1) + "%";
const epochTimeRemaining = slotsToHumanString(slotsInEpoch - slotIndex);
const transactionCount = (
<CountUp
start={txTracker.old}
end={txTracker.new}
duration={PERF_UPDATE_SEC + 2}
delay={0}
useEasing={false}
preserveValue={true}
separator=","
/>
);
const averageTps = Math.round(performanceInfo.avgTPS);

return (
<TableCardBody>
<tr>
<td className="w-100">Block</td>
<td className="text-right text-monospace">{currentBlock}</td>
</tr>
<tr>
<td className="w-100">Block time</td>
<td className="text-right text-monospace">{averageBlockTime}</td>
</tr>
<tr>
<td className="w-100">Epoch</td>
<td className="text-right text-monospace">{currentEpoch} </td>
</tr>
<tr>
<td className="w-100">Epoch progress</td>
<td className="text-right text-monospace">{epochProgress} </td>
</tr>
<tr>
<td className="w-100">Epoch time remaining</td>
<td className="text-right text-monospace">{epochTimeRemaining} </td>
</tr>
<tr>
<td className="w-100">Transaction count</td>
<td className="text-right text-monospace">{transactionCount} </td>
</tr>
<tr>
<td className="w-100">Transactions per second</td>
<td className="text-right text-monospace">{averageTps} </td>
</tr>
</TableCardBody>
);
}
5 changes: 4 additions & 1 deletion explorer/src/components/TabbedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useClusterModal } from "providers/cluster";
import ClusterStatusButton from "components/ClusterStatusButton";
import { pickCluster } from "utils/url";

export type Tab = "Transactions" | "Accounts" | "Supply";
export type Tab = "Transactions" | "Accounts" | "Supply" | "Stats";

type Props = { children: React.ReactNode; tab: Tab };
export default function TabbedPage({ children, tab }: Props) {
Expand All @@ -22,6 +22,9 @@ export default function TabbedPage({ children, tab }: Props) {
<div className="row align-items-center">
<div className="col">
<ul className="nav nav-tabs nav-overflow header-tabs">
<li className="nav-item">
<NavLink href="/" tab="Stats" current={tab} />
</li>
<li className="nav-item">
<NavLink
href="/transactions"
Expand Down
21 changes: 12 additions & 9 deletions explorer/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ import { RichListProvider } from "./providers/richList";
import { SupplyProvider } from "./providers/supply";
import { TransactionsProvider } from "./providers/transactions";
import { AccountsProvider } from "./providers/accounts";
import { StatsProvider } from "providers/stats";

ReactDOM.render(
<Router>
<ClusterProvider>
<SupplyProvider>
<RichListProvider>
<AccountsProvider>
<TransactionsProvider>
<App />
</TransactionsProvider>
</AccountsProvider>
</RichListProvider>
</SupplyProvider>
<StatsProvider>
<SupplyProvider>
<RichListProvider>
<AccountsProvider>
<TransactionsProvider>
<App />
</TransactionsProvider>
</AccountsProvider>
</RichListProvider>
</SupplyProvider>
</StatsProvider>
</ClusterProvider>
</Router>,
document.getElementById("root")
Expand Down
7 changes: 7 additions & 0 deletions explorer/src/providers/stats/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";
import { SolanaBeachProvider } from "./solanaBeach";

type Props = { children: React.ReactNode };
export function StatsProvider({ children }: Props) {
return <SolanaBeachProvider>{children}</SolanaBeachProvider>;
}
Loading

0 comments on commit 767bc2b

Please sign in to comment.