|
@@ -1,73 +1,272 @@
|
|
import React from 'react'
|
|
import React from 'react'
|
|
import ReactECharts from 'echarts-for-react'
|
|
import ReactECharts from 'echarts-for-react'
|
|
import * as echarts from 'echarts'
|
|
import * as echarts from 'echarts'
|
|
-const Page = (props) => {
|
|
|
|
- const [cross, setCross] = React.useState([])
|
|
|
|
- const [vertical, setVertical] = React.useState([])
|
|
|
|
|
|
+import { get } from 'lodash'
|
|
|
|
+import s from './index.less'
|
|
|
|
+import { Descriptions, Divider } from 'antd'
|
|
|
|
+const Page = props => {
|
|
|
|
+ const [data, setData] = React.useState({})
|
|
|
|
|
|
React.useEffect(() => {
|
|
React.useEffect(() => {
|
|
- const { pressureData = {} } = props
|
|
|
|
- setCross(pressureData.cross)
|
|
|
|
- setVertical(pressureData.vertical)
|
|
|
|
|
|
+ const { data = {} } = props
|
|
|
|
+ setData(data)
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
- const options = {
|
|
|
|
- tooltip: {
|
|
|
|
- trigger: 'axis',
|
|
|
|
- position: function (pt) {
|
|
|
|
- return [pt[0], '10%']
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- title: {
|
|
|
|
- left: 'center',
|
|
|
|
- text: '压测报告界面折线图'
|
|
|
|
- },
|
|
|
|
- toolbox: {
|
|
|
|
- feature: {
|
|
|
|
- saveAsImage: {}
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- xAxis: {
|
|
|
|
- type: 'category',
|
|
|
|
- boundaryGap: false,
|
|
|
|
- data: cross
|
|
|
|
- },
|
|
|
|
- yAxis: {
|
|
|
|
- type: 'value',
|
|
|
|
- boundaryGap: [0, '100%']
|
|
|
|
- },
|
|
|
|
- dataZoom: [{
|
|
|
|
- type: 'inside',
|
|
|
|
- start: 0,
|
|
|
|
- end: 100
|
|
|
|
- }, {
|
|
|
|
- start: 0,
|
|
|
|
- end: 100
|
|
|
|
- }],
|
|
|
|
- series: [
|
|
|
|
- {
|
|
|
|
- name: '数据',
|
|
|
|
- type: 'line',
|
|
|
|
- symbol: 'none',
|
|
|
|
- sampling: 'lttb',
|
|
|
|
- itemStyle: {
|
|
|
|
- color: 'rgb(255, 70, 131)'
|
|
|
|
|
|
+ const {
|
|
|
|
+ // saveAsImage = {
|
|
|
|
+ // icon:
|
|
|
|
+ // 'image://https://cdn.weipaitang.com/static/public/20210824acf87486-499f-7486499f-1d32-40d793b1fd05.svg'
|
|
|
|
+ // }
|
|
|
|
+ saveAsImage = false,
|
|
|
|
+ showTitle = true,
|
|
|
|
+ showLine = false
|
|
|
|
+ } = props
|
|
|
|
+
|
|
|
|
+ function getOptions (title, xData = [], yData = [], {
|
|
|
|
+ yFormat = '{value}',
|
|
|
|
+ tooltipFormat,
|
|
|
|
+ yMax,
|
|
|
|
+ showAreaStyle
|
|
|
|
+ } = {}) {
|
|
|
|
+ return {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ position: function (pt) {
|
|
|
|
+ return [pt[0], '10%']
|
|
},
|
|
},
|
|
- areaStyle: {
|
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
|
|
|
- offset: 0,
|
|
|
|
- color: 'rgb(255, 158, 68)'
|
|
|
|
- }, {
|
|
|
|
- offset: 1,
|
|
|
|
- color: 'rgb(255, 70, 131)'
|
|
|
|
- }])
|
|
|
|
|
|
+ formatter: function (params) {
|
|
|
|
+ let rez = `<div>${params[0].axisValue}</div>`
|
|
|
|
+ params.forEach(item => {
|
|
|
|
+ const strItem = `<div style="display: flex; justify-content: space-between;">
|
|
|
|
+ <div style="padding-right: 10px">
|
|
|
|
+ ${item.marker} ${item.seriesName}
|
|
|
|
+ </div>
|
|
|
|
+ <div style="color: #666;">
|
|
|
|
+ ${tooltipFormat ? tooltipFormat(item.value) : item.value}
|
|
|
|
+ </div>
|
|
|
|
+ </div>`
|
|
|
|
+ rez += strItem
|
|
|
|
+ })
|
|
|
|
+ return rez
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '60px',
|
|
|
|
+ right: '28px',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ title: {
|
|
|
|
+ left: 'center',
|
|
|
|
+ text: title,
|
|
|
|
+ textStyle: {
|
|
|
|
+ fontSize: 16,
|
|
|
|
+ fontWeight: 'normal',
|
|
|
|
+ color: '#666'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ toolbox: {
|
|
|
|
+ feature: {
|
|
|
|
+ saveAsImage: saveAsImage
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ boundaryGap: false,
|
|
|
|
+ data: xData
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ boundaryGap: [0, '100%'],
|
|
|
|
+ axisLabel: {
|
|
|
|
+ show: true,
|
|
|
|
+ interval: 'auto',
|
|
|
|
+ formatter: yFormat
|
|
},
|
|
},
|
|
- data: vertical
|
|
|
|
|
|
+ max: yMax
|
|
|
|
+ },
|
|
|
|
+ dataZoom: [
|
|
|
|
+ {
|
|
|
|
+ type: 'inside',
|
|
|
|
+ start: 0,
|
|
|
|
+ end: 100
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ start: 0,
|
|
|
|
+ end: 100
|
|
|
|
+ }
|
|
|
|
+ ],
|
|
|
|
+ series: yData.map((yItem, index) => {
|
|
|
|
+ const h = ((yData.length - index) * (255 / yData.length)) >> 0
|
|
|
|
+ return {
|
|
|
|
+ name: yItem.name,
|
|
|
|
+ type: 'line',
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ sampling: 'lttb',
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: yData.length === 1 ? 'rgb(255, 70, 131)' : `hsl(${h}, 100%, 64%)`
|
|
|
|
+ },
|
|
|
|
+ ...(
|
|
|
|
+ showAreaStyle ? {
|
|
|
|
+ areaStyle: {
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
+ {
|
|
|
|
+ offset: 0,
|
|
|
|
+ color: 'rgb(255, 158, 68)'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ offset: 1,
|
|
|
|
+ color: 'rgb(255, 70, 131)'
|
|
|
|
+ }
|
|
|
|
+ ])
|
|
|
|
+ }
|
|
|
|
+ } : {}
|
|
|
|
+ ),
|
|
|
|
+ data: yItem.data
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function getSeriesYData (data, format) {
|
|
|
|
+ const _data = data
|
|
|
|
+ return Object.keys(_data).map(key => {
|
|
|
|
+ return {
|
|
|
|
+ name: key,
|
|
|
|
+ data: _data[key].vertical.map(item => {
|
|
|
|
+ if (format) {
|
|
|
|
+ return format(item)
|
|
|
|
+ } else {
|
|
|
|
+ return item
|
|
|
|
+ }
|
|
|
|
+ })
|
|
}
|
|
}
|
|
- ]
|
|
|
|
|
|
+ })
|
|
}
|
|
}
|
|
|
|
|
|
- return <ReactECharts option={options} />
|
|
|
|
|
|
+ function getSeriesXData (data) {
|
|
|
|
+ return Object.keys(data).reduce((acc, key) => {
|
|
|
|
+ return data[key].cross
|
|
|
|
+ }, {})
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <div>
|
|
|
|
+ {
|
|
|
|
+ showLine && (
|
|
|
|
+ <Divider />
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ showTitle && (
|
|
|
|
+ <div className={s.title}>
|
|
|
|
+ {data.taskName}-压测报告
|
|
|
|
+ </div>
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ <div className={s.descriptionWrap}>
|
|
|
|
+ <Descriptions bordered>
|
|
|
|
+ <Descriptions.Item label="压测参数" span={24}>
|
|
|
|
+ -t {data.threads} -c {data.connects} -d {data.duration} -timeout {data.timeout}</Descriptions.Item>
|
|
|
|
+ <Descriptions.Item label="服务器配置" span={8}>{get(data, 'monitor.infoResult')}</Descriptions.Item>
|
|
|
|
+ <Descriptions.Item label="部署类型" span={8}>{get(data, 'monitor.deployType')}</Descriptions.Item>
|
|
|
|
+ <Descriptions.Item label="QPS">{get(data, 'report.summary_req_sec')}</Descriptions.Item>
|
|
|
|
+ <Descriptions.Item label="平均延时">{get(data, 'report.latency_mean')}</Descriptions.Item>
|
|
|
|
+ <Descriptions.Item label="99% 延时">{get(data, 'report.latency_p99')}</Descriptions.Item>
|
|
|
|
+ </Descriptions>
|
|
|
|
+ </div>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions('压力持续图', get(data, 'pressure_data.cross'), [
|
|
|
|
+ { name: '数据', data: get(data, 'pressure_data.vertical') }
|
|
|
|
+ ], { showAreaStyle: true })}
|
|
|
|
+ />
|
|
|
|
+ <div className={s.chartWrap}>
|
|
|
|
+ <div className={s.chartItem}>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions(
|
|
|
|
+ 'CPU 使用率',
|
|
|
|
+ getSeriesXData(get(data, 'monitor.metrics.node_cpu_seconds_total') || {}),
|
|
|
|
+ getSeriesYData(get(data, 'monitor.metrics.node_cpu_seconds_total') || {}),
|
|
|
|
+ {
|
|
|
|
+ yFormat: function (value) {
|
|
|
|
+ return value + '%'
|
|
|
|
+ },
|
|
|
|
+ tooltipFormat: function (value) {
|
|
|
|
+ return value + '%'
|
|
|
|
+ },
|
|
|
|
+ yMax: 100
|
|
|
|
+ }
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div className={s.chartItem}>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions(
|
|
|
|
+ '内存使用率',
|
|
|
|
+ getSeriesXData(get(data, 'monitor.metrics.memUsage') || {}),
|
|
|
|
+ getSeriesYData(get(data, 'monitor.metrics.memUsage') || {}),
|
|
|
|
+ {
|
|
|
|
+ yFormat: function (value) {
|
|
|
|
+ return value + '%'
|
|
|
|
+ },
|
|
|
|
+ tooltipFormat: function (value) {
|
|
|
|
+ return value + '%'
|
|
|
|
+ },
|
|
|
|
+ yMax: 100
|
|
|
|
+ }
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div className={s.chartWrap}>
|
|
|
|
+ <div className={s.chartItem}>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions(
|
|
|
|
+ '磁盘读取',
|
|
|
|
+ getSeriesXData(get(data, 'monitor.metrics.node_disk_read_bytes_total') || {}),
|
|
|
|
+ getSeriesYData(get(data, 'monitor.metrics.node_disk_read_bytes_total') || {}, value => (value / 1024).toFixed(2)),
|
|
|
|
+ {
|
|
|
|
+ yFormat: function (value) {
|
|
|
|
+ return value + 'kb/s'
|
|
|
|
+ },
|
|
|
|
+ tooltipFormat: function (value) {
|
|
|
|
+ return value + 'kb/s'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div className={s.chartItem}>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions(
|
|
|
|
+ '磁盘写入',
|
|
|
|
+ getSeriesXData(get(data, 'monitor.metrics.node_disk_written_bytes_total') || {}),
|
|
|
|
+ getSeriesYData(get(data, 'monitor.metrics.node_disk_written_bytes_total') || {}, value => (value / 1024).toFixed(2)),
|
|
|
|
+ {
|
|
|
|
+ yFormat: function (value) {
|
|
|
|
+ return value + 'kb/s'
|
|
|
|
+ },
|
|
|
|
+ tooltipFormat: function (value) {
|
|
|
|
+ return value + 'kb/s'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div className={s.chartWrap}>
|
|
|
|
+ <div className={s.chartItem}>
|
|
|
|
+ <ReactECharts
|
|
|
|
+ option={getOptions(
|
|
|
|
+ 'FPM',
|
|
|
|
+ getSeriesXData(get(data, 'monitor.metrics.phpfpm_processes_total') || {}),
|
|
|
|
+ getSeriesYData(get(data, 'monitor.metrics.phpfpm_processes_total') || {})
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
export default Page
|
|
export default Page
|