内存优化
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
/.idea
|
||||
/wwwroot
|
||||
/.playwright-cli/
|
||||
/output/
|
||||
|
||||
+8
-1
@@ -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>
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user