diff --git a/src/config/locale/en-US/device.json b/src/config/locale/en-US/device.json index 5fe217d9..1dd3f159 100644 --- a/src/config/locale/en-US/device.json +++ b/src/config/locale/en-US/device.json @@ -44,5 +44,18 @@ "singleNodeTitle": "Single Node Monitor", "waterLevel": "Resrouce water level", "core": "Core" - } + }, + "serviceResource": { + "singleServiceTitle": "Single Service Monitor", + "serviceName": "Service Name", + "context_switches_total": "Context Switch", + "cpu_seconds_total": "CPU Seconds", + "memory_bytes_gauge": "Memory Used", + "read_bytes_total": "Read Bytes", + "write_bytes_total": "Write Bytes", + "open_filedesc_gauge": "Open Files", + "process_count": "Status", + "running": "Running", + "exit": "Exited" + } } diff --git a/src/config/locale/zh-CN/device.json b/src/config/locale/zh-CN/device.json index cffe9362..64624c38 100644 --- a/src/config/locale/zh-CN/device.json +++ b/src/config/locale/zh-CN/device.json @@ -44,5 +44,18 @@ "singleNodeTitle": "单节点监控", "waterLevel": "资源水位", "core": "核" - } + }, + "serviceResource": { + "singleServiceTitle": "单服务监控", + "serviceName": "服务名", + "context_switches_total": "上下文切换", + "cpu_seconds_total": "CPU使用时间", + "memory_bytes_gauge": "内存使用量", + "read_bytes_total": "磁盘读取", + "write_bytes_total": "磁盘写入", + "open_filedesc_gauge": "文件句柄数", + "process_count": "服务状态", + "running": "运行中", + "exit": "已停止" + } } diff --git a/src/pages/MachineDashboard/index.module.less b/src/pages/MachineDashboard/index.module.less index 372adcd8..cc51a343 100644 --- a/src/pages/MachineDashboard/index.module.less +++ b/src/pages/MachineDashboard/index.module.less @@ -25,7 +25,7 @@ } .instanceSelect { - margin-left: 10px; + margin-left: 14px; height: 34px; :global { @@ -73,6 +73,7 @@ font-size: 16px; line-height: 22px; color: #000000; + display: flex; } .action { diff --git a/src/pages/MachineDashboard/index.tsx b/src/pages/MachineDashboard/index.tsx index fe8b4386..10ca1795 100644 --- a/src/pages/MachineDashboard/index.tsx +++ b/src/pages/MachineDashboard/index.tsx @@ -210,9 +210,8 @@ function MachineDashboard(props: IProps) {
-
{intl.get('device.nodeResource.singleNodeTitle')}
-
- +
+ {intl.get('device.nodeResource.singleNodeTitle')} { instanceList.map((instance: string) => ( @@ -221,6 +220,9 @@ function MachineDashboard(props: IProps) { }
+
+ +
diff --git a/src/pages/ServiceDashboard/OverviewTable.tsx b/src/pages/ServiceDashboard/OverviewTable.tsx new file mode 100644 index 00000000..349de21b --- /dev/null +++ b/src/pages/ServiceDashboard/OverviewTable.tsx @@ -0,0 +1,253 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Table, TableColumnType } from 'antd'; +import intl from 'react-intl-universal'; + +import { ServicePanelType } from './index'; + +import styles from './index.module.less'; +import { BatchQueryItem, ServiceName } from '@/utils/interface'; +import { connect } from 'react-redux'; +import { getClusterPrefix, VALUE_TYPE } from '@/utils/promQL'; +import { asyncBatchQueries } from '@/requests'; +import { getAutoLatency, getProperByteDesc } from '@/utils/dashboard'; +import EventBus from '@/utils/EventBus'; + +interface OverviewTableData { + serviceName: string; +} + +const metrics = [ + 'context_switches_total', + 'cpu_seconds_total', + 'memory_bytes_gauge', + 'read_bytes_total', + 'write_bytes_total', + 'open_filedesc_gauge', + 'count', +] + +const mapDispatch: any = (_dispatch: any) => ({ +}); + +const mapState = (state: any) => ({ + serviceMetric: state.serviceMetric, + ready: state.serviceMetric.ready, + cluster: state.cluster.cluster +}); + +interface IProps + extends ReturnType, + ReturnType { + // resourceInfos: NodeResourceInfo[]; + // loading: boolean; + serviceMap: ServicePanelType; +} + +const CellWidth = 150; + +const ShowedServices: string[] = [ServiceName.GRAPHD, ServiceName.METAD, ServiceName.STORAGED]; + +function OverviewTable(props: IProps) { + + const { serviceMap, serviceMetric, cluster } = props; + const [loading, setLoading] = useState(false); + const [frequencyValue, setFrequencyValue] = useState(0); + const [dataSource, setDataSource] = useState([]); + const pollingTimerRef = useRef(null); + + useEffect(() => { + const changeListener = (data) => { + const { value } = data.detail; + setFrequencyValue(value); + } + const freshListener = () => { handleRefresh(); } + EventBus.getInstance().on('serviceOverview_change', changeListener); + + EventBus.getInstance().on('serviceOverview_fresh', freshListener); + + return () => { + EventBus.getInstance().off('serviceOverview_change', changeListener); + EventBus.getInstance().off('serviceOverview_fresh', freshListener); + } + }, [cluster, serviceMap]); + + useEffect(() => { + if (pollingTimerRef.current) { + clearPolling(); + } + if (frequencyValue > 0) { + pollingData(); + } + }, [frequencyValue]) + + const handleRefresh = () => { + asyncGetServiceOverviewData(true); + } + + const clearPolling = () => { + if (pollingTimerRef.current) { + clearInterval(pollingTimerRef.current); + } + }; + + const pollingData = () => { + asyncGetServiceOverviewData(false); + if (frequencyValue > 0) { + pollingTimerRef.current = setInterval(() => { + asyncGetServiceOverviewData(false); + }, frequencyValue); + } + } + + const renderCell = (text: string, type: VALUE_TYPE = VALUE_TYPE.byte) => { + if (!text) return
-
+ let showText: string = ''; + switch (type) { + case VALUE_TYPE.number: + showText = text; + break; + case VALUE_TYPE.percentage: + showText = `${text}%`; + break; + case VALUE_TYPE.byte: + showText = getProperByteDesc(parseInt(text)).desc; + break; + case VALUE_TYPE.latency: + showText = getAutoLatency(parseInt(text)); + break; + default: + break; + } + return
{showText}
+ } + + useEffect(() => { + if (cluster?.id) { + asyncGetServiceOverviewData(true); + } + }, [cluster, serviceMap]) + + const columns: TableColumnType[] = [ + { + title: intl.get('device.serviceResource.serviceName'), + dataIndex: "serviceName", + render: (text, _) =>
{text || '-'}
, + }, + { + title: intl.get('device.serviceResource.context_switches_total'), + dataIndex: "context_switches_total", + render: (text, _) => renderCell(text, VALUE_TYPE.number), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.cpu_seconds_total'), + dataIndex: "cpu_seconds_total", + render: (text, _) => renderCell(text, VALUE_TYPE.percentage), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.memory_bytes_gauge'), + dataIndex: "memory_bytes_gauge", + render: (text, _) => renderCell(text), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.read_bytes_total'), + dataIndex: "read_bytes_total", + ellipsis: true, + render: (text, _) => renderCell(text), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.write_bytes_total'), + dataIndex: "write_bytes_total", + ellipsis: true, + render: (text, _) => renderCell(text), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.open_filedesc_gauge'), + dataIndex: "open_filedesc_gauge", + ellipsis: true, + render: (text, _) => renderCell(text, VALUE_TYPE.number), + width: CellWidth + }, + { + title: intl.get('device.serviceResource.process_count'), + dataIndex: "count", + ellipsis: true, + render: (text, _) => { + const isRunning = text === '1'; + return ( +
+ {isRunning ? intl.get('device.serviceResource.running') : intl.get('device.serviceResource.exit')} +
+ ) + }, + width: CellWidth + }, + ] + + const getQueries = () => { + if (!cluster?.id) return []; + const clusterSuffix1 = `{${getClusterPrefix()}="${cluster.id}"}`; + const queries: BatchQueryItem[] = []; + ShowedServices.forEach((service) => { + metrics.forEach((metric) => { + let query = `nebula_${service}_${metric}${clusterSuffix1} - 0`; + if (metric === 'cpu_seconds_total') { + query = `avg by (instanceName) (irate(nebula_${service}_${metric}${clusterSuffix1}[30s])) * 100` + } + queries.push({ + refId: `${service}$${metric}`, + query, + }) + }) + }) + return queries; + } + + const asyncGetServiceOverviewData = async (shouldLoading?: boolean) => { + if (!serviceMap[ServiceName.GRAPHD]) return; + shouldLoading && setLoading(true); + const queries = getQueries(); + const data: any = await asyncBatchQueries(queries); + const { results } = data; + const curDataSources: OverviewTableData[] = serviceMap[ServiceName.GRAPHD] + .concat(serviceMap[ServiceName.METAD]) + .concat(serviceMap[ServiceName.STORAGED]) + .map(item => ({ serviceName: item })); + Object.keys(results).forEach(refId => { + const [_serviceType, metricName] = refId.split('$'); + const metricItems = results[refId].result; + metricItems.forEach(({ metric, value }) => { + const curItem = curDataSources.find(item => item.serviceName === metric.instanceName); + if (curItem) { + curItem[metricName] = value[1]; + } else { + curDataSources.push({ + serviceName: metric.instanceName, + [metricName]: value[1] + }) + } + }) + }); + setLoading(false); + setDataSource(curDataSources); + } + + return ( + + ); +} + +export default connect(mapState, mapDispatch)(OverviewTable); diff --git a/src/pages/ServiceDashboard/ServiceOverview/index.module.less b/src/pages/ServiceDashboard/ServiceOverview/index.module.less index 7ffd9c3e..c5edfede 100644 --- a/src/pages/ServiceDashboard/ServiceOverview/index.module.less +++ b/src/pages/ServiceDashboard/ServiceOverview/index.module.less @@ -3,7 +3,7 @@ // border: 1px solid #D9D9D9; background-color: #fff; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.08); - margin-top: 24px; + // margin-top: 24px; } .headerTitle { diff --git a/src/pages/ServiceDashboard/ServiceOverview/index.tsx b/src/pages/ServiceDashboard/ServiceOverview/index.tsx index 2fd35b4b..82fc9235 100644 --- a/src/pages/ServiceDashboard/ServiceOverview/index.tsx +++ b/src/pages/ServiceDashboard/ServiceOverview/index.tsx @@ -23,12 +23,12 @@ const mapDispatch: any = (_dispatch: any) => ({ const mapState = (state: any) => ({ serviceMetric: state.serviceMetric, ready: state.serviceMetric.ready, + cluster: state.cluster.cluster }); interface IProps extends ReturnType, ReturnType { serviceType: ServiceName; - cluster: any; serviceNames: string[]; panelConfigData: ServicePanelConfig[]; timeRange: TIME_OPTION_TYPE | [number, number]; @@ -110,7 +110,11 @@ function ServiceOverview(props: IProps) { if (aggregation === AggregationType.Sum && !metricItem.isRawMetric) { query = `sum_over_time(${query}{instanceName="${curServiceName}"${clusterSuffix1}}[${15}s])`; } else { - query = `${query}{instanceName="${curServiceName}"${clusterSuffix1}}`; + if (query.includes('cpu_seconds_total')) { + query = `avg by (instanceName) (irate(${query}{instanceName="${curServiceName}"${clusterSuffix1}}[30s])) * 100` + } else { + query = `${query}{instanceName="${curServiceName}"${clusterSuffix1}}`; + } } return { refId: queryItem.query, @@ -214,4 +218,4 @@ function ServiceOverview(props: IProps) { ) } -export default connect(mapState, mapDispatch)(ServiceOverview);; \ No newline at end of file +export default connect(mapState, mapDispatch)(ServiceOverview); \ No newline at end of file diff --git a/src/pages/ServiceDashboard/defaultPanelConfig.ts b/src/pages/ServiceDashboard/defaultPanelConfig.ts index 286c8bdb..6f25345f 100644 --- a/src/pages/ServiceDashboard/defaultPanelConfig.ts +++ b/src/pages/ServiceDashboard/defaultPanelConfig.ts @@ -107,7 +107,7 @@ export const defaultServicePanelConfigData: ServicePanelConfigItem[] = [ }, { title: intl.get('metric_description.process_cpu'), - valueType: VALUE_TYPE.byte, + valueType: VALUE_TYPE.percentage, queries: [ { refId: 'cpu_seconds_total', @@ -216,7 +216,7 @@ export const defaultServicePanelConfigData: ServicePanelConfigItem[] = [ }, { title: intl.get('metric_description.process_cpu'), - valueType: VALUE_TYPE.byte, + valueType: VALUE_TYPE.percentage, queries: [ { refId: 'cpu_seconds_total', @@ -403,7 +403,7 @@ export const defaultServicePanelConfigData: ServicePanelConfigItem[] = [ }, { title: intl.get('metric_description.process_cpu'), - valueType: VALUE_TYPE.byte, + valueType: VALUE_TYPE.percentage, queries: [ { refId: 'cpu_seconds_total', diff --git a/src/pages/ServiceDashboard/index.module.less b/src/pages/ServiceDashboard/index.module.less index 2975b13d..c90bd35c 100644 --- a/src/pages/ServiceDashboard/index.module.less +++ b/src/pages/ServiceDashboard/index.module.less @@ -26,9 +26,10 @@ } .singelNodeMonitor { - background-color: #fff; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.08); - padding: 16px; + // background-color: #fff; + // box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.08); + // padding: 16px; + margin-top: 16px; } .singelNodeMonitorHeader { @@ -50,8 +51,56 @@ align-items: center; } +.servicePanel { + &:not(:first-child) { + margin-top: 16px; + } +} + .addPanelBtn { display: flex; align-items: center; margin-left: 10px; } + +.overviewTable { + :global { + .ant-table-tbody { + font-size: 80%; + } + + .ant-table-cell{ + padding: 0; + padding-right: 2px; + padding-bottom: 1px; + } + + .ant-table-thead .ant-table-cell { + padding: 16px !important; + } + } + + .tableCell { + width: 100%; + height: 100%; + padding: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .normal { + background-color:rgba(50, 172, 45, 0.97); + color: #fff; + } + + .warning { + background-color:rgba(237, 129, 40, 0.89); + color: #fff; + } + + .danger { + background-color:rgba(245, 54, 54, 0.9); + color: #fff; + } +} \ No newline at end of file diff --git a/src/pages/ServiceDashboard/index.tsx b/src/pages/ServiceDashboard/index.tsx index 627257e2..291a2749 100644 --- a/src/pages/ServiceDashboard/index.tsx +++ b/src/pages/ServiceDashboard/index.tsx @@ -13,6 +13,7 @@ import { defaultServicePanelConfigData } from './defaultPanelConfig'; import ServiceOverview from './ServiceOverview'; import styles from './index.module.less'; +import OverviewTable from './OverviewTable'; const mapDispatch: any = (_dispatch: any) => ({ }); @@ -36,6 +37,10 @@ const ServicePanels = [ ClusterServiceNameMap[ServiceName.Drainer], ] +export type ServicePanelType = { + [key in typeof ServicePanels[number]]: string[]; +}; + function ServiceDashboard(props: IProps) { const { cluster } = props; @@ -45,7 +50,7 @@ function ServiceDashboard(props: IProps) { setTimeRange(value); } - const [serviceNames, setServiceNames] = useState([]); + const [curServiceMap, setCurServiceMap] = useState({}); useEffect(() => { if (cluster.id) { @@ -54,20 +59,21 @@ function ServiceDashboard(props: IProps) { }, [cluster]) const getServiceNames = () => { - let services: string[] = []; + const serviceTypeMap: ServicePanelType = {}; ServicePanels.forEach((panel: string) => { - services = services.concat(cluster[panel].map(i => i.name)) + serviceTypeMap[panel] = (cluster[panel] || []).map(i => i.name); + // serviceTypeMap = services.concat(cluster[panel].map(i => i.name)) }); - setServiceNames(services); + setCurServiceMap(serviceTypeMap); } - - return (
+ {/* @ts-ignore */} +
-
{intl.get('device.nodeResource.singleNodeTitle')}
+
{intl.get('device.serviceResource.singleServiceTitle')}
- s.includes(ServiceName.GRAPHD))} - timeRange={timeRange} - serviceType={ServiceName.GRAPHD} - panelVisible - panelConfigData={defaultServicePanelConfigData.find(item => item.type === ServiceName.GRAPHD)?.panels || []} - /> - s.includes(ServiceName.METAD))} - timeRange={timeRange} - serviceType={ServiceName.METAD} - panelConfigData={defaultServicePanelConfigData.find(item => item.type === ServiceName.METAD)?.panels || []} - /> - s.includes(ServiceName.STORAGED))} - timeRange={timeRange} - serviceType={ServiceName.STORAGED} - panelConfigData={defaultServicePanelConfigData.find(item => item.type === ServiceName.STORAGED)?.panels || []} - /> +
+ item.type === ServiceName.GRAPHD)?.panels || []} + /> +
+
+ item.type === ServiceName.METAD)?.panels || []} + /> +
+
+ item.type === ServiceName.STORAGED)?.panels || []} + /> +
); } diff --git a/src/utils/promQL.ts b/src/utils/promQL.ts index d49582e0..51175040 100644 --- a/src/utils/promQL.ts +++ b/src/utils/promQL.ts @@ -300,19 +300,19 @@ export const getMachineMetricData = (instance, cluster) => { queries: [ { refId: 'cpu_total_used', - query: `(1 - avg(rate(node_cpu_seconds_total{mode="idle"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance))*100`, + query: `(1 - avg(irate(node_cpu_seconds_total{mode="idle"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance))*100`, }, { refId: 'cpu_system_used', - query: `avg(rate(node_cpu_seconds_total{mode="system"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, + query: `avg(irate(node_cpu_seconds_total{mode="system"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, }, { refId: 'cpu_user_used', - query: `avg(rate(node_cpu_seconds_total{mode="user"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, + query: `avg(irate(node_cpu_seconds_total{mode="user"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, }, { refId: 'cpu_io_wait_used', - query: `avg(rate(node_cpu_seconds_total{mode="iowait"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, + query: `avg(irate(node_cpu_seconds_total{mode="iowait"${clusterSuffix1},${instanceSuffix}}[30s])) by (instance) *100`, } ] },