From e5b10d8b7e8792a931593f3a9c8e44aad2c5b114 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 5 Jan 2021 14:22:12 -0800 Subject: [PATCH] explorer: display block time on cluster stats (#14439) * explorer: display block time on cluster stats * interpolate blocktime values between fetches * prevent time from going backwards --- explorer/src/pages/ClusterStatsPage.tsx | 16 +++++++- .../providers/stats/solanaClusterStats.tsx | 29 +++++++++++++- .../providers/stats/solanaDashboardInfo.tsx | 40 ++++++++++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/explorer/src/pages/ClusterStatsPage.tsx b/explorer/src/pages/ClusterStatsPage.tsx index 4f4a70013e..b110afe2de 100644 --- a/explorer/src/pages/ClusterStatsPage.tsx +++ b/explorer/src/pages/ClusterStatsPage.tsx @@ -10,6 +10,7 @@ import { import { slotsToHumanString } from "utils"; import { useCluster } from "providers/cluster"; import { TpsCard } from "components/TpsCard"; +import { displayTimestamp } from "utils/date"; const CLUSTER_STATS_TIMEOUT = 10000; @@ -52,7 +53,12 @@ function StatsCardBody() { return ; } - const { avgSlotTime_1h, avgSlotTime_1min, epochInfo } = dashboardInfo; + const { + avgSlotTime_1h, + avgSlotTime_1min, + epochInfo, + blockTime, + } = dashboardInfo; const hourlySlotTime = Math.round(1000 * avgSlotTime_1h); const averageSlotTime = Math.round(1000 * avgSlotTime_1min); const { slotIndex, slotsInEpoch } = epochInfo; @@ -80,6 +86,14 @@ function StatsCardBody() { )} + {blockTime && ( + + Block time + + {displayTimestamp(blockTime)} + + + )} Slot time (1min average) {averageSlotTime}ms diff --git a/explorer/src/providers/stats/solanaClusterStats.tsx b/explorer/src/providers/stats/solanaClusterStats.tsx index 726136e5d3..8672d0f22f 100644 --- a/explorer/src/providers/stats/solanaClusterStats.tsx +++ b/explorer/src/providers/stats/solanaClusterStats.tsx @@ -18,6 +18,7 @@ export const SAMPLE_HISTORY_HOURS = 6; export const PERFORMANCE_SAMPLE_INTERVAL = 60000; export const TRANSACTION_COUNT_INTERVAL = 5000; export const EPOCH_INFO_INTERVAL = 2000; +export const BLOCK_TIME_INTERVAL = 5000; export const LOADING_TIMEOUT = 10000; export enum ClusterStatsStatus { @@ -89,6 +90,7 @@ export function SolanaClusterStatsProvider({ children }: Props) { if (!active || !url) return; const connection = new Connection(url); + let lastSlot: number | null = null; const getPerformanceSamples = async () => { try { @@ -148,6 +150,7 @@ export function SolanaClusterStatsProvider({ children }: Props) { const getEpochInfo = async () => { try { const epochInfo = await connection.getEpochInfo(); + lastSlot = epochInfo.absoluteSlot; dispatchDashboardInfo({ type: DashboardInfoActionType.SetEpochInfo, data: epochInfo, @@ -164,6 +167,25 @@ export function SolanaClusterStatsProvider({ children }: Props) { } }; + const getBlockTime = async () => { + if (lastSlot) { + try { + const blockTime = await connection.getBlockTime(lastSlot); + if (blockTime !== null) { + dispatchDashboardInfo({ + type: DashboardInfoActionType.SetLastBlockTime, + data: { + slot: lastSlot, + blockTime: blockTime * 1000, + }, + }); + } + } catch (error) { + // let this fail gracefully + } + } + }; + const performanceInterval = setInterval( getPerformanceSamples, PERFORMANCE_SAMPLE_INTERVAL @@ -173,15 +195,20 @@ export function SolanaClusterStatsProvider({ children }: Props) { TRANSACTION_COUNT_INTERVAL ); const epochInfoInterval = setInterval(getEpochInfo, EPOCH_INFO_INTERVAL); + const blockTimeInterval = setInterval(getBlockTime, BLOCK_TIME_INTERVAL); getPerformanceSamples(); getTransactionCount(); - getEpochInfo(); + (async () => { + await getEpochInfo(); + await getBlockTime(); + })(); return () => { clearInterval(performanceInterval); clearInterval(transactionCountInterval); clearInterval(epochInfoInterval); + clearInterval(blockTimeInterval); }; }, [active, cluster, url]); diff --git a/explorer/src/providers/stats/solanaDashboardInfo.tsx b/explorer/src/providers/stats/solanaDashboardInfo.tsx index 67afb0e648..9e02a8c008 100644 --- a/explorer/src/providers/stats/solanaDashboardInfo.tsx +++ b/explorer/src/providers/stats/solanaDashboardInfo.tsx @@ -6,11 +6,19 @@ export type DashboardInfo = { avgSlotTime_1h: number; avgSlotTime_1min: number; epochInfo: EpochInfo; + blockTime?: number; + lastBlockTime?: BlockTimeInfo; +}; + +export type BlockTimeInfo = { + blockTime: number; + slot: number; }; export enum DashboardInfoActionType { SetPerfSamples, SetEpochInfo, + SetLastBlockTime, SetError, Reset, } @@ -35,17 +43,32 @@ export type DashboardInfoActionSetError = { data: string; }; +export type DashboardInfoActionSetLastBlockTime = { + type: DashboardInfoActionType.SetLastBlockTime; + data: BlockTimeInfo; +}; + export type DashboardInfoAction = | DashboardInfoActionSetPerfSamples | DashboardInfoActionSetEpochInfo | DashboardInfoActionReset - | DashboardInfoActionSetError; + | DashboardInfoActionSetError + | DashboardInfoActionSetLastBlockTime; export function dashboardInfoReducer( state: DashboardInfo, action: DashboardInfoAction ) { switch (action.type) { + case DashboardInfoActionType.SetLastBlockTime: { + const blockTime = state.blockTime || action.data.blockTime; + return { + ...state, + lastBlockTime: action.data, + blockTime, + }; + } + case DashboardInfoActionType.SetPerfSamples: { if (action.data.length < 1) { return state; @@ -85,10 +108,25 @@ export function dashboardInfoReducer( ? ClusterStatsStatus.Ready : ClusterStatsStatus.Loading; + let blockTime = state.blockTime; + + // interpolate blocktime based on last known blocktime and average slot time + if ( + state.lastBlockTime && + state.avgSlotTime_1h !== 0 && + action.data.absoluteSlot >= state.lastBlockTime.slot + ) { + blockTime = + state.lastBlockTime.blockTime + + (action.data.absoluteSlot - state.lastBlockTime.slot) * + Math.floor(state.avgSlotTime_1h * 1000); + } + return { ...state, epochInfo: action.data, status, + blockTime, }; }