From 5f14d9720b19a6f8400f26dabd2772eb667cca73 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 16 Jun 2023 17:19:45 -0600 Subject: [PATCH 01/12] fix(webapp): fix tps and tpb graphic - Add stress test dashboard page - Fix error that queried get-block more than once - Fix error in fastest endpoint query - Add current cpu and net usage --- webapp/src/context/state.context.js | 163 +++++++++--------- webapp/src/gql/producer.gql.js | 4 +- webapp/src/language/en.json | 7 +- webapp/src/language/es.json | 7 +- webapp/src/routes/Home/BlockProducerInfo.js | 71 +++++++- webapp/src/routes/Home/TransactionInfo.js | 75 +++++--- .../src/routes/StressTestDashboard/index.js | 32 ++++ webapp/src/routes/index.js | 9 + 8 files changed, 258 insertions(+), 110 deletions(-) create mode 100644 webapp/src/routes/StressTestDashboard/index.js diff --git a/webapp/src/context/state.context.js b/webapp/src/context/state.context.js index 93e3bff3..53da0425 100644 --- a/webapp/src/context/state.context.js +++ b/webapp/src/context/state.context.js @@ -42,6 +42,48 @@ const sharedStateReducer = (state, action) => { } } + case 'pushTPB': { + if (state.tpb[0].blocks[0] === action.payload.blocks[0]) return { ...state } + + const tpb = JSON.parse(JSON.stringify(state.tpb)) + + if (tpb.length >= 60) { + tpb.pop() + } + + return { + ...state, + tpsWaitingBlock: !state.tpsWaitingBlock ? action.payload : null, + tpb: [action.payload, ...tpb], + } + } + + case 'pushTPS': { + const previousBlock = state.tpb[1] + + if (!state.tpsWaitingBlock || !previousBlock) return { ...state } + + let tps = JSON.parse(JSON.stringify(state.tps)) + + if (state.tps.length >= 30) { + tps.pop() + } + + return { + ...state, + tps: [ + { + blocks: [previousBlock.blocks[0], action.payload.blocks[0]], + transactions: + previousBlock.transactions + action.payload.transactions, + cpu: action.payload.cpu + previousBlock.cpu, + net: action.payload.net + previousBlock.net, + }, + ...tps, + ], + } + } + case 'updateTransactionsStats': { return { ...state, @@ -107,7 +149,7 @@ export const SharedStateProvider = ({ ...props }) => { export const useSharedState = () => { const context = React.useContext(SharedStateContext) - const [lastBlock, setLastBlock] = useState() + const [intervalNumber, setIntervalNumber] = useState(null) if (!context) { throw new Error(`useSharedState must be used within a SharedStateContext`) @@ -139,75 +181,45 @@ export const useSharedState = () => { }) } - const getBlock = useCallback( async (block) => { - try { - const data = await eosApi.getBlock(block) - let tpb = state.tpb - - if (state.tpb.length >= 60) { - tpb.pop() - } + const getUsage = block => { + return block?.transactions?.reduce( + (total, current) => { + total.cpu += current.cpu_usage_us + total.net += current.net_usage_words + return total + }, + { net: 0, cpu: 0 }, + ) + } - tpb = [ - { - blocks: [block], - transactions: data.transactions.length, - }, - ...tpb, - ] + const getBlock = useCallback( + async (block) => { + try { + const data = await eosApi.getBlock(block) - if (!state.tpsWaitingBlock) { dispatch({ - type: 'updateTransactionsStats', + type: 'pushTPB', payload: { - tpb, - tpsWaitingBlock: { - block, - transactions: data.transactions.length, - }, + blocks: [block], + transactions: data.transactions.length, + ...getUsage(data), }, }) - return - } - - let tps = state.tps - - if (state.tps.length >= 30) { - tps.pop() + dispatch({ + type: 'pushTPS', + payload: { + blocks: [block], + transactions: data.transactions.length, + ...getUsage(data), + }, + }) + } catch (error) { + console.error(error?.message || error) } - - tps = [ - { - blocks: [state.tpsWaitingBlock.block, block], - transactions: - state.tpsWaitingBlock.transactions + data.transactions.length, - }, - ...tps, - ] - - dispatch({ - type: 'updateTransactionsStats', - payload: { - tps, - tpb, - tpsWaitingBlock: null, - }, - }) - } catch (error) { - console.error(error?.message || error) - } - }, [dispatch, state.tpb, state.tps, state.tpsWaitingBlock]) - - useEffect(() => { - if (!lastBlock) return - - const updateTransactions = async () => { - await getBlock(lastBlock) - } - - updateTransactions() - }, [lastBlock, getBlock]) + }, + [dispatch], + ) const startTrackingProducerSchedule = async ({ interval = 120 } = {}) => { if (scheduleInterval) return @@ -221,7 +233,7 @@ export const useSharedState = () => { console.error(error?.message || error) if (error?.message === ENDPOINTS_ERROR) { - await stopTrackingProducerSchedule() + stopTrackingProducerSchedule() setTimeout(() => { startTrackingProducerSchedule({ interval }) }, waitTrackingInterval) @@ -248,37 +260,28 @@ export const useSharedState = () => { payload: { ...info }, }) - setLastBlock(info.head_block_num) + await getBlock(info.head_block_num) } catch (error) { console.error(error?.message || error) if (error?.message === ENDPOINTS_ERROR) { - await stopTrackingInfo() - setTimeout(() => { - startTrackingInfo({ interval }) - }, waitTrackingInterval) + clearInterval(infoInterval) } } } - if (interval === 0) { - await handle() - return - } - - await handle() - if (infoInterval) return infoInterval = setInterval(handle, interval * 1000) + setIntervalNumber(infoInterval) } - const stopTrackingInfo = async () => { - if (!infoInterval) return + const stopTrackingInfo = useCallback(() => { + if (!intervalNumber) return - clearInterval(infoInterval) - infoInterval = null - } + clearInterval(intervalNumber) + setIntervalNumber(null) + }, [intervalNumber]) const stopTrackingProducerSchedule = () => { if (!scheduleInterval) return diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 1c204798..6a51adb6 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -212,7 +212,7 @@ export const EOSRATE_STATS_QUERY = gql` ` export const FASTEST_ENDPOINTS_QUERY = gql`query($today: date){ - endpoints: check_history_by_endpoint(limit: 5, order_by: [{availability: desc, avg_time: asc}], where: {date: {_eq: $today}}) { + endpoints: check_history_by_endpoint(limit: 5, order_by: [{availability: desc, avg_time: asc}], where: {date: {_eq: $today}}, distinct_on: [value]) { value avg_time availability @@ -220,7 +220,7 @@ export const FASTEST_ENDPOINTS_QUERY = gql`query($today: date){ }` export const HISTORY_ENDPOINTS_BY_PRODUCER_QUERY = gql`query($id: Int){ - endpoints: check_history_by_endpoint(order_by: [{value: asc},{date: asc}], where: {producer_id: {_eq: $id}}, distinct_on: [value]) { + endpoints: check_history_by_endpoint(order_by: [{value: asc},{date: asc}], where: {producer_id: {_eq: $id}}) { value date avg_time diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 8ab6f267..d93e393f 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -89,6 +89,9 @@ "/missed-blocks>sidebar": "Missed Blocks", "/missed-blocks>title": "Missed Blocks - EOSIO + Antelope Network Dashboard", "/missed-blocks>heading": "Missed Blocks", + "/stress-test>title": "Stress Test Dashboard", + "/stress-test>sidebar": "Stress Test", + "/stress-test>heading": "Stress Test", "/endpoints>sidebar": "API Endpoints", "/endpoints>title": "API Endpoints - EOSIO + Antelope Network Dashboard", "/endpoints>heading": "API Endpoints", @@ -150,7 +153,9 @@ "netLimitPerBlock": "Net Limit Per Block", "chainCpuLimit": "Chain CPU Limit", "chainNetLimit": "Chain Net Limit", - "timeToFinality": "Time to Finality" + "timeToFinality": "Time to Finality", + "cpuUsage": "CPU Usage", + "netUsage": "NET Usage" }, "blockProducersRoute": {}, "rewardsDistributionRoute": { diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index fff5d015..1766b62f 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -82,6 +82,9 @@ "/nodes-distribution>sidebar": "Geo Distribución", "/nodes-distribution>title": "Distribución Geográfica de Nodos", "/nodes-distribution>heading": "Distribución Geográfica", + "/stress-test>title": "Panel de Prueba de Estrés", + "/stress-test>sidebar": "Prueba de Estrés", + "/stress-test>heading": "Prueba de Estrés", "/accounts>sidebar": "Cuentas y Contratos", "/accounts>title": "Cuentas y Contratos - Panel", "/accounts>heading": "Cuentas y Contratos", @@ -156,7 +159,9 @@ "netLimitPerBlock": "Límite Red por bloque", "chainCpuLimit": "Límite de CPU en cadena", "chainNetLimit": "Límite de Net en cadena", - "timeToFinality": "Tiempo para finalidad" + "timeToFinality": "Tiempo para finalidad", + "cpuUsage": "Uso del CPU", + "netUsage": "Uso de NET" }, "blockProducersRoute": {}, "rewardsDistributionRoute": { diff --git a/webapp/src/routes/Home/BlockProducerInfo.js b/webapp/src/routes/Home/BlockProducerInfo.js index 820ab712..5b1cb33e 100644 --- a/webapp/src/routes/Home/BlockProducerInfo.js +++ b/webapp/src/routes/Home/BlockProducerInfo.js @@ -3,10 +3,11 @@ import React, { lazy, useEffect, useState } from 'react' import { useQuery } from '@apollo/client' import PropTypes from 'prop-types' +import eosApi from '../../utils/eosapi' import { formatWithThousandSeparator } from '../../utils' import { PRODUCERS_QUERY, PRODUCERS_SUMMARY_QUERY } from '../../gql' import { useSharedState } from '../../context/state.context' -import { eosConfig } from '../../config' +import { eosConfig, generalConfig } from '../../config' const Card = lazy(() => import('@mui/material/Card')) const CardContent = lazy(() => import('@mui/material/CardContent')) @@ -25,9 +26,31 @@ const BlockProducerInfo = ({ t, classes }) => { const { data: producersSummary, loading: producersLoading } = useQuery( PRODUCERS_SUMMARY_QUERY, ) - const [{ schedule: scheduleInfo, info }] = useSharedState() + const [{ schedule: scheduleInfo, info, tps }] = useSharedState() const [total, setTotal] = useState(0) const [schedule, setSchedule] = useState({ producers: [] }) + const [globalConfig, setGlobalConfig] = useState() + + useEffect(() => { + const getTable = async () => { + try { + const { rows } = await eosApi.getTableRows({ + code: 'eosio', + scope: 'eosio', + table: 'global', + json: true, + lower_bound: null, + }) + + setGlobalConfig({ + maxBlockCPU: rows[0]?.max_block_cpu_usage, + maxBlockNET: rows[0]?.max_block_net_usage, + }) + } catch (error) {} + } + + getTable() + }, []) useEffect(() => { const newProducers = scheduleInfo.producers.map((item) => { @@ -140,7 +163,11 @@ const BlockProducerInfo = ({ t, classes }) => {
- +
{loading && } @@ -161,6 +188,44 @@ const BlockProducerInfo = ({ t, classes }) => { } /> + {globalConfig && ( +
+ + + {t('cpuUsage')} + + {`${formatWithThousandSeparator( + (tps[0]?.cpu / globalConfig.maxBlockCPU) * 100 || 0, + 2, + )} %`} + + + +
+ )} + {globalConfig && ( +
+ + + {t('netUsage')} + + {`${formatWithThousandSeparator( + (tps[0]?.net / globalConfig.maxBlockNET) * 100 || 0, + 2, + )} %`} + + + +
+ )}
diff --git a/webapp/src/routes/Home/TransactionInfo.js b/webapp/src/routes/Home/TransactionInfo.js index 1df32228..8a390239 100644 --- a/webapp/src/routes/Home/TransactionInfo.js +++ b/webapp/src/routes/Home/TransactionInfo.js @@ -1,6 +1,7 @@ /* eslint camelcase: 0 */ import React, { useEffect, useState } from 'react' import { useLazyQuery } from '@apollo/client' +import { makeStyles } from '@mui/styles' import { useTheme } from '@mui/material/styles' import clsx from 'clsx' import PropTypes from 'prop-types' @@ -14,17 +15,25 @@ import Typography from '@mui/material/Typography' import PlayArrowIcon from '@mui/icons-material/PlayArrow' import LinearProgress from '@mui/material/LinearProgress' -import { TRANSACTION_HISTORY_QUERY } from '../../gql' +import { TRANSACTION_QUERY } from '../../gql' import { rangeOptions } from '../../utils' import TransactionsLineChart from '../../components/TransactionsLineChart' import { useSharedState } from '../../context/state.context' -import { generalConfig } from '../../config' import EqualIcon from './EqualIcon' +import styles from './styles' + +const useStyles = makeStyles(styles) const options = ['Live (30s)', ...rangeOptions] -const TransactionInfo = ({ t, classes }) => { +const TransactionInfo = ({ + t, + startTrackingInfo, + stopTrackingInfo, + historyEnabled, +}) => { + const classes = useStyles() const theme = useTheme() const [{ tps, tpb }] = useSharedState() const [graphicData, setGraphicData] = useState([ @@ -40,7 +49,7 @@ const TransactionInfo = ({ t, classes }) => { const [option, setOption] = useState(options[0]) const [pause, setPause] = useState(false) const [getTransactionHistory, { data, loading }] = useLazyQuery( - TRANSACTION_HISTORY_QUERY, + TRANSACTION_QUERY, { fetchPolicy: 'network-only' }, ) @@ -53,6 +62,8 @@ const TransactionInfo = ({ t, classes }) => { for (let index = 0; index < tpb.length; index++) { trxPerBlock.push({ name: `Block: ${tpb[index].blocks.join()}`, + cpu: tpb[index].cpu, + net: tpb[index].net, y: tpb[index].transactions, x: index > 0 ? index / 2 : index, }) @@ -61,6 +72,8 @@ const TransactionInfo = ({ t, classes }) => { for (let index = 0; index < tps.length; index++) { trxPerSecond.push({ name: `Blocks: ${tps[index].blocks.join(', ')}`, + cpu: tpb[index].cpu, + net: tps[index].net, y: tps[index].transactions, x: index, }) @@ -82,34 +95,37 @@ const TransactionInfo = ({ t, classes }) => { }, [option, tps, tpb]) useEffect(() => { - if (option === options[0]) return + if (option === options[0]) { + setPause(false) + startTrackingInfo() + return + } + + stopTrackingInfo() setGraphicData([]) getTransactionHistory({ - variables: {}, + variables: { + range: option, + }, }) + // eslint-disable-next-line }, [option, getTransactionHistory]) useEffect(() => { - const trxHistory = data?.trxHistory?.length - ? data.trxHistory[0].transaction_history - : null - if (option === option[0]) return - if (!trxHistory) { + if (!data?.transactions.length) { setGraphicData([]) return } - const intervalGraphicData = (trxHistory[option] || []).map( - (transactionHistory) => { - return [ - new Date(transactionHistory.datetime).getTime(), - transactionHistory.transactions_count || 0, - ] - }, - ) + const intervalGraphicData = data.transactions.map((transactionHistory) => { + return [ + new Date(transactionHistory.datetime).getTime(), + transactionHistory.transactions_count || 0, + ] + }) setGraphicData([ { @@ -137,7 +153,7 @@ const TransactionInfo = ({ t, classes }) => {
- {generalConfig.historyEnabled && ( + {historyEnabled && ( <> {t('timeFrame')}