From b66247d2200e26934a1b54920d42135d29e5c711 Mon Sep 17 00:00:00 2001 From: Sebastian Florek Date: Fri, 27 Sep 2024 20:32:10 +0200 Subject: [PATCH] feat(console): use new metrics queries for cluster/node metrics (#1416) --- .../components/cd/cluster/ClusterNodes.tsx | 29 +- .../components/cd/cluster/node/NodeInfo.tsx | 15 +- .../cluster/nodes/ClusterGauges.tsx | 134 +++------ .../cluster/nodes/ClusterMetrics.tsx | 91 ++++-- .../components/cluster/nodes/NodeGraphs.tsx | 223 +++++++++------ .../src/components/cluster/nodes/NodeInfo.tsx | 21 +- assets/src/components/cluster/nodes/Nodes.tsx | 35 +-- .../cluster/nodes/SaturationGraphs.tsx | 69 ++--- .../components/component/ComponentMetrics.tsx | 72 ++--- assets/src/components/utils/Graph.tsx | 4 +- assets/src/components/utils/GraphHeader.tsx | 27 +- assets/src/generated/graphql.ts | 267 +++++++++++++++++- assets/src/graph/cdClusters.graphql | 75 +++++ assets/src/graph/cdComponent.graphql | 37 +++ assets/src/utils/kubernetes.ts | 2 +- assets/src/utils/prometheus.ts | 82 ++++++ 16 files changed, 824 insertions(+), 359 deletions(-) create mode 100644 assets/src/utils/prometheus.ts diff --git a/assets/src/components/cd/cluster/ClusterNodes.tsx b/assets/src/components/cd/cluster/ClusterNodes.tsx index 85f750f202..53bc18b2f4 100644 --- a/assets/src/components/cd/cluster/ClusterNodes.tsx +++ b/assets/src/components/cd/cluster/ClusterNodes.tsx @@ -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, @@ -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, { @@ -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[] = useMemo( @@ -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 (
- {/* {!isEmpty(cluster.nodes) && prometheusConnection && ( + {!isEmpty(cluster.nodes) && metricsEnabled && ( !!node) || []} - usage={usage} + nodes={cluster?.nodes?.filter((node): boolean => !!node) || []} cluster={cluster} /> - )} */} + )}
- Overview - - - +
Pods diff --git a/assets/src/components/cluster/nodes/ClusterGauges.tsx b/assets/src/components/cluster/nodes/ClusterGauges.tsx index 763e2e800b..edd6eeafcd 100644 --- a/assets/src/components/cluster/nodes/ClusterGauges.tsx +++ b/assets/src/components/cluster/nodes/ClusterGauges.tsx @@ -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 + requests: Array + limits: Array + total: number +} -type Capacity = { cpu?: string; pods?: string; memory?: string } | undefined +interface MemoryClusterMetrics { + usage: Array + requests: Array + limits: Array + total: number +} -const datum = (data: MetricResponse[]) => - data[0]?.values?.[0]?.value ? parseFloat(data[0].values[0].value) : undefined +interface PodsClusterMetrics { + used: Array + 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 ( diff --git a/assets/src/components/cluster/nodes/ClusterMetrics.tsx b/assets/src/components/cluster/nodes/ClusterMetrics.tsx index 59d9849142..b637e448e2 100644 --- a/assets/src/components/cluster/nodes/ClusterMetrics.tsx +++ b/assets/src/components/cluster/nodes/ClusterMetrics.tsx @@ -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[] 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 + if (!shouldRenderMetrics) return null return ( @@ -52,20 +67,44 @@ export function ClusterMetrics({ wrap="wrap" > diff --git a/assets/src/components/cluster/nodes/NodeGraphs.tsx b/assets/src/components/cluster/nodes/NodeGraphs.tsx index 06b7ca5544..5e8c79decf 100644 --- a/assets/src/components/cluster/nodes/NodeGraphs.tsx +++ b/assets/src/components/cluster/nodes/NodeGraphs.tsx @@ -1,111 +1,172 @@ -import React, { useCallback, useMemo } from 'react' +import React, { useMemo } from 'react' import { Flex } from 'honorable' - -import { NodeStatus, NodeUsage, Pod } from 'generated/graphql' -import { cpuParser, memoryParser } from 'utils/kubernetes' +import { Node, useClusterNodeMetricsQuery } from 'generated/graphql' +import { isNull } from 'lodash' +import { Card } from '@pluralsh/design-system' +import { useTheme } from 'styled-components' import { getPodResources } from '../pods/getPodResources' - -import { NodeMetrics } from '../constants' import { getAllContainersFromPods } from '../utils' import { GaugeWrap, ResourceGauge } from '../Gauges' +import { useDeploymentSettings } from '../../contexts/DeploymentSettingsContext' +import { Prometheus } from '../../../utils/prometheus' +import RadialBarChart from '../../utils/RadialBarChart' +import LoadingIndicator from '../../utils/LoadingIndicator' +import { SubTitle } from '../../utils/SubTitle' import { SaturationGraphs } from './SaturationGraphs' export function NodeGraphs({ - status, - pods, name, - usage, + clusterId, + node, }: { - status?: NodeStatus - pods?: Pod[] name?: string - usage?: NodeUsage | null + clusterId?: string + node: Node }) { + const theme = useTheme() + const { prometheusConnection } = useDeploymentSettings() + const metricsEnabled = Prometheus.enabled(prometheusConnection) + const { data, loading } = useClusterNodeMetricsQuery({ + variables: { + clusterId: clusterId ?? '', + node: name ?? '', + }, + skip: !metricsEnabled || !clusterId || !name, + fetchPolicy: 'cache-and-network', + pollInterval: 60_000, + }) + + const cpuTotal = Prometheus.capacity(Prometheus.CapacityType.CPU, node) + const memTotal = Prometheus.capacity(Prometheus.CapacityType.Memory, node) + const podsTotal = Prometheus.capacity(Prometheus.CapacityType.Pods, node) + const shouldRenderMetrics = + metricsEnabled && + !isNull(cpuTotal) && + !isNull(memTotal) && + !!clusterId && + (data?.cluster?.clusterNodeMetrics?.cpuUsage?.length ?? 0) > 0 + const { cpu: cpuReservations, memory: memoryReservations } = useMemo(() => { - const allContainers = getAllContainersFromPods(pods) + const allContainers = getAllContainersFromPods(node?.pods) return getPodResources(allContainers) - }, [pods]) - - const localize = useCallback( - (metric) => metric.replaceAll('{instance}', name), - [name] - ) - const capacity = - (status?.capacity as unknown as { cpu?: string; memory?: string }) ?? {} + }, [node?.pods]) const chartData = useMemo(() => { - const cpuTotal = cpuParser(capacity.cpu) - const memTotal = memoryParser(capacity.memory) - - const cpuUsed = cpuParser(usage?.cpu) ?? undefined - const memUsed = memoryParser(usage?.memory) ?? undefined + const cpuUsed = Prometheus.toValues( + data?.cluster?.clusterNodeMetrics?.cpuUsage + ) + const memUsed = Prometheus.toValues( + data?.cluster?.clusterNodeMetrics?.memoryUsage + ) + const podsUsed = node?.pods?.length ?? 0 return { - cpu: cpuUsed !== undefined && - cpuTotal !== undefined && { - used: cpuUsed, - total: cpuTotal, - remainder: cpuTotal - cpuUsed || 0, - ...cpuReservations, - }, - memory: memUsed !== undefined && - memTotal !== undefined && { - used: memUsed, - total: memTotal, - remainder: memTotal - memUsed, - ...memoryReservations, - }, + cpu: { + used: Prometheus.avg(cpuUsed), + total: cpuTotal!, + ...cpuReservations, + }, + memory: { + used: Prometheus.avg(memUsed), + total: memTotal!, + ...memoryReservations, + }, + pods: { + used: podsUsed, + total: podsTotal!, + remainder: podsTotal! - podsUsed, + }, } }, [ - capacity.cpu, - capacity.memory, cpuReservations, + cpuTotal, + data?.cluster?.clusterNodeMetrics?.cpuUsage, + data?.cluster?.clusterNodeMetrics?.memoryUsage, + memTotal, memoryReservations, - usage?.cpu, - usage?.memory, + node?.pods?.length, + podsTotal, ]) - if (!chartData) { - return null - } + if (loading) return + if (!chartData) return null + if (!shouldRenderMetrics) return null return ( - - - - - - - - - + <> + Overview + + + + + + + + + + + + + + + ) } diff --git a/assets/src/components/cluster/nodes/NodeInfo.tsx b/assets/src/components/cluster/nodes/NodeInfo.tsx index f0bb447dd5..952d652182 100644 --- a/assets/src/components/cluster/nodes/NodeInfo.tsx +++ b/assets/src/components/cluster/nodes/NodeInfo.tsx @@ -1,10 +1,7 @@ import { useQuery } from '@apollo/client' import { useParams } from 'react-router-dom' -import { Card } from '@pluralsh/design-system' import { useTheme } from 'styled-components' - import { Event, NodeMetric, Node as NodeT, Pod } from 'generated/graphql' - import LoadingIndicator from 'components/utils/LoadingIndicator' import { POLL_INTERVAL } from '../constants' @@ -18,7 +15,6 @@ import { PodsList, } from '../pods/PodsList' import { NODE_Q } from '../queries' - import { SubTitle } from '../../utils/SubTitle' import { NodeGraphs } from './NodeGraphs' @@ -51,7 +47,7 @@ export default function NodeInfo() { if (!data) return - const { node, nodeMetric } = data + const { node } = data return (
-
- Overview - - - -
+
Pods { - if (!data) { - return null - } - const cpu = sumBy( - data.nodeMetrics, - (metrics) => cpuParser(metrics?.usage?.cpu) ?? 0 - ) - const mem = sumBy( - data.nodeMetrics, - (metrics) => memoryParser((metrics as any)?.usage?.memory) ?? 0 - ) - - return { cpu, mem } - }, [data]) - // Memoize columns to prevent rerendering entire table const columns: ColumnDef[] = useMemo( () => [ @@ -93,14 +69,7 @@ export default function Nodes() { direction="column" gap="xlarge" > - {!!prometheusConnection && ( - - - - )} + + memUsage: Array }) { - const { prometheusConnection } = useDeploymentSettings() - - const { data } = useQuery(CLUSTER_SATURATION, { - skip: !prometheusConnection, - variables: { - cpuUtilization: cpu, - memUtilization: mem, - clusterId, - offset: 2 * 60 * 60, - }, - fetchPolicy: 'network-only', - pollInterval: 10000, - }) - const result = useMemo(() => { - if (!data) { - return null - } - - const { cpuUtilization, memUtilization } = data - - if (!cpuUtilization[0] || !memUtilization[0]) { + if (!cpuUsage || !memUsage || cpuTotal === 0 || memTotal === 0) { return null } return [ - { id: 'CPU usage', data: cpuUtilization[0].values.map(datum) }, - { id: 'Memory usage', data: memUtilization[0].values.map(datum) }, + { + id: 'CPU usage', + data: cpuUsage + .map(({ timestamp, value }) => ({ + timestamp, + value: parseFloat(value ?? '0') / cpuTotal, + })) + .map(datum), + }, + { + id: 'Memory usage', + data: memUsage + .map(({ timestamp, value }) => ({ + timestamp, + value: parseFloat(value ?? '0') / memTotal, + })) + .map(datum), + }, ] - }, [data]) + }, [cpuTotal, cpuUsage, memTotal, memUsage]) if (!result) { return null diff --git a/assets/src/components/component/ComponentMetrics.tsx b/assets/src/components/component/ComponentMetrics.tsx index e048135a46..556e6006ff 100644 --- a/assets/src/components/component/ComponentMetrics.tsx +++ b/assets/src/components/component/ComponentMetrics.tsx @@ -7,14 +7,19 @@ import { isNonNullable } from 'utils/isNonNullable' import { useTheme } from 'styled-components' import isEmpty from 'lodash/isEmpty' -import { MetricResponseFragment, useUsageQuery } from 'generated/graphql' +import { + MetricResponseFragment, + useServiceDeploymentComponentMetricsQuery, +} from 'generated/graphql' import RangePicker from 'components/utils/RangePicker' import { Graph } from 'components/utils/Graph' import GraphHeader from 'components/utils/GraphHeader' import LoadingIndicator from 'components/utils/LoadingIndicator' -import { POLL_INTERVAL } from '../cluster/constants' +import moment from 'moment/moment' + +import { format } from '../apps/app/dashboards/dashboard/misc' import { ComponentDetailsContext } from './ComponentDetails' @@ -63,10 +68,13 @@ function Graphs({ flexGrow: 1, }} > - + format(v, 'percent')} tickRotation={undefined} />
@@ -134,10 +142,13 @@ function PodGraphs({ flexGrow: 1, }} > - + format(v, 'percent')} tickRotation={undefined} />
@@ -163,30 +174,31 @@ function PodGraphs({ } function Metric({ - name, - namespace, - regex, + serviceId, + componentId, duration: { step, offset }, - cluster, ...props }) { const theme = useTheme() - const clusterInfix = cluster ? `cluster="${cluster?.handle}",` : '' - const { data } = useUsageQuery({ + const start = useMemo( + () => moment().subtract({ second: offset }).toISOString(), + [offset] + ) + const { data, loading } = useServiceDeploymentComponentMetricsQuery({ variables: { - cpu: `sum(rate(container_cpu_usage_seconds_total{${clusterInfix}namespace="${namespace}",pod=~"${name}${regex}"}[5m]))`, - mem: `sum(container_memory_working_set_bytes{${clusterInfix}namespace="${namespace}",pod=~"${name}${regex}",image!="",container!=""})`, - podCpu: `sum(rate(container_cpu_usage_seconds_total{${clusterInfix}namespace="${namespace}",pod=~"${name}${regex}"}[5m])) by (pod)`, - podMem: `sum(container_memory_working_set_bytes{${clusterInfix}namespace="${namespace}",pod=~"${name}${regex}",image!="",container!=""}) by (pod)`, - clusterId: cluster?.id, + id: serviceId, + componentId, step, - offset, + start, }, - pollInterval: POLL_INTERVAL, + skip: !serviceId || !componentId, + pollInterval: 60_000, + fetchPolicy: 'cache-and-network', }) const { cpu, mem, podCpu, podMem } = useMemo(() => { - const { cpu, mem, podCpu, podMem } = data || {} + const { cpu, mem, podCpu, podMem } = + data?.serviceDeployment?.componentMetrics || {} return { cpu: (cpu || []).filter(isNonNullable), @@ -196,8 +208,6 @@ function Metric({ } }, [data]) - if (!data) return - let content = if (!isEmpty(cpu) || !isEmpty(mem) || !isEmpty(podCpu) || !isEmpty(podMem)) { @@ -215,6 +225,8 @@ function Metric({ ) } + if (loading && !data) return + return ( (DURATIONS[0]) - const { component, cluster } = useOutletContext() - const componentName = component.name?.toLowerCase() - const componentKind = component.kind?.toLowerCase() - const componentNamespace = component.namespace?.toLowerCase() + const { component, serviceId } = useOutletContext() return (
diff --git a/assets/src/components/utils/Graph.tsx b/assets/src/components/utils/Graph.tsx index 9859102083..b0ac592746 100644 --- a/assets/src/components/utils/Graph.tsx +++ b/assets/src/components/utils/Graph.tsx @@ -82,7 +82,7 @@ export function Graph({ }, [data, selected]) if (graph.length === 0) - return no data + return no data const hasData = !!graph[0].data[0] @@ -106,7 +106,7 @@ export function Graph({ type: 'linear', min: 0, max: 'auto', - stacked: true, + stacked: false, reverse: false, }} colors={COLORS} diff --git a/assets/src/components/utils/GraphHeader.tsx b/assets/src/components/utils/GraphHeader.tsx index e7d0506441..278538aa82 100644 --- a/assets/src/components/utils/GraphHeader.tsx +++ b/assets/src/components/utils/GraphHeader.tsx @@ -1,6 +1,13 @@ import { Div } from 'honorable' +import { InfoOutlineIcon, Tooltip, WrapWithIf } from '@pluralsh/design-system' -export default function GraphHeader({ title }: { title: string }) { +export default function GraphHeader({ + title, + tooltip, +}: { + title: string + tooltip?: string +}) { return (
- {title} + } + > + + {title} + {!!tooltip && ( + + )} + +
) } diff --git a/assets/src/generated/graphql.ts b/assets/src/generated/graphql.ts index efe48a4f41..b025ff5d45 100644 --- a/assets/src/generated/graphql.ts +++ b/assets/src/generated/graphql.ts @@ -8805,6 +8805,10 @@ export type AddonVersionBlockingFragment = { __typename?: 'AddonVersion', blocki export type ClustersRowFragment = { __typename?: 'Cluster', currentVersion?: string | null, id: string, self?: boolean | null, protect?: boolean | null, name: string, handle?: string | null, distro?: ClusterDistro | null, installed?: boolean | null, pingedAt?: string | null, deletedAt?: string | null, version?: string | null, kubeletVersion?: string | null, nodes?: Array<{ __typename?: 'Node', status: { __typename?: 'NodeStatus', capacity?: Record | null } } | null> | null, nodeMetrics?: Array<{ __typename?: 'NodeMetric', usage?: { __typename?: 'NodeUsage', cpu?: string | null, memory?: string | null } | null } | null> | null, provider?: { __typename?: 'ClusterProvider', id: string, cloud: string, name: string, namespace: string, supportedVersions?: Array | null } | null, prAutomations?: Array<{ __typename?: 'PrAutomation', id: string, name: string, documentation?: string | null, addon?: string | null, identifier: string, role?: PrRole | null, cluster?: { __typename?: 'Cluster', handle?: string | null, protect?: boolean | null, deletedAt?: string | null, version?: string | null, currentVersion?: string | null, id: string, name: string, self?: boolean | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', cloud: string } | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string } | null, repository?: { __typename?: 'GitRepository', url: string, refs?: Array | null } | null, connection?: { __typename?: 'ScmConnection', id: string, name: string, insertedAt?: string | null, updatedAt?: string | null, type: ScmType, username?: string | null, baseUrl?: string | null, apiUrl?: string | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, writeBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, configuration?: Array<{ __typename?: 'PrConfiguration', values?: Array | null, default?: string | null, documentation?: string | null, longform?: string | null, name: string, optional?: boolean | null, placeholder?: string | null, type: ConfigurationType, condition?: { __typename?: 'PrConfigurationCondition', field: string, operation: Operation, value?: string | null } | null } | null> | null } | null> | null, service?: { __typename?: 'ServiceDeployment', id: string, repository?: { __typename?: 'GitRepository', url: string } | null } | null, status?: { __typename?: 'ClusterStatus', conditions?: Array<{ __typename?: 'ClusterCondition', lastTransitionTime?: string | null, message?: string | null, reason?: string | null, severity?: string | null, status?: string | null, type?: string | null } | null> | null } | null, tags?: Array<{ __typename?: 'Tag', name: string, value: string } | null> | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null }; +export type ClusterMetricsFragmentFragment = { __typename?: 'Cluster', clusterMetrics?: { __typename?: 'ClusterMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuRequests?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuLimits?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memory?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryRequests?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryLimits?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, pods?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null }; + +export type ClusterNodeMetricsFragmentFragment = { __typename?: 'Cluster', clusterNodeMetrics?: { __typename?: 'ClusterNodeMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memory?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null }; + export type ClusterFragment = { __typename?: 'Cluster', currentVersion?: string | null, id: string, name: string, handle?: string | null, metadata?: Record | null, pingedAt?: string | null, self?: boolean | null, version?: string | null, protect?: boolean | null, distro?: ClusterDistro | null, installed?: boolean | null, deletedAt?: string | null, kubeletVersion?: string | null, apiDeprecations?: Array<{ __typename?: 'ApiDeprecation', availableIn?: string | null, blocking?: boolean | null, deprecatedIn?: string | null, removedIn?: string | null, replacement?: string | null, component?: { __typename?: 'ServiceComponent', group?: string | null, version?: string | null, kind: string, name: string, namespace?: string | null, service?: { __typename?: 'ServiceDeployment', git?: { __typename?: 'GitRef', ref: string, folder: string } | null, repository?: { __typename?: 'GitRepository', httpsPath?: string | null, urlFormat?: string | null } | null } | null } | null } | null> | null, nodePools?: Array<{ __typename?: 'NodePool', id: string, name: string, minSize: number, maxSize: number, instanceType: string, spot?: boolean | null, labels?: Record | null, taints?: Array<{ __typename?: 'Taint', effect: string, key: string, value: string } | null> | null } | null> | null, nodes?: Array<{ __typename?: 'Node', status: { __typename?: 'NodeStatus', capacity?: Record | null, phase?: string | null, allocatable?: Record | null, conditions?: Array<{ __typename?: 'NodeCondition', type?: string | null, status?: string | null, message?: string | null } | null> | null }, metadata: { __typename?: 'Metadata', uid?: string | null, name: string, namespace?: string | null, creationTimestamp?: string | null, labels?: Array<{ __typename?: 'LabelPair', name?: string | null, value?: string | null } | null> | null, annotations?: Array<{ __typename?: 'LabelPair', name?: string | null, value?: string | null } | null> | null }, spec: { __typename?: 'NodeSpec', podCidr?: string | null, providerId?: string | null } } | null> | null, nodeMetrics?: Array<{ __typename?: 'NodeMetric', timestamp?: string | null, window?: string | null, usage?: { __typename?: 'NodeUsage', cpu?: string | null, memory?: string | null } | null, metadata: { __typename?: 'Metadata', uid?: string | null, name: string, namespace?: string | null, creationTimestamp?: string | null, labels?: Array<{ __typename?: 'LabelPair', name?: string | null, value?: string | null } | null> | null, annotations?: Array<{ __typename?: 'LabelPair', name?: string | null, value?: string | null } | null> | null } } | null> | null, provider?: { __typename?: 'ClusterProvider', id: string, cloud: string, name: string, namespace: string, supportedVersions?: Array | null } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string, repository?: { __typename?: 'GitRepository', url: string } | null } | null, status?: { __typename?: 'ClusterStatus', controlPlaneReady?: boolean | null, failureMessage?: string | null, failureReason?: string | null, phase?: string | null, conditions?: Array<{ __typename?: 'ClusterCondition', lastTransitionTime?: string | null, message?: string | null, reason?: string | null, severity?: string | null, status?: string | null, type?: string | null } | null> | null } | null, tags?: Array<{ __typename?: 'Tag', name: string, value: string } | null> | null, prAutomations?: Array<{ __typename?: 'PrAutomation', id: string, name: string, documentation?: string | null, addon?: string | null, identifier: string, role?: PrRole | null, cluster?: { __typename?: 'Cluster', handle?: string | null, protect?: boolean | null, deletedAt?: string | null, version?: string | null, currentVersion?: string | null, id: string, name: string, self?: boolean | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', cloud: string } | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string } | null, repository?: { __typename?: 'GitRepository', url: string, refs?: Array | null } | null, connection?: { __typename?: 'ScmConnection', id: string, name: string, insertedAt?: string | null, updatedAt?: string | null, type: ScmType, username?: string | null, baseUrl?: string | null, apiUrl?: string | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, writeBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, configuration?: Array<{ __typename?: 'PrConfiguration', values?: Array | null, default?: string | null, documentation?: string | null, longform?: string | null, name: string, optional?: boolean | null, placeholder?: string | null, type: ConfigurationType, condition?: { __typename?: 'PrConfigurationCondition', field: string, operation: Operation, value?: string | null } | null } | null> | null } | null> | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null }; export type ClustersQueryVariables = Exact<{ @@ -8972,8 +8976,44 @@ export type ClusterLogsQueryVariables = Exact<{ export type ClusterLogsQuery = { __typename?: 'RootQueryType', cluster?: { __typename?: 'Cluster', logs?: Array<{ __typename?: 'LogStream', stream?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null }; +export type ClusterMetricsQueryVariables = Exact<{ + clusterId: Scalars['ID']['input']; + start?: InputMaybe; + stop?: InputMaybe; + step?: InputMaybe; +}>; + + +export type ClusterMetricsQuery = { __typename?: 'RootQueryType', cluster?: { __typename?: 'Cluster', clusterMetrics?: { __typename?: 'ClusterMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuRequests?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuLimits?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memory?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryRequests?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryLimits?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, pods?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null } | null }; + +export type ClusterNodeMetricsQueryVariables = Exact<{ + clusterId: Scalars['ID']['input']; + node: Scalars['String']['input']; + start?: InputMaybe; + stop?: InputMaybe; + step?: InputMaybe; +}>; + + +export type ClusterNodeMetricsQuery = { __typename?: 'RootQueryType', cluster?: { __typename?: 'Cluster', clusterNodeMetrics?: { __typename?: 'ClusterNodeMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, cpuUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memory?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, memoryUsage?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null } | null }; + export type MetricResponseFragment = { __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null }; +export type ComponentMetricsFragmentFragment = { __typename?: 'ServiceDeployment', componentMetrics?: { __typename?: 'ServiceComponentMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, mem?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, podCpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, podMem?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null }; + +export type ServiceDeploymentComponentMetricsQueryVariables = Exact<{ + id?: InputMaybe; + name?: InputMaybe; + cluster?: InputMaybe; + componentId: Scalars['ID']['input']; + start?: InputMaybe; + stop?: InputMaybe; + step?: InputMaybe; +}>; + + +export type ServiceDeploymentComponentMetricsQuery = { __typename?: 'RootQueryType', serviceDeployment?: { __typename?: 'ServiceDeployment', componentMetrics?: { __typename?: 'ServiceComponentMetrics', cpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, mem?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, podCpu?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null, podMem?: Array<{ __typename?: 'MetricResponse', metric?: Record | null, values?: Array<{ __typename?: 'MetricResult', timestamp?: any | null, value?: string | null } | null> | null } | null> | null } | null } | null }; + export type UsageQueryVariables = Exact<{ cpu: Scalars['String']['input']; mem: Scalars['String']['input']; @@ -10913,6 +10953,66 @@ export const RuntimeServiceDetailsFragmentDoc = gql` } ${AddonVersionFragmentDoc} ${AddonVersionBlockingFragmentDoc}`; +export const MetricResponseFragmentDoc = gql` + fragment MetricResponse on MetricResponse { + metric + values { + timestamp + value + } +} + `; +export const ClusterMetricsFragmentFragmentDoc = gql` + fragment ClusterMetricsFragment on Cluster { + clusterMetrics(start: $start, stop: $stop, step: $step) { + cpu { + ...MetricResponse + } + cpuUsage { + ...MetricResponse + } + cpuRequests { + ...MetricResponse + } + cpuLimits { + ...MetricResponse + } + memory { + ...MetricResponse + } + memoryUsage { + ...MetricResponse + } + memoryRequests { + ...MetricResponse + } + memoryLimits { + ...MetricResponse + } + pods { + ...MetricResponse + } + } +} + ${MetricResponseFragmentDoc}`; +export const ClusterNodeMetricsFragmentFragmentDoc = gql` + fragment ClusterNodeMetricsFragment on Cluster { + clusterNodeMetrics(node: $node, start: $start, stop: $stop, step: $step) { + cpu { + ...MetricResponse + } + cpuUsage { + ...MetricResponse + } + memory { + ...MetricResponse + } + memoryUsage { + ...MetricResponse + } + } +} + ${MetricResponseFragmentDoc}`; export const ClusterBindingsFragmentDoc = gql` fragment ClusterBindings on Cluster { readBindings { @@ -10938,15 +11038,29 @@ export const LogStreamFragmentDoc = gql` } } `; -export const MetricResponseFragmentDoc = gql` - fragment MetricResponse on MetricResponse { - metric - values { - timestamp - value +export const ComponentMetricsFragmentFragmentDoc = gql` + fragment ComponentMetricsFragment on ServiceDeployment { + componentMetrics( + componentId: $componentId + start: $start + stop: $stop + step: $step + ) { + cpu { + ...MetricResponse + } + mem { + ...MetricResponse + } + podCpu { + ...MetricResponse + } + podMem { + ...MetricResponse + } } } - `; + ${MetricResponseFragmentDoc}`; export const HelmRepositoryFragmentDoc = gql` fragment HelmRepository on HelmRepository { id @@ -15156,6 +15270,139 @@ export type ClusterLogsQueryHookResult = ReturnType; export type ClusterLogsLazyQueryHookResult = ReturnType; export type ClusterLogsSuspenseQueryHookResult = ReturnType; export type ClusterLogsQueryResult = Apollo.QueryResult; +export const ClusterMetricsDocument = gql` + query ClusterMetrics($clusterId: ID!, $start: DateTime, $stop: DateTime, $step: String) { + cluster(id: $clusterId) { + ...ClusterMetricsFragment + } +} + ${ClusterMetricsFragmentFragmentDoc}`; + +/** + * __useClusterMetricsQuery__ + * + * To run a query within a React component, call `useClusterMetricsQuery` and pass it any options that fit your needs. + * When your component renders, `useClusterMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useClusterMetricsQuery({ + * variables: { + * clusterId: // value for 'clusterId' + * start: // value for 'start' + * stop: // value for 'stop' + * step: // value for 'step' + * }, + * }); + */ +export function useClusterMetricsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ClusterMetricsDocument, options); + } +export function useClusterMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ClusterMetricsDocument, options); + } +export function useClusterMetricsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ClusterMetricsDocument, options); + } +export type ClusterMetricsQueryHookResult = ReturnType; +export type ClusterMetricsLazyQueryHookResult = ReturnType; +export type ClusterMetricsSuspenseQueryHookResult = ReturnType; +export type ClusterMetricsQueryResult = Apollo.QueryResult; +export const ClusterNodeMetricsDocument = gql` + query ClusterNodeMetrics($clusterId: ID!, $node: String!, $start: DateTime, $stop: DateTime, $step: String) { + cluster(id: $clusterId) { + ...ClusterNodeMetricsFragment + } +} + ${ClusterNodeMetricsFragmentFragmentDoc}`; + +/** + * __useClusterNodeMetricsQuery__ + * + * To run a query within a React component, call `useClusterNodeMetricsQuery` and pass it any options that fit your needs. + * When your component renders, `useClusterNodeMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useClusterNodeMetricsQuery({ + * variables: { + * clusterId: // value for 'clusterId' + * node: // value for 'node' + * start: // value for 'start' + * stop: // value for 'stop' + * step: // value for 'step' + * }, + * }); + */ +export function useClusterNodeMetricsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ClusterNodeMetricsDocument, options); + } +export function useClusterNodeMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ClusterNodeMetricsDocument, options); + } +export function useClusterNodeMetricsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ClusterNodeMetricsDocument, options); + } +export type ClusterNodeMetricsQueryHookResult = ReturnType; +export type ClusterNodeMetricsLazyQueryHookResult = ReturnType; +export type ClusterNodeMetricsSuspenseQueryHookResult = ReturnType; +export type ClusterNodeMetricsQueryResult = Apollo.QueryResult; +export const ServiceDeploymentComponentMetricsDocument = gql` + query ServiceDeploymentComponentMetrics($id: ID, $name: String, $cluster: String, $componentId: ID!, $start: DateTime, $stop: DateTime, $step: String) { + serviceDeployment(id: $id, name: $name, cluster: $cluster) { + ...ComponentMetricsFragment + } +} + ${ComponentMetricsFragmentFragmentDoc}`; + +/** + * __useServiceDeploymentComponentMetricsQuery__ + * + * To run a query within a React component, call `useServiceDeploymentComponentMetricsQuery` and pass it any options that fit your needs. + * When your component renders, `useServiceDeploymentComponentMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useServiceDeploymentComponentMetricsQuery({ + * variables: { + * id: // value for 'id' + * name: // value for 'name' + * cluster: // value for 'cluster' + * componentId: // value for 'componentId' + * start: // value for 'start' + * stop: // value for 'stop' + * step: // value for 'step' + * }, + * }); + */ +export function useServiceDeploymentComponentMetricsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ServiceDeploymentComponentMetricsDocument, options); + } +export function useServiceDeploymentComponentMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ServiceDeploymentComponentMetricsDocument, options); + } +export function useServiceDeploymentComponentMetricsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ServiceDeploymentComponentMetricsDocument, options); + } +export type ServiceDeploymentComponentMetricsQueryHookResult = ReturnType; +export type ServiceDeploymentComponentMetricsLazyQueryHookResult = ReturnType; +export type ServiceDeploymentComponentMetricsSuspenseQueryHookResult = ReturnType; +export type ServiceDeploymentComponentMetricsQueryResult = Apollo.QueryResult; export const UsageDocument = gql` query Usage($cpu: String!, $mem: String!, $podCpu: String!, $podMem: String!, $step: String!, $offset: Int!, $clusterId: ID) { cpu: metric(clusterId: $clusterId, query: $cpu, offset: $offset, step: $step) { @@ -22092,6 +22339,9 @@ export const namedOperations = { ClusterStatuses: 'ClusterStatuses', TagPairs: 'TagPairs', ClusterLogs: 'ClusterLogs', + ClusterMetrics: 'ClusterMetrics', + ClusterNodeMetrics: 'ClusterNodeMetrics', + ServiceDeploymentComponentMetrics: 'ServiceDeploymentComponentMetrics', Usage: 'Usage', GitRepositories: 'GitRepositories', HelmRepositories: 'HelmRepositories', @@ -22313,6 +22563,8 @@ export const namedOperations = { AddonVersion: 'AddonVersion', AddonVersionBlocking: 'AddonVersionBlocking', ClustersRow: 'ClustersRow', + ClusterMetricsFragment: 'ClusterMetricsFragment', + ClusterNodeMetricsFragment: 'ClusterNodeMetricsFragment', Cluster: 'Cluster', ClusterTiny: 'ClusterTiny', ClusterBasic: 'ClusterBasic', @@ -22321,6 +22573,7 @@ export const namedOperations = { ClusterStatusInfo: 'ClusterStatusInfo', LogStream: 'LogStream', MetricResponse: 'MetricResponse', + ComponentMetricsFragment: 'ComponentMetricsFragment', GitRepository: 'GitRepository', HelmRepository: 'HelmRepository', FluxHelmRepository: 'FluxHelmRepository', diff --git a/assets/src/graph/cdClusters.graphql b/assets/src/graph/cdClusters.graphql index a4b6aefda6..8f97a55c2a 100644 --- a/assets/src/graph/cdClusters.graphql +++ b/assets/src/graph/cdClusters.graphql @@ -216,6 +216,58 @@ fragment ClustersRow on Cluster { } } +fragment ClusterMetricsFragment on Cluster { + clusterMetrics(start: $start, stop: $stop, step: $step) { + cpu { + ...MetricResponse + } + cpuUsage { + ...MetricResponse + } + cpuRequests { + ...MetricResponse + } + cpuLimits { + ...MetricResponse + } + + memory { + ...MetricResponse + } + memoryUsage { + ...MetricResponse + } + memoryRequests { + ...MetricResponse + } + memoryLimits { + ...MetricResponse + } + + pods { + ...MetricResponse + } + } +} + +fragment ClusterNodeMetricsFragment on Cluster { + clusterNodeMetrics(node: $node, start: $start, stop: $stop, step: $step) { + cpu { + ...MetricResponse + } + cpuUsage { + ...MetricResponse + } + + memory { + ...MetricResponse + } + memoryUsage { + ...MetricResponse + } + } +} + fragment Cluster on Cluster { ...ClustersRow apiDeprecations { @@ -535,3 +587,26 @@ query ClusterLogs( } } } + +query ClusterMetrics( + $clusterId: ID! + $start: DateTime + $stop: DateTime + $step: String +) { + cluster(id: $clusterId) { + ...ClusterMetricsFragment + } +} + +query ClusterNodeMetrics( + $clusterId: ID! + $node: String! + $start: DateTime + $stop: DateTime + $step: String +) { + cluster(id: $clusterId) { + ...ClusterNodeMetricsFragment + } +} diff --git a/assets/src/graph/cdComponent.graphql b/assets/src/graph/cdComponent.graphql index 88a5265be4..014c1685b7 100644 --- a/assets/src/graph/cdComponent.graphql +++ b/assets/src/graph/cdComponent.graphql @@ -6,6 +6,43 @@ fragment MetricResponse on MetricResponse { } } +fragment ComponentMetricsFragment on ServiceDeployment { + componentMetrics( + componentId: $componentId + start: $start + stop: $stop + step: $step + ) { + cpu { + ...MetricResponse + } + mem { + ...MetricResponse + } + + podCpu { + ...MetricResponse + } + podMem { + ...MetricResponse + } + } +} + +query ServiceDeploymentComponentMetrics( + $id: ID + $name: String + $cluster: String + $componentId: ID! + $start: DateTime + $stop: DateTime + $step: String +) { + serviceDeployment(id: $id, name: $name, cluster: $cluster) { + ...ComponentMetricsFragment + } +} + query Usage( $cpu: String! $mem: String! diff --git a/assets/src/utils/kubernetes.ts b/assets/src/utils/kubernetes.ts index 7396300d5a..78776c201e 100644 --- a/assets/src/utils/kubernetes.ts +++ b/assets/src/utils/kubernetes.ts @@ -49,7 +49,7 @@ export function cpuFormat(value?: string | number | null) { return `${value * MULTIPLES.m}m` } - return `${value}` + return value.toPrecision(2) } export function memoryFormat(value?: number | null | undefined) { diff --git a/assets/src/utils/prometheus.ts b/assets/src/utils/prometheus.ts new file mode 100644 index 0000000000..146ea84858 --- /dev/null +++ b/assets/src/utils/prometheus.ts @@ -0,0 +1,82 @@ +import { sumBy } from 'lodash' + +import { + HttpConnection, + Maybe, + MetricResponse, + MetricResult, + Node, +} from '../generated/graphql' + +import { cpuParser, memoryParser } from './kubernetes' + +enum CapacityType { + CPU, + Memory, + Pods, +} + +type Capacity = { cpu?: string; pods?: string; memory?: string } | undefined + +function avg(metrics: Array): number | undefined { + if (!metrics || metrics?.length === 0) return undefined + + return ( + metrics.reduce((acc, cur) => acc + (cpuParser(cur?.value) ?? 0), 0) / + metrics.length ?? undefined + ) +} + +function capacity( + type: CapacityType, + ...nodes: Array | Node> +): number | null { + if (nodes?.length === 0) { + return null + } + + switch (type) { + case CapacityType.CPU: + return sumBy( + nodes, + (node) => cpuParser((node?.status?.capacity as Capacity)?.cpu) ?? 0 + ) + case CapacityType.Memory: + return sumBy( + nodes, + (n) => memoryParser((n?.status?.capacity as Capacity)?.memory) ?? 0 + ) + case CapacityType.Pods: + return sumBy(nodes, (n) => + parseInt((n?.status?.capacity as Capacity)?.pods ?? '0') + ) + } +} + +function pods(pods: Array): number { + return parseInt(pods?.at(pods.length - 1)?.value ?? '0') +} + +function toValues( + metrics: Array> | undefined | null +): Array { + if (!metrics || metrics?.length === 0) return [] + if (metrics.length > 1) throw new Error('expecting a single metric response') + + return (metrics.at(0)?.values ?? []) as Array +} + +function enabled(connection: Nullable): boolean { + return !!connection && connection?.host?.length > 0 +} + +export const Prometheus = { + // Functions + avg, + capacity, + toValues, + pods, + enabled, + // Types + CapacityType, +}