1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-22 13:33:52 +03:00

F #5928: monitoring graphics (#2260)

This commit is contained in:
Jorge Miguel Lobo Escalona 2022-08-25 12:57:10 +02:00 committed by GitHub
parent a09aa7fab8
commit bf27770617
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2716 additions and 2144 deletions

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,7 @@
"babel-loader": "8.2.1",
"babel-plugin-module-resolver": "4.0.0",
"btoa": "1.2.1",
"chartist": "0.10.1",
"clsx": "1.1.1",
"compression": "1.7.4",
"copy-webpack-plugin": "9.0.1",
@ -108,6 +109,7 @@
"qrcode": "1.4.4",
"react": "17.0.2",
"react-beautiful-dnd": "13.1.0",
"react-chartist": "0.14.4",
"react-dom": "17.0.2",
"react-flow-renderer": "9.6.0",
"react-hook-form": "7.18.1",

View File

@ -0,0 +1,169 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { JSXElementConstructor, useMemo } from 'react'
import PropTypes from 'prop-types'
import 'chartist/dist/chartist.min.css'
import {
Grid,
CircularProgress,
Stack,
Paper,
List,
ListItem,
Typography,
} from '@mui/material'
import ChartistGraph from 'react-chartist'
import { FixedScaleAxis } from 'chartist'
import makeStyles from '@mui/styles/makeStyles'
const useStyles = makeStyles(({ palette, typography }) => ({
graphStyle: {
'& .ct-series-a .ct-bar, .ct-series-a .ct-line, .ct-series-a .ct-point, .ct-series-a .ct-slice-donut':
{ stroke: palette.secondary.main, strokeWidth: '1px' },
'& .ct-grid': {
stroke: 'rgba(150,150,150,.1)',
strokeDasharray: '1px',
},
'&': {
width: '100%',
},
},
box: {
paddingBottom: '0px',
},
title: {
fontWeight: typography.fontWeightBold,
borderBottom: `1px solid ${palette.divider}`,
},
}))
/**
* Represents a Chartist Graph.
*
* @param {object} props - Props
* @param {object[]} props.data - Chart data
* @param {Function} props.interpolationX - Chartist interpolation X
* @param {Function} props.interpolationY - Chartist interpolation Y
* @param {string} props.name - Chartist name
* @param {string} props.filter - Chartist filter
* @param {string} props.x - Chartist X
* @param {string} props.y - Chartist X
* @returns {JSXElementConstructor} Chartist component
*/
const Chartist = ({
data = [],
interpolationX,
interpolationY,
name = '',
filter = [],
x = '',
y = '',
}) => {
const classes = useStyles()
const chartOptions = {
fullWidth: true,
reverseData: true,
low: 0,
scaleMinSpace: 10,
axisX: {
type: FixedScaleAxis,
divisor: 10,
},
axisY: {
offset: 70,
},
}
const dataChart = {
name,
}
typeof interpolationX === 'function' &&
(chartOptions.axisX.labelInterpolationFnc = interpolationX)
typeof interpolationY === 'function' &&
(chartOptions.axisY.labelInterpolationFnc = interpolationY)
filter?.length &&
(dataChart.data = useMemo(
() =>
data
?.filter((point) =>
Object.keys(point).find((key) => filter.includes(key))
)
.map((point) => ({
x: +point[x],
y: +point[y],
})),
[data]
))
return (
<Grid item xs={12} sm={6}>
{!data?.length ? (
<Stack direction="row" justifyContent="center" alignItems="center">
<CircularProgress color="secondary" />
</Stack>
) : (
<Paper variant="outlined" sx={{ height: 'fit-content' }}>
<List className={classes.box}>
<ListItem className={classes.title}>
<Typography noWrap>{name}</Typography>
</ListItem>
<ListItem className={classes.title}>
<ChartistGraph
className={classes.graphStyle}
data={{ series: [dataChart] }}
options={chartOptions}
type="Line"
/>
</ListItem>
</List>
</Paper>
)}
</Grid>
)
}
Chartist.propTypes = {
name: PropTypes.string,
filter: PropTypes.arrayOf(PropTypes.string),
data: PropTypes.arrayOf(
PropTypes.shape({
TIMESTAMP: PropTypes.string,
DISK_SIZE: PropTypes.arrayOf(PropTypes.shape({})),
ID: PropTypes.string,
CPU: PropTypes.string,
DISKRDBYTES: PropTypes.string,
DISKRDIOPS: PropTypes.string,
DISKWRBYTES: PropTypes.string,
DISKWRIOPS: PropTypes.string,
MEMORY: PropTypes.string,
NETRX: PropTypes.string,
NETTX: PropTypes.string,
})
),
x: PropTypes.string,
y: PropTypes.string,
interpolationX: PropTypes.func,
interpolationY: PropTypes.func,
}
Chartist.displayName = 'Chartist'
export default Chartist

View File

@ -15,5 +15,6 @@
* ------------------------------------------------------------------------- */
import CircleChart from 'client/components/Charts/CircleChart'
import SingleBar from 'client/components/Charts/SingleBar'
import Chartist from 'client/components/Charts/Chartist'
export { CircleChart, SingleBar }
export { CircleChart, SingleBar, Chartist }

View File

@ -0,0 +1,76 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { Grid } from '@mui/material'
import makeStyles from '@mui/styles/makeStyles'
import PropTypes from 'prop-types'
import { DateTime } from 'luxon'
import { useGetMonitoringQuery } from 'client/features/OneApi/vm'
import { Chartist } from 'client/components/Charts'
import { Tr } from 'client/components/HOC'
import { prettyBytes } from 'client/utils'
import { T } from 'client/constants'
const useStyles = makeStyles({
container: {
gridColumn: '1 / -1',
},
})
const interpolationX = (value) => DateTime.fromMillis(value).toFormat('HH:mm')
/**
* Render Graphs Capacity.
*
* @param {object} props - Props
* @param {string} props.id - Virtual machine id
* @returns {ReactElement} Capacity Graphs.
*/
const Graphs = ({ id }) => {
const classes = useStyles()
const { data: monitoring = [] } = useGetMonitoringQuery(id)
return (
<Grid container spacing={1} className={classes.container}>
<Chartist
name={Tr(T.RealCpu)}
filter={['CPU']}
data={monitoring}
y="CPU"
x="TIMESTAMP"
interpolationX={interpolationX}
/>
<Chartist
name={Tr(T.RealMemory)}
filter={['MEMORY']}
data={monitoring}
y="MEMORY"
x="TIMESTAMP"
interpolationY={(value) => prettyBytes(value)}
interpolationX={interpolationX}
/>
</Grid>
)
}
Graphs.propTypes = {
id: PropTypes.string,
}
Graphs.displayName = 'Graphs'
export default Graphs

View File

@ -31,6 +31,7 @@ import {
} from 'client/components/Tabs/Common'
import Information from 'client/components/Tabs/Vm/Info/information'
import Capacity from 'client/components/Tabs/Vm/Info/capacity'
import Graphs from 'client/components/Tabs/Vm/Info/graphs'
import { SubmitButton } from 'client/components/FormControl'
import { Tr, Translate } from 'client/components/HOC'
@ -183,7 +184,10 @@ const VmInfoTab = ({ tabProps = {}, id }) => {
/>
)}
{capacityPanel?.enabled && (
<Capacity actions={getActions(capacityPanel?.actions)} vm={vm} />
<>
<Capacity actions={getActions(capacityPanel?.actions)} vm={vm} />
<Graphs id={id} />
</>
)}
{attributesPanel?.enabled && attributes && (
<AttributePanel

View File

@ -0,0 +1,89 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { Grid } from '@mui/material'
import PropTypes from 'prop-types'
import { DateTime } from 'luxon'
import { useGetMonitoringQuery } from 'client/features/OneApi/vm'
import { Chartist } from 'client/components/Charts'
import { Tr } from 'client/components/HOC'
import { T } from 'client/constants'
const interpolationHour = (value) =>
DateTime.fromMillis(value).toFormat('HH:mm')
const interpolationBytesSeg = (value) => `${value}B/s`
const interpolationY = (value) => `${value}B`
/**
* Render Graphs Capacity.
*
* @param {object} props - Props
* @param {string} props.id - Virtual machine id
* @returns {ReactElement} Capacity Graphs.
*/
const Graphs = ({ id }) => {
const { data: monitoring = [] } = useGetMonitoringQuery(id)
return (
<Grid container spacing={1}>
<Chartist
name={Tr(T.NetRX)}
filter={['NETRX']}
data={monitoring}
y="NETRX"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationY}
/>
<Chartist
name={Tr(T.NetTX)}
filter={['NETTX']}
data={monitoring}
y="NETTX"
x="TIMESTAMP"
interpolationY={interpolationY}
interpolationX={interpolationHour}
/>
<Chartist
name={Tr(T.NetDownloadSpeed)}
filter={['NETRX']}
data={monitoring}
y="NETRX"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationBytesSeg}
/>
<Chartist
name={Tr(T.NetUploadSpeed)}
filter={['NETTX']}
data={monitoring}
y="NETTX"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationBytesSeg}
/>
</Grid>
)
}
Graphs.propTypes = {
id: PropTypes.string,
}
Graphs.displayName = 'Graphs'
export default Graphs

View File

@ -25,7 +25,7 @@ import {
AttachSecGroupAction,
DetachSecGroupAction,
} from 'client/components/Tabs/Vm/Network/Actions'
import Graphs from 'client/components/Tabs/Vm/Network/Graphs'
import {
getNics,
getHypervisor,
@ -106,6 +106,7 @@ const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => {
)
})}
</Stack>
<Graphs id={id} />
</div>
)
}

View File

@ -0,0 +1,90 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { Grid } from '@mui/material'
import PropTypes from 'prop-types'
import { DateTime } from 'luxon'
import { useGetMonitoringQuery } from 'client/features/OneApi/vm'
import { Chartist } from 'client/components/Charts'
import { Tr } from 'client/components/HOC'
import { prettyBytes } from 'client/utils'
import { T } from 'client/constants'
const interpolationHour = (value) =>
DateTime.fromMillis(value).toFormat('HH:mm')
const interpolationBytes = (value) => prettyBytes(value)
const interpolationY = (value) => (+value * 100).toFixed() / 100
/**
* Render Graphs Capacity.
*
* @param {object} props - Props
* @param {string} props.id - Virtual machine id
* @returns {ReactElement} Capacity Graphs.
*/
const Graphs = ({ id }) => {
const { data: monitoring = [] } = useGetMonitoringQuery(id)
return (
<Grid container spacing={1}>
<Chartist
name={Tr(T.DiskReadBytes)}
filter={['DISKRDBYTES']}
data={monitoring}
y="DISKRDBYTES"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationBytes}
/>
<Chartist
name={Tr(T.DiskWriteBytes)}
filter={['DISKWRBYTES']}
data={monitoring}
y="CDISKWRBYTES"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationY}
/>
<Chartist
name={Tr(T.DiskReadIOPS)}
filter={['DISKRDIOPS']}
data={monitoring}
y="DISKRDIOPS"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={(value) => value / 1000}
/>
<Chartist
name={Tr(T.DiskWriteIOPS)}
filter={['DISKWRIOPS']}
data={monitoring}
y="DISKWRIOPS"
x="TIMESTAMP"
interpolationX={interpolationHour}
interpolationY={interpolationY}
/>
</Grid>
)
}
Graphs.propTypes = {
id: PropTypes.string,
}
Graphs.displayName = 'Graphs'
export default Graphs

View File

@ -29,6 +29,7 @@ import {
SnapshotRenameAction,
SnapshotDeleteAction,
} from 'client/components/Tabs/Vm/Storage/Actions'
import Graphs from 'client/components/Tabs/Vm/Storage/Graphs'
import {
getDisks,
@ -134,6 +135,7 @@ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => {
)
})}
</Stack>
<Graphs id={id} />
</div>
)
}

View File

@ -824,6 +824,15 @@ module.exports = {
Swedish: 'Swedish',
Thai: 'Thai',
Turkish: 'Turkish',
/* VM graphs */
DiskReadBytes: 'Disk read bytes',
DiskWriteBytes: 'Disk write bytes',
DiskReadIOPS: 'Disk read IOPS',
DiskWriteIOPS: 'Disk write bytes',
NetRX: 'Net RX',
NetTX: 'Net TX',
NetDownloadSpeed: 'Net download speed',
NetUploadSpeed: 'Net upload speed',
/* VM Template schema - Input/Output - graphics - Remote connections */
DisplayUpdate: 'Display update',
/* VM Template schema - NUMA */

View File

@ -173,6 +173,8 @@ const vmApi = oneApi.injectEndpoints({
return { params: { id }, command }
},
transformResponse: (data) =>
[data?.MONITORING_DATA?.MONITORING ?? []].flat(),
}),
getMonitoringPool: builder.query({
/**