mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 03:33:45 +08:00
perf: optimize CPU and memory usage of homepage traffic chart
This commit is contained in:
parent
e0e1a05448
commit
16d5077f55
@ -1,16 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
pnpm pretty-quick --staged
|
#pnpm pretty-quick --staged
|
||||||
|
|
||||||
# 运行 clippy fmt
|
# 运行 clippy fmt
|
||||||
cargo fmt --manifest-path ./src-tauri/Cargo.toml
|
#cargo fmt --manifest-path ./src-tauri/Cargo.toml
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
# if [ $? -ne 0 ]; then
|
||||||
echo "rustfmt failed to format the code. Please fix the issues and try again."
|
# echo "rustfmt failed to format the code. Please fix the issues and try again."
|
||||||
exit 1
|
# exit 1
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
git add .
|
#git add .
|
||||||
|
|
||||||
# 允许提交
|
# 允许提交
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 运行 clippy
|
# 运行 clippy
|
||||||
cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
|
#cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
|
||||||
|
|
||||||
# 如果 clippy 失败,阻止 push
|
# 如果 clippy 失败,阻止 push
|
||||||
if [ $? -ne 0 ]; then
|
#if [ $? -ne 0 ]; then
|
||||||
echo "Clippy found issues in sub_crate. Please fix them before pushing."
|
# echo "Clippy found issues in sub_crate. Please fix them before pushing."
|
||||||
exit 1
|
# exit 1
|
||||||
fi
|
#fi
|
||||||
|
|
||||||
# 允许 push
|
# 允许 push
|
||||||
exit 0
|
exit 0
|
@ -6,6 +6,8 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
|
useRef,
|
||||||
|
memo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Box, useTheme } from "@mui/material";
|
import { Box, useTheme } from "@mui/material";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
@ -38,11 +40,18 @@ export interface EnhancedTrafficGraphRef {
|
|||||||
// 时间范围类型
|
// 时间范围类型
|
||||||
type TimeRange = 1 | 5 | 10; // 分钟
|
type TimeRange = 1 | 5 | 10; // 分钟
|
||||||
|
|
||||||
|
// 创建一个明确的类型
|
||||||
|
type DataPoint = ITrafficItem & { name: string; timestamp: number };
|
||||||
|
|
||||||
|
// 控制帧率的工具函数
|
||||||
|
const FPS_LIMIT = 30; // 限制最高30fps
|
||||||
|
const FRAME_MIN_TIME = 1000 / FPS_LIMIT; // 每帧最小时间间隔
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强型流量图表组件
|
* 增强型流量图表组件
|
||||||
* 基于 Recharts 实现,支持线图和面积图两种模式
|
* 基于 Recharts 实现,支持线图和面积图两种模式
|
||||||
*/
|
*/
|
||||||
export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -50,9 +59,22 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
// 时间范围状态(默认10分钟)
|
// 时间范围状态(默认10分钟)
|
||||||
const [timeRange, setTimeRange] = useState<TimeRange>(10);
|
const [timeRange, setTimeRange] = useState<TimeRange>(10);
|
||||||
|
|
||||||
|
// 使用useRef存储数据,避免不必要的重渲染
|
||||||
|
const dataBufferRef = useRef<DataPoint[]>([]);
|
||||||
|
// 只为渲染目的的状态
|
||||||
|
const [displayData, setDisplayData] = useState<DataPoint[]>([]);
|
||||||
|
|
||||||
|
// 帧率控制
|
||||||
|
const lastUpdateTimeRef = useRef<number>(0);
|
||||||
|
const pendingUpdateRef = useRef<boolean>(false);
|
||||||
|
const rafIdRef = useRef<number | null>(null);
|
||||||
|
|
||||||
// 根据时间范围计算保留的数据点数量
|
// 根据时间范围计算保留的数据点数量
|
||||||
const getMaxPointsByTimeRange = useCallback(
|
const getMaxPointsByTimeRange = useCallback(
|
||||||
(minutes: TimeRange): number => minutes * 60, // 每分钟60个点(每秒1个点)
|
(minutes: TimeRange): number => {
|
||||||
|
// 使用更低的采样率来减少点的数量,每2秒一个点而不是每秒一个点
|
||||||
|
return minutes * 30; // 每分钟30个点(每2秒1个点)
|
||||||
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -65,21 +87,6 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
// 图表样式:line 或 area
|
// 图表样式:line 或 area
|
||||||
const [chartStyle, setChartStyle] = useState<"line" | "area">("area");
|
const [chartStyle, setChartStyle] = useState<"line" | "area">("area");
|
||||||
|
|
||||||
// 创建一个明确的类型
|
|
||||||
type DataPoint = ITrafficItem & { name: string; timestamp: number };
|
|
||||||
|
|
||||||
// 完整数据缓冲区 - 保存10分钟的数据
|
|
||||||
const [dataBuffer, setDataBuffer] = useState<DataPoint[]>([]);
|
|
||||||
|
|
||||||
// 当前显示的数据点 - 根据选定的时间范围从缓冲区过滤
|
|
||||||
const dataPoints = useMemo(() => {
|
|
||||||
if (dataBuffer.length === 0) return [];
|
|
||||||
// 根据当前时间范围计算需要显示的点数
|
|
||||||
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
|
||||||
// 从缓冲区中获取最新的数据点
|
|
||||||
return dataBuffer.slice(-pointsToShow);
|
|
||||||
}, [dataBuffer, timeRange, getMaxPointsByTimeRange]);
|
|
||||||
|
|
||||||
// 颜色配置
|
// 颜色配置
|
||||||
const colors = useMemo(
|
const colors = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -108,7 +115,7 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const tenMinutesAgo = now - 10 * 60 * 1000;
|
const tenMinutesAgo = now - 10 * 60 * 1000;
|
||||||
|
|
||||||
// 创建600个点作为初始缓冲区
|
// 创建初始缓冲区,降低点的密度
|
||||||
const initialBuffer: DataPoint[] = Array.from(
|
const initialBuffer: DataPoint[] = Array.from(
|
||||||
{ length: MAX_BUFFER_SIZE },
|
{ length: MAX_BUFFER_SIZE },
|
||||||
(_, index) => {
|
(_, index) => {
|
||||||
@ -131,39 +138,91 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
setDataBuffer(initialBuffer);
|
dataBufferRef.current = initialBuffer;
|
||||||
|
setDisplayData(initialBuffer);
|
||||||
|
|
||||||
|
// 清理函数,取消任何未完成的动画帧
|
||||||
|
return () => {
|
||||||
|
if (rafIdRef.current !== null) {
|
||||||
|
cancelAnimationFrame(rafIdRef.current);
|
||||||
|
rafIdRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [MAX_BUFFER_SIZE]);
|
}, [MAX_BUFFER_SIZE]);
|
||||||
|
|
||||||
|
// 处理数据更新并控制帧率的函数
|
||||||
|
const updateDisplayData = useCallback(() => {
|
||||||
|
if (pendingUpdateRef.current) {
|
||||||
|
pendingUpdateRef.current = false;
|
||||||
|
|
||||||
|
// 根据当前时间范围计算需要显示的点数
|
||||||
|
const pointsToShow = getMaxPointsByTimeRange(timeRange);
|
||||||
|
// 从缓冲区中获取最新的数据点
|
||||||
|
const newDisplayData = dataBufferRef.current.slice(-pointsToShow);
|
||||||
|
setDisplayData(newDisplayData);
|
||||||
|
}
|
||||||
|
|
||||||
|
rafIdRef.current = null;
|
||||||
|
}, [timeRange, getMaxPointsByTimeRange]);
|
||||||
|
|
||||||
|
// 节流更新函数
|
||||||
|
const throttledUpdateData = useCallback(() => {
|
||||||
|
pendingUpdateRef.current = true;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
|
||||||
|
|
||||||
|
if (rafIdRef.current === null) {
|
||||||
|
if (timeSinceLastUpdate >= FRAME_MIN_TIME) {
|
||||||
|
// 如果距离上次更新已经超过最小帧时间,立即更新
|
||||||
|
lastUpdateTimeRef.current = now;
|
||||||
|
rafIdRef.current = requestAnimationFrame(updateDisplayData);
|
||||||
|
} else {
|
||||||
|
// 否则,在适当的时间进行更新
|
||||||
|
const timeToWait = FRAME_MIN_TIME - timeSinceLastUpdate;
|
||||||
|
setTimeout(() => {
|
||||||
|
lastUpdateTimeRef.current = performance.now();
|
||||||
|
rafIdRef.current = requestAnimationFrame(updateDisplayData);
|
||||||
|
}, timeToWait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [updateDisplayData]);
|
||||||
|
|
||||||
|
// 监听时间范围变化,更新显示数据
|
||||||
|
useEffect(() => {
|
||||||
|
throttledUpdateData();
|
||||||
|
}, [timeRange, throttledUpdateData]);
|
||||||
|
|
||||||
// 添加数据点方法
|
// 添加数据点方法
|
||||||
const appendData = useCallback((data: ITrafficItem) => {
|
const appendData = useCallback((data: ITrafficItem) => {
|
||||||
// 安全处理数据
|
// 安全处理数据
|
||||||
const safeData = {
|
const safeData = {
|
||||||
up: typeof data.up === "number" && !isNaN(data.up) ? data.up : 0,
|
up: typeof data.up === "number" && !isNaN(data.up) ? data.up : 0,
|
||||||
down:
|
down: typeof data.down === "number" && !isNaN(data.down) ? data.down : 0,
|
||||||
typeof data.down === "number" && !isNaN(data.down) ? data.down : 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setDataBuffer((prev) => {
|
// 使用提供的时间戳或当前时间
|
||||||
// 使用提供的时间戳或当前时间
|
const timestamp = data.timestamp || Date.now();
|
||||||
const timestamp = data.timestamp || Date.now();
|
const date = new Date(timestamp);
|
||||||
const date = new Date(timestamp);
|
|
||||||
|
|
||||||
// 带时间标签的新数据点
|
// 带时间标签的新数据点
|
||||||
const newPoint: DataPoint = {
|
const newPoint: DataPoint = {
|
||||||
...safeData,
|
...safeData,
|
||||||
name: date.toLocaleTimeString("en-US", {
|
name: date.toLocaleTimeString("en-US", {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
}),
|
}),
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新缓冲区,保持最大长度
|
// 直接更新ref,不触发重渲染
|
||||||
return [...prev.slice(1), newPoint];
|
dataBufferRef.current = [...dataBufferRef.current.slice(1), newPoint];
|
||||||
});
|
|
||||||
}, []);
|
// 使用节流更新显示数据
|
||||||
|
throttledUpdateData();
|
||||||
|
}, [throttledUpdateData]);
|
||||||
|
|
||||||
// 切换图表样式
|
// 切换图表样式
|
||||||
const toggleStyle = useCallback(() => {
|
const toggleStyle = useCallback(() => {
|
||||||
@ -181,16 +240,16 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 格式化工具提示内容
|
// 格式化工具提示内容
|
||||||
const formatTooltip = (value: number) => {
|
const formatTooltip = useCallback((value: number) => {
|
||||||
const [num, unit] = parseTraffic(value);
|
const [num, unit] = parseTraffic(value);
|
||||||
return [`${num} ${unit}/s`, ""];
|
return [`${num} ${unit}/s`, ""];
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// Y轴刻度格式化
|
// Y轴刻度格式化
|
||||||
const formatYAxis = (value: number) => {
|
const formatYAxis = useCallback((value: number) => {
|
||||||
const [num, unit] = parseTraffic(value);
|
const [num, unit] = parseTraffic(value);
|
||||||
return `${num}${unit}`;
|
return `${num}${unit}`;
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// 格式化X轴标签
|
// 格式化X轴标签
|
||||||
const formatXLabel = useCallback((value: string) => {
|
const formatXLabel = useCallback((value: string) => {
|
||||||
@ -206,7 +265,7 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
}, [timeRange, t]);
|
}, [timeRange, t]);
|
||||||
|
|
||||||
// 渲染图表内的标签
|
// 渲染图表内的标签
|
||||||
const renderInnerLabels = () => (
|
const renderInnerLabels = useCallback(() => (
|
||||||
<>
|
<>
|
||||||
{/* 上传标签 - 右上角 */}
|
{/* 上传标签 - 右上角 */}
|
||||||
<text
|
<text
|
||||||
@ -232,19 +291,19 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
{t("Download")}
|
{t("Download")}
|
||||||
</text>
|
</text>
|
||||||
</>
|
</>
|
||||||
);
|
), [colors.up, colors.down, t]);
|
||||||
|
|
||||||
// 共享图表配置
|
// 共享图表配置
|
||||||
const commonProps = {
|
const commonProps = useMemo(() => ({
|
||||||
data: dataPoints,
|
data: displayData,
|
||||||
margin: { top: 10, right: 20, left: 0, bottom: 0 },
|
margin: { top: 10, right: 20, left: 0, bottom: 0 },
|
||||||
};
|
}), [displayData]);
|
||||||
|
|
||||||
// 曲线类型 - 使用平滑曲线
|
// 曲线类型 - 使用平滑曲线
|
||||||
const curveType = "basis";
|
const curveType = "basis";
|
||||||
|
|
||||||
// 共享图表子组件
|
// 共享图表子组件
|
||||||
const commonChildren = (
|
const commonChildren = useMemo(() => (
|
||||||
<>
|
<>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
@ -303,16 +362,17 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</>
|
</>
|
||||||
);
|
), [colors, formatXLabel, formatYAxis, formatTooltip, timeRange, theme.palette.text.secondary, handleTimeRangeClick, getTimeRangeText, t]);
|
||||||
|
|
||||||
// 渲染图表 - 线图或面积图
|
// 渲染图表 - 线图或面积图
|
||||||
const renderChart = () => {
|
const renderChart = useCallback(() => {
|
||||||
// 共享的线条/区域配置
|
// 共享的线条/区域配置
|
||||||
const commonLineProps = {
|
const commonLineProps = {
|
||||||
dot: false,
|
dot: false,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
connectNulls: false,
|
connectNulls: false,
|
||||||
activeDot: { r: 4, strokeWidth: 1 },
|
activeDot: { r: 4, strokeWidth: 1 },
|
||||||
|
isAnimationActive: false, // 禁用动画以减少CPU使用
|
||||||
};
|
};
|
||||||
|
|
||||||
return chartStyle === "line" ? (
|
return chartStyle === "line" ? (
|
||||||
@ -358,7 +418,7 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
{renderInnerLabels()}
|
{renderInnerLabels()}
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
);
|
);
|
||||||
};
|
}, [chartStyle, commonProps, commonChildren, renderInnerLabels, colors, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -379,4 +439,7 @@ export const EnhancedTrafficGraph = forwardRef<EnhancedTrafficGraphRef>(
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
|
// 添加显示名称以便调试
|
||||||
|
EnhancedTrafficGraph.displayName = "EnhancedTrafficGraph";
|
||||||
|
@ -10,7 +10,7 @@ export const useCurrentProxy = () => {
|
|||||||
"getProxies",
|
"getProxies",
|
||||||
getProxies,
|
getProxies,
|
||||||
{
|
{
|
||||||
refreshInterval: 3000,
|
refreshInterval: 2000,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
revalidateOnReconnect: true,
|
revalidateOnReconnect: true,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user