Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update service monitor overview #237

Merged
merged 1 commit into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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