@@ -76,14 +76,41 @@ const devworkspaceReadyDuration = new Trend('devworkspace_ready_duration');
7676const devworkspaceReadyFailed = new Counter ( 'devworkspace_ready_failed' ) ;
7777const operatorCpu = new Trend ( 'average_operator_cpu' ) ; // in milli cores
7878const operatorMemory = new Trend ( 'average_operator_memory' ) ; // in Mi
79+ const etcdCpu = new Trend ( 'average_etcd_cpu' ) ; // in milli cores
80+ const etcdMemory = new Trend ( 'average_etcd_memory' ) ; // in Mi
7981const devworkspacesCreated = new Counter ( 'devworkspace_create_count' ) ;
8082const operatorCpuViolations = new Counter ( 'operator_cpu_violations' ) ;
8183const operatorMemViolations = new Counter ( 'operator_mem_violations' ) ;
8284
8385const maxCpuMillicores = 250 ;
8486const maxMemoryBytes = 200 * 1024 * 1024 ;
8587
88+ let etcdNamespace = 'openshift-etcd' ;
89+ let etcdPodNamePattern = 'etcd' ;
90+
91+ function detectClusterType ( ) {
92+ const apiGroupsUrl = `${ apiServer } /apis` ;
93+ const res = http . get ( apiGroupsUrl , { headers} ) ;
94+
95+ if ( res . status === 200 ) {
96+ try {
97+ const data = JSON . parse ( res . body ) ;
98+ const groups = data . groups || [ ] ;
99+ const hasOpenShiftRoutes = groups . some ( g => g . name === 'route.openshift.io' ) ;
100+
101+ if ( ! hasOpenShiftRoutes ) {
102+ etcdNamespace = __ENV . ETCD_NAMESPACE || 'kube-system' ;
103+ etcdPodNamePattern = __ENV . ETCD_POD_NAME_PATTERN || 'kube-proxy' ;
104+ console . log ( 'Detected Kubernetes cluster - using kube-system namespace with kube-proxy' ) ;
105+ }
106+ } catch ( e ) {
107+ console . warn ( `Failed to detect cluster type: ${ e . message } , using defaults` ) ;
108+ }
109+ }
110+ }
111+
86112export function setup ( ) {
113+ detectClusterType ( ) ;
87114 if ( shouldCreateAutomountResources ) {
88115 createNewAutomountConfigMap ( ) ;
89116 createNewAutomountSecret ( ) ;
@@ -155,7 +182,7 @@ export function final_cleanup() {
155182}
156183
157184export function handleSummary ( data ) {
158- const allowed = [ 'devworkspace_create_count' , 'devworkspace_create_duration' , 'devworkspace_delete_duration' , 'devworkspace_ready_duration' , 'devworkspace_ready' , 'devworkspace_ready_failed' , 'operator_cpu_violations' , 'operator_mem_violations' , 'average_operator_cpu' , 'average_operator_memory' ] ;
185+ const allowed = [ 'devworkspace_create_count' , 'devworkspace_create_duration' , 'devworkspace_delete_duration' , 'devworkspace_ready_duration' , 'devworkspace_ready' , 'devworkspace_ready_failed' , 'operator_cpu_violations' , 'operator_mem_violations' , 'average_operator_cpu' , 'average_operator_memory' , 'etcd_cpu_violations' , 'etcd_mem_violations' , 'average_etcd_cpu' , 'average_etcd_memory' ] ;
159186
160187 const filteredData = JSON . parse ( JSON . stringify ( data ) ) ;
161188 for ( const key of Object . keys ( filteredData . metrics ) ) {
@@ -227,6 +254,7 @@ function waitUntilDevWorkspaceIsReady(vuId, crName, namespace) {
227254 }
228255
229256 checkDevWorkspaceOperatorMetrics ( ) ;
257+ checkEtcdMetrics ( ) ;
230258 sleep ( pollWaitInterval ) ;
231259 attempts ++ ;
232260 }
@@ -295,6 +323,57 @@ function checkDevWorkspaceOperatorMetrics() {
295323 }
296324}
297325
326+ function checkEtcdMetrics ( ) {
327+ if ( ! etcdNamespace || ! etcdPodNamePattern ) {
328+ console . warn ( `[ETCD METRICS] Variables not initialized: etcdNamespace=${ etcdNamespace } , etcdPodNamePattern=${ etcdPodNamePattern } ` ) ;
329+ return ;
330+ }
331+
332+ const metricsUrl = `${ apiServer } /apis/metrics.k8s.io/v1beta1/namespaces/${ etcdNamespace } /pods` ;
333+ const res = http . get ( metricsUrl , { headers} ) ;
334+
335+ check ( res , {
336+ 'Fetched etcd pod metrics successfully' : ( r ) => r . status === 200 ,
337+ } ) ;
338+
339+ if ( res . status !== 200 ) {
340+ return ;
341+ }
342+
343+ const data = JSON . parse ( res . body ) ;
344+ const etcdPods = data . items . filter ( p => p . metadata . name . includes ( etcdPodNamePattern ) ) ;
345+
346+ if ( etcdPods . length === 0 ) {
347+ if ( data . items && data . items . length > 0 ) {
348+ const podNames = data . items . map ( p => p . metadata . name ) . join ( ', ' ) ;
349+ console . warn ( `[ETCD METRICS] No pods found matching pattern '${ etcdPodNamePattern } ' in namespace '${ etcdNamespace } '. Available pods: ${ podNames } ` ) ;
350+ } else {
351+ console . warn ( `[ETCD METRICS] No pods found in namespace '${ etcdNamespace } '` ) ;
352+ }
353+ return ;
354+ }
355+
356+ for ( const pod of etcdPods ) {
357+ if ( ! pod . containers || pod . containers . length === 0 ) {
358+ console . warn ( `[ETCD METRICS] Pod ${ pod . metadata . name } has no containers` ) ;
359+ continue ;
360+ }
361+ const container = pod . containers [ 0 ] ;
362+ const name = pod . metadata . name ;
363+
364+ if ( ! container . usage || ! container . usage . cpu || ! container . usage . memory ) {
365+ console . warn ( `[ETCD METRICS] Pod ${ name } has no usage data:` , JSON . stringify ( container . usage ) ) ;
366+ continue ;
367+ }
368+
369+ const cpu = parseCpuToMillicores ( container . usage . cpu ) ;
370+ const memory = parseMemoryToBytes ( container . usage . memory ) ;
371+
372+ etcdCpu . add ( cpu ) ;
373+ etcdMemory . add ( memory / 1024 / 1024 ) ;
374+ }
375+ }
376+
298377function createNewNamespace ( namespaceName ) {
299378 const url = `${ apiServer } /api/v1/namespaces` ;
300379
0 commit comments