From 1ae4de361746e720a52d768f77dda61f07f99ff5 Mon Sep 17 00:00:00 2001 From: Joshna907 Date: Thu, 20 Aug 2026 15:34:59 +0530 Subject: [PATCH 1/3] prometheus: util: Support Argo CD Application metrics Signed-off-by: Joshna907 --- prometheus/src/util.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus/src/util.ts b/prometheus/src/util.ts index 321cba5de9..6593a848f2 100644 --- a/prometheus/src/util.ts +++ b/prometheus/src/util.ts @@ -164,6 +164,7 @@ function getResourceApiVersion(resource?: ResourceIdentity): string | undefined const resourceApiVersionRules: Record = { Job: /^batch\/v1$/, Queue: /^scheduling\.volcano\.sh\/v1beta1$/, + Application: /^argoproj\.io\/v1alpha1$/, Cluster: /^cluster\.x-k8s\.io\//, Machine: /^cluster\.x-k8s\.io\//, MachineDeployment: /^cluster\.x-k8s\.io\//, From dd2d559d150297024a052713275adc6335061559 Mon Sep 17 00:00:00 2001 From: Joshna907 Date: Thu, 20 Aug 2026 15:34:59 +0530 Subject: [PATCH 2/3] prometheus: charts: Add Argo CD Application metric charts Signed-off-by: Joshna907 --- .../ArgoCDApplicationChart.test.ts | 26 ++++ .../ArgoCDApplicationChart.tsx | 126 ++++++++++++++++++ prometheus/src/index.tsx | 13 ++ prometheus/src/util.test.ts | 4 +- prometheus/src/util.ts | 1 + 5 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.test.ts create mode 100644 prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.tsx diff --git a/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.test.ts b/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.test.ts new file mode 100644 index 0000000000..bcfae8db52 --- /dev/null +++ b/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.test.ts @@ -0,0 +1,26 @@ +import { getArgoCDApplicationChartConfigs } from './ArgoCDApplicationChart'; + +describe('Argo CD Application chart configuration', () => { + test('creates the three Application-specific charts', () => { + const configs = getArgoCDApplicationChartConfigs('argocd', 'guestbook'); + + expect(configs.map(config => config.key)).toEqual([ + 'sync-activity', + 'sync-duration', + 'orphaned-resources', + ]); + expect(configs[0].queries.query).toContain('argocd_app_sync_total'); + expect(configs[1].queries.query).toContain('argocd_app_sync_duration_seconds_total'); + expect(configs[1].queries.query).toContain('argocd_app_sync_total'); + expect(configs[1].queries.query).not.toContain('argocd_app_sync_duration_seconds_sum'); + expect(configs[1].queries.query).not.toContain('argocd_app_sync_duration_seconds_count'); + expect(configs[2].queries.query).toContain('argocd_app_orphaned_resources_count'); + }); + + test('escapes Application labels before embedding them in PromQL', () => { + const configs = getArgoCDApplicationChartConfigs('team"one', 'app\\name'); + + expect(configs[0].queries.query).toContain('namespace="team\\"one"'); + expect(configs[0].queries.query).toContain('name="app\\\\name"'); + }); +}); diff --git a/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.tsx b/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.tsx new file mode 100644 index 0000000000..c35be1a3c8 --- /dev/null +++ b/prometheus/src/components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart.tsx @@ -0,0 +1,126 @@ +import { useTranslation } from '@kinvolk/headlamp-plugin/lib'; +import { blue } from '@mui/material/colors'; +import { alpha, useTheme } from '@mui/material/styles'; +import { fetchMetrics } from '../../../request'; +import { createDataProcessor, createTickTimestampFormatter } from '../../../util'; +import Chart from '../Chart/Chart'; + +interface ArgoCDApplicationMetricChartProps { + refresh: boolean; + prometheusPrefix: string; + resolution: string; + subPath?: string; + timespan: string; + query: string; + metricLabel: string; + yAxisLabel: string; + CustomTooltip: (props: any) => JSX.Element | null; +} + +/** Renders one Application-specific Argo CD metric using the shared chart behaviour. */ +export function ArgoCDApplicationMetricChart(props: ArgoCDApplicationMetricChartProps) { + const { t } = useTranslation(); + const theme = useTheme(); + const xTickFormatter = createTickTimestampFormatter(props.timespan); + + return ( + { + const value = xTickFormatter(tickProps.payload.value); + if (!value) return null; + + return ( + + + {value} + + + ); + }, + }} + yAxisProps={{ + domain: [0, 'auto'], + width: 80, + label: { + value: t(props.yAxisLabel), + angle: -90, + position: 'insideLeft', + style: { textAnchor: 'middle' }, + }, + }} + CustomTooltip={props.CustomTooltip} + fetchMetrics={fetchMetrics} + autoRefresh={props.refresh} + prometheusPrefix={props.prometheusPrefix} + interval={props.timespan} + resolution={props.resolution} + subPath={props.subPath} + /> + ); +} + +function escapePrometheusLabelValue(value: string) { + return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n'); +} + +function applicationSelector(namespace: string, name: string) { + return `namespace="${escapePrometheusLabelValue(namespace)}",name="${escapePrometheusLabelValue( + name + )}"`; +} + +export function getArgoCDApplicationChartConfigs(namespace: string, name: string) { + const selector = applicationSelector(namespace, name); + + return [ + { + key: 'sync-activity', + label: 'Sync activity', + icon: 'mdi:sync', + queries: { + query: `sum(increase(argocd_app_sync_total{${selector}}[5m]))`, + metricLabel: 'Sync operations', + yAxisLabel: 'Operations', + }, + component: ArgoCDApplicationMetricChart, + }, + { + key: 'sync-duration', + label: 'Sync duration', + icon: 'mdi:timer-outline', + queries: { + query: `sum(rate(argocd_app_sync_duration_seconds_total{${selector}}[5m])) / sum(rate(argocd_app_sync_total{${selector}}[5m]))`, + metricLabel: 'Average sync duration', + yAxisLabel: 'Seconds', + }, + component: ArgoCDApplicationMetricChart, + }, + { + key: 'orphaned-resources', + label: 'Orphaned resources', + icon: 'mdi:alert-outline', + queries: { + query: `argocd_app_orphaned_resources_count{${selector}}`, + metricLabel: 'Orphaned resources', + yAxisLabel: 'Resources', + }, + component: ArgoCDApplicationMetricChart, + }, + ]; +} diff --git a/prometheus/src/index.tsx b/prometheus/src/index.tsx index a313e57288..246f9b4c36 100644 --- a/prometheus/src/index.tsx +++ b/prometheus/src/index.tsx @@ -6,6 +6,7 @@ import { } from '@kinvolk/headlamp-plugin/lib'; import type { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/KubeObject'; import { KarpenterChart } from '../src/components/Chart/KarpenterChart/KarpenterChart'; +import { getArgoCDApplicationChartConfigs } from './components/Chart/ArgoCDApplicationChart/ArgoCDApplicationChart'; import { CapiChart } from './components/Chart/CapiChart/CapiChart'; import { DiskMetricsChart } from './components/Chart/DiskMetricsChart/DiskMetricsChart'; import { GenericMetricsChart } from './components/Chart/GenericMetricsChart/GenericMetricsChart'; @@ -182,6 +183,18 @@ function PrometheusMetrics(resource: KubeObject) { return ; } + if (resourceKind === 'Application' && resource.jsonData?.apiVersion === 'argoproj.io/v1alpha1') { + return ( + + ); + } + // cluster api resources if (resource.kind === 'Cluster') { const name = resource.jsonData.metadata.name; diff --git a/prometheus/src/util.test.ts b/prometheus/src/util.test.ts index 7e145d45d1..5e48463a2d 100644 --- a/prometheus/src/util.test.ts +++ b/prometheus/src/util.test.ts @@ -139,12 +139,12 @@ describe('supportsPrometheusMetrics', () => { ], ['rejects Queues without apiVersion', { kind: 'Queue', jsonData: { kind: 'Queue' } }, false], [ - 'does not show Application metrics before the chart UI is available', + 'supports Argo CD Applications when the chart UI is available', { kind: 'Application', jsonData: { kind: 'Application', apiVersion: 'argoproj.io/v1alpha1' }, }, - false, + true, ], [ 'rejects non-Argo Applications with the same kind', diff --git a/prometheus/src/util.ts b/prometheus/src/util.ts index 6593a848f2..153c71bcde 100644 --- a/prometheus/src/util.ts +++ b/prometheus/src/util.ts @@ -220,6 +220,7 @@ const ChartEnabledKinds = [ 'Service', 'Revision', 'Queue', + 'Application', 'Cluster', 'MachineDeployment', 'MachineSet', From 78f7e04c17af762a37b5cab285f0194e5dbf5191 Mon Sep 17 00:00:00 2001 From: Joshna907 Date: Thu, 20 Aug 2026 15:34:59 +0530 Subject: [PATCH 3/3] prometheus: README: Document Argo CD metrics support Signed-off-by: Joshna907 --- prometheus/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prometheus/README.md b/prometheus/README.md index 1304a2c394..a6c8ee882a 100644 --- a/prometheus/README.md +++ b/prometheus/README.md @@ -2,6 +2,11 @@ This plugin adds advanced charts to the details view of workload resources. +It also shows optional Argo CD Application charts for sync activity, average sync duration, and +orphaned resources. These charts appear only when Prometheus is enabled for the selected cluster +and the resource is `argoproj.io/v1alpha1` `Application`. Missing Argo CD metrics simply show the +normal no-data state. + ## Enabling Charts For the charts to be shown, Prometheus must be installed in the cluster.