fix: 修复延迟以及丢包率的计算

This commit is contained in:
Montia37
2025-09-09 15:17:42 +08:00
parent 32d0e396ef
commit de86264262
3 changed files with 95 additions and 18 deletions

View File

@@ -19,7 +19,10 @@ import { Label } from "@radix-ui/react-label";
import type { NodeData } from "@/types/node";
import Loading from "@/components/loading";
import { usePingChart } from "@/hooks/usePingChart";
import fillMissingTimePoints, { cutPeakValues } from "@/utils/RecordHelper";
import fillMissingTimePoints, {
cutPeakValues,
calculateTaskStats,
} from "@/utils/RecordHelper";
import { useConfigItem } from "@/config";
import { CustomTooltip } from "@/components/ui/tooltip";
import Tips from "@/components/ui/tips";
@@ -164,7 +167,7 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
const sortedTasks = useMemo(() => {
if (!pingHistory?.tasks) return [];
return [...pingHistory.tasks].sort((a, b) => a.name.localeCompare(b.name));
return [...pingHistory.tasks].sort((a, b) => a.id - b.id);
}, [pingHistory?.tasks]);
const generateColor = useCallback(
@@ -224,6 +227,25 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
return points;
}, [chartData, sortedTasks, visiblePingTasks, generateColor, connectBreaks]);
const taskStats = useMemo(() => {
if (!pingHistory?.records || !sortedTasks.length) return [];
return sortedTasks.map((task) => {
const { loss, latestValue, latestTime } = calculateTaskStats(
pingHistory.records,
task.id,
timeRange
);
return {
...task,
value: latestValue,
time: latestTime,
loss: loss,
color: generateColor(task.name, sortedTasks.length),
};
});
}, [pingHistory?.records, sortedTasks, generateColor, timeRange]);
return (
<div className="relative space-y-4">
{loading && (
@@ -238,20 +260,19 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
)}
{pingHistory?.tasks && pingHistory.tasks.length > 0 && (
<Card>
<Card className="relative">
<div className="absolute top-2 right-2">
<Tips>
<span
dangerouslySetInnerHTML={{
__html: "<p>丢包率计算算法并不准确,谨慎参考</p>",
}}></span>
</Tips>
</div>
<CardContent className="p-2">
<div className="flex flex-wrap gap-2 items-center justify-center">
{sortedTasks.map((task) => {
const values = chartData
.map((d) => d[task.id])
.filter((v) => v !== null && v !== undefined) as number[];
const loss =
chartData.length > 0
? (1 - values.length / chartData.length) * 100
: 0;
const min = values.length > 0 ? Math.min(...values) : 0;
{taskStats.map((task) => {
const isVisible = visiblePingTasks.includes(task.id);
const color = generateColor(task.name, sortedTasks.length);
return (
<div
@@ -261,13 +282,21 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
}`}
onClick={() => handleTaskVisibilityToggle(task.id)}
style={{
outlineColor: isVisible ? color : undefined,
boxShadow: isVisible ? `0 0 8px ${color}` : undefined,
outlineColor: isVisible ? task.color : undefined,
boxShadow: isVisible
? `0 0 8px ${task.color}`
: undefined,
}}>
<div className="font-semibold">{task.name}</div>
<span className="text-xs font-normal">
{loss.toFixed(1)}% | {min.toFixed(0)}ms
</span>
<div className="flex text-xs font-normal">
<span>
{task.value !== null
? `${task.value.toFixed(1)} ms | ${task.loss.toFixed(
1
)}%`
: "N/A"}
</span>
</div>
</div>
);
})}

1
src/types/node.d.ts vendored
View File

@@ -95,6 +95,7 @@ export interface PingTask {
id: number;
interval: number;
name: string;
loss: number;
}
export interface PingHistoryResponse {

View File

@@ -356,3 +356,50 @@ export function sampleDataByRetention(
return result;
}
/**
* 根据 ping 历史记录计算丢包率。
* @param records - ping 历史记录数组。
* @param taskId - 要计算丢包率的任务 ID。
* @param timeRange - 用于筛选记录的可选时间范围 [开始, 结束]。
* @returns 以百分比表示的丢包率。
*/
/**
* 根据 ping 历史记录计算任务的统计数据(丢包率和最新值)。
* @param records - ping 历史记录数组。
* @param taskId - 要计算的任务 ID。
* @param timeRange - 用于筛选记录的可选时间范围 [开始, 结束]。
* @returns 包含丢包率和最新值的对象。
*/
export function calculateTaskStats(
records: { time: string; task_id: number; value: number }[],
taskId: number,
timeRange: [number, number] | null
): { loss: number; latestValue: number | null; latestTime: string | null } {
const relevantRecords = timeRange
? records.filter((rec) => {
const t = new Date(rec.time).getTime();
return t >= timeRange[0] && t <= timeRange[1];
})
: records;
const taskRecords =
relevantRecords?.filter((rec) => rec.task_id === taskId) || [];
const totalPings = taskRecords.length;
const successfulPings = taskRecords.filter((rec) => rec.value >= 0);
const loss =
totalPings > 0 ? (1 - successfulPings.length / totalPings) * 100 : 0;
let latestValue: number | null = null;
let latestTime: string | null = null;
if (successfulPings.length > 0) {
const latestRecord = successfulPings.reduce((latest, current) => {
return new Date(current.time) > new Date(latest.time) ? current : latest;
});
latestValue = latestRecord.value;
latestTime = latestRecord.time;
}
return { loss, latestValue, latestTime };
}