Skip to content

Commit

Permalink
F #6490: Finish host tab (#2936)
Browse files Browse the repository at this point in the history
* Added Graphs tab
* Added PCI tab
* MultiChart is used for PCI subtab 
  -  this also enables exporting in CSV & PDF format

Signed-off-by: Victor Hansson <[email protected]>
  • Loading branch information
vichansson authored Feb 12, 2024
1 parent e6e8202 commit 5ba0cd9
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 19 deletions.
4 changes: 3 additions & 1 deletion src/fireedge/etc/sunstone/admin/host-tab.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ info-tabs:

vms:
enabled: true
graphs:
enabled: true
wild:
enabled: true
zombies:
Expand All @@ -84,4 +86,4 @@ dialogs:
not_on:
- kvm
- lxc
- firecracker
- firecracker
61 changes: 45 additions & 16 deletions src/fireedge/src/client/components/Charts/Chartist.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { JSXElementConstructor, useMemo } from 'react'
import { ArgumentScale, ValueScale } from '@devexpress/dx-react-chart'
import {
ArgumentAxis,
Legend,
Chart,
LineSeries,
ValueAxis,
Expand Down Expand Up @@ -78,9 +79,12 @@ const calculateDerivative = (data) =>
* @param {string} props.name - Chartist name
* @param {string} props.filter - Chartist filter
* @param {string} props.x - Chartist X
* @param {string} props.y - Chartist X
* @param {Array|string} props.y - Chartist Y
* @param {Function} props.interpolationY - Chartist interpolation Y
* @param {boolean} props.derivative - Display delta values
* @param {boolean} props.enableLegend - Enable graph legend
* @param {Array} props.legendNames - List of legend names
* @param {Array} props.lineColors - Array of line colors
* @returns {JSXElementConstructor} Chartist component
*/
const Chartist = ({
Expand All @@ -91,23 +95,28 @@ const Chartist = ({
y = '',
interpolationY = (value) => value,
derivative = false,
enableLegend = false,
legendNames = [],
lineColors = [],
}) => {
const classes = useStyles()

const dataChart = filter?.length
? useMemo(
() =>
data
?.filter(
(point) =>
!!Object.keys(point).find((key) => filter.includes(key))
)
.map((point, i) => ({
x: x === 'TIMESTAMP' ? new Date(+point[x] * 1000) : +point[x],
y: Math.round(+point[y]),
})),
[data]
)
? useMemo(() => {
let filteredData = data
if (filter.length) {
filteredData = data.filter((point) =>
Object.keys(point).some((key) => filter.includes(key))
)
}

return filteredData.map((point) => ({
x: x === 'TIMESTAMP' ? new Date(+point[x] * 1000) : +point[x],
...(Array.isArray(y)
? Object.fromEntries(y.map((pt) => [pt, Math.round(+point[pt])]))
: { y: Math.round(+point[y]) }),
}))
}, [data])
: []

const processedData = derivative ? calculateDerivative(dataChart) : dataChart
Expand All @@ -134,7 +143,21 @@ const Chartist = ({
<ArgumentAxis showLine={true} />
<ValueScale />
<ValueAxis showLine={true} tickFormat={() => interpolationY} />
<LineSeries name={name} valueField="y" argumentField="x" />
{Array.isArray(y) ? (
y.map((yValue, index) => (
<LineSeries
key={`${yValue}-${index}`}
name={legendNames?.[index] ?? ''}
valueField={yValue}
argumentField="x"
{...(lineColors?.[index] && { color: lineColors?.[index] })}
/>
))
) : (
<LineSeries name={name} valueField="y" argumentField="x" />
)}
{enableLegend && <Legend position="bottom" />}

<ZoomAndPan />
</Chart>
)}
Expand All @@ -149,9 +172,15 @@ Chartist.propTypes = {
filter: PropTypes.arrayOf(PropTypes.string),
data: PropTypes.array,
x: PropTypes.string,
y: PropTypes.string,
y: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
]),
interpolationY: PropTypes.func,
derivative: PropTypes.bool,
enableLegend: PropTypes.bool,
legendNames: PropTypes.arrayOf(PropTypes.string),
lineColors: PropTypes.arrayOf(PropTypes.string),
}

Chartist.displayName = 'Chartist'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
* @param {string} groupBy - The attribute by which data should be grouped (e.g., 'NAME', 'OID').
* @returns {Array<object>} An array of processed data items, each structured with properties for every metric from every dataset.
*/
import _ from 'lodash'

/**
* @param {Array} uniqueGroups - Groups to sort by
* @param {Array} datasets - All datasets in pool
* @param {Array} visibleDatasetIDs - Dataset ID's to render
* @param {string} groupBy - Group data by key
* @returns {object} - Processed dataset
*/
export const processDataForChart = (
uniqueGroups,
datasets,
Expand Down Expand Up @@ -94,16 +103,20 @@ const findFirstArray = (obj, depth = 0, maxDepth = Infinity) => {
* @param {Array} metricKeys - An array of keys to aggregate for the metrics.
* @param {Function} labelingFunction - A function to generate the label for the dataset.
* @param {number} depth - Depth of recursion when finding data array.
* @param {string} dataArrayPath - Path to data array in API response
* @returns {object} - The transformed dataset.
*/
export const transformApiResponseToDataset = (
apiResponse,
keyMap,
metricKeys,
labelingFunction,
depth = 0
depth = 0,
dataArrayPath
) => {
const dataArray = findFirstArray(apiResponse, depth)
const dataArray = dataArrayPath
? _.get(apiResponse, dataArrayPath)
: findFirstArray(apiResponse, depth)

const flattenObject = (obj, prefix = '') =>
Object.keys(obj).reduce((acc, k) => {
Expand Down
79 changes: 79 additions & 0 deletions src/fireedge/src/client/components/Tabs/Host/Graphs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2023, 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 { Grid } from '@mui/material'
import { prettyBytes } from 'client/utils'
import PropTypes from 'prop-types'
import { ReactElement } from 'react'
import { Chartist } from 'client/components/Charts'
import { T } from 'client/constants'
import { useGetHostMonitoringQuery } from 'client/features/OneApi/host'

/**
* Renders the host Graph tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} Graphs tab
*/
const HostGraphTab = ({ id }) => {
const {
data: { MONITORING_DATA: { MONITORING: monitoring = [] } = {} } = {},
} = useGetHostMonitoringQuery(id) || {}

const cpuMemoryData = monitoring?.map(({ TIMESTAMP, CAPACITY }) => ({
TIMESTAMP,
...CAPACITY,
}))

return (
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<Chartist
name={'CPU'}
filter={['FREE_CPU', 'USED_CPU']}
data={cpuMemoryData}
y={['FREE_CPU', 'USED_CPU']}
x="TIMESTAMP"
enableLegend={true}
legendNames={[T.FreeCPU, T.UsedCPU]}
lineColors={['#039be5', '#757575']}
/>
</Grid>
<Grid item xs={12} sm={12}>
<Chartist
name={T.Memory}
filter={['FREE_MEMORY', 'USED_MEMORY']}
data={cpuMemoryData}
y={['FREE_MEMORY', 'USED_MEMORY']}
x="TIMESTAMP"
enableLegend={true}
lineColors={['#039be5', '#757575']}
legendNames={[T.FreeMemory, T.UsedMemory]}
interpolationY={(value) => prettyBytes(value)}
/>
</Grid>
</Grid>
)
}

HostGraphTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}

HostGraphTab.displayName = 'HostGraphTab'

export default HostGraphTab
136 changes: 136 additions & 0 deletions src/fireedge/src/client/components/Tabs/Host/PCI/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2023, 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 PropTypes from 'prop-types'
import { ReactElement, useState, useCallback } from 'react'
import { MultiChart } from 'client/components/Charts'
import { useGetHostQuery } from 'client/features/OneApi/host'
import { transformApiResponseToDataset } from 'client/components/Charts/MultiChart/helpers/scripts'
import { Select, MenuItem, InputLabel, FormControl, Box } from '@mui/material'
import _ from 'lodash'

const keyMap = {
ADDRESS: 'fullAddress',
SHORT_ADDRESS: 'shortAddress',
TYPE: 'type',
DEVICE_NAME: 'deviceName',
VENDOR_NAME: 'vendor',
VMID: 'vmId',
}

const DataGridColumns = [
{ field: 'vmId', headerName: 'VM', flex: 1, type: 'number' },
{ field: 'shortAddress', headerName: 'Address', flex: 1 },
{ field: 'type', headerName: 'Type', flex: 1 },
{ field: 'deviceName', headerName: 'Name', flex: 1 },
{ field: 'vendor', headerName: 'Vendor', flex: 1, type: 'number' },
]

const metricKeys = ['vmId']

const metricNames = {
shortAddress: 'PCI Address',
type: 'Type',
deviceName: 'Name',
vmId: 'VMs',
}

const labelingFunc = () => `PCI`

/**
* Renders mainly information tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} Information tab
*/
const HostPciTab = ({ id }) => {
const [chartType, setChartType] = useState('table')
const { data } = useGetHostQuery({ id })

const pciDevicesPath = 'HOST_SHARE.PCI_DEVICES.PCI'

const transformedDataset = transformApiResponseToDataset(
data,
keyMap,
metricKeys,
labelingFunc,
0,
pciDevicesPath
)

const handleChartTypeChange = useCallback((event) => {
const newChartType = event.target.value
setChartType(newChartType)
}, [])

transformedDataset.dataset.data.forEach((dev, idx) => {
if (+dev.vmId < 0) {
_.set(transformedDataset.dataset.data, [idx, 'vmId'], '0')
}
})

return (
<>
<Box
display="flex"
alignItems="center"
justifyContent="flex-start"
gap={2}
>
<FormControl
variant="outlined"
size="small"
style={{ marginRight: 2, minWidth: 120 }}
>
<InputLabel>Chart Type</InputLabel>
<Select
value={chartType}
onChange={handleChartTypeChange}
label="Chart Type"
>
<MenuItem value="line">Line Chart</MenuItem>
<MenuItem value="bar">Bar Chart</MenuItem>
<MenuItem value="area">Area Chart</MenuItem>
<MenuItem value="table">Table Chart</MenuItem>
</Select>
</FormControl>
</Box>
<MultiChart
datasets={[
{
...transformedDataset.dataset,
...transformedDataset.error,
...transformedDataset.isEmpty,
},
]}
metricNames={metricNames}
chartType={chartType}
ItemsPerPage={10}
tableColumns={DataGridColumns}
groupBy={'deviceName'}
/>
</>
)
}

HostPciTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}

HostPciTab.displayName = 'HostPciTab'

export default HostPciTab
4 changes: 4 additions & 0 deletions src/fireedge/src/client/components/Tabs/Host/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { getAvailableInfoTabs } from 'client/models/Helper'

import Tabs from 'client/components/Tabs'
import Info from 'client/components/Tabs/Host/Info'
import Graph from 'client/components/Tabs/Host/Graphs'
import PCI from 'client/components/Tabs/Host/PCI'
import Numa from 'client/components/Tabs/Host/Numa'
import Vms from 'client/components/Tabs/Host/Vms'
import Wilds from 'client/components/Tabs/Host/Wilds'
Expand All @@ -32,9 +34,11 @@ import Zombies from 'client/components/Tabs/Host/Zombies'
const getTabComponent = (tabName) =>
({
info: Info,
graphs: Graph,
vms: Vms,
wild: Wilds,
numa: Numa,
pci: PCI,
zombies: Zombies,
}[tabName])

Expand Down
Loading

0 comments on commit 5ba0cd9

Please sign in to comment.