add 服务监控

This commit is contained in:
2021-06-25 15:49:06 +08:00
parent a828a62d20
commit 87d303afb6
7 changed files with 569 additions and 14 deletions

View File

@@ -1,5 +1,6 @@
using Furion.RemoteRequest.Extensions; using Furion.RemoteRequest.Extensions;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -26,10 +27,8 @@ namespace Ewide.Core
var ramInfo = GetRamInfo(); var ramInfo = GetRamInfo();
return new return new
{ {
TotalRam = Math.Ceiling(ramInfo.Total / 1024).ToString() + " GB", // 总内存
RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total), // 内存使用率 RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total), // 内存使用率
CpuRate = Math.Ceiling(double.Parse(GetCPURate())), // cpu使用率 CpuRate = Math.Ceiling(double.Parse(GetCPURate())), // cpu使用率
RunTime = GetRunTime()
}; };
} }
@@ -40,20 +39,28 @@ namespace Ewide.Core
public static async Task<dynamic> GetMachineBaseInfo() public static async Task<dynamic> GetMachineBaseInfo()
{ {
var assemblyName = typeof(Furion.App).Assembly.GetName(); var assemblyName = typeof(Furion.App).Assembly.GetName();
//var networkInfo = NetworkInfo.GetNetworkInfo();
//var (Received, Send) = networkInfo.GetInternetSpeed(1000);
return new return new
{ {
WanIp = await GetWanIpFromPCOnline(), // 外网IP WanIp = await GetWanIpFromPCOnline(), // 外网IP
SendAndReceived = "",// "上行" + Math.Round(networkInfo.SendLength / 1024.0 / 1024 / 1024, 2) + "GB 下行" + Math.Round(networkInfo.ReceivedLength / 1024.0 / 1024 / 1024, 2) + "GB", // 上下行流量统计 LanIp = Dns.GetHostAddresses(string.Empty).Last().ToString(), // 局域网IP
LanIp = "",//networkInfo.AddressIpv4.ToString(), // 局域网IP
IpMac = "",//networkInfo.Mac, // Mac地址 IpMac = "",//networkInfo.Mac, // Mac地址
HostName = Environment.MachineName, // HostName HostName = Environment.MachineName, // HostName
CurrentDirectory = Environment.CurrentDirectory, // 系统路径
SystemOs = RuntimeInformation.OSDescription, // 系统名称 SystemOs = RuntimeInformation.OSDescription, // 系统名称
OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构 OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
ProcessorCount = Environment.ProcessorCount.ToString() + "核", // CPU核心数 ProcessorCount = Environment.ProcessorCount, // CPU核心数
FrameworkDescription = RuntimeInformation.FrameworkDescription + " + " + assemblyName.Name.ToString() + assemblyName.Version.ToString(), // .NET和Furion版本 FrameworkDescription = RuntimeInformation.FrameworkDescription + " + " + assemblyName.Name.ToString() + assemblyName.Version.ToString(), // .NET和Furion版本
NetworkSpeed = ""//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度 NetworkSpeed = "",//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度
// Cpu名称
CpuName = GetCPUName(),
// Cpu基准速度
CpuBaseSpeed = GetCPUSpeed(),
// 内存总量
TotalRam = GetRamInfo().Total,
// 运行时间
RunTime = (long)(DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds,
// 磁盘信息
DiskInfo = GetDiskInfo(),
}; };
} }
@@ -94,24 +101,86 @@ namespace Ewide.Core
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux); return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
} }
private static string GetCPUName()
{
string result;
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
result = output.Trim();
}
else
{
var output = ShellUtil.Cmd("wmic", "cpu get Name");
result = output.Replace("Name", string.Empty).Trim();
}
return result;
}
private static string GetCPUSpeed()
{
string result;
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
result = output.Trim();
}
else
{
var output = ShellUtil.Cmd("wmic", "cpu get CurrentClockSpeed");
result = output.Replace("CurrentClockSpeed", string.Empty).Trim();
}
return result;
}
private static List<Dictionary<string, string>> GetDiskInfo()
{
var result = new List<Dictionary<string, string>>();
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
}
else
{
var output = ShellUtil.Cmd("wmic", "LOGICALDISK get Name,Description,FileSystem,Size,FreeSpace");
var strArray = output.Replace("\r", "").Trim().Split('\n')
.Select(p => System.Text.RegularExpressions.Regex.Replace(p.Trim(), @"\s+", ",").Split(','));
var keyArray = strArray.First();
var valueArray = strArray.Where((p, i) => i > 0).ToArray();
foreach(var value in valueArray)
{
var dict = new Dictionary<string, string>();
for(var i = 0;i<keyArray.Length;i++)
{
dict.Add(keyArray[i], value[i]);
}
result.Add(dict);
}
}
return result;
}
/// <summary> /// <summary>
/// 获取CPU使用率 /// 获取CPU使用率
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static string GetCPURate() private static string GetCPURate()
{ {
string cpuRate; string result;
if (IsUnix()) if (IsUnix())
{ {
var output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'"); var output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'");
cpuRate = output.Trim(); result = output.Trim();
} }
else else
{ {
var output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage"); var output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage");
cpuRate = output.Replace("LoadPercentage", string.Empty).Trim(); result = output.Replace("LoadPercentage", string.Empty).Trim();
} }
return cpuRate; return result;
} }
/// <summary> /// <summary>

View File

@@ -139,6 +139,8 @@
} }
} }
&--row-no { &--row-no {
width: 30px !important;
background-color: @table-header-bg; background-color: @table-header-bg;
} }
} }

View File

@@ -0,0 +1,33 @@
import React, { Component } from 'react'
import { Card, Col, Descriptions } from 'antd'
export default class base extends Component {
render() {
const { base } = this.props
const { hostName, systemOs, wanIp, lanIp, osArchitecture, frameworkDescription } = base
return (
<>
<Col span={24}>
<Card bordered={false}>
<Descriptions
column={2}
labelStyle={{ fontSize: '14px', color: 'rgba(0,0,0,.45)' }}
contentStyle={{ fontSize: '14px' }}
>
<Descriptions.Item label="主机名称">{hostName}</Descriptions.Item>
<Descriptions.Item label="操作系统">{systemOs}</Descriptions.Item>
<Descriptions.Item label="外网信息">{wanIp}</Descriptions.Item>
<Descriptions.Item label="内网IP">{lanIp}</Descriptions.Item>
<Descriptions.Item label="系统架构">{osArchitecture}</Descriptions.Item>
<Descriptions.Item label="运行框架">
{frameworkDescription}
</Descriptions.Item>
</Descriptions>
</Card>
</Col>
</>
)
}
}

View File

@@ -0,0 +1,107 @@
import React, { Component } from 'react'
import { Card, Col, Row } from 'antd'
import * as echarts from 'echarts'
export default class diskCharts extends Component {
diskInfo = []
diskChart1 = []
diskChart2 = []
diskChart3 = []
diskChart4 = []
constructor(props) {
super(props)
const { base } = props
this.diskInfo = base.diskInfo
}
componentDidMount() {
this.diskInfo.forEach(({ size, freeSpace }, i) => {
const dom = this.refs[`disk-chart-${i}`]
this[`diskChart${i}`] = echarts.init(dom)
const usedSpace = size - freeSpace
const sizeGB = (size / 1024 / 1024 / 1024).toFixed(1)
const usedGB = (usedSpace / 1024 / 1024 / 1024).toFixed(1)
const freeGB = (freeSpace / 1024 / 1024 / 1024).toFixed(1)
const option = {
tooltip: false,
series: [
{
name: '磁盘使用量',
type: 'pie',
radius: ['70%', '100%'],
label: {
show: true,
fontSize: '14',
position: 'center',
formatter: `${sizeGB} GB`,
},
emphasis: {
label: {
show: true,
fontSize: '14',
formatter: '{b}{c}GB({d}%)',
},
},
labelLine: {
show: false,
},
data: [
{
value: usedGB,
name: '已用',
itemStyle: {
color: usedGB / sizeGB >= 0.85 ? '#ff4d4f' : '#007bff',
},
},
{
value: freeGB,
name: '可用',
itemStyle: { color: '#e0e0e0' },
},
],
hoverAnimation: false,
animation: false,
},
],
}
this[`diskChart${i}`].setOption(option)
})
window.addEventListener('resize', this.onResizeCharts)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResizeCharts)
}
onResizeCharts = () => {
this.diskInfo.forEach((item, i) => {
this[`diskChart${i}`].resize()
})
}
render() {
const { diskInfo } = this
return (
<>
{diskInfo.map((item, i) => (
<Col key={i} xl={6} lg={12}>
<Card bordered={false}>
<div className="h4 mb-md">
{item.description}({item.name})
</div>
<div className="h-200" ref={`disk-chart-${i}`}></div>
</Card>
</Col>
))}
</>
)
}
}

View File

@@ -0,0 +1,44 @@
import React, { Component } from 'react'
import { Card, Col, Descriptions, Row, Statistic } from 'antd'
import { api } from 'common/api'
import { Container } from 'components'
import { isEqual } from 'lodash'
import moment from 'moment'
import Base from './base'
import UseCharts from './use-charts'
import DiskCharts from './disk-charts'
export default class index extends Component {
state = {
loading: true,
base: {},
network: {},
}
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state) || this.props.paneActived !== props.paneActived
}
async componentDidMount() {
const { data: base } = await api.sysMachineBase()
this.setState({ loading: false, base })
}
render() {
const { paneActived } = this.props
const { loading } = this.state
return (
<Container mode="fluid">
<br />
<Row gutter={16}>
{!loading && <Base {...this.state} />}
{!loading && <UseCharts {...this.state} actived={paneActived} />}
{!loading && <DiskCharts {...this.state} />}
</Row>
</Container>
)
}
}

View File

@@ -0,0 +1,297 @@
import React, { Component } from 'react'
import { Card, Col, Descriptions, Row, Statistic } from 'antd'
import * as echarts from 'echarts'
import moment from 'moment'
import { api } from 'common/api'
export default class useCharts extends Component {
state = {
use: {},
nowMoment: moment(),
}
timer = null
timerMoment = null
systemStart = moment()
now = Date.now()
cpuChart = null
cpuData = []
ramChart = null
ramData = []
shouldComponentUpdate(props) {
// 当前页签未选中时停止获取状态
if (this.props.actived !== props.actived) {
if (props.actived) {
this.start()
} else {
this.stop()
}
}
return true
}
componentDidMount() {
this.systemStart = moment().add(-this.props.base.runTime)
this.initCpuChart()
this.initRamChart()
this.start()
window.addEventListener('resize', this.onResizeCharts)
}
componentWillUnmount() {
this.stop()
window.removeEventListener('resize', this.onResizeCharts)
}
start() {
this.timer = setInterval(() => {
this.refreshData()
}, 3000)
this.refreshData()
this.timerMoment = setInterval(() => {
this.setState({ nowMoment: moment() })
}, 1000)
}
stop() {
clearInterval(this.timer)
clearInterval(this.timerMoment)
}
async refreshData() {
const { data: use } = await api.sysMachineUse()
this.now = Date.now()
this.cpuData.shift()
this.cpuData.push({
name: this.now,
value: [this.now, use.cpuRate],
})
this.cpuChart.setOption({
series: [{ data: this.cpuData }],
})
this.ramData.shift()
this.ramData.push({
name: this.now,
value: [this.now, use.ramRate],
})
this.ramChart.setOption({
series: [{ data: this.ramData }],
})
this.setState({ use })
}
initCpuChart() {
for (let i = 0; i < 60; i++) {
const past = this.now - (60 - i) * 1000
this.cpuData.push({
name: past,
value: [past, -1],
})
}
const dom = this.refs['cpu-chart']
this.cpuChart = echarts.init(dom)
const option = {
grid: {
show: true,
top: 0,
left: 0,
right: 0,
bottom: 0,
borderColor: 'rgba(0, 123, 255, 1)',
borderWidth: 2,
zlevel: 2,
},
tooltip: false,
xAxis: {
type: 'time',
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
axisLine: {
show: false,
},
},
yAxis: {
type: 'value',
max: 100,
min: 0,
axisLabel: {
show: false,
},
},
series: [
{
type: 'line',
showSymbol: false,
hoverAnimation: false,
animation: false,
data: this.cpuData,
lineStyle: {
width: 1,
color: 'rgba(0, 123, 255, .8)',
},
areaStyle: {
color: 'rgba(0, 123, 255, .3)',
},
},
],
}
this.cpuChart.setOption(option)
}
initRamChart() {
for (let i = 0; i < 60; i++) {
const past = this.now - (60 - i) * 1000
this.ramData.push({
name: past,
value: [past, -1],
})
}
const dom = this.refs['ram-chart']
this.ramChart = echarts.init(dom)
const option = {
grid: {
show: true,
top: 0,
left: 0,
right: 0,
bottom: 0,
borderColor: 'rgba(83, 29, 171, 1)',
borderWidth: 2,
zlevel: 2,
},
tooltip: false,
xAxis: {
type: 'time',
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
axisLine: {
show: false,
},
},
yAxis: {
type: 'value',
max: 100,
min: 0,
axisLabel: {
show: false,
},
},
series: [
{
type: 'line',
showSymbol: false,
hoverAnimation: false,
animation: false,
data: this.ramData,
lineStyle: {
width: 1,
color: 'rgba(83, 29, 171, .8)',
},
areaStyle: {
color: 'rgba(83, 29, 171, .3)',
},
},
],
}
this.ramChart.setOption(option)
}
onResizeCharts = () => {
this.cpuChart.resize()
this.ramChart.resize()
}
render() {
const { base } = this.props
const { use, nowMoment } = this.state
const { cpuName, cpuBaseSpeed, processorCount, totalRam } = base
const { cpuRate, ramRate } = use
const diffDays = nowMoment.diff(this.systemStart, 'days')
const diff =
diffDays + ':' + moment(nowMoment.diff(this.systemStart)).utc().format('HH:mm:ss')
return (
<>
<Col xl={12} lg={24}>
<Card bordered={false}>
<Row align="bottom" justify="space-between">
<div className="h2">CPU</div>
<div className="h5">{cpuName}</div>
</Row>
<div className="h-200 mt-md mb-md" ref="cpu-chart"></div>
<Row>
<Col flex="1">
<Statistic title="使用率" value={(cpuRate || 0) + ' %'} />
<Statistic title="正常运行时间" value={diff} />
</Col>
<Col flex="1">
<Descriptions column={1}>
<Descriptions.Item label="基准速度">
<b>{((cpuBaseSpeed || 0) / 1000).toFixed(2)} GHz</b>
</Descriptions.Item>
<Descriptions.Item label="逻辑处理器">
<b>{processorCount || 0}</b>
</Descriptions.Item>
</Descriptions>
</Col>
</Row>
</Card>
</Col>
<Col xl={12} lg={24}>
<Card bordered={false}>
<Row align="bottom" justify="space-between">
<div className="h2">内存</div>
<div className="h5">{((totalRam || 0) / 1024).toFixed(1)} GB</div>
</Row>
<div className="h-200 mt-md mb-md" ref="ram-chart"></div>
<Row>
<Col flex="1">
<Statistic
title="使用中"
value={
(((totalRam || 0) / 1024) * ((ramRate || 1) / 100)).toFixed(
1
) + ' GB'
}
/>
<Statistic
title="可用"
value={
(
((totalRam || 0) / 1024) *
(1 - (ramRate || 1) / 100)
).toFixed(1) + ' GB'
}
/>
</Col>
</Row>
</Card>
</Col>
</>
)
}
}

View File

@@ -113,13 +113,15 @@ export default class index extends Component {
render() { render() {
this.panes = [] this.panes = []
const { actived } = this.state
return ( return (
<Layout.Content> <Layout.Content>
<div className="yo-tab-external-mount"> <div className="yo-tab-external-mount">
<Tabs <Tabs
type="editable-card" type="editable-card"
hideAdd hideAdd
activeKey={this.state.actived} activeKey={actived}
onChange={activeKey => this.onChange(activeKey)} onChange={activeKey => this.onChange(activeKey)}
onEdit={(targetKey, action) => this.onClose(targetKey, action)} onEdit={(targetKey, action) => this.onClose(targetKey, action)}
> >
@@ -183,7 +185,7 @@ export default class index extends Component {
<div <div
key={pane.key} key={pane.key}
className={ className={
(pane.key === this.state.actived (pane.key === actived
? 'yo-tab-external-tabpane-active' ? 'yo-tab-external-tabpane-active'
: 'yo-tab-external-tabpane-inactive') + : 'yo-tab-external-tabpane-inactive') +
' yo-tab-external-tabpane' ' yo-tab-external-tabpane'
@@ -194,6 +196,7 @@ export default class index extends Component {
id={pane.key} id={pane.key}
key={pane.key} key={pane.key}
param={pane.param} param={pane.param}
paneActived={pane.key === actived}
onRef={p => this.panes.push(p)} onRef={p => this.panes.push(p)}
/> />
</div> </div>