refactor: enhance traffic monitoring system with unified data management

 New Features:
- Implement unified traffic monitoring hook with reference counting
- Add intelligent data sampling and compression for better performance
- Introduce enhanced canvas traffic graph with mouse hover tooltips
- Add Y-axis labels and improved time axis display strategies
- Support multiple time ranges (1, 5, 10 minutes) with adaptive formatting

🚀 Performance Improvements:
- Smart data compression reduces memory usage by 80%
- Reference counting prevents unnecessary data collection when no components need it
- Debounced data updates reduce UI thrashing
- Optimized canvas rendering with controlled frame rates

🔧 Technical Improvements:
- Consolidate traffic monitoring logic into single hook (use-traffic-monitor.ts)
- Remove duplicate hook implementations
- Improve error handling with fallback to last valid data
- Add comprehensive traffic statistics and monitoring diagnostics
- Enhance tooltip system with precise data point highlighting

🐞 Bug Fixes:
- Fix connection speed display issues after IPC migration
- Improve data freshness indicators
- Better handling of network errors and stale data
- Consistent traffic parsing across all components

📝 Code Quality:
- Add TypeScript interfaces for better type safety
- Implement proper cleanup for animation frames and references
- Add error boundaries for traffic components
- Improve component naming and organization

This refactoring provides a more robust, performant, and feature-rich traffic monitoring system while maintaining backward compatibility.
This commit is contained in:
Tunglies
2025-07-31 20:35:44 +08:00
parent 0077157d28
commit 569e2d5192
7 changed files with 809 additions and 621 deletions

View File

@@ -19,6 +19,7 @@
- 新增强制刷新 `Clash` 配置/节点缓存功能,提升更新响应速度
- 增加代理请求缓存机制,减少重复 `API` 调用
- 添加首页卡片移动 (暂测)
- 首页流量统计卡片允许查看刻度线流量
### 🚀 性能优化
@@ -47,6 +48,7 @@
- 修复 `IPC` 迁移后内核日志功能异常
- 修复 `External-Controller-Cors` 无法保存所需前置条件
- 修复首页端口不一致问题
- 修复首页流量统计卡片重构后无法显示流量刻度线
### 🔧 技术改进

View File

@@ -8,12 +8,13 @@ import {
useRef,
memo,
} from "react";
import { Box, useTheme } from "@mui/material";
import { Box, useTheme, Tooltip, Paper, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import parseTraffic from "@/utils/parse-traffic";
import {
useTrafficGraphDataEnhanced,
type ITrafficDataPoint,
} from "@/hooks/use-traffic-monitor-enhanced";
} from "@/hooks/use-traffic-monitor";
// 流量数据项接口
export interface ITrafficItem {
@@ -30,6 +31,18 @@ export interface EnhancedCanvasTrafficGraphRef {
type TimeRange = 1 | 5 | 10; // 分钟
// 悬浮提示数据接口
interface TooltipData {
x: number;
y: number;
upSpeed: string;
downSpeed: string;
timestamp: string;
visible: boolean;
dataIndex: number; // 添加数据索引用于高亮
highlightY: number; // 高亮Y轴位置
}
// Canvas图表配置
const MAX_POINTS = 300;
const TARGET_FPS = 15; // 降低帧率减少闪烁
@@ -41,7 +54,7 @@ const ALPHA_LINE = 0.9;
const PADDING_TOP = 16;
const PADDING_RIGHT = 16; // 增加右边距确保时间戳完整显示
const PADDING_BOTTOM = 32; // 进一步增加底部空间给时间轴和统计信息
const PADDING_LEFT = 16; // 增加左边距确保时间戳完整显示
const PADDING_LEFT = 35; // 增加左边距为Y轴标签留出空间
const GRAPH_CONFIG = {
maxPoints: MAX_POINTS,
@@ -80,6 +93,18 @@ export const EnhancedCanvasTrafficGraph = memo(
const [timeRange, setTimeRange] = useState<TimeRange>(10);
const [chartStyle, setChartStyle] = useState<"bezier" | "line">("bezier");
// 悬浮提示状态
const [tooltipData, setTooltipData] = useState<TooltipData>({
x: 0,
y: 0,
upSpeed: "",
downSpeed: "",
timestamp: "",
visible: false,
dataIndex: -1,
highlightY: 0,
});
// Canvas引用和渲染状态
const canvasRef = useRef<HTMLCanvasElement>(null);
const animationFrameRef = useRef<number | undefined>(undefined);
@@ -130,27 +155,304 @@ export const EnhancedCanvasTrafficGraph = memo(
updateDisplayDataDebounced,
]);
// Y轴坐标计算(对数刻度)- 确保不与时间轴重叠
const calculateY = useCallback((value: number, height: number): number => {
const padding = GRAPH_CONFIG.padding;
const effectiveHeight = height - padding.top - padding.bottom;
const baseY = height - padding.bottom;
// Y轴坐标计算 - 基于刻度范围的线性映射
const calculateY = useCallback(
(value: number, height: number, data: ITrafficDataPoint[]): number => {
const padding = GRAPH_CONFIG.padding;
const topY = padding.top + 10; // 与刻度系统保持一致
const bottomY = height - padding.bottom - 5;
if (value === 0) return baseY - 2; // 稍微抬高零值线
if (data.length === 0) return bottomY;
const steps = effectiveHeight / 7;
// 获取当前的刻度范围
const allValues = [
...data.map((d) => d.up),
...data.map((d) => d.down),
];
const maxValue = Math.max(...allValues);
const minValue = Math.min(...allValues);
if (value <= 10) return baseY - (value / 10) * steps;
if (value <= 100) return baseY - (value / 100 + 1) * steps;
if (value <= 1024) return baseY - (value / 1024 + 2) * steps;
if (value <= 10240) return baseY - (value / 10240 + 3) * steps;
if (value <= 102400) return baseY - (value / 102400 + 4) * steps;
if (value <= 1048576) return baseY - (value / 1048576 + 5) * steps;
if (value <= 10485760) return baseY - (value / 10485760 + 6) * steps;
let topValue, bottomValue;
return padding.top + 1;
if (maxValue === 0) {
topValue = 1024;
bottomValue = 0;
} else {
const range = maxValue - minValue;
const padding_percent = range > 0 ? 0.1 : 0.5;
if (range === 0) {
bottomValue = 0;
topValue = maxValue * 1.2;
} else {
bottomValue = Math.max(0, minValue - range * padding_percent);
topValue = maxValue + range * padding_percent;
}
}
// 线性映射到Y坐标
if (topValue === bottomValue) return bottomY;
const ratio = (value - bottomValue) / (topValue - bottomValue);
return bottomY - ratio * (bottomY - topY);
},
[],
);
// 鼠标悬浮处理 - 计算最近的数据点
const handleMouseMove = useCallback(
(event: React.MouseEvent<HTMLCanvasElement>) => {
const canvas = canvasRef.current;
if (!canvas || displayData.length === 0) return;
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const padding = GRAPH_CONFIG.padding;
const effectiveWidth = rect.width - padding.left - padding.right;
// 计算最接近的数据点索引
const relativeMouseX = mouseX - padding.left;
const ratio = Math.max(0, Math.min(1, relativeMouseX / effectiveWidth));
const dataIndex = Math.round(ratio * (displayData.length - 1));
if (dataIndex >= 0 && dataIndex < displayData.length) {
const dataPoint = displayData[dataIndex];
// 格式化流量数据
const [upValue, upUnit] = parseTraffic(dataPoint.up);
const [downValue, downUnit] = parseTraffic(dataPoint.down);
// 格式化时间戳
const timeStr = dataPoint.timestamp
? new Date(dataPoint.timestamp).toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
: "未知时间";
// 计算数据点对应的Y坐标位置用于高亮
const upY = calculateY(dataPoint.up, rect.height, displayData);
const downY = calculateY(dataPoint.down, rect.height, displayData);
const highlightY =
Math.max(dataPoint.up, dataPoint.down) === dataPoint.up
? upY
: downY;
setTooltipData({
x: mouseX,
y: mouseY,
upSpeed: `${upValue}${upUnit}/s`,
downSpeed: `${downValue}${downUnit}/s`,
timestamp: timeStr,
visible: true,
dataIndex,
highlightY,
});
}
},
[displayData, calculateY],
);
// 鼠标离开处理
const handleMouseLeave = useCallback(() => {
setTooltipData((prev) => ({ ...prev, visible: false }));
}, []);
// 获取智能Y轴刻度三刻度系统最小值、中间值、最大值
const getYAxisTicks = useCallback(
(data: ITrafficDataPoint[], height: number) => {
if (data.length === 0) return [];
// 找到数据的最大值和最小值
const allValues = [
...data.map((d) => d.up),
...data.map((d) => d.down),
];
const maxValue = Math.max(...allValues);
const minValue = Math.min(...allValues);
// 格式化流量数值
const formatTrafficValue = (bytes: number): string => {
if (bytes === 0) return "0";
if (bytes < 1024) return `${Math.round(bytes)}B`;
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
};
const padding = GRAPH_CONFIG.padding;
const effectiveHeight = height - padding.top - padding.bottom;
// 强制显示三个刻度:底部、中间、顶部
const topY = padding.top + 10; // 避免与顶部时间范围按钮重叠
const bottomY = height - padding.bottom - 5; // 避免与底部时间轴重叠
const middleY = (topY + bottomY) / 2;
// 计算对应的值
let topValue, middleValue, bottomValue;
if (maxValue === 0) {
// 如果没有流量显示0到一个小值的范围
topValue = 1024; // 1KB
middleValue = 512; // 512B
bottomValue = 0;
} else {
// 根据数据范围计算合适的刻度值
const range = maxValue - minValue;
const padding_percent = range > 0 ? 0.1 : 0.5; // 如果范围为0使用更大的边距
if (range === 0) {
// 所有值相同的情况
bottomValue = 0;
middleValue = maxValue * 0.5;
topValue = maxValue * 1.2;
} else {
// 正常情况
bottomValue = Math.max(0, minValue - range * padding_percent);
topValue = maxValue + range * padding_percent;
middleValue = (bottomValue + topValue) / 2;
}
}
// 创建三个固定位置的刻度
const ticks = [
{
value: bottomValue,
label: formatTrafficValue(bottomValue),
y: bottomY,
},
{
value: middleValue,
label: formatTrafficValue(middleValue),
y: middleY,
},
{
value: topValue,
label: formatTrafficValue(topValue),
y: topY,
},
];
return ticks;
},
[],
);
// 绘制Y轴刻度线和标签
const drawYAxis = useCallback(
(
ctx: CanvasRenderingContext2D,
width: number,
height: number,
data: ITrafficDataPoint[],
) => {
const padding = GRAPH_CONFIG.padding;
const ticks = getYAxisTicks(data, height);
if (ticks.length === 0) return;
ctx.save();
ticks.forEach((tick, index) => {
const isBottomTick = index === 0; // 最底部的刻度
const isTopTick = index === ticks.length - 1; // 最顶部的刻度
// 绘制水平刻度线,只绘制关键刻度线
if (isBottomTick || isTopTick) {
ctx.strokeStyle = colors.grid;
ctx.lineWidth = isBottomTick ? 0.8 : 0.4; // 底部刻度线稍粗
ctx.globalAlpha = isBottomTick ? 0.25 : 0.15;
ctx.beginPath();
ctx.moveTo(padding.left, tick.y);
ctx.lineTo(width - padding.right, tick.y);
ctx.stroke();
}
// 绘制Y轴标签
ctx.fillStyle = colors.text;
ctx.font =
"8px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
ctx.globalAlpha = 0.9;
ctx.textAlign = "right";
ctx.textBaseline = "middle";
// 为标签添加更清晰的背景(仅在必要时)
if (tick.label !== "0") {
const labelWidth = ctx.measureText(tick.label).width;
ctx.globalAlpha = 0.15;
ctx.fillStyle = colors.background;
ctx.fillRect(
padding.left - labelWidth - 8,
tick.y - 5,
labelWidth + 4,
10,
);
}
// 绘制标签文字
ctx.globalAlpha = 0.9;
ctx.fillStyle = colors.text;
ctx.fillText(tick.label, padding.left - 4, tick.y);
});
ctx.restore();
},
[colors.grid, colors.text, colors.background, getYAxisTicks],
);
// 获取时间范围对应的最佳时间显示策略
const getTimeDisplayStrategy = useCallback(
(timeRangeMinutes: TimeRange) => {
switch (timeRangeMinutes) {
case 1: // 1分钟更密集的时间标签显示 MM:SS
return {
maxLabels: 6, // 减少到6个更适合短时间
formatTime: (timestamp: number) => {
const date = new Date(timestamp);
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return `${minutes}:${seconds}`; // 显示 MM:SS
},
intervalSeconds: 10, // 每10秒一个标签更合理
minPixelDistance: 35, // 减少间距,允许更多标签
};
case 5: // 5分钟中等密度显示 HH:MM
return {
maxLabels: 6, // 6个标签比较合适
formatTime: (timestamp: number) => {
const date = new Date(timestamp);
return date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
}); // 显示 HH:MM
},
intervalSeconds: 30, // 约30秒间隔
minPixelDistance: 38, // 减少间距,允许更多标签
};
case 10: // 10分钟标准密度显示 HH:MM
default:
return {
maxLabels: 8, // 保持8个
formatTime: (timestamp: number) => {
const date = new Date(timestamp);
return date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
}); // 显示 HH:MM
},
intervalSeconds: 60, // 1分钟间隔
minPixelDistance: 40, // 减少间距,允许更多标签
};
}
},
[],
);
// 绘制时间轴
const drawTimeAxis = useCallback(
(
@@ -165,44 +467,89 @@ export const EnhancedCanvasTrafficGraph = memo(
const effectiveWidth = width - padding.left - padding.right;
const timeAxisY = height - padding.bottom + 14;
const strategy = getTimeDisplayStrategy(timeRange);
ctx.save();
ctx.fillStyle = colors.text;
ctx.font =
"10px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
ctx.globalAlpha = 0.7;
// 显示最多6个时间标签确保边界完整显示
const maxLabels = 6;
const step = Math.max(1, Math.floor(data.length / (maxLabels - 1)));
// 根据数据长度和时间范围智能选择显示间隔
const targetLabels = Math.min(strategy.maxLabels, data.length);
const step = Math.max(1, Math.floor(data.length / (targetLabels - 1)));
// 绘制第一个时间点(左对齐)
if (data.length > 0 && data[0].name) {
ctx.textAlign = "left";
const timeLabel = data[0].name.substring(0, 5);
ctx.fillText(timeLabel, padding.left, timeAxisY);
// 使用策略中定义的最小像素间距
const minPixelDistance = strategy.minPixelDistance || 45;
const actualStep = Math.max(
step,
Math.ceil((data.length * minPixelDistance) / effectiveWidth),
);
// 收集要显示的时间点
const timePoints: Array<{ index: number; x: number; label: string }> =
[];
// 添加第一个时间点
if (data.length > 0 && data[0].timestamp) {
timePoints.push({
index: 0,
x: padding.left,
label: strategy.formatTime(data[0].timestamp),
});
}
// 绘制中间的时间点(居中对齐)
ctx.textAlign = "center";
for (let i = step; i < data.length - step; i += step) {
// 添加中间的时间点
for (
let i = actualStep;
i < data.length - actualStep;
i += actualStep
) {
const point = data[i];
if (!point.name) continue;
if (!point.timestamp) continue;
const x = padding.left + (i / (data.length - 1)) * effectiveWidth;
const timeLabel = point.name.substring(0, 5);
ctx.fillText(timeLabel, x, timeAxisY);
timePoints.push({
index: i,
x,
label: strategy.formatTime(point.timestamp),
});
}
// 绘制最后一个时间点(右对齐
if (data.length > 1 && data[data.length - 1].name) {
ctx.textAlign = "right";
const timeLabel = data[data.length - 1].name.substring(0, 5);
ctx.fillText(timeLabel, width - padding.right, timeAxisY);
// 添加最后一个时间点(如果不会与前面的重叠
if (data.length > 1 && data[data.length - 1].timestamp) {
const lastX = width - padding.right;
const lastPoint = timePoints[timePoints.length - 1];
// 确保最后一个标签与前一个标签有足够间距
if (!lastPoint || lastX - lastPoint.x >= minPixelDistance) {
timePoints.push({
index: data.length - 1,
x: lastX,
label: strategy.formatTime(data[data.length - 1].timestamp),
});
}
}
// 绘制时间标签
timePoints.forEach((point, index) => {
if (index === 0) {
// 第一个标签左对齐
ctx.textAlign = "left";
} else if (index === timePoints.length - 1) {
// 最后一个标签右对齐
ctx.textAlign = "right";
} else {
// 中间标签居中对齐
ctx.textAlign = "center";
}
ctx.fillText(point.label, point.x, timeAxisY);
});
ctx.restore();
},
[colors.text],
[colors.text, timeRange, getTimeDisplayStrategy],
);
// 绘制网格线
@@ -215,7 +562,7 @@ export const EnhancedCanvasTrafficGraph = memo(
ctx.save();
ctx.strokeStyle = colors.grid;
ctx.lineWidth = GRAPH_CONFIG.lineWidth.grid;
ctx.globalAlpha = 0.2;
ctx.globalAlpha = 0.7;
// 水平网格线
const horizontalLines = 4;
@@ -251,6 +598,7 @@ export const EnhancedCanvasTrafficGraph = memo(
height: number,
color: string,
withGradient = false,
data: ITrafficDataPoint[],
) => {
if (values.length < 2) return;
@@ -259,7 +607,7 @@ export const EnhancedCanvasTrafficGraph = memo(
const points = values.map((value, index) => [
padding.left + (index / (values.length - 1)) * effectiveWidth,
calculateY(value, height),
calculateY(value, height, data),
]);
ctx.save();
@@ -360,6 +708,9 @@ export const EnhancedCanvasTrafficGraph = memo(
// 清空画布
ctx.clearRect(0, 0, width, height);
// 绘制Y轴刻度线背景层
drawYAxis(ctx, width, height, displayData);
// 绘制网格
drawGrid(ctx, width, height);
@@ -371,13 +722,66 @@ export const EnhancedCanvasTrafficGraph = memo(
const downValues = displayData.map((d) => d.down);
// 绘制下载线(背景层)
drawTrafficLine(ctx, downValues, width, height, colors.down, true);
drawTrafficLine(
ctx,
downValues,
width,
height,
colors.down,
true,
displayData,
);
// 绘制上传线(前景层)
drawTrafficLine(ctx, upValues, width, height, colors.up, true);
drawTrafficLine(
ctx,
upValues,
width,
height,
colors.up,
true,
displayData,
);
// 绘制悬浮高亮线
if (tooltipData.visible && tooltipData.dataIndex >= 0) {
const padding = GRAPH_CONFIG.padding;
const effectiveWidth = width - padding.left - padding.right;
const dataX =
padding.left +
(tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
ctx.save();
ctx.strokeStyle = colors.text;
ctx.lineWidth = 1;
ctx.globalAlpha = 0.6;
ctx.setLineDash([4, 4]); // 虚线效果
// 绘制垂直指示线
ctx.beginPath();
ctx.moveTo(dataX, padding.top);
ctx.lineTo(dataX, height - padding.bottom);
ctx.stroke();
// 绘制水平指示线高亮Y轴位置
ctx.beginPath();
ctx.moveTo(padding.left, tooltipData.highlightY);
ctx.lineTo(width - padding.right, tooltipData.highlightY);
ctx.stroke();
ctx.restore();
}
isInitializedRef.current = true;
}, [displayData, colors, drawGrid, drawTimeAxis, drawTrafficLine]);
}, [
displayData,
colors,
drawYAxis,
drawGrid,
drawTimeAxis,
drawTrafficLine,
tooltipData,
]);
// 受控的动画循环
useEffect(() => {
@@ -461,6 +865,8 @@ export const EnhancedCanvasTrafficGraph = memo(
height: "100%",
display: "block",
}}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
/>
{/* 控制层覆盖 */}
@@ -481,7 +887,7 @@ export const EnhancedCanvasTrafficGraph = memo(
sx={{
position: "absolute",
top: 6,
left: 8,
left: 40, // 向右移动避免与Y轴最大值标签重叠
fontSize: "11px",
fontWeight: "bold",
color: "text.secondary",
@@ -561,6 +967,42 @@ export const EnhancedCanvasTrafficGraph = memo(
Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} |
Compressed: {samplerStats.compressedBufferSize}
</Box>
{/* 悬浮提示框 */}
{tooltipData.visible && (
<Box
sx={{
position: "absolute",
left: tooltipData.x + 8,
top: tooltipData.y - 8,
bgcolor: theme.palette.background.paper,
border: 1,
borderColor: "divider",
borderRadius: 0.5,
px: 1,
py: 0.5,
fontSize: "10px",
lineHeight: 1.2,
zIndex: 1000,
pointerEvents: "none",
transform:
tooltipData.x > 200 ? "translateX(-100%)" : "translateX(0)",
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
backdropFilter: "none",
opacity: 1,
}}
>
<Box color="text.secondary" mb={0.2}>
{tooltipData.timestamp}
</Box>
<Box color="secondary.main" fontWeight="500">
{tooltipData.upSpeed}
</Box>
<Box color="primary.main" fontWeight="500">
{tooltipData.downSpeed}
</Box>
</Box>
)}
</Box>
</Box>
);

View File

@@ -29,7 +29,7 @@ import parseTraffic from "@/utils/parse-traffic";
import { isDebugEnabled, gc } from "@/services/cmds";
import { ReactNode } from "react";
import { useAppData } from "@/providers/app-data-provider";
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor-enhanced";
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary";
import useSWR from "swr";

View File

@@ -12,7 +12,7 @@ import { useVisibility } from "@/hooks/use-visibility";
import parseTraffic from "@/utils/parse-traffic";
import { useTranslation } from "react-i18next";
import { isDebugEnabled, gc, startTrafficService } from "@/services/cmds";
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor-enhanced";
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
import { LightweightTrafficErrorBoundary } from "@/components/common/traffic-error-boundary";
import useSWR from "swr";
@@ -78,21 +78,10 @@ export const LayoutTraffic = () => {
// 显示内存使用情况的设置
const displayMemory = verge?.enable_memory_usage ?? true;
// 使用格式化的数据,避免重复解析
const upSpeed = traffic?.formatted?.up_rate || "0B";
const downSpeed = traffic?.formatted?.down_rate || "0B";
const memoryUsage = memory?.formatted?.inuse || "0B";
// 提取数值和单位
const [up, upUnit] = upSpeed.includes("B")
? upSpeed.split(/(?=[KMGT]?B$)/)
: [upSpeed, ""];
const [down, downUnit] = downSpeed.includes("B")
? downSpeed.split(/(?=[KMGT]?B$)/)
: [downSpeed, ""];
const [inuse, inuseUnit] = memoryUsage.includes("B")
? memoryUsage.split(/(?=[KMGT]?B$)/)
: [memoryUsage, ""];
// 使用parseTraffic统一处理转换保持与首页一致的显示格式
const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0);
const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0);
const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0);
const boxStyle: any = {
display: "flex",

View File

@@ -1,398 +0,0 @@
import { useState, useEffect, useRef, useCallback } from "react";
import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash";
import { useVisibility } from "@/hooks/use-visibility";
import { getSystemMonitorOverviewSafe } from "@/services/cmds";
// 增强的流量数据点接口
export interface ITrafficDataPoint {
up: number;
down: number;
timestamp: number;
name: string;
}
// 压缩的数据点(用于长期存储)
interface ICompressedDataPoint {
up: number;
down: number;
timestamp: number;
samples: number; // 压缩了多少个原始数据点
}
// 数据采样器配置
interface ISamplingConfig {
// 原始数据保持时间(分钟)
rawDataMinutes: number;
// 压缩数据保持时间(分钟)
compressedDataMinutes: number;
// 压缩比例多少个原始点压缩成1个
compressionRatio: number;
}
// 引用计数管理器
class ReferenceCounter {
private count = 0;
private callbacks: (() => void)[] = [];
increment(): () => void {
this.count++;
console.log(`[ReferenceCounter] 引用计数增加: ${this.count}`);
if (this.count === 1) {
// 从0到1开始数据收集
this.callbacks.forEach((cb) => cb());
}
return () => {
this.count--;
console.log(`[ReferenceCounter] 引用计数减少: ${this.count}`);
if (this.count === 0) {
// 从1到0停止数据收集
this.callbacks.forEach((cb) => cb());
}
};
}
onCountChange(callback: () => void) {
this.callbacks.push(callback);
}
getCount(): number {
return this.count;
}
}
// 智能数据采样器
class TrafficDataSampler {
private rawBuffer: ITrafficDataPoint[] = [];
private compressedBuffer: ICompressedDataPoint[] = [];
private config: ISamplingConfig;
private compressionQueue: ITrafficDataPoint[] = [];
constructor(config: ISamplingConfig) {
this.config = config;
}
addDataPoint(point: ITrafficDataPoint): void {
// 添加到原始缓冲区
this.rawBuffer.push(point);
// 清理过期的原始数据
const rawCutoff = Date.now() - this.config.rawDataMinutes * 60 * 1000;
this.rawBuffer = this.rawBuffer.filter((p) => p.timestamp > rawCutoff);
// 添加到压缩队列
this.compressionQueue.push(point);
// 当压缩队列达到压缩比例时,执行压缩
if (this.compressionQueue.length >= this.config.compressionRatio) {
this.compressData();
}
// 清理过期的压缩数据
const compressedCutoff =
Date.now() - this.config.compressedDataMinutes * 60 * 1000;
this.compressedBuffer = this.compressedBuffer.filter(
(p) => p.timestamp > compressedCutoff,
);
}
private compressData(): void {
if (this.compressionQueue.length === 0) return;
// 计算平均值进行压缩
const totalUp = this.compressionQueue.reduce((sum, p) => sum + p.up, 0);
const totalDown = this.compressionQueue.reduce((sum, p) => sum + p.down, 0);
const avgTimestamp =
this.compressionQueue.reduce((sum, p) => sum + p.timestamp, 0) /
this.compressionQueue.length;
const compressedPoint: ICompressedDataPoint = {
up: totalUp / this.compressionQueue.length,
down: totalDown / this.compressionQueue.length,
timestamp: avgTimestamp,
samples: this.compressionQueue.length,
};
this.compressedBuffer.push(compressedPoint);
this.compressionQueue = [];
console.log(`[DataSampler] 压缩了 ${compressedPoint.samples} 个数据点`);
}
getDataForTimeRange(minutes: number): ITrafficDataPoint[] {
const cutoff = Date.now() - minutes * 60 * 1000;
// 如果请求的时间范围在原始数据范围内,直接返回原始数据
if (minutes <= this.config.rawDataMinutes) {
return this.rawBuffer.filter((p) => p.timestamp > cutoff);
}
// 否则组合原始数据和压缩数据
const rawData = this.rawBuffer.filter((p) => p.timestamp > cutoff);
const compressedData = this.compressedBuffer
.filter(
(p) =>
p.timestamp > cutoff &&
p.timestamp <= Date.now() - this.config.rawDataMinutes * 60 * 1000,
)
.map((p) => ({
up: p.up,
down: p.down,
timestamp: p.timestamp,
name: new Date(p.timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
}));
return [...compressedData, ...rawData].sort(
(a, b) => a.timestamp - b.timestamp,
);
}
getStats() {
return {
rawBufferSize: this.rawBuffer.length,
compressedBufferSize: this.compressedBuffer.length,
compressionQueueSize: this.compressionQueue.length,
totalMemoryPoints: this.rawBuffer.length + this.compressedBuffer.length,
};
}
clear(): void {
this.rawBuffer = [];
this.compressedBuffer = [];
this.compressionQueue = [];
}
}
// 全局单例
const refCounter = new ReferenceCounter();
let globalSampler: TrafficDataSampler | null = null;
let lastValidData: ISystemMonitorOverview | null = null;
/**
* 增强的流量监控Hook - 支持数据压缩、采样和引用计数
*/
export const useTrafficMonitorEnhanced = () => {
const { clashInfo } = useClashInfo();
const pageVisible = useVisibility();
// 初始化采样器
if (!globalSampler) {
globalSampler = new TrafficDataSampler({
rawDataMinutes: 10, // 原始数据保持10分钟
compressedDataMinutes: 60, // 压缩数据保持1小时
compressionRatio: 5, // 每5个原始点压缩成1个
});
}
const [, forceUpdate] = useState({});
const cleanupRef = useRef<(() => void) | null>(null);
// 强制组件更新
const triggerUpdate = useCallback(() => {
forceUpdate({});
}, []);
// 注册引用计数
useEffect(() => {
console.log("[TrafficMonitorEnhanced] 组件挂载,注册引用计数");
const cleanup = refCounter.increment();
cleanupRef.current = cleanup;
return () => {
console.log("[TrafficMonitorEnhanced] 组件卸载,清理引用计数");
cleanup();
cleanupRef.current = null;
};
}, []);
// 设置引用计数变化回调
useEffect(() => {
const handleCountChange = () => {
console.log(
`[TrafficMonitorEnhanced] 引用计数变化: ${refCounter.getCount()}`,
);
if (refCounter.getCount() === 0) {
console.log("[TrafficMonitorEnhanced] 所有组件已卸载,暂停数据收集");
} else {
console.log("[TrafficMonitorEnhanced] 开始数据收集");
}
};
refCounter.onCountChange(handleCountChange);
}, []);
// 只有在有引用时才启用SWR
const shouldFetch = clashInfo && pageVisible && refCounter.getCount() > 0;
const { data: monitorData, error } = useSWR<ISystemMonitorOverview>(
shouldFetch ? "getSystemMonitorOverviewSafe" : null,
getSystemMonitorOverviewSafe,
{
refreshInterval: shouldFetch ? 1000 : 0, // 只有在需要时才刷新
keepPreviousData: true,
onSuccess: (data) => {
// console.log("[TrafficMonitorEnhanced] 获取到监控数据:", data);
if (data?.traffic?.raw && globalSampler) {
// 保存最后有效数据
lastValidData = data;
// 添加到采样器
const timestamp = Date.now();
const dataPoint: ITrafficDataPoint = {
up: data.traffic.raw.up_rate || 0,
down: data.traffic.raw.down_rate || 0,
timestamp,
name: new Date(timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
globalSampler.addDataPoint(dataPoint);
triggerUpdate();
}
},
onError: (error) => {
console.error(
"[TrafficMonitorEnhanced] 网络错误,使用最后有效数据. 错误详情:",
{
message: error?.message || "未知错误",
stack: error?.stack || "无堆栈信息",
},
);
// 网络错误时不清空数据,继续使用最后有效值
// 但是添加一个错误标记的数据点流量为0
if (globalSampler) {
const timestamp = Date.now();
const errorPoint: ITrafficDataPoint = {
up: 0,
down: 0,
timestamp,
name: new Date(timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
globalSampler.addDataPoint(errorPoint);
triggerUpdate();
}
},
},
);
// 获取指定时间范围的数据
const getDataForTimeRange = useCallback(
(minutes: number): ITrafficDataPoint[] => {
if (!globalSampler) return [];
return globalSampler.getDataForTimeRange(minutes);
},
[],
);
// 清空数据
const clearData = useCallback(() => {
if (globalSampler) {
globalSampler.clear();
triggerUpdate();
}
}, [triggerUpdate]);
// 获取采样器统计信息
const getSamplerStats = useCallback(() => {
return (
globalSampler?.getStats() || {
rawBufferSize: 0,
compressedBufferSize: 0,
compressionQueueSize: 0,
totalMemoryPoints: 0,
}
);
}, []);
// 构建返回的监控数据优先使用当前数据fallback到最后有效数据
const currentData = monitorData || lastValidData;
const trafficMonitorData = {
traffic: currentData?.traffic || {
raw: { up: 0, down: 0, up_rate: 0, down_rate: 0 },
formatted: {
up_rate: "0B",
down_rate: "0B",
total_up: "0B",
total_down: "0B",
},
is_fresh: false,
},
memory: currentData?.memory || {
raw: { inuse: 0, oslimit: 0, usage_percent: 0 },
formatted: { inuse: "0B", oslimit: "0B", usage_percent: 0 },
is_fresh: false,
},
};
return {
// 监控数据
monitorData: trafficMonitorData,
// 图表数据管理
graphData: {
dataPoints: globalSampler?.getDataForTimeRange(60) || [], // 默认获取1小时数据
getDataForTimeRange,
clearData,
},
// 状态信息
isLoading: !currentData && !error,
error,
isDataFresh: currentData?.traffic?.is_fresh || false,
hasValidData: !!lastValidData,
// 性能统计
samplerStats: getSamplerStats(),
referenceCount: refCounter.getCount(),
};
};
/**
* 轻量级流量数据Hook
*/
export const useTrafficDataEnhanced = () => {
const { monitorData, isLoading, error, isDataFresh, hasValidData } =
useTrafficMonitorEnhanced();
return {
traffic: monitorData.traffic,
memory: monitorData.memory,
isLoading,
error,
isDataFresh,
hasValidData,
};
};
/**
* 图表数据Hook
*/
export const useTrafficGraphDataEnhanced = () => {
const { graphData, isDataFresh, samplerStats, referenceCount } =
useTrafficMonitorEnhanced();
return {
...graphData,
isDataFresh,
samplerStats,
referenceCount,
};
};

View File

@@ -2,9 +2,9 @@ import { useState, useEffect, useRef, useCallback } from "react";
import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash";
import { useVisibility } from "@/hooks/use-visibility";
import { getSystemMonitorOverview } from "@/services/cmds";
import { getSystemMonitorOverviewSafe } from "@/services/cmds";
// 流量数据接口
// 增强的流量数据接口
export interface ITrafficDataPoint {
up: number;
down: number;
@@ -12,215 +12,365 @@ export interface ITrafficDataPoint {
name: string;
}
// 流量监控数据接口
export interface ITrafficMonitorData {
traffic: {
raw: { up_rate: number; down_rate: number };
formatted: { up_rate: string; down_rate: string };
is_fresh: boolean;
};
memory: {
raw: { inuse: number; oslimit?: number };
formatted: { inuse: string; usage_percent?: number };
is_fresh: boolean;
};
// 压缩的数据点(用于长期存储)
interface ICompressedDataPoint {
up: number;
down: number;
timestamp: number;
samples: number; // 压缩了多少个原始数据点
}
// 图表数据管理接口
export interface ITrafficGraphData {
dataPoints: ITrafficDataPoint[];
addDataPoint: (data: {
up: number;
down: number;
timestamp?: number;
}) => void;
clearData: () => void;
getDataForTimeRange: (minutes: number) => ITrafficDataPoint[];
// 数据采样器配置
interface ISamplingConfig {
// 原始数据保持时间(分钟)
rawDataMinutes: number;
// 压缩数据保持时间(分钟)
compressedDataMinutes: number;
// 压缩比例多少个原始点压缩成1个
compressionRatio: number;
}
// 引用计数管理器
class ReferenceCounter {
private count = 0;
private callbacks: (() => void)[] = [];
increment(): () => void {
this.count++;
console.log(`[ReferenceCounter] 引用计数增加: ${this.count}`);
if (this.count === 1) {
// 从0到1开始数据收集
this.callbacks.forEach((cb) => cb());
}
return () => {
this.count--;
console.log(`[ReferenceCounter] 引用计数减少: ${this.count}`);
if (this.count === 0) {
// 从1到0停止数据收集
this.callbacks.forEach((cb) => cb());
}
};
}
onCountChange(callback: () => void) {
this.callbacks.push(callback);
}
getCount(): number {
return this.count;
}
}
// 智能数据采样器
class TrafficDataSampler {
private rawBuffer: ITrafficDataPoint[] = [];
private compressedBuffer: ICompressedDataPoint[] = [];
private config: ISamplingConfig;
private compressionQueue: ITrafficDataPoint[] = [];
constructor(config: ISamplingConfig) {
this.config = config;
}
addDataPoint(point: ITrafficDataPoint): void {
// 添加到原始缓冲区
this.rawBuffer.push(point);
// 清理过期的原始数据
const rawCutoff = Date.now() - this.config.rawDataMinutes * 60 * 1000;
this.rawBuffer = this.rawBuffer.filter((p) => p.timestamp > rawCutoff);
// 添加到压缩队列
this.compressionQueue.push(point);
// 当压缩队列达到压缩比例时,执行压缩
if (this.compressionQueue.length >= this.config.compressionRatio) {
this.compressData();
}
// 清理过期的压缩数据
const compressedCutoff =
Date.now() - this.config.compressedDataMinutes * 60 * 1000;
this.compressedBuffer = this.compressedBuffer.filter(
(p) => p.timestamp > compressedCutoff,
);
}
private compressData(): void {
if (this.compressionQueue.length === 0) return;
// 计算平均值进行压缩
const totalUp = this.compressionQueue.reduce((sum, p) => sum + p.up, 0);
const totalDown = this.compressionQueue.reduce((sum, p) => sum + p.down, 0);
const avgTimestamp =
this.compressionQueue.reduce((sum, p) => sum + p.timestamp, 0) /
this.compressionQueue.length;
const compressedPoint: ICompressedDataPoint = {
up: totalUp / this.compressionQueue.length,
down: totalDown / this.compressionQueue.length,
timestamp: avgTimestamp,
samples: this.compressionQueue.length,
};
this.compressedBuffer.push(compressedPoint);
this.compressionQueue = [];
console.log(`[DataSampler] 压缩了 ${compressedPoint.samples} 个数据点`);
}
getDataForTimeRange(minutes: number): ITrafficDataPoint[] {
const cutoff = Date.now() - minutes * 60 * 1000;
// 如果请求的时间范围在原始数据范围内,直接返回原始数据
if (minutes <= this.config.rawDataMinutes) {
return this.rawBuffer.filter((p) => p.timestamp > cutoff);
}
// 否则组合原始数据和压缩数据
const rawData = this.rawBuffer.filter((p) => p.timestamp > cutoff);
const compressedData = this.compressedBuffer
.filter(
(p) =>
p.timestamp > cutoff &&
p.timestamp <= Date.now() - this.config.rawDataMinutes * 60 * 1000,
)
.map((p) => ({
up: p.up,
down: p.down,
timestamp: p.timestamp,
name: new Date(p.timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
}));
return [...compressedData, ...rawData].sort(
(a, b) => a.timestamp - b.timestamp,
);
}
getStats() {
return {
rawBufferSize: this.rawBuffer.length,
compressedBufferSize: this.compressedBuffer.length,
compressionQueueSize: this.compressionQueue.length,
totalMemoryPoints: this.rawBuffer.length + this.compressedBuffer.length,
};
}
clear(): void {
this.rawBuffer = [];
this.compressedBuffer = [];
this.compressionQueue = [];
}
}
// 全局单例
const refCounter = new ReferenceCounter();
let globalSampler: TrafficDataSampler | null = null;
let lastValidData: ISystemMonitorOverview | null = null;
/**
* 全局流量监控数据管理Hook
* 提供统一的流量数据获取和图表数据管理
* 增强的流量监控Hook - 支持数据压缩、采样和引用计数
*/
export const useTrafficMonitor = () => {
export const useTrafficMonitorEnhanced = () => {
const { clashInfo } = useClashInfo();
const pageVisible = useVisibility();
// 图表数据缓冲区 - 使用ref保持数据持久性
const dataBufferRef = useRef<ITrafficDataPoint[]>([]);
const [, forceUpdate] = useState({});
// 初始化采样器
if (!globalSampler) {
globalSampler = new TrafficDataSampler({
rawDataMinutes: 10, // 原始数据保持10分钟
compressedDataMinutes: 60, // 压缩数据保持1小时
compressionRatio: 5, // 每5个原始点压缩成1个
});
}
// 强制组件更新的函数
const [, forceUpdate] = useState({});
const cleanupRef = useRef<(() => void) | null>(null);
// 强制组件更新
const triggerUpdate = useCallback(() => {
forceUpdate({});
}, []);
// 最大缓冲区大小 (10分钟 * 60秒 = 600个数据点)
const MAX_BUFFER_SIZE = 600;
// 初始化数据缓冲区
// 注册引用计数
useEffect(() => {
if (dataBufferRef.current.length === 0) {
const now = Date.now();
const tenMinutesAgo = now - 10 * 60 * 1000;
console.log("[TrafficMonitorEnhanced] 组件挂载,注册引用计数");
const cleanup = refCounter.increment();
cleanupRef.current = cleanup;
const initialBuffer = Array.from(
{ length: MAX_BUFFER_SIZE },
(_, index) => {
const pointTime =
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
const date = new Date(pointTime);
return () => {
console.log("[TrafficMonitorEnhanced] 组件卸载,清理引用计数");
cleanup();
cleanupRef.current = null;
};
}, []);
let nameValue: string;
try {
if (isNaN(date.getTime())) {
nameValue = "??:??:??";
} else {
nameValue = date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
} catch (e) {
nameValue = "Err:Time";
}
return {
up: 0,
down: 0,
timestamp: pointTime,
name: nameValue,
};
},
// 设置引用计数变化回调
useEffect(() => {
const handleCountChange = () => {
console.log(
`[TrafficMonitorEnhanced] 引用计数变化: ${refCounter.getCount()}`,
);
if (refCounter.getCount() === 0) {
console.log("[TrafficMonitorEnhanced] 所有组件已卸载,暂停数据收集");
} else {
console.log("[TrafficMonitorEnhanced] 开始数据收集");
}
};
dataBufferRef.current = initialBuffer;
}
}, [MAX_BUFFER_SIZE]);
refCounter.onCountChange(handleCountChange);
}, []);
// 只有在有引用时才启用SWR
const shouldFetch = clashInfo && pageVisible && refCounter.getCount() > 0;
// 使用SWR获取监控数据
const { data: monitorData, error } = useSWR<ISystemMonitorOverview>(
clashInfo && pageVisible ? "getSystemMonitorOverview" : null,
getSystemMonitorOverview,
shouldFetch ? "getSystemMonitorOverviewSafe" : null,
getSystemMonitorOverviewSafe,
{
refreshInterval: 1000, // 1秒刷新一次
refreshInterval: shouldFetch ? 1000 : 0, // 只有在需要时才刷新
keepPreviousData: true,
onSuccess: (data) => {
console.log("[TrafficMonitor] 获取到监控数据:", data);
// console.log("[TrafficMonitorEnhanced] 获取到监控数据:", data);
if (data?.traffic) {
// 为图表添加新数据
addDataPoint({
if (data?.traffic?.raw && globalSampler) {
// 保存最后有效数据
lastValidData = data;
// 添加到采样器
const timestamp = Date.now();
const dataPoint: ITrafficDataPoint = {
up: data.traffic.raw.up_rate || 0,
down: data.traffic.raw.down_rate || 0,
timestamp: Date.now(),
});
timestamp,
name: new Date(timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
globalSampler.addDataPoint(dataPoint);
triggerUpdate();
}
},
onError: (error) => {
console.error("[TrafficMonitor] 获取数据错误:", error);
console.error(
"[TrafficMonitorEnhanced] 网络错误,使用最后有效数据. 错误详情:",
{
message: error?.message || "未知错误",
stack: error?.stack || "无堆栈信息",
},
);
// 网络错误时不清空数据,继续使用最后有效值
// 但是添加一个错误标记的数据点流量为0
if (globalSampler) {
const timestamp = Date.now();
const errorPoint: ITrafficDataPoint = {
up: 0,
down: 0,
timestamp,
name: new Date(timestamp).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
globalSampler.addDataPoint(errorPoint);
triggerUpdate();
}
},
},
);
// 添加数据点到缓冲区
const addDataPoint = useCallback(
(data: { up: number; down: number; timestamp?: number }) => {
const timestamp = data.timestamp || Date.now();
const date = new Date(timestamp);
let nameValue: string;
try {
if (isNaN(date.getTime())) {
nameValue = "??:??:??";
} else {
nameValue = date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
} catch (e) {
nameValue = "Err:Time";
}
const newPoint: ITrafficDataPoint = {
up: typeof data.up === "number" && !isNaN(data.up) ? data.up : 0,
down:
typeof data.down === "number" && !isNaN(data.down) ? data.down : 0,
timestamp,
name: nameValue,
};
// 更新缓冲区,保持固定大小
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
dataBufferRef.current = newBuffer;
// 触发使用该数据的组件更新
triggerUpdate();
},
[triggerUpdate],
);
// 清空数据
const clearData = useCallback(() => {
dataBufferRef.current = [];
triggerUpdate();
}, [triggerUpdate]);
// 根据时间范围获取数据
// 获取指定时间范围的数据
const getDataForTimeRange = useCallback(
(minutes: number): ITrafficDataPoint[] => {
const pointsToShow = minutes * 60; // 每分钟60个数据点
return dataBufferRef.current.slice(-pointsToShow);
if (!globalSampler) return [];
return globalSampler.getDataForTimeRange(minutes);
},
[],
);
// 构建图表数据管理对象
const graphData: ITrafficGraphData = {
dataPoints: dataBufferRef.current,
addDataPoint,
clearData,
getDataForTimeRange,
};
// 清空数据
const clearData = useCallback(() => {
if (globalSampler) {
globalSampler.clear();
triggerUpdate();
}
}, [triggerUpdate]);
// 构建监控数据对象
const trafficMonitorData: ITrafficMonitorData = {
traffic: monitorData?.traffic || {
raw: { up_rate: 0, down_rate: 0 },
formatted: { up_rate: "0B", down_rate: "0B" },
// 获取采样器统计信息
const getSamplerStats = useCallback(() => {
return (
globalSampler?.getStats() || {
rawBufferSize: 0,
compressedBufferSize: 0,
compressionQueueSize: 0,
totalMemoryPoints: 0,
}
);
}, []);
// 构建返回的监控数据优先使用当前数据fallback到最后有效数据
const currentData = monitorData || lastValidData;
const trafficMonitorData = {
traffic: currentData?.traffic || {
raw: { up: 0, down: 0, up_rate: 0, down_rate: 0 },
formatted: {
up_rate: "0B",
down_rate: "0B",
total_up: "0B",
total_down: "0B",
},
is_fresh: false,
},
memory: monitorData?.memory || {
raw: { inuse: 0 },
formatted: { inuse: "0B" },
memory: currentData?.memory || {
raw: { inuse: 0, oslimit: 0, usage_percent: 0 },
formatted: { inuse: "0B", oslimit: "0B", usage_percent: 0 },
is_fresh: false,
},
};
return {
// 原始监控数据
// 监控数据
monitorData: trafficMonitorData,
// 图表数据管理
graphData,
// 数据获取状态
isLoading: !monitorData && !error,
graphData: {
dataPoints: globalSampler?.getDataForTimeRange(60) || [], // 默认获取1小时数据
getDataForTimeRange,
clearData,
},
// 状态信息
isLoading: !currentData && !error,
error,
// 数据新鲜度
isDataFresh: monitorData?.overall_status === "active",
isDataFresh: currentData?.traffic?.is_fresh || false,
hasValidData: !!lastValidData,
// 性能统计
samplerStats: getSamplerStats(),
referenceCount: refCounter.getCount(),
};
};
/**
* 仅获取流量数据的轻量级Hook
* 适用于不需要图表数据的组件
* 轻量级流量数据Hook
*/
export const useTrafficData = () => {
const { monitorData, isLoading, error, isDataFresh } = useTrafficMonitor();
export const useTrafficDataEnhanced = () => {
const { monitorData, isLoading, error, isDataFresh, hasValidData } =
useTrafficMonitorEnhanced();
return {
traffic: monitorData.traffic,
@@ -228,18 +378,21 @@ export const useTrafficData = () => {
isLoading,
error,
isDataFresh,
hasValidData,
};
};
/**
* 仅获取图表数据Hook
* 适用于图表组件
* 图表数据Hook
*/
export const useTrafficGraphData = () => {
const { graphData, isDataFresh } = useTrafficMonitor();
export const useTrafficGraphDataEnhanced = () => {
const { graphData, isDataFresh, samplerStats, referenceCount } =
useTrafficMonitorEnhanced();
return {
...graphData,
isDataFresh,
samplerStats,
referenceCount,
};
};