1- import React , { useState , useEffect } from 'react' ;
1+ import React , { useState , useEffect , useRef } from 'react' ;
22import { Header } from './components/Header' ;
33import { Footer } from './components/Footer' ;
44import { MetricsTotal } from './components/MetricsTotal' ;
55import { ChainTable } from './components/ChainTable' ;
66import { RelaySelector } from './components/RelaySelector' ;
77import './App.css' ;
8-
98import {
109 polkadotChainNames ,
1110 kusamaChainNames ,
@@ -14,125 +13,179 @@ import {
1413 PolkadotChainName ,
1514 KusamaChainName ,
1615} from './types/chains' ;
17-
1816import useWeightConsumption from './hooks/useWeightConsumption' ;
1917import { PROOF_SIZE_MB , MB_TO_GAS , GAS_TO_MGAS } from './constants' ;
2018
21- // ema smoothing factor
2219const ALPHA = 0.3 ;
20+ const TARGET_WINDOW_SIZE = 30000 ; // 30s
21+ const MAX_BLOCKS_STORE = 2000 ;
2322
24- // helper to calculate ema
25- function calculateEma ( oldValue : number , newValue : number ) : number {
23+ // standard ema
24+ function calculateEma ( oldValue : number , newValue : number ) {
2625 return ALPHA * newValue + ( 1 - ALPHA ) * oldValue ;
2726}
2827
28+ // compute rolling tps, mbs, gas from blocks in the last 30s
29+ function calculateRollingMetrics (
30+ blocks : Array < { blockNumber : number ; timestamp : number ; extrinsics : number ; proofSize : number } >
31+ ) {
32+ if ( blocks . length < 2 ) return { tps : 0 , mbs : 0 , gas : 0 } ;
33+
34+ const now = Date . now ( ) ;
35+ const cutoff = now - TARGET_WINDOW_SIZE ;
36+ const relevant = blocks . filter ( b => b . timestamp >= cutoff ) ;
37+ if ( relevant . length === 0 ) return { tps : 0 , mbs : 0 , gas : 0 } ;
38+
39+ relevant . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
40+ const timeWindow = relevant [ relevant . length - 1 ] . timestamp - relevant [ 0 ] . timestamp ;
41+ if ( timeWindow <= 0 ) return { tps : 0 , mbs : 0 , gas : 0 } ;
42+
43+ // adjust extrinsics with -2 offset if desired
44+ const sumExtrinsics = relevant . reduce ( ( acc , b ) => acc + Math . max ( 0 , b . extrinsics - 2 ) , 0 ) ;
45+ const sumProofSize = relevant . reduce ( ( acc , b ) => acc + b . proofSize , 0 ) ;
46+
47+ const tps = ( sumExtrinsics * 1000 ) / timeWindow ;
48+ const mbs = ( sumProofSize * PROOF_SIZE_MB * 1000 ) / timeWindow ;
49+ const gas = mbs * ( MB_TO_GAS / PROOF_SIZE_MB / GAS_TO_MGAS ) ;
50+
51+ return { tps, mbs, gas } ;
52+ }
53+
54+ interface BlockRecord {
55+ chainKey : string ;
56+ blockNumber : number ;
57+ timestamp : number ;
58+ extrinsics : number ;
59+ proofSize : number ;
60+ }
61+
2962const App : React . FC = ( ) => {
3063 const [ selectedRelay , setSelectedRelay ] = useState < 'Polkadot' | 'Kusama' > ( 'Polkadot' ) ;
3164
32- // fetch consumption data
65+ // data feed
3366 const consumptionData = useWeightConsumption ( 'https://stream.freeside.network/events' ) ;
3467
35- // determine chain names and mappings
36- const chainNames = selectedRelay === 'Polkadot'
37- ? polkadotChainNames
38- : kusamaChainNames ;
39-
40- const paraIdToChainName = selectedRelay === 'Polkadot'
41- ? polkadotParaIdToChainName
42- : kusamaParaIdToChainName ;
68+ // figure out which chains we're interested in
69+ const chainNames = selectedRelay === 'Polkadot' ? polkadotChainNames : kusamaChainNames ;
70+ const paraIdToChainName =
71+ selectedRelay === 'Polkadot' ? polkadotParaIdToChainName : kusamaParaIdToChainName ;
4372
44- // calculate proof-size-based weights
73+ // gather the "weightData" for all chains (just the mandatory + total_proof_size, for example)
4574 const weightData : Record < PolkadotChainName | KusamaChainName , number > = chainNames . reduce (
4675 ( acc , chain ) => {
4776 const paraId = Object . keys ( paraIdToChainName ) . find (
4877 ( key ) => paraIdToChainName [ Number ( key ) ] === chain
4978 ) ;
5079 if ( paraId ) {
5180 const key = `${ selectedRelay } -${ paraId } ` ;
52- const chainData = consumptionData [ key ] ?. current || { } ;
53- const proofSize = chainData . ref_time ?. mandatory ?? chainData . total_proof_size ?? 0 ;
54- return { ...acc , [ chain ] : proofSize } ;
81+ const chainData = consumptionData [ key ] ?. current ;
82+ if ( chainData ) {
83+ // example: combine mandatory + total_proof_size
84+ const mandatorySize = chainData . ref_time ?. mandatory ?? 0 ;
85+ const totalSize = chainData . total_proof_size ?? 0 ;
86+ const combinedProof = mandatorySize + totalSize ;
87+ return { ...acc , [ chain ] : combinedProof } ;
88+ }
5589 }
5690 return acc ;
5791 } ,
5892 { } as Record < PolkadotChainName | KusamaChainName , number >
5993 ) ;
6094
61- // naive tps and mbs calculations
62- const totalTps = chainNames . reduce ( ( acc , chain ) => {
63- const paraId = Object . keys ( paraIdToChainName ) . find (
64- ( key ) => paraIdToChainName [ Number ( key ) ] === chain
65- ) ;
66- if ( paraId ) {
67- const key = `${ selectedRelay } -${ paraId } ` ;
68- const chainData = consumptionData [ key ] ?. current || { } ;
69- const extrinsicsNum = chainData . extrinsics_num || 0 ;
70- const blockTimeSec = chainData . block_time_seconds || 0 ;
71- if ( blockTimeSec > 0 ) {
72- return acc + extrinsicsNum / blockTimeSec ;
73- }
74- }
75- return acc ;
76- } , 0 ) ;
77-
78- const totalMbs = chainNames . reduce ( ( acc , chain ) => {
79- const paraId = Object . keys ( paraIdToChainName ) . find (
80- ( key ) => paraIdToChainName [ Number ( key ) ] === chain
81- ) ;
82- if ( paraId ) {
83- const key = `${ selectedRelay } -${ paraId } ` ;
84- const chainData = consumptionData [ key ] ?. current || { } ;
85- const blockTimeSec = chainData . block_time_seconds || 0 ;
86- const chainProofSize = weightData [ chain ] || 0 ;
87- if ( blockTimeSec > 0 ) {
88- return acc + ( chainProofSize * PROOF_SIZE_MB ) / blockTimeSec ;
89- }
90- }
91- return acc ;
92- } , 0 ) ;
95+ // store rolling metrics
96+ //const [rollingTps, setRollingTps] = useState(0);
97+ const [ rollingTpsEma , setRollingTpsEma ] = useState ( 0 ) ;
9398
94- const totalMGas = totalMbs * ( MB_TO_GAS / PROOF_SIZE_MB / GAS_TO_MGAS ) ;
99+ //const [rollingMbs, setRollingMbs] = useState(0);
100+ const [ rollingMbsEma , setRollingMbsEma ] = useState ( 0 ) ;
95101
96- // relay chain-specific stats
102+ //const [rollingGas, setRollingGas] = useState(0);
103+ const [ rollingGasEma , setRollingGasEma ] = useState ( 0 ) ;
104+
105+ // dedup blocks
106+ const blocksRef = useRef < BlockRecord [ ] > ( [ ] ) ;
107+ const lastBlockNumberByChain = useRef < Record < string , number > > ( { } ) ;
108+
109+ // we also want the immediate (non-ema) relay data
110+ let weightRelay = 0 ;
111+ let tpsRelay = 0 ;
112+ let gasRelay = 0 ;
113+
114+ // figure out the relay chain's data
97115 const relayChainName = selectedRelay === 'Polkadot' ? 'Polkadot' : 'Kusama' ;
98116 const relayParaId = Object . keys ( paraIdToChainName ) . find (
99- ( key ) => paraIdToChainName [ Number ( key ) ] === relayChainName
117+ ( k ) => paraIdToChainName [ Number ( k ) ] === relayChainName
100118 ) ;
101- const relayKey = relayParaId ? `${ selectedRelay } -${ relayParaId } ` : '' ;
102- const relayChainData = relayKey ? consumptionData [ relayKey ] ?. current || { } : { } ;
119+ if ( relayParaId ) {
120+ const relayKey = `${ selectedRelay } -${ relayParaId } ` ;
121+ const relayData = consumptionData [ relayKey ] ?. current ;
122+ if ( relayData && relayData . block_time_seconds ) {
123+ const blockTime = relayData . block_time_seconds || 6 ;
124+ const mandatorySize = relayData . ref_time ?. mandatory ?? 0 ;
125+ const totalSize = relayData . total_proof_size ?? 0 ;
126+ weightRelay = mandatorySize + totalSize ;
127+ tpsRelay = ( relayData . extrinsics_num ?? 0 ) / blockTime ;
128+
129+ const chainMbs = ( weightRelay * PROOF_SIZE_MB ) / blockTime ;
130+ gasRelay = chainMbs * ( MB_TO_GAS / PROOF_SIZE_MB / GAS_TO_MGAS ) ;
131+ }
132+ }
133+
134+ useEffect ( ( ) => {
135+ const newBlocks : BlockRecord [ ] = [ ] ;
136+
137+ chainNames . forEach ( ( chain ) => {
138+ const paraId = Object . keys ( paraIdToChainName ) . find (
139+ ( key ) => paraIdToChainName [ Number ( key ) ] === chain
140+ ) ;
141+ if ( ! paraId ) return ;
142+ const key = `${ selectedRelay } -${ paraId } ` ;
143+ const chainData = consumptionData [ key ] ?. current ;
144+ if ( ! chainData ) return ;
145+
146+ const blockNum = chainData . block_number ;
147+ const ts = chainData . timestamp ;
148+ // only add if we haven't seen this block_number before for this chain
149+ if (
150+ typeof blockNum === 'number' &&
151+ ts &&
152+ ( lastBlockNumberByChain . current [ key ] === undefined ||
153+ blockNum > lastBlockNumberByChain . current [ key ] )
154+ ) {
155+ lastBlockNumberByChain . current [ key ] = blockNum ;
156+
157+ // combine mandatory + total_proof_size
158+ const mandatorySize = chainData . ref_time ?. mandatory ?? 0 ;
159+ const totalSize = chainData . total_proof_size ?? 0 ;
160+ const totalProof = mandatorySize + totalSize ;
161+
162+ newBlocks . push ( {
163+ chainKey : key ,
164+ blockNumber : blockNum ,
165+ timestamp : ts ,
166+ extrinsics : chainData . extrinsics_num ?? 0 ,
167+ proofSize : totalProof ,
168+ } ) ;
169+ }
170+ } ) ;
103171
104- const tpsRelay = relayChainData . block_time_seconds
105- ? ( relayChainData . extrinsics_num || 0 ) / relayChainData . block_time_seconds
106- : 0 ;
172+ // if we actually have new blocks, compute rolling metrics
173+ if ( newBlocks . length > 0 ) {
174+ // add them, prune old
175+ blocksRef . current = [ ...blocksRef . current , ...newBlocks ] . slice ( - MAX_BLOCKS_STORE ) ;
107176
108- const weightRelay = weightData [ relayChainName ] || 0 ;
109- const gasRelay = weightRelay * PROOF_SIZE_MB / ( relayChainData . block_time_seconds || 6 ) ;
177+ const { tps, mbs, gas } = calculateRollingMetrics ( blocksRef . current ) ;
110178
111- // store ema values and reset on relay change
112- const [ emaTps , setEmaTps ] = useState ( totalTps ) ;
113- const [ emaMbs , setEmaMbs ] = useState ( totalMbs ) ;
114- const [ emaMGas , setEmaMGas ] = useState ( totalMGas ) ;
179+ //setRollingTps(tps);
180+ setRollingTpsEma ( ( prev ) => calculateEma ( prev || tps , tps ) ) ;
115181
116- // recalculate ema whenever raw totals change
117- useEffect ( ( ) => {
118- setEmaTps ( ( prev ) => calculateEma ( prev , totalTps ) ) ;
119- setEmaMbs ( ( prev ) => calculateEma ( prev , totalMbs ) ) ;
120- setEmaMGas ( ( prev ) => calculateEma ( prev , totalMGas ) ) ;
121- } , [ totalTps , totalMbs , totalMGas ] ) ;
182+ //setRollingMbs(mbs);
183+ setRollingMbsEma ( ( prev ) => calculateEma ( prev || mbs , mbs ) ) ;
122184
123- // reset ema when relay changes
124- useEffect ( ( ) => {
125- setEmaTps ( totalTps ) ;
126- setEmaMbs ( totalMbs ) ;
127- setEmaMGas ( totalMGas ) ;
128- } , [ selectedRelay ] ) ;
129-
130- // format for display
131- const formattedTotalTps = emaTps . toFixed ( 2 ) ;
132- const formattedTotalMbs = emaMbs . toFixed ( 2 ) ;
133- const formattedTotalGas = emaMGas . toFixed ( 2 ) ;
134- const formattedTpsRelay = tpsRelay . toFixed ( 2 ) ;
135- const formattedGasRelay = gasRelay . toFixed ( 2 ) ;
185+ // setRollingGas(gas);
186+ setRollingGasEma ( ( prev ) => calculateEma ( prev || gas , gas ) ) ;
187+ }
188+ } , [ consumptionData , chainNames , paraIdToChainName , selectedRelay ] ) ;
136189
137190 return (
138191 < div className = "app-container" >
@@ -147,21 +200,28 @@ const App: React.FC = () => {
147200 < RelaySelector selectedRelay = { selectedRelay } onRelayChange = { setSelectedRelay } />
148201
149202 < MetricsTotal
150- totalTps = { formattedTotalTps }
151- totalMbs = { formattedTotalMbs }
152- totalGas = { formattedTotalGas }
153- tpsRelay = { parseFloat ( formattedTpsRelay ) }
203+ // the rolling 30s metrics
204+ // rollingTps={rollingTps.toFixed(2)}
205+ totalTps = { rollingTpsEma . toFixed ( 2 ) }
206+ // rollingMbs={rollingMbs.toFixed(2)}
207+ totalMbs = { rollingMbsEma . toFixed ( 2 ) }
208+ // rollingGas={rollingGas.toFixed(2)}
209+ totalGas = { rollingGasEma . toFixed ( 2 ) }
210+
211+ // immediate relay chain-only stats (non-ema)
212+ tpsRelay = { tpsRelay }
154213 weightRelay = { weightRelay }
155- gasRelay = { parseFloat ( formattedGasRelay ) }
214+ gasRelay = { gasRelay }
156215 />
157216
158217 < div className = "textbox" >
159218 < ChainTable
160219 consumptionData = { consumptionData }
161- weightData = { weightData }
162220 chains = { chainNames }
163221 selectedRelay = { selectedRelay }
164222 paraIdToChainName = { paraIdToChainName }
223+ // pass in weightData for chain usage
224+ weightData = { weightData }
165225 />
166226 </ div >
167227 </ div >
@@ -174,4 +234,3 @@ const App: React.FC = () => {
174234} ;
175235
176236export default App ;
177-
0 commit comments