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
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
import { slotsToHumanString } from "utils";
|
import { slotsToHumanString } from "utils";
|
||||||
import { useCluster } from "providers/cluster";
|
import { useCluster } from "providers/cluster";
|
||||||
import { TpsCard } from "components/TpsCard";
|
import { TpsCard } from "components/TpsCard";
|
||||||
|
import { displayTimestamp } from "utils/date";
|
||||||
|
|
||||||
const CLUSTER_STATS_TIMEOUT = 10000;
|
const CLUSTER_STATS_TIMEOUT = 10000;
|
||||||
|
|
||||||
@@ -52,7 +53,12 @@ function StatsCardBody() {
|
|||||||
return <StatsNotReady error={error} />;
|
return <StatsNotReady error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { avgSlotTime_1h, avgSlotTime_1min, epochInfo } = dashboardInfo;
|
const {
|
||||||
|
avgSlotTime_1h,
|
||||||
|
avgSlotTime_1min,
|
||||||
|
epochInfo,
|
||||||
|
blockTime,
|
||||||
|
} = dashboardInfo;
|
||||||
const hourlySlotTime = Math.round(1000 * avgSlotTime_1h);
|
const hourlySlotTime = Math.round(1000 * avgSlotTime_1h);
|
||||||
const averageSlotTime = Math.round(1000 * avgSlotTime_1min);
|
const averageSlotTime = Math.round(1000 * avgSlotTime_1min);
|
||||||
const { slotIndex, slotsInEpoch } = epochInfo;
|
const { slotIndex, slotsInEpoch } = epochInfo;
|
||||||
@@ -80,6 +86,14 @@ function StatsCardBody() {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
{blockTime && (
|
||||||
|
<tr>
|
||||||
|
<td className="w-100">Block time</td>
|
||||||
|
<td className="text-lg-right text-monospace">
|
||||||
|
{displayTimestamp(blockTime)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
<tr>
|
<tr>
|
||||||
<td className="w-100">Slot time (1min average)</td>
|
<td className="w-100">Slot time (1min average)</td>
|
||||||
<td className="text-lg-right text-monospace">{averageSlotTime}ms</td>
|
<td className="text-lg-right text-monospace">{averageSlotTime}ms</td>
|
||||||
|
@@ -18,6 +18,7 @@ export const SAMPLE_HISTORY_HOURS = 6;
|
|||||||
export const PERFORMANCE_SAMPLE_INTERVAL = 60000;
|
export const PERFORMANCE_SAMPLE_INTERVAL = 60000;
|
||||||
export const TRANSACTION_COUNT_INTERVAL = 5000;
|
export const TRANSACTION_COUNT_INTERVAL = 5000;
|
||||||
export const EPOCH_INFO_INTERVAL = 2000;
|
export const EPOCH_INFO_INTERVAL = 2000;
|
||||||
|
export const BLOCK_TIME_INTERVAL = 5000;
|
||||||
export const LOADING_TIMEOUT = 10000;
|
export const LOADING_TIMEOUT = 10000;
|
||||||
|
|
||||||
export enum ClusterStatsStatus {
|
export enum ClusterStatsStatus {
|
||||||
@@ -89,6 +90,7 @@ export function SolanaClusterStatsProvider({ children }: Props) {
|
|||||||
if (!active || !url) return;
|
if (!active || !url) return;
|
||||||
|
|
||||||
const connection = new Connection(url);
|
const connection = new Connection(url);
|
||||||
|
let lastSlot: number | null = null;
|
||||||
|
|
||||||
const getPerformanceSamples = async () => {
|
const getPerformanceSamples = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -148,6 +150,7 @@ export function SolanaClusterStatsProvider({ children }: Props) {
|
|||||||
const getEpochInfo = async () => {
|
const getEpochInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const epochInfo = await connection.getEpochInfo();
|
const epochInfo = await connection.getEpochInfo();
|
||||||
|
lastSlot = epochInfo.absoluteSlot;
|
||||||
dispatchDashboardInfo({
|
dispatchDashboardInfo({
|
||||||
type: DashboardInfoActionType.SetEpochInfo,
|
type: DashboardInfoActionType.SetEpochInfo,
|
||||||
data: epochInfo,
|
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(
|
const performanceInterval = setInterval(
|
||||||
getPerformanceSamples,
|
getPerformanceSamples,
|
||||||
PERFORMANCE_SAMPLE_INTERVAL
|
PERFORMANCE_SAMPLE_INTERVAL
|
||||||
@@ -173,15 +195,20 @@ export function SolanaClusterStatsProvider({ children }: Props) {
|
|||||||
TRANSACTION_COUNT_INTERVAL
|
TRANSACTION_COUNT_INTERVAL
|
||||||
);
|
);
|
||||||
const epochInfoInterval = setInterval(getEpochInfo, EPOCH_INFO_INTERVAL);
|
const epochInfoInterval = setInterval(getEpochInfo, EPOCH_INFO_INTERVAL);
|
||||||
|
const blockTimeInterval = setInterval(getBlockTime, BLOCK_TIME_INTERVAL);
|
||||||
|
|
||||||
getPerformanceSamples();
|
getPerformanceSamples();
|
||||||
getTransactionCount();
|
getTransactionCount();
|
||||||
getEpochInfo();
|
(async () => {
|
||||||
|
await getEpochInfo();
|
||||||
|
await getBlockTime();
|
||||||
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(performanceInterval);
|
clearInterval(performanceInterval);
|
||||||
clearInterval(transactionCountInterval);
|
clearInterval(transactionCountInterval);
|
||||||
clearInterval(epochInfoInterval);
|
clearInterval(epochInfoInterval);
|
||||||
|
clearInterval(blockTimeInterval);
|
||||||
};
|
};
|
||||||
}, [active, cluster, url]);
|
}, [active, cluster, url]);
|
||||||
|
|
||||||
|
@@ -6,11 +6,19 @@ export type DashboardInfo = {
|
|||||||
avgSlotTime_1h: number;
|
avgSlotTime_1h: number;
|
||||||
avgSlotTime_1min: number;
|
avgSlotTime_1min: number;
|
||||||
epochInfo: EpochInfo;
|
epochInfo: EpochInfo;
|
||||||
|
blockTime?: number;
|
||||||
|
lastBlockTime?: BlockTimeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BlockTimeInfo = {
|
||||||
|
blockTime: number;
|
||||||
|
slot: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum DashboardInfoActionType {
|
export enum DashboardInfoActionType {
|
||||||
SetPerfSamples,
|
SetPerfSamples,
|
||||||
SetEpochInfo,
|
SetEpochInfo,
|
||||||
|
SetLastBlockTime,
|
||||||
SetError,
|
SetError,
|
||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
@@ -35,17 +43,32 @@ export type DashboardInfoActionSetError = {
|
|||||||
data: string;
|
data: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DashboardInfoActionSetLastBlockTime = {
|
||||||
|
type: DashboardInfoActionType.SetLastBlockTime;
|
||||||
|
data: BlockTimeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
export type DashboardInfoAction =
|
export type DashboardInfoAction =
|
||||||
| DashboardInfoActionSetPerfSamples
|
| DashboardInfoActionSetPerfSamples
|
||||||
| DashboardInfoActionSetEpochInfo
|
| DashboardInfoActionSetEpochInfo
|
||||||
| DashboardInfoActionReset
|
| DashboardInfoActionReset
|
||||||
| DashboardInfoActionSetError;
|
| DashboardInfoActionSetError
|
||||||
|
| DashboardInfoActionSetLastBlockTime;
|
||||||
|
|
||||||
export function dashboardInfoReducer(
|
export function dashboardInfoReducer(
|
||||||
state: DashboardInfo,
|
state: DashboardInfo,
|
||||||
action: DashboardInfoAction
|
action: DashboardInfoAction
|
||||||
) {
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case DashboardInfoActionType.SetLastBlockTime: {
|
||||||
|
const blockTime = state.blockTime || action.data.blockTime;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastBlockTime: action.data,
|
||||||
|
blockTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case DashboardInfoActionType.SetPerfSamples: {
|
case DashboardInfoActionType.SetPerfSamples: {
|
||||||
if (action.data.length < 1) {
|
if (action.data.length < 1) {
|
||||||
return state;
|
return state;
|
||||||
@@ -85,10 +108,25 @@ export function dashboardInfoReducer(
|
|||||||
? ClusterStatsStatus.Ready
|
? ClusterStatsStatus.Ready
|
||||||
: ClusterStatsStatus.Loading;
|
: 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 {
|
return {
|
||||||
...state,
|
...state,
|
||||||
epochInfo: action.data,
|
epochInfo: action.data,
|
||||||
status,
|
status,
|
||||||
|
blockTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user