Expose API stats tab (#7678)
This commit is contained in:
parent
49f2c4c3bb
commit
471e62fc3f
@ -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<Props> {
|
||||
const { t } = useTranslation();
|
||||
const stats = useContext(ApiStatsContext);
|
||||
|
||||
const { requestsLegend } = useMemo(
|
||||
const { bytesLegend, requestsLegend } = useMemo(
|
||||
() => ({
|
||||
bytesLegend: [t<string>('sent'), t<string>('recv'), t<string>('average')],
|
||||
errorsLegend: [t<string>('errors')],
|
||||
requestsLegend: [t<string>('total'), t<string>('requests'), t<string>('subscriptions'), t<string>('average')]
|
||||
}), [t]
|
||||
);
|
||||
|
||||
const { requests } = useMemo(
|
||||
const { bytesChart, requestsChart } = useMemo(
|
||||
() => getPoints(stats),
|
||||
[stats]
|
||||
);
|
||||
|
||||
if (stats.length <= 2) {
|
||||
if (stats.length <= 3) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
@ -93,10 +128,20 @@ function Api ({ className }: Props): React.ReactElement<Props> {
|
||||
<h1>{t<string>('requests & subscriptions')}</h1>
|
||||
<Chart.Line
|
||||
colors={COLORS_REQUESTS}
|
||||
labels={requests.labels}
|
||||
labels={requestsChart.labels}
|
||||
legends={requestsLegend}
|
||||
options={OPTIONS}
|
||||
values={requests.values}
|
||||
values={requestsChart.values}
|
||||
/>
|
||||
</div>
|
||||
<div className='container'>
|
||||
<h1>{t<string>('bytes')}</h1>
|
||||
<Chart.Line
|
||||
colors={COLORS_BYTES}
|
||||
labels={bytesChart.labels}
|
||||
legends={bytesLegend}
|
||||
options={OPTIONS}
|
||||
values={bytesChart.values}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -65,7 +65,6 @@ function createItemsRef (t: TFunction): TabItem[] {
|
||||
text: t<string>('Node info')
|
||||
},
|
||||
{
|
||||
isHidden: true,
|
||||
name: 'api',
|
||||
text: t<string>('API stats')
|
||||
}
|
||||
|
||||
@ -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<ApiProps>(
|
||||
() => 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]
|
||||
);
|
||||
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<Stats[]>([]);
|
||||
const timerId = useRef<NodeJS.Timeout | null>(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 => {
|
||||
|
||||
@ -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<ApiPromise | undefined>();
|
||||
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user