import React from "react";
import { Bar } from "react-chartjs-2";
import CountUp from "react-countup";
import {
usePerformanceInfo,
PERF_UPDATE_SEC,
ClusterStatsStatus,
} from "providers/stats/solanaClusterStats";
import classNames from "classnames";
import { TableCardBody } from "components/common/TableCardBody";
import { ChartOptions, ChartTooltipModel } from "chart.js";
import { PerformanceInfo } from "providers/stats/solanaPerformanceInfo";
import { StatsNotReady } from "pages/ClusterStatsPage";
export function TpsCard() {
return (
);
}
function TpsCardBody() {
const performanceInfo = usePerformanceInfo();
if (performanceInfo.status !== ClusterStatsStatus.Ready) {
return (
);
}
return ;
}
type Series = "short" | "medium" | "long";
const SERIES: Series[] = ["short", "medium", "long"];
const SERIES_INFO = {
short: {
label: (index: number) => index,
interval: "30m",
},
medium: {
label: (index: number) => index * 4,
interval: "2h",
},
long: {
label: (index: number) => index * 12,
interval: "6h",
},
};
const CUSTOM_TOOLTIP = function (this: any, tooltipModel: ChartTooltipModel) {
// Tooltip Element
let tooltipEl = document.getElementById("chartjs-tooltip");
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement("div");
tooltipEl.id = "chartjs-tooltip";
tooltipEl.innerHTML = ``;
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = "0";
return;
}
// Set Text
if (tooltipModel.body) {
const { label, value } = tooltipModel.dataPoints[0];
const tooltipContent = tooltipEl.querySelector("div");
if (tooltipContent) {
let innerHtml = `${value} TPS
`;
innerHtml += `${label}
`;
tooltipContent.innerHTML = innerHtml;
}
}
// Enable tooltip and set position
const canvas: Element = this._chart.canvas;
const position = canvas.getBoundingClientRect();
tooltipEl.style.opacity = "1";
tooltipEl.style.left =
position.left + window.pageXOffset + tooltipModel.caretX + "px";
tooltipEl.style.top =
position.top + window.pageYOffset + tooltipModel.caretY + "px";
};
const CHART_OPTIONS = (historyMaxTps: number): ChartOptions => {
return {
tooltips: {
intersect: false, // Show tooltip when cursor in between bars
enabled: false, // Hide default tooltip
custom: CUSTOM_TOOLTIP,
},
legend: {
display: false,
},
scales: {
xAxes: [
{
ticks: {
display: false,
},
gridLines: {
display: false,
},
},
],
yAxes: [
{
ticks: {
stepSize: 100,
fontSize: 10,
fontColor: "#EEE",
beginAtZero: true,
display: true,
suggestedMax: historyMaxTps,
},
gridLines: {
display: false,
},
},
],
},
animation: {
duration: 0, // general animation time
},
hover: {
animationDuration: 0, // duration of animations when hovering an item
},
responsiveAnimationDuration: 0, // animation duration after a resize
};
};
type TpsBarChartProps = { performanceInfo: PerformanceInfo };
function TpsBarChart({ performanceInfo }: TpsBarChartProps) {
const { perfHistory, avgTps, historyMaxTps } = performanceInfo;
const [series, setSeries] = React.useState("short");
const averageTps = Math.round(avgTps).toLocaleString("en-US");
const transactionCount = ;
const seriesData = perfHistory[series];
const chartOptions = React.useMemo(
() => CHART_OPTIONS(historyMaxTps),
[historyMaxTps]
);
const seriesLength = seriesData.length;
const chartData: Chart.ChartData = {
labels: seriesData.map((val, i) => {
return `${SERIES_INFO[series].label(seriesLength - i)}min ago`;
}),
datasets: [
{
backgroundColor: "#00D192",
hoverBackgroundColor: "#00D192",
borderWidth: 0,
data: seriesData.map((val) => val || 0),
},
],
};
return (
<>
Transaction count |
{transactionCount} |
Transactions per second (TPS) |
{averageTps} |
TPS history
{SERIES.map((key) => (
))}
>
);
}
function AnimatedTransactionCount({ info }: { info: PerformanceInfo }) {
const txCountRef = React.useRef(0);
const countUpRef = React.useRef({ start: 0, period: 0, lastUpdate: 0 });
const countUp = countUpRef.current;
const { transactionCount: txCount, avgTps } = info;
// Track last tx count to reset count up options
if (txCount !== txCountRef.current) {
if (countUp.lastUpdate > 0) {
// Since we overshoot below, calculate the elapsed value
// and start from there.
const elapsed = Date.now() - countUp.lastUpdate;
const elapsedPeriods = elapsed / (PERF_UPDATE_SEC * 1000);
countUp.start = countUp.start + elapsedPeriods * countUp.period;
countUp.period = txCount - countUp.start;
} else {
// Since this is the first tx count value, estimate the previous
// tx count in order to have a starting point for our animation
countUp.period = PERF_UPDATE_SEC * avgTps;
countUp.start = txCount - countUp.period;
}
countUp.lastUpdate = Date.now();
txCountRef.current = txCount;
}
// Overshoot the target tx count in case the next update is delayed
const COUNT_PERIODS = 3;
const countUpEnd = countUp.start + COUNT_PERIODS * countUp.period;
return (
);
}