Skip to content

Commit

Permalink
feat: update service monitor overview (#237)
Browse files Browse the repository at this point in the history
  • Loading branch information
xigongdaEricyang authored Apr 13, 2023
1 parent a286e82 commit d70d5f7
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 120 deletions.
63 changes: 29 additions & 34 deletions src/components/DashboardCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { RouteComponentProps, useHistory, withRouter } from 'react-router-dom';
import Icon from '../Icon';
import { trackPageView } from '@/utils/stat';
import './index.less';
Expand All @@ -8,50 +8,45 @@ interface IProps extends RouteComponentProps {
title: React.ElementRef<any>;
children: any;
viewPath?: string;
type?: string;
onConfigPanel?: () => void;
}

class DashboardCard extends React.PureComponent<IProps> {
handleViewDetail = () => {
const { viewPath, type } = this.props;
function DashboardCard(props: IProps) {
const { viewPath, title, children, onConfigPanel } = props;
const history = useHistory();

const handleViewDetail = () => {
if (!viewPath) return;
if (type) {
localStorage.setItem('detailType', type);
}
trackPageView(viewPath);
this.props.history.push(viewPath);
history.push(viewPath);
};

render() {
const { title, children, onConfigPanel, viewPath } = this.props;
return (
<div className="dashboard-card">
<div className="inner">
<div className="header">
<h3>{title}</h3>
{
viewPath && (
<Icon
className="icon-watch blue"
icon="#iconwatch"
onClick={this.handleViewDetail}
/>
)
}
{onConfigPanel && (
return (
<div className="dashboard-card">
<div className="inner">
<div className="header">
<h3>{title}</h3>
{
viewPath && (
<Icon
className="icon-setup blue"
icon="#iconSet_up"
onClick={onConfigPanel}
className="icon-watch blue"
icon="#iconwatch"
onClick={handleViewDetail}
/>
)}
</div>
<div className="content">{children}</div>
)
}
{onConfigPanel && (
<Icon
className="icon-setup blue"
icon="#iconSet_up"
onClick={onConfigPanel}
/>
)}
</div>
<div className="content">{children}</div>
</div>
);
}
</div>
);
}

export default withRouter(DashboardCard);
14 changes: 13 additions & 1 deletion src/config/locale/en-US/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,17 @@
},
"addhostSuccess": "{host} add host success",
"successDelay": "Success, Please refresh the page later to view",
"running": "Running"
"running": "Running",
"panelName": "Panel Name",
"panelNamePlaceholder": "Please enter a panel name",
"panelMetricType": "Metric Type",
"panelMetricModal": {
"PERCENTAGE": "percentage",
"BYTE": "byte",
"BYTE_SECOND": "byte/s",
"DISK_IO_NET": "io/s",
"NUMBER": "number",
"numberSecond": "number/s",
"latency": "us/ms/s"
}
}
14 changes: 13 additions & 1 deletion src/config/locale/zh-CN/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,17 @@
},
"addhostSuccess": "{host} add host 成功",
"successDelay": "操作成功,请稍后刷新页面查看",
"running": "运行中"
"running": "运行中",
"panelName": "面板名称",
"panelNamePlaceholder": "请输入监控面板名称",
"panelMetricType": "监控指标类型",
"panelMetricModal": {
"PERCENTAGE": "百分比",
"BYTE": "字节",
"BYTE_SECOND": "字节/秒",
"DISK_IO_NET": "io/秒",
"NUMBER": "数值",
"numberSecond": "数值/秒",
"latency": "秒/微妙/毫秒"
}
}
124 changes: 89 additions & 35 deletions src/pages/ServiceDashboard/OverviewTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import intl from 'react-intl-universal';
import { ServicePanelType } from './index';

import styles from './index.module.less';
import { BatchQueryItem, ServiceName } from '@/utils/interface';
import { BatchQueryItem, CellHealtyLevel, 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';
import { calcNodeHealty } from '@/utils';

interface OverviewTableData {
serviceName: string;
Expand All @@ -30,7 +31,6 @@ const mapDispatch: any = (_dispatch: any) => ({
});

const mapState = (state: any) => ({
serviceMetric: state.serviceMetric,
ready: state.serviceMetric.ready,
cluster: state.cluster.cluster
});
Expand All @@ -45,11 +45,17 @@ interface IProps

const CellWidth = 150;

const Percent_Range = {
[CellHealtyLevel.normal]: [0, 40],
[CellHealtyLevel.warning]: [40, 80],
[CellHealtyLevel.danger]: [80, 100],
}

const ShowedServices: string[] = [ServiceName.GRAPHD, ServiceName.METAD, ServiceName.STORAGED];

function OverviewTable(props: IProps) {

const { serviceMap, serviceMetric, cluster } = props;
const { serviceMap, cluster } = props;
const [loading, setLoading] = useState<boolean>(false);
const [frequencyValue, setFrequencyValue] = useState<number>(0);
const [dataSource, setDataSource] = useState<OverviewTableData[]>([]);
Expand Down Expand Up @@ -99,7 +105,7 @@ function OverviewTable(props: IProps) {
}
}

const renderCell = (text: string, type: VALUE_TYPE = VALUE_TYPE.byte) => {
const renderCell = (text: string, type: VALUE_TYPE = VALUE_TYPE.byte, shouldCalcNodeHealty: boolean = false) => {
if (!text) return <div className={`${styles.tableCell}`}>-</div>
let showText: string = '';
switch (type) {
Expand All @@ -118,7 +124,12 @@ function OverviewTable(props: IProps) {
default:
break;
}
return <div className={`${styles.tableCell}`}>{showText}</div>
let level: CellHealtyLevel = CellHealtyLevel.normal ;
if (showText?.includes('%')) {
const num = parseFloat(text.slice(0, text.length - 1));
level = calcNodeHealty(num);
}
return <div className={`${styles.tableCell} ${shouldCalcNodeHealty ? styles[level] : undefined}`}>{showText}</div>
}

useEffect(() => {
Expand All @@ -137,19 +148,7 @@ function OverviewTable(props: IProps) {
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
width: 110
},
{
title: intl.get('device.serviceResource.read_bytes_total'),
Expand All @@ -170,6 +169,25 @@ function OverviewTable(props: IProps) {
dataIndex: "open_filedesc_gauge",
ellipsis: true,
render: (text, _) => renderCell(text, VALUE_TYPE.number),
width: 110
},
{
title: intl.get('device.serviceResource.cpu_seconds_total'),
dataIndex: "cpu_seconds_total",
render: (text, _) => renderCell(text, VALUE_TYPE.percentage, true),
width: CellWidth
},
{
title: intl.get('device.serviceResource.memory_bytes_gauge'),
dataIndex: "memory_bytes_gauge",
render: (text, record) => {
const value = getProperByteDesc(parseInt(text)).desc;
const percent = (parseInt(text) / parseInt(record['memory_total']) as any).toFixed(3) * 100;
const level: CellHealtyLevel = calcNodeHealty(percent, Percent_Range);
return (
<div className={`${styles.tableCell} ${styles[level]}`}>{value} ({percent}%)</div>
);
},
width: CellWidth
},
{
Expand All @@ -180,7 +198,7 @@ function OverviewTable(props: IProps) {
const isRunning = text === '1';
return (
<div className={`${styles.tableCell} ${isRunning ? styles.normal : styles.danger}`}>
{isRunning ? intl.get('device.serviceResource.running') : intl.get('device.serviceResource.exit')}
{isRunning ? intl.get('device.serviceResource.running') : intl.get('device.serviceResource.exit')}
</div>
)
},
Expand All @@ -207,31 +225,67 @@ function OverviewTable(props: IProps) {
return queries;
}

const getMachineQueries = (servicNames: string[]) => {
const hosts: string[] = [...new Set(servicNames
.map((service) => service.split('-')[0]))];
const clusterSuffix1 = cluster ? `,${getClusterPrefix()}="${cluster.id}"` : '';
const queries: BatchQueryItem[] = hosts.map(host => {
const instanceSuffix = `instance=~"^${host.replaceAll(".", "\.")}.*"`;
return (
{
refId: `node$${host}$memory_total`,
query: `node_memory_MemTotal_bytes{${instanceSuffix}${clusterSuffix1}}`
}
)
})
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 }));
const machineInfoQueries: BatchQueryItem[] = getMachineQueries(curDataSources.map(item => item.serviceName))
const queries = getQueries();
const data: any = await asyncBatchQueries(queries.concat(machineInfoQueries));
const { results } = data;
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]
})
}
})
if (refId.startsWith('node$')) {
const [_, machineHost, metricName] = refId.split('$');
const metricItems = results[refId].result;
metricItems.forEach(({ metric, value }) => {
const curItems = curDataSources.filter(item => item.serviceName.includes(machineHost));
if (curItems.length) {
curItems.forEach(item => {
item[metricName] = value[1];
})
} else {
curDataSources.push({
serviceName: metric.instanceName,
[metricName]: value[1]
})
}
})
} else {
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);
}
Expand Down
13 changes: 11 additions & 2 deletions src/pages/ServiceDashboard/ServiceOverview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import intl from 'react-intl-universal';
import { connect } from 'react-redux';

import { BatchQueryItem, IServiceMetricItem, ServiceName } from '@/utils/interface';
import ServiceHeader from '@/components/Service/ServiceHeader';
Expand All @@ -12,11 +13,12 @@ import styles from './index.module.less';
import { getClusterPrefix } from '@/utils/promQL';
import { getQueryByMetricType } from '@/utils/metric';
import DashboardCard from '@/components/DashboardCard';
import { connect } from 'react-redux';

import { Spin } from 'antd';
import { DashboardSelect, Option } from '@/components/DashboardSelect';
import EventBus from '@/utils/EventBus';


const mapDispatch: any = (_dispatch: any) => ({
});

Expand All @@ -33,10 +35,11 @@ interface IProps extends ReturnType<typeof mapDispatch>,
panelConfigData: ServicePanelConfig[];
timeRange: TIME_OPTION_TYPE | [number, number];
panelVisible?: boolean;
onEditPanel: (panelItem: ServicePanelConfig) => void;
}

function ServiceOverview(props: IProps) {
const { serviceType, panelConfigData, serviceNames, timeRange, cluster, ready, serviceMetric, panelVisible } = props;
const { serviceType, panelConfigData, serviceNames, timeRange, cluster, ready, serviceMetric, panelVisible, onEditPanel } = props;
const [loading, setLoading] = useState<boolean>(false);
const [frequencyValue, setFrequencyValue] = useState<number>(0);
const [curPanelVisile, setCurPanelVisible] = useState<boolean>(panelVisible || false);
Expand Down Expand Up @@ -164,6 +167,10 @@ function ServiceOverview(props: IProps) {
});
}

const getViewPath = (serviceType: string, serviceName: string, panelName: string) => {
return `/clusters/${cluster.id}/service-metric/${serviceType}/${serviceName}/${encodeURIComponent(panelName)}`;
}

return (
<div className={styles.serviceTableItem}>
<ServiceHeader serviceType={serviceType} title={
Expand Down Expand Up @@ -207,6 +214,8 @@ function ServiceOverview(props: IProps) {
<DashboardCard
title={configItem.title}
key={index}
viewPath={getViewPath(serviceType, curServiceName, configItem.title)}
onConfigPanel={() => onEditPanel(configItem)}
>
<MetricCard
ref={ref => metricRefs[index + 1] = ref}
Expand Down
Loading

0 comments on commit d70d5f7

Please sign in to comment.