Skip to content

Commit

Permalink
feat(console): use new metrics queries for cluster/node metrics (#1416)
Browse files Browse the repository at this point in the history
  • Loading branch information
floreks authored Sep 27, 2024
1 parent 5b16a34 commit b66247d
Show file tree
Hide file tree
Showing 16 changed files with 824 additions and 359 deletions.
29 changes: 8 additions & 21 deletions assets/src/components/cd/cluster/ClusterNodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useOutletContext } from 'react-router-dom'
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import { useTheme } from 'styled-components'

import { Cluster } from 'generated/graphql'
import { isEmpty } from 'lodash'

import {
ColCpuTotal,
Expand All @@ -20,6 +20,8 @@ import {
} from '../../cluster/nodes/NodesList'
import { TableCaretLink } from '../../cluster/TableElements'
import { getNodeDetailsPath } from '../../../routes/cdRoutesConsts'
import { useDeploymentSettings } from '../../contexts/DeploymentSettingsContext'
import { ClusterMetrics } from '../../cluster/nodes/ClusterMetrics'

export const ColActions = (clusterId?: string) =>
columnHelper.accessor(() => null, {
Expand All @@ -38,6 +40,8 @@ export const ColActions = (clusterId?: string) =>

export default function ClusterNodes() {
const theme = useTheme()
const metricsEnabled =
!!useDeploymentSettings()?.prometheusConnection ?? false
const { cluster } = useOutletContext() as { cluster: Cluster }

const columns: ColumnDef<TableData, any>[] = useMemo(
Expand All @@ -55,22 +59,6 @@ export default function ClusterNodes() {
[cluster]
)

// const usage: ResourceUsage = useMemo(() => {
// if (!cluster) {
// return null
// }
// const cpu = sumBy(
// cluster.nodeMetrics,
// (metrics) => cpuParser(metrics?.usage?.cpu) ?? 0
// )
// const mem = sumBy(
// cluster.nodeMetrics,
// (metrics) => memoryParser((metrics as any)?.usage?.memory) ?? 0
// )

// return { cpu, mem }
// }, [cluster])

return (
<div
css={{
Expand All @@ -79,13 +67,12 @@ export default function ClusterNodes() {
gap: theme.spacing.medium,
}}
>
{/* {!isEmpty(cluster.nodes) && prometheusConnection && (
{!isEmpty(cluster.nodes) && metricsEnabled && (
<ClusterMetrics
nodes={cluster?.nodes?.filter((node): node is Node => !!node) || []}
usage={usage}
nodes={cluster?.nodes?.filter((node): boolean => !!node) || []}
cluster={cluster}
/>
)} */}
)}
<NodesList
nodes={cluster?.nodes || []}
nodeMetrics={cluster?.nodeMetrics || []}
Expand Down
15 changes: 5 additions & 10 deletions assets/src/components/cd/cluster/node/NodeInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useMemo } from 'react'
import { useOutletContext, useParams } from 'react-router-dom'
import { Card } from '@pluralsh/design-system'
import { useTheme } from 'styled-components'
import { Node, NodeMetric } from 'generated/graphql'
import LoadingIndicator from 'components/utils/LoadingIndicator'
Expand Down Expand Up @@ -67,15 +66,11 @@ export default function NodeInfo() {
}}
>
<section>
<SubTitle>Overview</SubTitle>
<Card css={{ padding: theme.spacing.medium }}>
<NodeGraphs
status={node.status}
pods={pods}
name={nodeName}
usage={nodeMetric.usage}
/>
</Card>
<NodeGraphs
node={node}
name={nodeName}
clusterId={clusterId}
/>
</section>
<section>
<SubTitle>Pods</SubTitle>
Expand Down
134 changes: 47 additions & 87 deletions assets/src/components/cluster/nodes/ClusterGauges.tsx
Original file line number Diff line number Diff line change
@@ -1,114 +1,74 @@
import { useMemo } from 'react'
import { useQuery } from '@apollo/client'
import { memoryParser } from 'kubernetes-resource-parser'
import { sumBy } from 'lodash'

import { ClusterFragment, MetricResponse, Node } from 'generated/graphql'
import { cpuParser } from 'utils/kubernetes'

import { MetricResult } from 'generated/graphql'
import RadialBarChart from 'components/utils/RadialBarChart'

import { useDeploymentSettings } from 'components/contexts/DeploymentSettingsContext'

import { ClusterMetrics as Metrics } from '../constants'
import { NODE_METRICS_Q } from '../queries'
import { GaugeWrap, ResourceGauge } from '../Gauges'
import { Prometheus } from '../../../utils/prometheus'

import { ResourceUsage } from './Nodes'
import { replaceMetric } from './ClusterMetrics'
interface CPUClusterMetrics {
usage: Array<MetricResult>
requests: Array<MetricResult>
limits: Array<MetricResult>
total: number
}

type Capacity = { cpu?: string; pods?: string; memory?: string } | undefined
interface MemoryClusterMetrics {
usage: Array<MetricResult>
requests: Array<MetricResult>
limits: Array<MetricResult>
total: number
}

const datum = (data: MetricResponse[]) =>
data[0]?.values?.[0]?.value ? parseFloat(data[0].values[0].value) : undefined
interface PodsClusterMetrics {
used: Array<MetricResult>
total: number
}

export function ClusterGauges({
nodes,
usage,
cluster,
cpu,
memory,
pods,
}: {
nodes: Node[]
usage: ResourceUsage
cluster?: ClusterFragment
cpu: CPUClusterMetrics
memory: MemoryClusterMetrics
pods: PodsClusterMetrics
}) {
const { prometheusConnection } = useDeploymentSettings()
const { data } = useQuery<{
cpuRequests: MetricResponse[]
cpuLimits: MetricResponse[]
memRequests: MetricResponse[]
memLimits: MetricResponse[]
pods: MetricResponse[]
}>(NODE_METRICS_Q, {
skip: !prometheusConnection,
variables: {
clusterId: cluster?.id,
cpuRequests: cluster
? replaceMetric(Metrics.CPURequestsCD, cluster?.handle)
: Metrics.CPURequests,
cpuLimits: cluster
? replaceMetric(Metrics.CPULimitsCD, cluster?.handle)
: Metrics.CPULimits,
memRequests: cluster
? replaceMetric(Metrics.MemoryRequestsCD, cluster?.handle)
: Metrics.MemoryRequests,
memLimits: cluster
? replaceMetric(Metrics.MemoryLimitsCD, cluster?.handle)
: Metrics.MemoryLimits,
pods: cluster
? replaceMetric(Metrics.PodsCD, cluster?.handle)
: Metrics.Pods,
offset: 5 * 60,
},
fetchPolicy: 'network-only',
pollInterval: 5000,
})
const hasCPUMetrics =
!!cpu?.usage && !!cpu?.limits && !!cpu?.requests && cpu?.total > 0
const hasMemoryMetrics =
!!memory?.usage &&
!!memory?.limits &&
!!memory?.requests &&
memory?.total > 0
const hasPodMetrics = !!pods?.used && pods?.total > 0

const chartData = useMemo(() => {
if (!data) {
if (!hasCPUMetrics || !hasMemoryMetrics || !hasPodMetrics) {
return null
}
const cpuRequests = datum(data.cpuRequests)
const cpuLimits = datum(data.cpuLimits)
const memRequests = datum(data.memRequests)
const memLimits = datum(data.memLimits)
const podsUsed = datum(data.pods)

const cpuTotal = sumBy(
nodes,
(n) => cpuParser((n?.status?.capacity as Capacity)?.cpu) ?? 0
)
const memTotal = sumBy(nodes, (n) =>
memoryParser((n?.status?.capacity as Capacity)?.memory)
)
const podsTotal = sumBy(nodes, (n) => {
const pods = (n?.status?.capacity as Capacity)?.pods

return pods ? parseInt(pods) ?? 0 : 0
})
const { cpu: cpuUsed, mem: memUsed } = usage || {}

return {
cpu: cpuUsed !== undefined && {
used: cpuUsed,
total: cpuTotal,
requests: cpuRequests,
limits: cpuLimits,
cpu: {
used: Prometheus.avg(cpu.usage),
total: cpu.total,
requests: Prometheus.avg(cpu.requests),
limits: Prometheus.avg(cpu.limits),
},
memory: memUsed !== undefined && {
used: memUsed,
total: memTotal,
requests: memRequests || 0,
limits: memLimits,
memory: {
used: Prometheus.avg(memory.usage),
total: memory.total,
requests: Prometheus.avg(memory.requests),
limits: Prometheus.avg(memory.limits),
},
pods: {
used: podsUsed || 0,
total: podsTotal,
remainder: podsTotal - (podsUsed || 0),
used: Prometheus.pods(pods.used),
total: pods.total,
remainder: pods.total - Prometheus.pods(pods.used),
},
}
}, [data, nodes, usage])
}, [cpu, memory, pods, hasCPUMetrics, hasMemoryMetrics, hasPodMetrics])

if (!prometheusConnection) return null
if (!chartData) return null

return (
Expand Down
91 changes: 65 additions & 26 deletions assets/src/components/cluster/nodes/ClusterMetrics.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
import { Flex } from 'honorable'
import { ClusterFragment, Node } from 'generated/graphql'

import {
ClusterFragment,
Maybe,
Node,
useClusterMetricsQuery,
} from 'generated/graphql'
import { useDeploymentSettings } from 'components/contexts/DeploymentSettingsContext'

import { Card } from '@pluralsh/design-system'

import { useTheme } from 'styled-components'
import { isNull } from 'lodash'

import { ClusterMetrics as Metrics } from '../constants'
import { Prometheus } from '../../../utils/prometheus'
import LoadingIndicator from '../../utils/LoadingIndicator'

import { ClusterGauges } from './ClusterGauges'
import { ResourceUsage } from './Nodes'
import { SaturationGraphs } from './SaturationGraphs'

export function replaceMetric(metric, cluster) {
if (!cluster) return metric

return metric.replaceAll(`cluster=""`, `cluster="${cluster}"`)
}

export function ClusterMetrics({
nodes,
usage,
cluster,
}: {
nodes: Node[]
usage: ResourceUsage
nodes: Maybe<Node>[]
cluster?: ClusterFragment
}) {
const theme = useTheme()
const { prometheusConnection } = useDeploymentSettings()
const metricsEnabled = Prometheus.enabled(prometheusConnection)
const { data, loading } = useClusterMetricsQuery({
variables: {
clusterId: cluster?.id ?? '',
},
skip: !metricsEnabled || !cluster?.id,
fetchPolicy: 'cache-and-network',
pollInterval: 60_000,
})

const cpuTotal = Prometheus.capacity(Prometheus.CapacityType.CPU, ...nodes)
const memTotal = Prometheus.capacity(Prometheus.CapacityType.Memory, ...nodes)
const podsTotal = Prometheus.capacity(Prometheus.CapacityType.Pods, ...nodes)
const shouldRenderMetrics =
metricsEnabled &&
!isNull(cpuTotal) &&
!isNull(memTotal) &&
!!cluster?.id &&
(data?.cluster?.clusterMetrics?.cpuUsage?.length ?? 0) > 0

if (!prometheusConnection) return null
if (loading) return <LoadingIndicator />
if (!shouldRenderMetrics) return null

return (
<Card css={{ padding: theme.spacing.xlarge }}>
Expand All @@ -52,20 +67,44 @@ export function ClusterMetrics({
wrap="wrap"
>
<ClusterGauges
nodes={nodes}
usage={usage}
cluster={cluster}
cpu={{
usage: Prometheus.toValues(
data?.cluster?.clusterMetrics?.cpuUsage
),
requests: Prometheus.toValues(
data?.cluster?.clusterMetrics?.cpuRequests
),
limits: Prometheus.toValues(
data?.cluster?.clusterMetrics?.cpuLimits
),
total: cpuTotal!,
}}
memory={{
usage: Prometheus.toValues(
data?.cluster?.clusterMetrics?.memoryUsage
),
requests: Prometheus.toValues(
data?.cluster?.clusterMetrics?.memoryRequests
),
limits: Prometheus.toValues(
data?.cluster?.clusterMetrics?.memoryLimits
),
total: memTotal!,
}}
pods={{
used: Prometheus.toValues(data?.cluster?.clusterMetrics?.pods),
total: podsTotal!,
}}
/>
<SaturationGraphs
clusterId={cluster?.id}
cpu={replaceMetric(
cluster ? Metrics.CPUCD : Metrics.CPU,
cluster?.handle
cpuUsage={Prometheus.toValues(
data?.cluster?.clusterMetrics?.cpuUsage
)}
mem={replaceMetric(
cluster ? Metrics.MemoryCD : Metrics.Memory,
cluster?.handle
cpuTotal={cpuTotal!}
memUsage={Prometheus.toValues(
data?.cluster?.clusterMetrics?.memoryUsage
)}
memTotal={memTotal!}
/>
</Flex>
</Flex>
Expand Down
Loading

0 comments on commit b66247d

Please sign in to comment.