fix(monitoring): отображение всех метрик узла на графиках
Refs EventHub/EventHubFrontAdmin#24 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Card, Col, Row, Select, Tabs } from 'antd';
|
import { Card, Col, Row, Select, Table, Tabs } from 'antd';
|
||||||
import {
|
import {
|
||||||
LineChart,
|
LineChart,
|
||||||
Line,
|
Line,
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMetricsStore, NodeMetric } from '../../store/metricsStore';
|
import { useMetricsStore, NodeMetric } from '../../store/metricsStore';
|
||||||
|
import { monitoringApi } from '../../api/monitoringApi';
|
||||||
|
|
||||||
const NODE_COLORS = ['#1677ff', '#52c41a', '#fa8c16', '#eb2f96', '#722ed1', '#13c2c2'];
|
const NODE_COLORS = ['#1677ff', '#52c41a', '#fa8c16', '#eb2f96', '#722ed1', '#13c2c2'];
|
||||||
const TIME_RANGES = [
|
const TIME_RANGES = [
|
||||||
@@ -21,23 +22,35 @@ const TIME_RANGES = [
|
|||||||
{ label: '1 час', value: 60 },
|
{ label: '1 час', value: 60 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const bytesToMb = (value?: number) => (value ?? 0) / 1048576;
|
||||||
|
|
||||||
const formatTime = (ts: string) => dayjs(ts).format('HH:mm:ss');
|
const formatTime = (ts: string) => dayjs(ts).format('HH:mm:ss');
|
||||||
|
|
||||||
const buildChartData = (metrics: NodeMetric[]) =>
|
const buildChartData = (metrics: NodeMetric[]) =>
|
||||||
metrics.map((m) => ({
|
metrics.map((m) => ({
|
||||||
time: formatTime(m.timestamp),
|
time: formatTime(m.timestamp),
|
||||||
cpu: m.cpu_utilization ?? 0,
|
cpu: m.cpu_utilization ?? 0,
|
||||||
memoryMb: (m.memory_total ?? 0) / 1048576,
|
memoryMb: bytesToMb(m.memory_total),
|
||||||
|
memoryProcessesMb: bytesToMb(m.memory_processes),
|
||||||
|
memoryAtomMb: bytesToMb(m.memory_atom),
|
||||||
|
memoryBinaryMb: bytesToMb(m.memory_binary),
|
||||||
|
memoryEtsMb: bytesToMb(m.memory_ets),
|
||||||
|
memoryAvailableMb: bytesToMb(m.memory_available),
|
||||||
ws: m.ws_connections ?? 0,
|
ws: m.ws_connections ?? 0,
|
||||||
runQueue: m.run_queue ?? 0,
|
runQueue: m.run_queue ?? 0,
|
||||||
|
processCount: m.process_count ?? 0,
|
||||||
|
activeSessions: m.active_sessions ?? 0,
|
||||||
|
ioIn: m.io_in ?? 0,
|
||||||
|
ioOut: m.io_out ?? 0,
|
||||||
mnesiaCommits: m.mnesia_commits ?? 0,
|
mnesiaCommits: m.mnesia_commits ?? 0,
|
||||||
mnesiaFailures: m.mnesia_failures ?? 0,
|
mnesiaFailures: m.mnesia_failures ?? 0,
|
||||||
|
tableSizes: m.table_sizes ?? {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const MetricChart: React.FC<{ data: ReturnType<typeof buildChartData>; lines: Array<{ key: string; color: string; name: string }> }> = ({
|
const MetricChart: React.FC<{
|
||||||
data,
|
data: ReturnType<typeof buildChartData>;
|
||||||
lines,
|
lines: Array<{ key: string; color: string; name: string }>;
|
||||||
}) => (
|
}> = ({ data, lines }) => (
|
||||||
<ResponsiveContainer width="100%" height={280}>
|
<ResponsiveContainer width="100%" height={280}>
|
||||||
<LineChart data={data}>
|
<LineChart data={data}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
@@ -55,9 +68,38 @@ const MetricChart: React.FC<{ data: ReturnType<typeof buildChartData>; lines: Ar
|
|||||||
const MonitoringPage: React.FC = () => {
|
const MonitoringPage: React.FC = () => {
|
||||||
const allHistory = useMetricsStore((s) => s.allHistory);
|
const allHistory = useMetricsStore((s) => s.allHistory);
|
||||||
const current = useMetricsStore((s) => s.current);
|
const current = useMetricsStore((s) => s.current);
|
||||||
|
const setAllHistory = useMetricsStore((s) => s.setAllHistory);
|
||||||
const filterVisibleHistory = useMetricsStore((s) => s.filterVisibleHistory);
|
const filterVisibleHistory = useMetricsStore((s) => s.filterVisibleHistory);
|
||||||
const visibleHistory = useMetricsStore((s) => s.visibleHistory);
|
const visibleHistory = useMetricsStore((s) => s.visibleHistory);
|
||||||
const [minutes, setMinutes] = useState(15);
|
const [minutes, setMinutes] = useState(15);
|
||||||
|
const [loadingHistory, setLoadingHistory] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const loadHistory = async () => {
|
||||||
|
setLoadingHistory(true);
|
||||||
|
try {
|
||||||
|
const to = dayjs().toISOString();
|
||||||
|
const from = dayjs().subtract(minutes, 'minute').toISOString();
|
||||||
|
const metrics = await monitoringApi.getNodeMetrics(from, to);
|
||||||
|
if (!cancelled && metrics.length > 0) {
|
||||||
|
setAllHistory(metrics);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// live WS дополняет историю; REST недоступен — не блокируем страницу
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) {
|
||||||
|
setLoadingHistory(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadHistory();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [minutes, setAllHistory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
filterVisibleHistory(minutes);
|
filterVisibleHistory(minutes);
|
||||||
@@ -83,6 +125,7 @@ const MonitoringPage: React.FC = () => {
|
|||||||
const tabItems = allNodes.map((node, idx) => {
|
const tabItems = allNodes.map((node, idx) => {
|
||||||
const nodeMetrics = metricsByNode.get(node) || [];
|
const nodeMetrics = metricsByNode.get(node) || [];
|
||||||
const chartData = buildChartData(nodeMetrics);
|
const chartData = buildChartData(nodeMetrics);
|
||||||
|
const latestTableSizes = chartData.at(-1)?.tableSizes ?? {};
|
||||||
const color = NODE_COLORS[idx % NODE_COLORS.length];
|
const color = NODE_COLORS[idx % NODE_COLORS.length];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -96,7 +139,21 @@ const MonitoringPage: React.FC = () => {
|
|||||||
data={chartData}
|
data={chartData}
|
||||||
lines={[
|
lines={[
|
||||||
{ key: 'cpu', color, name: 'CPU %' },
|
{ key: 'cpu', color, name: 'CPU %' },
|
||||||
{ key: 'memoryMb', color: '#1890ff', name: 'Память МБ' },
|
{ key: 'memoryMb', color: '#1890ff', name: 'Память всего' },
|
||||||
|
{ key: 'memoryAvailableMb', color: '#13c2c2', name: 'Память доступно' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title="Память по типам (МБ)">
|
||||||
|
<MetricChart
|
||||||
|
data={chartData}
|
||||||
|
lines={[
|
||||||
|
{ key: 'memoryProcessesMb', color: '#1677ff', name: 'Processes' },
|
||||||
|
{ key: 'memoryAtomMb', color: '#52c41a', name: 'Atom' },
|
||||||
|
{ key: 'memoryBinaryMb', color: '#fa8c16', name: 'Binary' },
|
||||||
|
{ key: 'memoryEtsMb', color: '#eb2f96', name: 'ETS' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -108,6 +165,8 @@ const MonitoringPage: React.FC = () => {
|
|||||||
lines={[
|
lines={[
|
||||||
{ key: 'ws', color: '#52c41a', name: 'WS' },
|
{ key: 'ws', color: '#52c41a', name: 'WS' },
|
||||||
{ key: 'runQueue', color: '#fa8c16', name: 'Run queue' },
|
{ key: 'runQueue', color: '#fa8c16', name: 'Run queue' },
|
||||||
|
{ key: 'processCount', color: '#722ed1', name: 'Processes' },
|
||||||
|
{ key: 'activeSessions', color: '#1677ff', name: 'Sessions' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -123,6 +182,34 @@ const MonitoringPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title="I/O">
|
||||||
|
<MetricChart
|
||||||
|
data={chartData}
|
||||||
|
lines={[
|
||||||
|
{ key: 'ioIn', color: '#1677ff', name: 'IO in' },
|
||||||
|
{ key: 'ioOut', color: '#52c41a', name: 'IO out' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title="Размеры таблиц Mnesia (последняя точка)">
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
rowKey="table"
|
||||||
|
dataSource={Object.entries(latestTableSizes).map(([table, size]) => ({
|
||||||
|
table,
|
||||||
|
size,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{ title: 'Таблица', dataIndex: 'table', key: 'table' },
|
||||||
|
{ title: 'Записей', dataIndex: 'size', key: 'size' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -141,7 +228,9 @@ const MonitoringPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{allNodes.length === 0 ? (
|
{allNodes.length === 0 ? (
|
||||||
<Card>Ожидание метрик по WebSocket…</Card>
|
<Card loading={loadingHistory}>
|
||||||
|
{loadingHistory ? 'Загрузка метрик…' : 'Ожидание метрик по WebSocket…'}
|
||||||
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<Tabs items={tabItems} />
|
<Tabs items={tabItems} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user