Skip to content

Commit

Permalink
Added Top Consumers to ceph storage plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Ankush Behl <cloudbehl@gmail.com>
  • Loading branch information
cloudbehl committed Jul 16, 2019
1 parent d64af33 commit b6594c0
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 0 deletions.
@@ -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;
}
@@ -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;
};
6 changes: 6 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/constants/index.ts
Expand Up @@ -7,3 +7,9 @@ export const CEPH_CLUSTER_NAME = 'rook-ceph';
export const ONE_HR = '1 Hour';
export const SIX_HR = '6 Hours';
export const TWENTY_FOUR_HR = '24 Hours';
export const PROJECTS = 'Projects';
export const STORAGE_CLASSES = 'Storage Classes';
export const PODS = 'Pods';
export const VMS = 'VMs';
export const BY_USED = 'By Used Capacity';
export const BY_REQUESTED = 'By Requested Capacity';
27 changes: 27 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/constants/queries.ts
Expand Up @@ -8,6 +8,14 @@ export enum StorageDashboardQuery {
UTILIZATION_LATENCY_QUERY = 'UTILIZATION_LATENCY_QUERY',
UTILIZATION_THROUGHPUT_QUERY = 'UTILIZATION_THROUGHPUT_QUERY',
UTILIZATION_RECOVERY_RATE_QUERY = 'UTILIZATION_RECOVERY_RATE_QUERY',
PODS_BY_REQUESTED = 'PODS_BY_REQUESTED',
PODS_BY_USED = 'PODS_BY_USED',
PROJECTS_BY_REQUESTED = 'PROJECTS_BY_REQUESTED',
PROJECTS_BY_USED = 'PROJECTS_BY_USED',
STORAGE_CLASSES_BY_REQUESTED = 'STORAGE_CLASSES_BY_REQUESTED',
STORAGE_CLASSES_BY_USED = 'STORAGE_CLASSES_BY_USED',
VMS_BY_REQUESTED = 'VMS_BY_REQUESTED',
VMS_BY_USED = 'VMS_BY_USED',
}

export const STORAGE_HEALTH_QUERIES = {
Expand Down Expand Up @@ -35,3 +43,22 @@ export const UTILIZATION_QUERY_HOUR_MAP = {
[SIX_HR]: '[6h:1h]',
[TWENTY_FOUR_HR]: '[24h:4h]',
};

export const TOP_CONSUMER_QUERIES = {
[StorageDashboardQuery.PODS_BY_REQUESTED]:
'(sort(topk(5, sum(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(pod) kube_pod_spec_volumes_persistentvolumeclaims_info) by (pod))))[10m:1m]',
[StorageDashboardQuery.PODS_BY_USED]:
'(sort(topk(5, sum(avg_over_time(kubelet_volume_stats_used_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(pod) kube_pod_spec_volumes_persistentvolumeclaims_info) by (pod))))[10m:1m]',
[StorageDashboardQuery.PROJECTS_BY_REQUESTED]:
'(sort(topk(5, sum(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(storageclass) kube_persistentvolumeclaim_info) by (namespace))))[10m:1m]',
[StorageDashboardQuery.PROJECTS_BY_USED]:
'(sort(topk(5, sum(avg_over_time(kubelet_volume_stats_used_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(storageclass) kube_persistentvolumeclaim_info) by (namespace))))[10m:1m]',
[StorageDashboardQuery.STORAGE_CLASSES_BY_REQUESTED]:
'(sort(topk(5, sum(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(storageclass) kube_persistentvolumeclaim_info) by (storageclass))))[10m:1m]',
[StorageDashboardQuery.STORAGE_CLASSES_BY_USED]:
'(sort(topk(5, sum(avg_over_time(kubelet_volume_stats_used_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(storageclass) kube_persistentvolumeclaim_info) by (storageclass))))[10m:1m]',
[StorageDashboardQuery.VMS_BY_REQUESTED]:
'(sort(topk(5, sum(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(pod) kube_pod_spec_volumes_persistentvolumeclaims_info{pod=~"virt-launcher-.*"}) by (pod))))[10m:1m]',
[StorageDashboardQuery.VMS_BY_USED]:
'(sort(topk(5, sum(max(avg_over_time(kubelet_volume_stats_used_bytes[1h]) * on (namespace,persistentvolumeclaim) group_left(pod) kube_pod_spec_volumes_persistentvolumeclaims_info{pod=~"virt-launcher-.*"}) by (pod,persistentvolumeclaim)) by (pod))))[10m:1m]',
};
11 changes: 11 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/plugin.ts
Expand Up @@ -91,6 +91,17 @@ const plugin: Plugin<ConsumedExtensions> = [
).then((m) => m.default),
},
},
{
type: 'Dashboards/Card',
properties: {
tab: 'persistent-storage',
position: GridPosition.MAIN,
loader: () =>
import(
'./components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card' /* webpackChunkName: "ceph-storage-top-consumers-card" */
).then((m) => m.default),
},
},
];

export default plugin;
22 changes: 22 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/utils/index.ts
@@ -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[];

0 comments on commit b6594c0

Please sign in to comment.