diff --git a/packages/page-explorer/src/Api/index.tsx b/packages/page-explorer/src/Api/index.tsx index bf19380852..ceb871da8f 100644 --- a/packages/page-explorer/src/Api/index.tsx +++ b/packages/page-explorer/src/Api/index.tsx @@ -21,10 +21,15 @@ interface ChartContents { } interface ChartInfo { - requests: ChartContents; + bytesChart: ChartContents; + errorsChart: ChartContents; + requestsChart: ChartContents; } -const COLORS_REQUESTS = ['#008c8c', '#00448c', '#8c0044', '#acacac']; +// const COLORS_ERRORS = ['#8c0044', '#acacac']; + +const COLORS_BYTES = ['#00448c', '#008c44', '#acacac']; +const COLORS_REQUESTS = ['#008c8c', '#00448c', '#8c4400', '#acacac']; const OPTIONS = { animation: { duration: 0 @@ -34,44 +39,74 @@ const OPTIONS = { }; function getPoints (all: Stats[]): ChartInfo { - const requests: ChartContents = { + const bytesChart: ChartContents = { + labels: [], + values: [[], [], []] + }; + const errorsChart: ChartContents = { + labels: [], + values: [[]] + }; + const requestsChart: ChartContents = { labels: [], values: [[], [], [], []] }; const reqBase = all.reduce((a, { stats: { active: { requests, subscriptions } } }) => a + requests + subscriptions, 0); + let { bytesRecv: prevRecv, bytesSent: prevSent, errors: prevErrors } = all[0].stats.total; + let recvTotal = 0; - for (let i = 0; i < all.length; i++) { - const { stats: { active: { requests: aReq, subscriptions: aSub } }, when } = all[i]; + for (let i = 1; i < all.length; i++) { + const { stats: { active: { requests: aReq, subscriptions: aSub }, total: { bytesRecv, bytesSent, errors } }, when } = all[i]; const time = new Date(when).toLocaleTimeString(); - requests.labels.push(time); - requests.values[0].push(aReq + aSub); - requests.values[1].push(aReq); - requests.values[2].push(aSub); - requests.values[3].push(reqBase / all.length); + bytesChart.labels.push(time); + bytesChart.values[0].push(bytesSent - prevSent); + bytesChart.values[1].push(bytesRecv - prevRecv); + + errorsChart.labels.push(time); + errorsChart.values[0].push(errors - prevErrors); + + requestsChart.labels.push(time); + requestsChart.values[0].push(aReq + aSub); + requestsChart.values[1].push(aReq); + requestsChart.values[2].push(aSub); + requestsChart.values[3].push(reqBase / all.length); + + recvTotal += bytesRecv - prevRecv; + prevErrors = errors; + prevRecv = bytesRecv; + prevSent = bytesSent; } - return { requests }; + const recvAvg = recvTotal / (all.length - 1); + + for (let i = 1; i < all.length; i++) { + bytesChart.values[2].push(recvAvg); + } + + return { bytesChart, errorsChart, requestsChart }; } function Api ({ className }: Props): React.ReactElement { const { t } = useTranslation(); const stats = useContext(ApiStatsContext); - const { requestsLegend } = useMemo( + const { bytesLegend, requestsLegend } = useMemo( () => ({ + bytesLegend: [t('sent'), t('recv'), t('average')], + errorsLegend: [t('errors')], requestsLegend: [t('total'), t('requests'), t('subscriptions'), t('average')] }), [t] ); - const { requests } = useMemo( + const { bytesChart, requestsChart } = useMemo( () => getPoints(stats), [stats] ); - if (stats.length <= 2) { + if (stats.length <= 3) { return ; } @@ -93,10 +128,20 @@ function Api ({ className }: Props): React.ReactElement {

{t('requests & subscriptions')}

+ +
+

{t('bytes')}

+
diff --git a/packages/page-explorer/src/index.tsx b/packages/page-explorer/src/index.tsx index dc0fbc0599..9a661cc507 100644 --- a/packages/page-explorer/src/index.tsx +++ b/packages/page-explorer/src/index.tsx @@ -65,7 +65,6 @@ function createItemsRef (t: TFunction): TabItem[] { text: t('Node info') }, { - isHidden: true, name: 'api', text: t('API stats') } diff --git a/packages/react-api/src/Api.tsx b/packages/react-api/src/Api.tsx index 34a275ce47..1e862330c4 100644 --- a/packages/react-api/src/Api.tsx +++ b/packages/react-api/src/Api.tsx @@ -61,9 +61,8 @@ export const DEFAULT_AUX = ['Aux1', 'Aux2', 'Aux3', 'Aux4', 'Aux5', 'Aux6', 'Aux const DISALLOW_EXTENSIONS: string[] = []; let api: ApiPromise; -let apiSystem: ApiPromise; -export { api, apiSystem }; +export { api }; function isKeyringLoaded () { try { @@ -255,7 +254,7 @@ async function createApi (apiUrl: string, signer: ApiSigner, onError: (error: un const isLight = apiUrl.startsWith('light://'); try { - const providers = [0, 1].map((): ProviderInterface => + const providers = [0].map((): ProviderInterface => isLight ? new ScProvider(getWellKnownChain(apiUrl.replace('light://substrate-connect/', ''))) : new WsProvider(apiUrl) @@ -269,7 +268,6 @@ async function createApi (apiUrl: string, signer: ApiSigner, onError: (error: un typesBundle, typesChain }); - apiSystem = new ApiPromise({ provider: providers[1] }); // See https://github.com/polkadot-js/api/pull/4672#issuecomment-1078843960 if (isLight) { @@ -300,7 +298,7 @@ function Api ({ apiUrl, children, isElectron, store }: Props): React.ReactElemen ); const apiRelay = useApiUrl(relayUrls); const value = useMemo( - () => objectSpread({}, state, { api, apiEndpoint, apiError, apiRelay, apiSystem, apiUrl, createLink: createLink(apiUrl, isElectron), extensions, getStats, isApiConnected, isApiInitialized, isElectron, isWaitingInjected: !extensions }), + () => objectSpread({}, state, { api, apiEndpoint, apiError, apiRelay, apiUrl, createLink: createLink(apiUrl, isElectron), extensions, getStats, isApiConnected, isApiInitialized, isElectron, isWaitingInjected: !extensions }), [apiError, extensions, isApiConnected, isApiInitialized, isElectron, state, apiEndpoint, apiRelay, apiUrl] ); diff --git a/packages/react-api/src/types.ts b/packages/react-api/src/types.ts index 97462c78f9..fe8d561886 100644 --- a/packages/react-api/src/types.ts +++ b/packages/react-api/src/types.ts @@ -35,7 +35,6 @@ export interface ApiProps extends ApiState { apiEndpoint: LinkOption | null; apiError: string | null; apiRelay: ApiPromise | null; - apiSystem: ApiPromise; apiUrl?: string; createLink: (path: string, apiUrl?: string) => string; extensions?: InjectedExtension[]; diff --git a/packages/react-components/src/ApiStats/types.ts b/packages/react-components/src/ApiStats/types.ts index 4047d5be6b..528c3ea823 100644 --- a/packages/react-components/src/ApiStats/types.ts +++ b/packages/react-components/src/ApiStats/types.ts @@ -3,14 +3,7 @@ import type { ProviderStats } from '@polkadot/rpc-provider/types'; -export interface StatsExtended extends ProviderStats { - max: { - requests: number; - subscriptions: number; - }; -} - export interface Stats { - stats: StatsExtended; + stats: ProviderStats; when: number; } diff --git a/packages/react-components/src/ApiStats/useApiStats.ts b/packages/react-components/src/ApiStats/useApiStats.ts index 1814540a8d..64d21a2119 100644 --- a/packages/react-components/src/ApiStats/useApiStats.ts +++ b/packages/react-components/src/ApiStats/useApiStats.ts @@ -7,11 +7,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { createNamedHook, useApi } from '@polkadot/react-hooks'; -const MAX_NUM = 100; +const MAX_NUM = 60; // 5 minutes const INTERVAL = 5_000; function useApiStatsImpl (): Stats[] { - const { api, apiSystem, getStats } = useApi(); + const { api, getStats } = useApi(); const [stats, setStats] = useState([]); const timerId = useRef(null); @@ -19,25 +19,18 @@ function useApiStatsImpl (): Stats[] { (): void => { timerId.current = null; - const [stats, when] = getStats(api, apiSystem); + const [stats, when] = getStats(api); setStats((prev): Stats[] => { - if (prev.length === 0) { - return [{ stats: { ...stats, max: { requests: stats.active.requests, subscriptions: stats.active.subscriptions } }, when }]; - } - - const last = prev[prev.length - 1].stats; const curr = { - stats: { - ...stats, - max: { - requests: Math.max(stats.active.requests, last.max.requests), - subscriptions: Math.max(stats.active.subscriptions, last.max.subscriptions) - } - }, + stats, when }; + if (prev.length === 0) { + return [curr]; + } + return prev.length === MAX_NUM ? prev.concat(curr).slice(-MAX_NUM) : prev.concat(curr); @@ -45,7 +38,7 @@ function useApiStatsImpl (): Stats[] { timerId.current = setTimeout(() => fireTimer(), INTERVAL); }, - [api, apiSystem, getStats, timerId] + [api, getStats, timerId] ); useEffect((): () => void => { diff --git a/packages/react-hooks/src/useSystemApi.ts b/packages/react-hooks/src/useSystemApi.ts index 6de86a06c6..4f181cc1a5 100644 --- a/packages/react-hooks/src/useSystemApi.ts +++ b/packages/react-hooks/src/useSystemApi.ts @@ -3,24 +3,15 @@ import type { ApiPromise } from '@polkadot/api'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { createNamedHook } from './createNamedHook'; import { useApi } from './useApi'; function useSystemApiImpl (): ApiPromise | undefined { - const { apiRelay, apiSystem } = useApi(); - const [apiDefault, setApiDefault] = useState(); + const { api, apiRelay } = useApi(); - // This system API does not use the global ready, so we - // explicitly wait for it here - useEffect((): void => { - apiSystem && apiSystem.isReady - .then(setApiDefault) - .catch(console.error); - }, [apiSystem]); - - return useMemo(() => apiRelay || apiDefault, [apiRelay, apiDefault]); + return useMemo(() => apiRelay || api, [apiRelay, api]); } export const useSystemApi = createNamedHook('useSystemApi', useSystemApiImpl);