内存优化

This commit is contained in:
2026-08-28 11:49:04 +08:00
parent c8e146e5ac
commit 1fc7b0fc76
3 changed files with 170 additions and 1 deletions
+1
View File
@@ -2,3 +2,4 @@
/.idea
/wwwroot
/.playwright-cli/
/output/
+8 -1
View File
@@ -4,7 +4,7 @@ import {Aircraft_List} from "./Map/Aircraft_List.tsx";
import enhance from './core/enhance.tsx';
import {useEffect} from 'react';
import {SettingOutlined} from '@ant-design/icons';
import {DashboardOutlined, SettingOutlined} from '@ant-design/icons';
import {Menu, type MenuProps, message, Switch} from 'antd';
import {
@@ -23,6 +23,7 @@ import axios from "axios";
import {baseURL} from "./Global.ts";
import {is_cesium_webgl_available, webgl_unavailable_message} from "./Map/WebGL_Support.ts";
import {Data_Source_Config} from "./Data_Source/Data_Source_Config.tsx";
import {MemoryStatusPage} from "./Memory/Memory_Status.tsx";
import L from 'leaflet';
@@ -83,6 +84,11 @@ class App extends enhance.Base {
icon: <SettingOutlined/>,
key: '/topology',
},
{
label: '内存监控',
icon: <DashboardOutlined/>,
key: '/memory',
},
];
axios.post(`${baseURL}/version`, {}).then(res => {
@@ -226,6 +232,7 @@ class App extends enhance.Base {
<Route path="/ui/map3d" element={map3d("/ui/map")}/>
{["/aircraftlist", "/ui/aircraftlist"].map(path => <Route key={path} path={path} element={aircraftList}/>)}
{["/topology", "/ui/topology"].map(path => <Route key={path} path={path} element={dataTopology}/>)}
{["/memory", "/ui/memory"].map(path => <Route key={path} path={path} element={<MemoryStatusPage/>}/>)}
<Route path="*" element={<Navigate to="/map"/>}/>
</Routes>
+161
View File
@@ -0,0 +1,161 @@
import {Alert, Card, Col, Flex, Progress, Row, Spin, Statistic, Table, Tag, Typography} from "antd";
import type {ColumnsType} from "antd/es/table";
import axios from "axios";
import {useEffect, useMemo, useState} from "react";
import {baseURL} from "../Global.ts";
type MemorySample = {
timestamp_ms: number;
rss_bytes: number;
peak_rss_bytes: number;
anonymous_rss_bytes: number;
file_rss_bytes: number;
shared_rss_bytes: number;
virtual_bytes: number;
swap_bytes: number;
commit_bytes: number;
peak_commit_bytes: number;
system_total_bytes: number;
system_available_bytes: number;
page_faults: number;
allocator_live_bytes: number;
allocator_peak_live_bytes: number;
pmr_live_bytes: number;
pmr_peak_live_bytes: number;
};
type BacklogState = {
active: boolean;
normal_read_bytes: number;
drain_read_bytes: number;
last_read_request_bytes: number;
last_batch_raw_bytes: number;
largest_batch_raw_bytes: number;
events_total: number;
batches_total: number;
raw_bytes_total: number;
dropped_mode_ac_frames_total: number;
dropped_mode_ac_bytes_total: number;
};
type BacklogRow = BacklogState & {key: string};
type MemoryState = {
allocator: string;
allocator_version: number;
current: MemorySample;
process_memory_percent: number;
system_available_percent: number;
pressure: "normal" | "warning" | "critical";
sample_count: number;
window_seconds: number;
growth_window_seconds: number;
rss_growth_bytes_per_hour: number;
anonymous_growth_bytes_per_hour: number;
recent_anonymous_growth_bytes_per_hour: number;
allocator_growth_bytes_per_hour: number;
sustained_growth: boolean;
allocated_bytes_total: number;
allocation_count: number;
deallocation_count: number;
pmr_allocated_bytes_total: number;
pmr_allocation_count: number;
pmr_deallocation_count: number;
history: MemorySample[];
dll_sources: Record<string, BacklogState>;
iq_dropped_frames_total: number;
};
const mebibyte = 1024 * 1024;
function formatBytes(value: number): string {
if (!Number.isFinite(value)) return "-";
const absolute = Math.abs(value);
const sign = value < 0 ? "-" : "";
if (absolute >= 1024 * mebibyte) return `${sign}${(absolute / (1024 * mebibyte)).toFixed(2)} GiB`;
if (absolute >= mebibyte) return `${sign}${(absolute / mebibyte).toFixed(1)} MiB`;
if (absolute >= 1024) return `${sign}${(absolute / 1024).toFixed(1)} KiB`;
return `${value} B`;
}
function MemoryTrend({history}: {history: MemorySample[]}) {
const width = 900;
const height = 240;
const top = 18;
const bottom = 28;
const plotHeight = height - top - bottom;
const maximum = Math.max(mebibyte, ...history.flatMap(sample => [sample.rss_bytes, sample.anonymous_rss_bytes, sample.shared_rss_bytes, sample.allocator_live_bytes]));
const points = (getter: (sample: MemorySample) => number) => history.map((sample, index) => {
const x = history.length <= 1 ? 0 : index * width / (history.length - 1);
const y = top + plotHeight - getter(sample) * plotHeight / maximum;
return `${x.toFixed(1)},${y.toFixed(1)}`;
}).join(" ");
if (history.length < 2) return <div style={{height, display: "grid", placeItems: "center", color: "#999"}}></div>;
return <div style={{width: "100%", overflowX: "auto"}}>
<svg viewBox={`0 0 ${width} ${height}`} style={{display: "block", minWidth: 620, width: "100%", height}}>
{[0, 0.25, 0.5, 0.75, 1].map(value => <g key={value}>
<line x1={0} y1={top + plotHeight * value} x2={width} y2={top + plotHeight * value} stroke="#f0f0f0"/>
<text x={4} y={top + plotHeight * value - 3} fill="#999" fontSize={11}>{formatBytes(maximum * (1 - value))}</text>
</g>)}
<polyline fill="none" stroke="#1677ff" strokeWidth={2} points={points(sample => sample.rss_bytes)}/>
<polyline fill="none" stroke="#fa8c16" strokeWidth={2} points={points(sample => sample.anonymous_rss_bytes)}/>
<polyline fill="none" stroke="#722ed1" strokeWidth={2} points={points(sample => sample.shared_rss_bytes)}/>
<polyline fill="none" stroke="#52c41a" strokeWidth={2} points={points(sample => sample.allocator_live_bytes)}/>
<g transform={`translate(${width - 405},${height - 8})`} fontSize={12}>
<text x={0} fill="#1677ff"> RSS</text>
<text x={88} fill="#fa8c16"></text>
<text x={184} fill="#722ed1"></text>
<text x={272} fill="#52c41a">mimalloc </text>
</g>
</svg>
</div>;
}
export function MemoryStatusPage() {
const [state, setState] = useState<MemoryState | null>(null);
const [error, setError] = useState("");
useEffect(() => {
let active = true;
const refresh = () => axios.post<MemoryState>(`${baseURL}/memory_status`, {}).then(response => {
if (!active) return;
setState(response.data);
setError("");
}).catch(reason => {
if (active) setError(reason instanceof Error ? reason.message : String(reason));
});
refresh();
const timer = window.setInterval(refresh, 3000);
return () => {
active = false;
window.clearInterval(timer);
};
}, []);
const backlogRows = useMemo<BacklogRow[]>(() => Object.entries(state?.dll_sources ?? {}).map(([key, value]) => ({key, ...value})), [state]);
if (!state) return <Flex vertical align="center" justify="center" style={{height: "100%"}} gap={16}><Spin/><Typography.Text>{error || "正在读取内存状态"}</Typography.Text></Flex>;
const current = state.current;
const pressureColor = state.pressure === "critical" ? "red" : state.pressure === "warning" ? "orange" : "green";
const columns: ColumnsType<BacklogRow> = [
{title: "数据源", dataIndex: "key", key: "key"},
{title: "状态", dataIndex: "active", key: "active", render: active => <Tag color={active ? "red" : "green"}>{active ? "正在积压" : "正常"}</Tag>},
{title: "最近批次", dataIndex: "last_batch_raw_bytes", key: "last_batch_raw_bytes", render: formatBytes},
{title: "积压事件", dataIndex: "events_total", key: "events_total"},
{title: "追读总量", dataIndex: "raw_bytes_total", key: "raw_bytes_total", render: formatBytes},
{title: "丢弃 Mode A/C", dataIndex: "dropped_mode_ac_frames_total", key: "dropped_mode_ac_frames_total"},
{title: "丢弃字节", dataIndex: "dropped_mode_ac_bytes_total", key: "dropped_mode_ac_bytes_total", render: formatBytes}
];
return <div style={{padding: 20, overflow: "auto", width: "100%"}}>
<Flex align="center" gap={12} style={{marginBottom: 16}}>
<Typography.Title level={3} style={{margin: 0}}></Typography.Title>
<Tag color={pressureColor}>{state.pressure.toUpperCase()}</Tag>
<Typography.Text type="secondary">{state.allocator} {state.allocator_version}</Typography.Text>
</Flex>
{error && <Alert type="warning" showIcon message="刷新失败" description={error} style={{marginBottom: 16}}/>}
{state.sustained_growth && <Alert type="error" showIcon message="匿名内存持续增长" description={`${Math.round(state.growth_window_seconds / 60)} 分钟趋势 ${formatBytes(state.anonymous_growth_bytes_per_hour)}/小时,最近 5 分钟 ${formatBytes(state.recent_anonymous_growth_bytes_per_hour)}/小时`} style={{marginBottom: 16}}/>}
<Row gutter={[12, 12]}>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="进程 RSS" value={formatBytes(current.rss_bytes)}/><Progress percent={Number(state.process_memory_percent.toFixed(1))} status={state.pressure === "critical" ? "exception" : "normal"}/></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="匿名内存" value={formatBytes(current.anonymous_rss_bytes)}/><Typography.Text type="secondary"> {formatBytes(state.anonymous_growth_bytes_per_hour)}/</Typography.Text></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="共享映射" value={formatBytes(current.shared_rss_bytes)}/><Typography.Text type="secondary"> RSS</Typography.Text></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="文件映射" value={formatBytes(current.file_rss_bytes)}/></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="mimalloc 活跃" value={formatBytes(current.allocator_live_bytes)}/><Typography.Text type="secondary"> {formatBytes(current.allocator_peak_live_bytes)}</Typography.Text></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="PMR 活跃" value={formatBytes(current.pmr_live_bytes)}/><Typography.Text type="secondary"> {formatBytes(current.pmr_peak_live_bytes)}</Typography.Text></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="系统可用" value={formatBytes(current.system_available_bytes)}/><Progress percent={Number(state.system_available_percent.toFixed(1))}/></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="进程峰值 RSS" value={formatBytes(current.peak_rss_bytes)}/></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="Swap" value={formatBytes(current.swap_bytes)}/></Card></Col>
<Col xs={24} sm={12} lg={6}><Card><Statistic title="IQ 丢帧" value={state.iq_dropped_frames_total}/></Card></Col>
</Row>
<Card title={`内存趋势(${Math.round(state.window_seconds / 60)} 分钟,${state.sample_count} 个样本)`} style={{marginTop: 16}}><MemoryTrend history={state.history}/></Card>
<Card title="DLL 输入积压" style={{marginTop: 16}}><Table rowKey="key" size="small" pagination={false} dataSource={backlogRows} columns={columns}/></Card>
</div>;
}