Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Top Consumers to ceph storage plugin
Signed-off-by: Ankush Behl <cloudbehl@gmail.com>
- Loading branch information
Showing
6 changed files
with
265 additions
and
0 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
...rc/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.ceph-top-consumer-card__dropdown { | ||
display: flex; | ||
margin: 0.6em 0em; | ||
} | ||
.ceph-top-consumer-card__dropdown-item { | ||
margin-right: 0.4em; | ||
} |
192 changes: 192 additions & 0 deletions
192
...src/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import * as React from 'react'; | ||
import * as _ from 'lodash'; | ||
import { ChartLineIcon } from '@patternfly/react-icons'; | ||
import { | ||
ChartGroup, | ||
ChartLine, | ||
ChartThemeColor, | ||
ChartTooltip, | ||
Chart, | ||
ChartAxis, | ||
ChartLegend, | ||
ChartVoronoiContainer, | ||
} from '@patternfly/react-charts'; | ||
import { EmptyState, EmptyStateVariant, EmptyStateIcon, Title } from '@patternfly/react-core'; | ||
import './top-consumers-card.scss'; | ||
|
||
import { DashboardCard } from '@console/internal/components/dashboard/dashboard-card/card'; | ||
import { DashboardCardBody } from '@console/internal/components/dashboard/dashboard-card/card-body'; | ||
import { DashboardCardHeader } from '@console/internal/components/dashboard/dashboard-card/card-header'; | ||
import { DashboardCardTitle } from '@console/internal/components/dashboard/dashboard-card/card-title'; | ||
import { Dropdown } from '@console/internal/components/utils/dropdown'; | ||
import { PrometheusResponse, DataPoint } from '@console/internal/components/graphs'; | ||
import { | ||
DashboardItemProps, | ||
withDashboardResources, | ||
} from '@console/internal/components/dashboards-page/with-dashboard-resources'; | ||
import { humanizeBinaryBytesWithoutB } from '@console/internal/components/utils'; | ||
import { PROJECTS, STORAGE_CLASSES, PODS, VMS, BY_USED, BY_REQUESTED } from '../../../../constants'; | ||
import { StorageDashboardQuery, TOP_CONSUMER_QUERIES } from '../../../../constants/queries'; | ||
import { formatToShortTime, getGraphVectorStats } from '../../../../utils'; | ||
|
||
const TopConsumerResourceValue = { | ||
[PROJECTS]: 'PROJECTS_', | ||
[STORAGE_CLASSES]: 'STORAGE_CLASSES_', | ||
[PODS]: 'PODS_', | ||
[VMS]: 'VMS_', | ||
}; | ||
const TopConsumerSortByValue = { | ||
[BY_USED]: 'BY_USED', | ||
[BY_REQUESTED]: 'BY_REQUESTED', | ||
}; | ||
|
||
const TopConsumerResourceValueMapping = { | ||
Projects: 'namespace', | ||
'Storage Classes': 'storageclass', | ||
Pods: 'pod', | ||
}; | ||
|
||
export const TopConsumersBody: React.FC<TopConsumerBodyProps> = React.memo( | ||
({ topConsumerStats, metricType, sortByOption }) => { | ||
const topConsumerStatsResult = _.get(topConsumerStats, 'data.result', []); | ||
if (topConsumerStatsResult.length) { | ||
const legends = topConsumerStatsResult.map((resource) => ({ | ||
name: _.get(resource, ['metric', metricType], ''), | ||
})); | ||
const resourceValues = _.flatMap(topConsumerStatsResult, (resource) => resource.values); | ||
const maxCapacity = _.maxBy(resourceValues, (value) => Number(value[1])); | ||
const maxCapacityConverted = humanizeBinaryBytesWithoutB(Number(maxCapacity[1])); | ||
|
||
const chartData = getGraphVectorStats( | ||
topConsumerStats, | ||
metricType, | ||
maxCapacityConverted.unit, | ||
); | ||
const yTickValues = [ | ||
Math.floor(maxCapacityConverted.value / 4), | ||
Math.floor(maxCapacityConverted.value / 2), | ||
Math.floor((3 * maxCapacityConverted.value) / 4), | ||
Math.floor(maxCapacityConverted.value), | ||
Math.floor((5 * maxCapacityConverted.value) / 4), | ||
Math.floor((6 * maxCapacityConverted.value) / 4), | ||
]; | ||
const chartLineList = chartData.map((data, i) => ( | ||
<ChartLine key={i} data={data as DataPoint[]} /> | ||
)); | ||
return ( | ||
<> | ||
<span className="text-secondary ceph-top-consumer__y-axis-label">{`${sortByOption}(${ | ||
maxCapacityConverted.unit | ||
})`}</span> | ||
<Chart | ||
domain={{ y: [0, 1.5 * maxCapacityConverted.value] }} | ||
height={175} | ||
padding={{ top: 20, bottom: 20, left: 20, right: 20 }} | ||
containerComponent={ | ||
<ChartVoronoiContainer | ||
labels={(datum) => `${datum.y} ${maxCapacityConverted.unit}`} | ||
labelComponent={<ChartTooltip style={{ fontSize: 8, padding: 5 }} />} | ||
/> | ||
} | ||
themeColor={ChartThemeColor.multi} | ||
scale={{ x: 'time' }} | ||
> | ||
<ChartAxis | ||
tickFormat={(x) => formatToShortTime(x)} | ||
style={{ tickLabels: { fontSize: 8, padding: 5 } }} | ||
/> | ||
<ChartAxis | ||
dependentAxis | ||
tickValues={yTickValues} | ||
style={{ tickLabels: { fontSize: 8, padding: 5 }, grid: { stroke: '#4d525840' } }} | ||
/> | ||
<ChartGroup>{chartLineList}</ChartGroup> | ||
</Chart> | ||
<ChartLegend | ||
data={legends} | ||
themeColor={ChartThemeColor.multi} | ||
orientation="horizontal" | ||
height={20} | ||
/> | ||
</> | ||
); | ||
} | ||
return ( | ||
<EmptyState className="graph-empty-state" variant={EmptyStateVariant.full}> | ||
<EmptyStateIcon size="sm" icon={ChartLineIcon} /> | ||
<Title size="sm">No Prometheus datapoints found.</Title> | ||
</EmptyState> | ||
); | ||
}, | ||
); | ||
|
||
const metricTypes = _.keys(TopConsumerResourceValue); | ||
const sortByTypes = _.keys(TopConsumerSortByValue); | ||
|
||
const metricTypesOptions = _.zipObject(metricTypes, metricTypes); | ||
const sortByOptions = _.zipObject(sortByTypes, sortByTypes); | ||
|
||
const TopConsumerCard: React.FC<DashboardItemProps> = ({ | ||
watchPrometheus, | ||
stopWatchPrometheusQuery, | ||
prometheusResults, | ||
}) => { | ||
const [metricType, setMetricType] = React.useState(metricTypes[0]); | ||
const [sortBy, setSortBy] = React.useState(sortByTypes[0]); | ||
React.useEffect(() => { | ||
const query = | ||
TOP_CONSUMER_QUERIES[ | ||
StorageDashboardQuery[TopConsumerResourceValue[metricType] + TopConsumerSortByValue[sortBy]] | ||
]; | ||
watchPrometheus(query); | ||
return () => stopWatchPrometheusQuery(query); | ||
}, [watchPrometheus, stopWatchPrometheusQuery, metricType, sortBy]); | ||
|
||
const topConsumerstats = prometheusResults.getIn([ | ||
TOP_CONSUMER_QUERIES[ | ||
StorageDashboardQuery[TopConsumerResourceValue[metricType] + TopConsumerSortByValue[sortBy]] | ||
], | ||
'result', | ||
]); | ||
|
||
return ( | ||
<DashboardCard> | ||
<DashboardCardHeader> | ||
<DashboardCardTitle>Top Consumers</DashboardCardTitle> | ||
<div className="ceph-top-consumer-card__dropdown"> | ||
<Dropdown | ||
className="ceph-top-consumer-card__dropdown-item" | ||
id="metric-type" | ||
items={metricTypesOptions} | ||
onChange={setMetricType} | ||
selectedKey={metricType} | ||
title={metricType} | ||
/> | ||
<Dropdown | ||
className="ceph-top-consumer-card__dropdown-item" | ||
id="sort-by" | ||
items={sortByOptions} | ||
onChange={setSortBy} | ||
selectedKey={sortBy} | ||
title={sortBy} | ||
/> | ||
</div> | ||
</DashboardCardHeader> | ||
<DashboardCardBody> | ||
<TopConsumersBody | ||
topConsumerStats={topConsumerstats} | ||
metricType={TopConsumerResourceValueMapping[metricType]} | ||
sortByOption={sortBy} | ||
/> | ||
</DashboardCardBody> | ||
</DashboardCard> | ||
); | ||
}; | ||
|
||
export default withDashboardResources(TopConsumerCard); | ||
|
||
type TopConsumerBodyProps = { | ||
topConsumerStats: PrometheusResponse[]; | ||
metricType?: string; | ||
sortByOption?: string; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as _ from 'lodash'; | ||
import { humanizeBinaryBytesWithoutB } from '@console/internal/components/utils'; | ||
import { PrometheusResponse, DataPoint } from '@console/internal/components/graphs'; | ||
|
||
export const formatToShortTime = (timestamp) => { | ||
const dt = new Date(timestamp); | ||
// returns in HH:MM format | ||
return dt.toString().substring(16, 21); | ||
}; | ||
|
||
export const getGraphVectorStats: GetStats = (response, metric, unit) => { | ||
const result = _.get(response, 'data.result', []); | ||
return result.map((r) => { | ||
return r.values.map((arr) => ({ | ||
name: _.get(r, ['metric', metric], ''), | ||
x: new Date(arr[0] * 1000), | ||
y: Number(humanizeBinaryBytesWithoutB(arr[1], unit).value), | ||
})); | ||
}); | ||
}; | ||
|
||
type GetStats = (response: PrometheusResponse[], metric?: string, unit?: string) => DataPoint[]; |