From 633985af8e6739cfc24046dc76a96d081f6e73a3 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 8 Dec 2023 17:14:32 +0800 Subject: [PATCH] Finish memory statistics information --- src/global.d.ts | 2 + src/pages/system/index.tsx | 293 ++++++++++++++++++++++++++++++------- src/util/common.tsx | 77 ++++++++++ 3 files changed, 322 insertions(+), 50 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index e05f83a..16a1fab 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -372,4 +372,6 @@ interface MemoryInfoVo { virtualMax: number swapTotal: number swapUsed: number + jvmTotal: number + jvmFree: number } diff --git a/src/pages/system/index.tsx b/src/pages/system/index.tsx index ec76b2e..6add3f4 100644 --- a/src/pages/system/index.tsx +++ b/src/pages/system/index.tsx @@ -15,6 +15,7 @@ import { utcToLocalTime } from '@/util/datetime' import { r_sys_statistics_cpu, r_sys_statistics_hardware, + r_sys_statistics_memory, r_sys_statistics_software } from '@/services/system' import Card from '@/components/common/Card' @@ -22,6 +23,7 @@ import FlexBox from '@/components/common/FlexBox' import FitFullScreen from '@/components/common/FitFullScreen' import HideScrollbar from '@/components/common/HideScrollbar' import LoadingMask from '@/components/common/LoadingMask' +import { formatByteSize } from '@/util/common.tsx' echarts.use([TooltipComponent, GridComponent, BarChart, SVGRenderer]) type EChartsOption = echarts.ComposeOption< @@ -34,6 +36,50 @@ interface CommonCardProps extends React.PropsWithChildren { loading?: boolean } +const barDefaultSeriesOption: BarSeriesOption = { + type: 'bar', + stack: 'total', + itemStyle: { + color: (params) => { + switch (params.seriesName) { + case 'idle': + case 'free': + return '#F5F5F5' + default: + return params.color ?? echarts.color.random() + } + } + } +} + +const eChartsBaseOption: EChartsOption = { + tooltip: { + axisPointer: { + axis: 'x' + } + }, + xAxis: { + show: false + }, + yAxis: { + axisLine: { + show: false + }, + axisLabel: { + show: false + }, + axisTick: { + show: false + }, + splitLine: { + show: false + }, + axisPointer: { + show: false + } + } +} + const CommonCard: React.FC = (props) => { return ( @@ -181,26 +227,15 @@ const CPUInfo: React.FC = () => { const [isLoading, setIsLoading] = useState(true) const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState([]) - const defaultSeriesOption: BarSeriesOption = { - type: 'bar', - stack: 'total', - itemStyle: { - color: (params) => { - switch (params.seriesName) { - case 'idle': - return '#F5F5F5' - default: - return params.color ?? echarts.color.random() - } - } - }, + const cpuDefaultSeriesOption: BarSeriesOption = { + ...barDefaultSeriesOption, tooltip: { valueFormatter: (value) => `${((value as number) * 100).toFixed(2)}%` } } useUpdatedEffect(() => { - const intervalId = setInterval(() => getCpuInfo(), 2000) + const intervalId = setInterval(getCpuInfo(), 2000) const handleOnWindowResize = () => { setTimeout(() => { @@ -216,30 +251,6 @@ const CPUInfo: React.FC = () => { } }, []) - const cpuInfoEChartBaseOption: EChartsOption = { - tooltip: {}, - xAxis: { - show: false - }, - yAxis: { - axisLine: { - show: false - }, - axisLabel: { - show: false - }, - axisTick: { - show: false - }, - splitLine: { - show: false - }, - axisPointer: { - show: false - } - } - } - const getCpuInfo = () => { void r_sys_statistics_cpu().then((res) => { const response = res.data @@ -258,9 +269,9 @@ const CPUInfo: React.FC = () => { setCpuInfoEChartsOption( dataList.map((value, index) => ({ - ...cpuInfoEChartBaseOption, + ...eChartsBaseOption, yAxis: { - ...cpuInfoEChartBaseOption.yAxis, + ...eChartsBaseOption.yAxis, data: [index === 0 ? '总占用' : `CPU ${index - 1}`] }, series: value @@ -299,13 +310,15 @@ const CPUInfo: React.FC = () => { } } }) + + return getCpuInfo } const cpuInfoVoToCpuInfoData = (cpuInfoVo: CpuInfoVo) => Object.entries(cpuInfoVo) .filter(([key]) => !['total', 'processors'].includes(key)) .map(([key, value]) => ({ - ...defaultSeriesOption, + ...cpuDefaultSeriesOption, name: key, data: [(value as number) / cpuInfoVo.total] })) @@ -347,17 +360,198 @@ const CPUInfo: React.FC = () => { } const MemoryInfo: React.FC = () => { - return ( - <> - - - ) -} + const percentDivRef = useRef(null) + const memoryInfoDivRef = useRef(null) + const memoryInfoEChatsRef = useRef([]) + const [isLoading, setIsLoading] = useState(true) + const [memoryInfoEChartsOption, setMemoryInfoEChartsOption] = useState([]) + + const memoryDefaultSeriesOption: BarSeriesOption = { + ...barDefaultSeriesOption, + tooltip: { valueFormatter: (value) => formatByteSize(value as number) } + } + + useUpdatedEffect(() => { + const intervalId = setInterval(getMemoryInfo(), 2000) + + const handleOnWindowResize = () => { + setTimeout(() => { + memoryInfoEChatsRef.current.forEach((value) => value.resize()) + }, 50) + } + + window.addEventListener('resize', handleOnWindowResize) + + return () => { + clearInterval(intervalId) + window.removeEventListener('resize', handleOnWindowResize) + } + }, []) + + const getMemoryInfo = () => { + void r_sys_statistics_memory().then((res) => { + const response = res.data + if (response.success) { + const data = response.data + if (data) { + if (isLoading) { + setIsLoading(false) + } + + setTimeout(() => { + const eEchartsOptions = [ + { + ...eChartsBaseOption, + xAxis: { + ...eChartsBaseOption.xAxis, + max: data.total + }, + yAxis: { + ...eChartsBaseOption.yAxis, + data: ['物理内存'] + }, + series: [ + { + ...memoryDefaultSeriesOption, + name: 'used', + data: [data.total - data.free] + }, + { + ...memoryDefaultSeriesOption, + name: 'free', + data: [data.free] + } + ] + }, + { + ...eChartsBaseOption, + xAxis: { + ...eChartsBaseOption.xAxis, + max: data.virtualMax + }, + yAxis: { + ...eChartsBaseOption.yAxis, + data: ['虚拟'] + }, + series: [ + { + ...memoryDefaultSeriesOption, + name: 'used', + data: [data.virtualInUse] + }, + { + ...memoryDefaultSeriesOption, + name: 'free', + data: [data.virtualMax - data.virtualInUse] + } + ] + }, + { + ...eChartsBaseOption, + xAxis: { + ...eChartsBaseOption.xAxis, + max: data.swapTotal + }, + yAxis: { + ...eChartsBaseOption.yAxis, + data: ['swap'] + }, + series: [ + { + ...memoryDefaultSeriesOption, + name: 'used', + data: [data.swapUsed] + }, + { + ...memoryDefaultSeriesOption, + name: 'free', + data: [data.swapTotal - data.swapUsed] + } + ] + }, + { + ...eChartsBaseOption, + xAxis: { + ...eChartsBaseOption.xAxis, + max: data.jvmTotal + }, + yAxis: { + ...eChartsBaseOption.yAxis, + data: ['jvm 内存'] + }, + series: [ + { + ...memoryDefaultSeriesOption, + name: 'used', + data: [data.jvmTotal - data.jvmFree] + }, + { + ...memoryDefaultSeriesOption, + name: 'free', + data: [data.jvmFree] + } + ] + } + ] + setMemoryInfoEChartsOption(eEchartsOptions) + + if (percentDivRef.current) { + percentDivRef.current.innerHTML = '' + eEchartsOptions.forEach((value) => { + const percentElement = document.createElement('div') + percentElement.innerText = `${( + (value.series[0].data[0] / + (value.series[0].data[0] + value.series[1].data[0])) * + 100 + ).toFixed(2)}%` + + percentDivRef.current?.appendChild(percentElement) + }) + } + + if (!memoryInfoEChatsRef.current.length) { + memoryInfoDivRef.current && (memoryInfoDivRef.current.innerHTML = '') + + eEchartsOptions.forEach(() => { + const element = document.createElement('div') + memoryInfoDivRef.current?.appendChild(element) + memoryInfoEChatsRef.current.push( + echarts.init(element, null, { renderer: 'svg' }) + ) + }) + } + }) + } + } + }) + + return getMemoryInfo + } + + useEffect(() => { + memoryInfoEChatsRef.current?.forEach((value, index) => { + try { + value.setOption(memoryInfoEChartsOption[index]) + } catch (e) { + /* empty */ + } + }) + }, [memoryInfoEChartsOption]) -const JvmInfo: React.FC = () => { return ( <> - + + + +
物理内存
+
虚拟内存
+
swap
+
jvm 内存
+
+ + + +
) } @@ -372,7 +566,6 @@ const System: React.FC = () => { - diff --git a/src/util/common.tsx b/src/util/common.tsx index 099ff6a..1e014ea 100644 --- a/src/util/common.tsx +++ b/src/util/common.tsx @@ -1,5 +1,6 @@ import ReactDOM from 'react-dom/client' import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask' +import { floor } from 'lodash' export const randomInt = (start: number, end: number) => { if (start > end) { @@ -54,3 +55,79 @@ export const removeLoadingMask = (id: string) => { value.parentNode?.removeChild(value) }) } + +export enum ByteUnit { + B = 'B', + KiB = 'KiB', + Mib = 'Mib', + GiB = 'GiB', + TiB = 'TiB', + PiB = 'PiB', + EiB = 'EiB', + ZiB = 'ZiB', + YiB = 'YiB' +} + +export const formatByteSize = (byteSize: number): string => { + const BASE = 1024 + if (byteSize <= -1) { + return byteSize.toString() + } + + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.B) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.KiB) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.Mib) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.GiB) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.TiB) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.PiB) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.EiB) + } + + byteSize /= BASE + if (floor(byteSize / BASE) <= 0) { + return formatByte(byteSize, ByteUnit.ZiB) + } + + byteSize /= BASE + return formatByte(byteSize, ByteUnit.YiB) +} + +const formatByte = (size: number, unit: ByteUnit): string => { + let precision + if ((size * 1000) % 10 > 0) { + precision = 3 + } else if ((size * 100) % 10 > 0) { + precision = 2 + } else if ((size * 10) % 10 > 0) { + precision = 1 + } else { + precision = 0 + } + + return `${size.toFixed(precision)}${unit}` +}