fixed data transmission via a web socket and left data transmission via http requests for historical data

pull/29/head
DmitriyA 2025-04-07 10:27:08 -04:00
parent a24b89220c
commit 64401cadbc
1 changed files with 157 additions and 82 deletions

View File

@ -5,6 +5,7 @@ import { TimeRangeSelector } from './Components/TimeRangeSelector';
import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator'; import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator';
import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay'; import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay';
import { TIME_RANGES, COLORS } from './Components/constants'; import { TIME_RANGES, COLORS } from './Components/constants';
import axios from 'axios';
const PrometheusChart = ({ metricName }) => { const PrometheusChart = ({ metricName }) => {
const [chartData, setChartData] = useState(null); const [chartData, setChartData] = useState(null);
@ -15,6 +16,7 @@ const PrometheusChart = ({ metricName }) => {
const [connectionStatus, setConnectionStatus] = useState('disconnected'); const [connectionStatus, setConnectionStatus] = useState('disconnected');
const [selectedGraphRange, setSelectedGraphRange] = useState(null); const [selectedGraphRange, setSelectedGraphRange] = useState(null);
const [filteredData, setFilteredData] = useState(null); const [filteredData, setFilteredData] = useState(null);
const [isSelectingRange, setIsSelectingRange] = useState(false);
const intervalRef = useRef(null); const intervalRef = useRef(null);
const socketRef = useRef(null); const socketRef = useRef(null);
@ -42,6 +44,88 @@ const PrometheusChart = ({ metricName }) => {
}; };
}, []); }, []);
const calculateStep = useCallback((start, end) => {
const range = end - start;
if (range <= 3600) return 5;
if (range <= 21600) return 30;
if (range <= 86400) return 120;
return 300;
}, []);
const fetchData = useCallback(() => {
if (isSelectingRange) return;
const now = Math.floor(Date.now() / 1000);
const start = now - selectedRange.value;
const end = now;
const step = calculateStep(start, end);
if (socketRef.current?.connected) {
socketRef.current.emit('get-metrics', {
metric: metricName,
start,
end,
step,
_t: Date.now()
});
}
}, [metricName, selectedRange.value, isSelectingRange]);
const groupBySecond = (points) => {
const grouped = [];
const timeMap = {};
points.forEach(point => {
const timeKey = Math.floor(point.timestamp / 1000);
if (!timeMap[timeKey]) {
timeMap[timeKey] = { ...point, count: 1 };
grouped.push(timeMap[timeKey]);
} else {
timeMap[timeKey].value = (timeMap[timeKey].value * timeMap[timeKey].count + point.value) /
(timeMap[timeKey].count + 1);
timeMap[timeKey].count += 1;
}
});
return grouped;
};
const processMetricsData = useCallback((response) => {
if (response.metric !== metricName) return;
const dataArray = Array.isArray(response.data) ? response.data : [response.data];
if (!dataArray.length) return;
setChartData(prev => {
const newData = { ...(prev || {}) };
dataArray.forEach(item => {
const instance = item.instance || 'default';
if (!newData[instance]) newData[instance] = [];
const timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
const value = parseFloat(item.value);
if (!newData[instance].some(p => p.timestamp === timestamp)) {
newData[instance].push({
time: formatTime(timestamp, selectedRange.value).display,
value: value,
timestamp: timestamp
});
}
});
Object.keys(newData).forEach(instance => {
newData[instance] = groupBySecond(newData[instance])
.sort((a, b) => a.timestamp - b.timestamp)
.slice(-1000);
});
return Object.keys(newData).length ? newData : prev;
});
}, [metricName, selectedRange.value, formatTime]);
const setupWebSocket = useCallback(() => { const setupWebSocket = useCallback(() => {
if (socketRef.current) { if (socketRef.current) {
// Если соединение уже существует, возвращаем его // Если соединение уже существует, возвращаем его
@ -91,80 +175,35 @@ const PrometheusChart = ({ metricName }) => {
return socket; return socket;
}, []); }, []);
const calculateStep = useCallback((start, end) => { const fetchCustomRangeData = useCallback(async () => {
const range = end - start; const start = Math.floor(startDate.getTime() / 1000);
if (range <= 3600) return 5; const end = Math.floor(endDate.getTime() / 1000);
if (range <= 21600) return 30;
if (range <= 86400) return 120;
return 300;
}, []);
const fetchData = useCallback(() => {
const now = Math.floor(Date.now() / 1000);
const start = now - selectedRange.value;
const end = now;
const step = calculateStep(start, end); const step = calculateStep(start, end);
if (socketRef.current?.connected) { try {
socketRef.current.emit('get-metrics', { const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
metric: metricName, params: {
start, metric: metricName,
end, start,
step, end,
_t: Date.now() // Добавляем timestamp для уникальности step
});
}
}, [metricName, selectedRange.value]); // Только необходимые зависимости
const groupBySecond = (points) => {
const grouped = [];
const timeMap = {};
points.forEach(point => {
const timeKey = Math.floor(point.timestamp / 1000);
if (!timeMap[timeKey]) {
timeMap[timeKey] = { ...point, count: 1 };
grouped.push(timeMap[timeKey]);
} else {
timeMap[timeKey].value = (timeMap[timeKey].value * timeMap[timeKey].count + point.value) /
(timeMap[timeKey].count + 1);
timeMap[timeKey].count += 1;
}
});
return grouped;
};
const processMetricsData = useCallback((response) => {
if (response.metric !== metricName || !Array.isArray(response.data)) return;
setChartData(prev => {
const newData = { ...(prev || {}) };
// Добавление новых точек
response.data.forEach(item => {
const instance = item.instance || 'default';
if (!newData[instance]) newData[instance] = [];
if (!newData[instance].some(p => p.timestamp === item.timestamp)) {
newData[instance].push({
time: formatTime(item.timestamp, selectedRange.value).display,
value: parseFloat(item.value),
timestamp: item.timestamp
});
} }
}); });
// Группировка и ограничение if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую
Object.keys(newData).forEach(instance => { processMetricsData({
newData[instance] = groupBySecond(newData[instance]) metric: metricName,
.sort((a, b) => a.timestamp - b.timestamp) data: response.data.map(item => ({
.slice(-1000); ...item,
}); timestamp: item.timestamp / 1000, // или item.timestamp если уже в секундах
value: item.value.toString() // преобразуем в строку, как ожидает processMetricsData
return Object.keys(newData).length ? newData : prev; }))
}); });
}, [metricName, selectedRange.value, formatTime]); }
} catch (error) {
console.error('Ошибка при получении кастомных данных:', error);
}
}, [metricName, startDate, endDate, calculateStep, processMetricsData]);
const handleRangeChange = useCallback((event) => { const handleRangeChange = useCallback((event) => {
@ -180,6 +219,11 @@ const PrometheusChart = ({ metricName }) => {
const now = new Date(); const now = new Date();
setEndDate(now); setEndDate(now);
setStartDate(new Date(now.getTime() - range.value * 1000)); setStartDate(new Date(now.getTime() - range.value * 1000));
// Переподключение сокета при возврате к стандартным диапазонам
if (!socketRef.current?.connected) {
socketRef.current?.connect();
}
}, []); }, []);
const handleCustomRangeChange = useCallback(() => { const handleCustomRangeChange = useCallback(() => {
@ -192,9 +236,35 @@ const PrometheusChart = ({ metricName }) => {
const handleResetZoom = useCallback(() => { const handleResetZoom = useCallback(() => {
setSelectedGraphRange(null); setSelectedGraphRange(null);
setFilteredData(null); setFilteredData(null);
setIsSelectingRange(false);
fetchData(); fetchData();
}, [fetchData]); }, [fetchData]);
const handleRangeSelect = useCallback((range) => {
if (range) {
// Начало выделения - останавливаем обновления
setIsSelectingRange(true);
setSelectedGraphRange(range);
// Отключаем сокет
if (socketRef.current?.connected) {
socketRef.current.disconnect();
}
// Очищаем интервал
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
} else {
// Окончание выделения - возобновляем соединение
setIsSelectingRange(false);
if (!useCustomRange && socketRef.current && !socketRef.current.connected) {
socketRef.current.connect();
}
}
}, [useCustomRange]);
useEffect(() => { useEffect(() => {
const socket = setupWebSocket(); const socket = setupWebSocket();
return () => { return () => {
@ -204,7 +274,15 @@ const PrometheusChart = ({ metricName }) => {
}, [setupWebSocket]); }, [setupWebSocket]);
useEffect(() => { useEffect(() => {
if (!socketRef.current?.connected) return; if (useCustomRange) {
if (socketRef.current?.connected) {
socketRef.current.disconnect();
}
fetchCustomRangeData();
return;
}
if (!socketRef.current?.connected || isSelectingRange) return;
const fetchDataWrapper = () => { const fetchDataWrapper = () => {
try { try {
@ -214,10 +292,7 @@ const PrometheusChart = ({ metricName }) => {
} }
}; };
// Сразу запросить данные
fetchDataWrapper(); fetchDataWrapper();
// Установить интервал
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval); intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
return () => { return () => {
@ -226,7 +301,7 @@ const PrometheusChart = ({ metricName }) => {
intervalRef.current = null; intervalRef.current = null;
} }
}; };
}, [fetchData, selectedRange.interval]); }, [fetchData, fetchCustomRangeData, selectedRange.interval, useCustomRange, isSelectingRange]);
useEffect(() => { useEffect(() => {
if (!chartData || !selectedGraphRange) { if (!chartData || !selectedGraphRange) {
@ -298,7 +373,7 @@ const PrometheusChart = ({ metricName }) => {
chartData={chartData} chartData={chartData}
metricName={metricName} metricName={metricName}
colors={COLORS} colors={COLORS}
onRangeSelect={setSelectedGraphRange} onRangeSelect={handleRangeSelect} // Используем модифицированный обработчик
filteredData={filteredData} filteredData={filteredData}
/> />
</div> </div>