Repository: kubernetes-sigs/kueue

Issue:
Flaky Test: Race detection on MultiKueue AdmissionCheck controller
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

If the matter is security related, please disclose it privately via https://kubernetes.io/security/
-->


**What happened**:

The CI detects race conditions in the following:

- `sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue: TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match`

```shell
{Failed  === RUN   TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match
==================
WARNING: DATA RACE
Read at 0x00c000438043 by goroutine 203:
  testing.(*common).logDepth()
      /usr/local/go/src/testing/testing.go:1053 +0xca
  testing.(*common).log()
      /usr/local/go/src/testing/testing.go:1046 +0x7b
  testing.(*common).Log()
      /usr/local/go/src/testing/testing.go:1087 +0x53
  github.com/go-logr/logr/testr.logInfo()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/testr/testr.go:101 +0x193
  github.com/go-logr/logr/testr.testloggerInterface.Info()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/testr/testr.go:148 +0x13c
  github.com/go-logr/logr/testr.(*testloggerInterface).Info()
      <autogenerated>:1 +0x147
  github.com/go-logr/logr.Logger.Info()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/logr.go:280 +0x107
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.(*KubeConfigFSWatcher).Start.func1()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch.go:92 +0x904

Previous write at 0x00c000438043 by goroutine 197:
  testing.tRunner.func1()
      /usr/local/go/src/testing/testing.go:1779 +0x8b9
  runtime.deferreturn()
      /usr/local/go/src/runtime/panic.go:610 +0x5d
  testing.(*T).Run.gowrap1()
      /usr/local/go/src/testing/testing.go:1851 +0x44

Goroutine 203 (running) created at:
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.(*KubeConfigFSWatcher).Start()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch.go:70 +0x245
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.TestFSWatchAddRm.func2()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch_test.go:227 +0x44
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.TestFSWatchAddRm.func13()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch_test.go:452 +0x5a
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1792 +0x225
  testing.(*T).Run.gowrap1()
      /usr/local/go/src/testing/testing.go:1851 +0x44

Goroutine 197 (finished) created at:
  testing.(*T).Run()
      /usr/local/go/src/testing/testing.go:1851 +0x8f2
  testing.runTests.func1()
      /usr/local/go/src/testing/testing.go:2279 +0x85
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1792 +0x225
  testing.runTests()
      /usr/local/go/src/testing/testing.go:2277 +0x96c
  testing.(*M).Run()
      /usr/local/go/src/testing/testing.go:2142 +0xeea
  main.main()
      _testmain.go:71 +0x164
==================
    testing.go:1490: race detected during execution of test
--- FAIL: TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match (0.04s)
}
```

- `sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue: TestListMultiKueueClustersUsingKubeConfig`

```shell
{Failed  === RUN   TestListMultiKueueClustersUsingKubeConfig
--- FAIL: TestListMultiKueueClustersUsingKubeConfig (0.13s)
}
```

**What you expected to happen**:

No race condition

**How to reproduce it (as minimally and precisely as possible)**:

https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_kueue/6673/pull-kueue-test-unit-main/1960302981727391744

**Anything else we need to know?**:

**Environment**:
- Kubernetes version (use `kubectl version`):
- Kueue version (use `git describe --tags --dirty --always`):
- Cloud provider or hardware configuration:
- OS (e.g: `cat /etc/os-release`):
- Kernel (e.g. `uname -a`):
- Install tools:
- Others:

Hints from issue comments:
@tenzen-y: /kind flake@tenzen-y: Can anyone take a look at this one? It seems that this occurs frequently.
@tenzen-y: We observed this issue in the periodic test as well: https://prow.k8s.io/view/gs/kubernetes-ci-logs/logs/periodic-kueue-test-unit-main/1960409512091324416
@mbobrovskyi: /assign

Solution patch:
diff --git a/cmd/kueueviz/frontend/package-lock.json b/cmd/kueueviz/frontend/package-lock.json
index d19f355b6..d4c7095b7 100644
--- a/cmd/kueueviz/frontend/package-lock.json
+++ b/cmd/kueueviz/frontend/package-lock.json
@@ -17,13 +17,13 @@
         "react": "^19.1.1",
         "react-ace": "^14.0.1",
         "react-dom": "^19.1.1",
-        "react-router-dom": "^7.8.1",
+        "react-router-dom": "^7.8.2",
         "react-toastify": "^11.0.5"
       },
       "devDependencies": {
-        "@vitejs/plugin-react": "^5.0.0",
+        "@vitejs/plugin-react": "^5.0.1",
         "depcheck": "^1.4.7",
-        "vite": "^7.1.2"
+        "vite": "^7.1.3"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -65,22 +65,22 @@
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
-      "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
+      "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.27.1",
-        "@babel/generator": "^7.28.0",
+        "@babel/generator": "^7.28.3",
         "@babel/helper-compilation-targets": "^7.27.2",
-        "@babel/helper-module-transforms": "^7.27.3",
-        "@babel/helpers": "^7.27.6",
-        "@babel/parser": "^7.28.0",
+        "@babel/helper-module-transforms": "^7.28.3",
+        "@babel/helpers": "^7.28.3",
+        "@babel/parser": "^7.28.3",
         "@babel/template": "^7.27.2",
-        "@babel/traverse": "^7.28.0",
-        "@babel/types": "^7.28.0",
+        "@babel/traverse": "^7.28.3",
+        "@babel/types": "^7.28.2",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -103,13 +103,13 @@
       "license": "MIT"
     },
     "node_modules/@babel/generator": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
-      "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+      "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
       "license": "MIT",
       "dependencies": {
-        "@babel/parser": "^7.28.0",
-        "@babel/types": "^7.28.0",
+        "@babel/parser": "^7.28.3",
+        "@babel/types": "^7.28.2",
         "@jridgewell/gen-mapping": "^0.3.12",
         "@jridgewell/trace-mapping": "^0.3.28",
         "jsesc": "^3.0.2"
@@ -158,15 +158,15 @@
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.27.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
-      "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+      "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-module-imports": "^7.27.1",
         "@babel/helper-validator-identifier": "^7.27.1",
-        "@babel/traverse": "^7.27.3"
+        "@babel/traverse": "^7.28.3"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -214,26 +214,26 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.27.6",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
-      "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
+      "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/template": "^7.27.2",
-        "@babel/types": "^7.27.6"
+        "@babel/types": "^7.28.2"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
-      "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
+      "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
       "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.28.0"
+        "@babel/types": "^7.28.2"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -298,17 +298,17 @@
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
-      "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
+      "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
       "license": "MIT",
       "dependencies": {
         "@babel/code-frame": "^7.27.1",
-        "@babel/generator": "^7.28.0",
+        "@babel/generator": "^7.28.3",
         "@babel/helper-globals": "^7.28.0",
-        "@babel/parser": "^7.28.0",
+        "@babel/parser": "^7.28.3",
         "@babel/template": "^7.27.2",
-        "@babel/types": "^7.28.0",
+        "@babel/types": "^7.28.2",
         "debug": "^4.3.1"
       },
       "engines": {
@@ -316,9 +316,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.28.1",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
-      "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
+      "version": "7.28.2",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+      "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
       "license": "MIT",
       "dependencies": {
         "@babel/helper-string-parser": "^7.27.1",
@@ -1170,9 +1170,9 @@
       }
     },
     "node_modules/@rolldown/pluginutils": {
-      "version": "1.0.0-beta.30",
-      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz",
-      "integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==",
+      "version": "1.0.0-beta.32",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz",
+      "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==",
       "dev": true,
       "license": "MIT"
     },
@@ -1559,16 +1559,16 @@
       }
     },
     "node_modules/@vitejs/plugin-react": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz",
-      "integrity": "sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz",
+      "integrity": "sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/core": "^7.28.0",
+        "@babel/core": "^7.28.3",
         "@babel/plugin-transform-react-jsx-self": "^7.27.1",
         "@babel/plugin-transform-react-jsx-source": "^7.27.1",
-        "@rolldown/pluginutils": "1.0.0-beta.30",
+        "@rolldown/pluginutils": "1.0.0-beta.32",
         "@types/babel__core": "^7.20.5",
         "react-refresh": "^0.17.0"
       },
@@ -2866,9 +2866,9 @@
       }
     },
     "node_modules/react-router": {
-      "version": "7.8.1",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz",
-      "integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==",
+      "version": "7.8.2",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz",
+      "integrity": "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==",
       "license": "MIT",
       "dependencies": {
         "cookie": "^1.0.1",
@@ -2888,12 +2888,12 @@
       }
     },
     "node_modules/react-router-dom": {
-      "version": "7.8.1",
-      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.1.tgz",
-      "integrity": "sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==",
+      "version": "7.8.2",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.2.tgz",
+      "integrity": "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==",
       "license": "MIT",
       "dependencies": {
-        "react-router": "7.8.1"
+        "react-router": "7.8.2"
       },
       "engines": {
         "node": ">=20.0.0"
@@ -3245,14 +3245,14 @@
       }
     },
     "node_modules/vite": {
-      "version": "7.1.2",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
-      "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
+      "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "esbuild": "^0.25.0",
-        "fdir": "^6.4.6",
+        "fdir": "^6.5.0",
         "picomatch": "^4.0.3",
         "postcss": "^8.5.6",
         "rollup": "^4.43.0",
@@ -3320,11 +3320,14 @@
       }
     },
     "node_modules/vite/node_modules/fdir": {
-      "version": "6.4.6",
-      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
-      "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
       "dev": true,
       "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
       "peerDependencies": {
         "picomatch": "^3 || ^4"
       },
diff --git a/cmd/kueueviz/frontend/package.json b/cmd/kueueviz/frontend/package.json
index 510a4ffed..434fddcc3 100644
--- a/cmd/kueueviz/frontend/package.json
+++ b/cmd/kueueviz/frontend/package.json
@@ -21,13 +21,13 @@
     "react": "^19.1.1",
     "react-ace": "^14.0.1",
     "react-dom": "^19.1.1",
-    "react-router-dom": "^7.8.1",
+    "react-router-dom": "^7.8.2",
     "react-toastify": "^11.0.5"
   },
   "devDependencies": {
-    "@vitejs/plugin-react": "^5.0.0",
+    "@vitejs/plugin-react": "^5.0.1",
     "depcheck": "^1.4.7",
-    "vite": "^7.1.2"
+    "vite": "^7.1.3"
   },
   "browserslist": {
     "production": [
diff --git a/pkg/cache/snapshot.go b/pkg/cache/snapshot.go
index 4cd01c3a4..33cc9e75a 100644
--- a/pkg/cache/snapshot.go
+++ b/pkg/cache/snapshot.go
@@ -203,7 +203,7 @@ func (c *Cache) snapshotClusterQueue(ctx context.Context, cq *clusterQueue, afsE
 	for i, rg := range cq.ResourceGroups {
 		cc.ResourceGroups[i] = rg.Clone()
 	}
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(c.admissionFairSharing) {
 		if cq.AdmissionScope != nil {
 			cc.AdmissionScope = *cq.AdmissionScope.DeepCopy()
 		}
diff --git a/pkg/controller/core/localqueue_controller.go b/pkg/controller/core/localqueue_controller.go
index 572ec27fd..12982ae86 100644
--- a/pkg/controller/core/localqueue_controller.go
+++ b/pkg/controller/core/localqueue_controller.go
@@ -172,7 +172,7 @@ func (r *LocalQueueReconciler) Reconcile(ctx context.Context, req ctrl.Request)
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
 
-	if r.admissionFSConfig != nil && features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(r.admissionFSConfig) {
 		updated := r.initializeAdmissionFsStatus(&queueObj)
 		sinceLastUpdate := r.clock.Now().Sub(queueObj.Status.FairSharing.AdmissionFairSharingStatus.LastUpdate.Time)
 		lqKey := utilqueue.Key(&queueObj)
diff --git a/pkg/controller/core/workload_controller.go b/pkg/controller/core/workload_controller.go
index b8bc0afcc..8917a3f41 100644
--- a/pkg/controller/core/workload_controller.go
+++ b/pkg/controller/core/workload_controller.go
@@ -233,12 +233,13 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		}
 	} else {
 		var updated, evicted bool
+		var underlyingCause string
 		reason := kueue.WorkloadDeactivated
 		message := "The workload is deactivated"
 		dtCond := apimeta.FindStatusCondition(wl.Status.Conditions, kueue.WorkloadDeactivationTarget)
 		if !apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadEvicted) {
 			if dtCond != nil {
-				reason = fmt.Sprintf("%sDueTo%s", reason, dtCond.Reason)
+				underlyingCause = dtCond.Reason
 				message = fmt.Sprintf("%s due to %s", message, dtCond.Message)
 			}
 			updated = true
@@ -253,7 +254,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		}
 		if updated {
 			if evicted {
-				if err := workload.Evict(ctx, r.client, r.recorder, &wl, reason, "", message, r.clock); err != nil {
+				if err := workload.Evict(ctx, r.client, r.recorder, &wl, reason, underlyingCause, message, r.clock); err != nil {
 					if !apierrors.IsNotFound(err) {
 						return ctrl.Result{}, fmt.Errorf("setting eviction: %w", err)
 					}
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index 14617d093..507d167bd 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -167,11 +167,13 @@ The label 'reason' can have the following values:
 - "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.
 - "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.
 - "Deactivated" means that the workload was evicted because spec.active is set to false.
-- "DeactivatedDueToAdmissionCheck" means that the workload was evicted and deactivated by Kueue due to a rejected admission check.
-- "DeactivatedDueToMaximumExecutionTimeExceeded" means that the workload was evicted and deactivated by Kueue due to maximum execution time exceeded.
-- "DeactivatedDueToRequeuingLimitExceeded" means that the workload was evicted and deactivated by Kueue due to requeuing limit exceeded.`,
+The label 'underlying_cause' can have the following values:
+- "" means that the value in 'reason' label is the root cause for eviction.
+- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.
+- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.
+- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded.`,
 			Buckets: generateExponentialBuckets(14),
-		}, []string{"cluster_queue", "reason"},
+		}, []string{"cluster_queue", "reason", "underlying_cause"},
 	)
 
 	localQueueQuotaReservedWaitTime = prometheus.NewHistogramVec(
@@ -284,10 +286,12 @@ The label 'reason' can have the following values:
 - "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.
 - "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.
 - "Deactivated" means that the workload was evicted because spec.active is set to false.
-- "DeactivatedDueToAdmissionCheck" means that the workload was evicted and deactivated by Kueue due to a rejected admission check.
-- "DeactivatedDueToMaximumExecutionTimeExceeded" means that the workload was evicted and deactivated by Kueue due to maximum execution time exceeded.
-- "DeactivatedDueToRequeuingLimitExceeded" means that the workload was evicted and deactivated by Kueue due to requeuing limit exceeded.`,
-		}, []string{"cluster_queue", "reason"},
+The label 'underlying_cause' can have the following values:
+- "" means that the value in 'reason' label is the root cause for eviction.
+- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.
+- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.
+- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded.`,
+		}, []string{"cluster_queue", "reason", "underlying_cause"},
 	)
 
 	ReplacedWorkloadSlicesTotal = prometheus.NewCounterVec(
@@ -311,10 +315,12 @@ The label 'reason' can have the following values:
 - "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.
 - "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.
 - "Deactivated" means that the workload was evicted because spec.active is set to false.
-- "DeactivatedDueToAdmissionCheck" means that the workload was evicted and deactivated by Kueue due to a rejected admission check.
-- "DeactivatedDueToMaximumExecutionTimeExceeded" means that the workload was evicted and deactivated by Kueue due to maximum execution time exceeded.
-- "DeactivatedDueToRequeuingLimitExceeded" means that the workload was evicted and deactivated by Kueue due to requeuing limit exceeded.`,
-		}, []string{"name", "namespace", "reason"},
+The label 'underlying_cause' can have the following values:
+- "" means that the value in 'reason' label is the root cause for eviction.
+- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.
+- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.
+- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded.`,
+		}, []string{"name", "namespace", "reason", "underlying_cause"},
 	)
 
 	EvictedWorkloadsOnceTotal = prometheus.NewCounterVec(
@@ -330,9 +336,13 @@ The label 'reason' can have the following values:
 - "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.
 - "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.
 - "Deactivated" means that the workload was evicted because spec.active is set to false.
-- "DeactivatedDueToAdmissionCheck" means that the workload was evicted and deactivated by Kueue due to a rejected admission check.
-- "DeactivatedDueToMaximumExecutionTimeExceeded" means that the workload was evicted and deactivated by Kueue due to maximum execution time exceeded.
-- "DeactivatedDueToRequeuingLimitExceeded" means that the workload was evicted and deactivated by Kueue due to requeuing limit exceeded.`,
+The label 'detailed_reason' can have the following values:
+- "" means that the value in 'reason' label is the root cause for eviction.
+- "WaitForStart" means that the pods have not been ready since admission, or the workload is not admitted.
+- "WaitForRecovery" means that the Pods were ready since the workload admission, but some pod has failed.
+- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.
+- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.
+- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded.`,
 		}, []string{"cluster_queue", "reason", "detailed_reason"},
 	)
 
@@ -554,16 +564,16 @@ func ReportLocalQueuePendingWorkloads(lq LocalQueueReference, active, inadmissib
 	LocalQueuePendingWorkloads.WithLabelValues(string(lq.Name), lq.Namespace, PendingStatusInadmissible).Set(float64(inadmissible))
 }
 
-func ReportEvictedWorkloads(cqName kueue.ClusterQueueReference, evictionReason string) {
-	EvictedWorkloadsTotal.WithLabelValues(string(cqName), evictionReason).Inc()
+func ReportEvictedWorkloads(cqName kueue.ClusterQueueReference, evictionReason, underlyingCause string) {
+	EvictedWorkloadsTotal.WithLabelValues(string(cqName), evictionReason, underlyingCause).Inc()
 }
 
 func ReportReplacedWorkloadSlices(cqName kueue.ClusterQueueReference) {
 	ReplacedWorkloadSlicesTotal.WithLabelValues(string(cqName)).Inc()
 }
 
-func ReportLocalQueueEvictedWorkloads(lq LocalQueueReference, reason string) {
-	LocalQueueEvictedWorkloadsTotal.WithLabelValues(string(lq.Name), lq.Namespace, reason).Inc()
+func ReportLocalQueueEvictedWorkloads(lq LocalQueueReference, reason, underlyingCause string) {
+	LocalQueueEvictedWorkloadsTotal.WithLabelValues(string(lq.Name), lq.Namespace, reason, underlyingCause).Inc()
 }
 
 func ReportEvictedWorkloadsOnce(cqName kueue.ClusterQueueReference, reason, underlyingCause string) {
diff --git a/pkg/queue/manager.go b/pkg/queue/manager.go
index 2405541a9..a53bc709a 100644
--- a/pkg/queue/manager.go
+++ b/pkg/queue/manager.go
@@ -38,6 +38,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/hierarchy"
 	"sigs.k8s.io/kueue/pkg/metrics"
+	afs "sigs.k8s.io/kueue/pkg/util/admissionfairsharing"
 	utilmaps "sigs.k8s.io/kueue/pkg/util/maps"
 	"sigs.k8s.io/kueue/pkg/util/queue"
 	"sigs.k8s.io/kueue/pkg/workload"
@@ -61,9 +62,7 @@ func WithClock(c clock.WithDelayedExecution) Option {
 
 func WithAdmissionFairSharing(cfg *config.AdmissionFairSharing) Option {
 	return func(m *Manager) {
-		if features.Enabled(features.AdmissionFairSharing) {
-			m.admissionFairSharingConfig = cfg
-		}
+		m.admissionFairSharingConfig = cfg
 	}
 }
 
@@ -182,7 +181,7 @@ func (m *Manager) AddClusterQueue(ctx context.Context, cq *kueue.ClusterQueue) e
 	}
 
 	var afsEntryPenalties *utilmaps.SyncMap[queue.LocalQueueReference, corev1.ResourceList]
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(m.admissionFairSharingConfig) {
 		afsEntryPenalties = m.afsEntryPenalties.GetPenalties()
 	}
 	cqImpl, err := newClusterQueue(ctx, m.client, cq, m.workloadOrdering, m.admissionFairSharingConfig, afsEntryPenalties)
diff --git a/pkg/scheduler/preemption/classical/candidate_generator.go b/pkg/scheduler/preemption/classical/candidate_generator.go
index 0e6a4fe66..8e4e3a00c 100644
--- a/pkg/scheduler/preemption/classical/candidate_generator.go
+++ b/pkg/scheduler/preemption/classical/candidate_generator.go
@@ -72,19 +72,26 @@ func splitEvicted(workloads []*candidateElem) ([]*candidateElem, []*candidateEle
 // NewCandidateIterator creates a new iterator that yields candidate workloads for preemption
 // The iterator can be used to perform two independent runs over the list of candidates:
 // with and without borrowing. The runs are independent which means that the same candidates
-// might be returned for both, but note that the candidates with borrrowing are a subset of
+// might be returned for both, but note that the candidates with borrowing are a subset of
 // candidates without borrowing.
-func NewCandidateIterator(hierarchicalReclaimCtx *HierarchicalPreemptionCtx, frsNeedPreemption sets.Set[resources.FlavorResource], snapshot *cache.Snapshot, clock clock.Clock, ordering func(logr.Logger, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) bool) *candidateIterator {
+func NewCandidateIterator(
+	hierarchicalReclaimCtx *HierarchicalPreemptionCtx,
+	enabledAfs bool,
+	frsNeedPreemption sets.Set[resources.FlavorResource],
+	snapshot *cache.Snapshot,
+	clock clock.Clock,
+	ordering func(logr.Logger, bool, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) bool,
+) *candidateIterator {
 	sameQueueCandidates := collectSameQueueCandidates(hierarchicalReclaimCtx)
 	hierarchyCandidates, priorityCandidates := collectCandidatesForHierarchicalReclaim(hierarchicalReclaimCtx)
 	sort.Slice(sameQueueCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, sameQueueCandidates[i].wl, sameQueueCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, sameQueueCandidates[i].wl, sameQueueCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 	sort.Slice(priorityCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, priorityCandidates[i].wl, priorityCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, priorityCandidates[i].wl, priorityCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 	sort.Slice(hierarchyCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, hierarchyCandidates[i].wl, hierarchyCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, hierarchyCandidates[i].wl, hierarchyCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 
 	evictedHierarchicalReclaimCandidates, nonEvictedHierarchicalReclaimCandidates := splitEvicted(hierarchyCandidates)
diff --git a/pkg/scheduler/preemption/preemption.go b/pkg/scheduler/preemption/preemption.go
index 35f41abd1..e5651bfba 100644
--- a/pkg/scheduler/preemption/preemption.go
+++ b/pkg/scheduler/preemption/preemption.go
@@ -39,7 +39,6 @@ import (
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
 	"sigs.k8s.io/kueue/pkg/cache"
 	"sigs.k8s.io/kueue/pkg/controller/constants"
-	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/resources"
 	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
 	"sigs.k8s.io/kueue/pkg/scheduler/preemption/classical"
@@ -64,6 +63,8 @@ type Preemptor struct {
 
 	// stubs
 	applyPreemption func(ctx context.Context, w *kueue.Workload, reason, message string) error
+
+	enabledAfs bool
 }
 
 type preemptionCtx struct {
@@ -81,6 +82,7 @@ func New(
 	workloadOrdering workload.Ordering,
 	recorder record.EventRecorder,
 	fs config.FairSharing,
+	enabledAfs bool,
 	clock clock.Clock,
 ) *Preemptor {
 	p := &Preemptor{
@@ -90,6 +92,7 @@ func New(
 		workloadOrdering:  workloadOrdering,
 		enableFairSharing: fs.Enable,
 		fsStrategies:      parseStrategies(fs.PreemptionStrategies),
+		enabledAfs:        enabledAfs,
 	}
 	p.applyPreemption = p.applyPreemptionWithSSA
 	return p
@@ -218,7 +221,7 @@ func (p *Preemptor) classicalPreemptions(preemptionCtx *preemptionCtx) []*Target
 		Requests:          preemptionCtx.workloadUsage.Quota,
 		WorkloadOrdering:  p.workloadOrdering,
 	}
-	candidatesGenerator := classical.NewCandidateIterator(hierarchicalReclaimCtx, preemptionCtx.frsNeedPreemption, preemptionCtx.snapshot, p.clock, CandidatesOrdering)
+	candidatesGenerator := classical.NewCandidateIterator(hierarchicalReclaimCtx, p.enabledAfs, preemptionCtx.frsNeedPreemption, preemptionCtx.snapshot, p.clock, CandidatesOrdering)
 	var attemptPossibleOpts []preemptionAttemptOpts
 	borrowWithinCohortForbidden, _ := classical.IsBorrowingWithinCohortForbidden(preemptionCtx.preemptorCQ)
 	// We have three types of candidates:
@@ -384,7 +387,7 @@ func (p *Preemptor) fairPreemptions(preemptionCtx *preemptionCtx, strategies []f
 		return nil
 	}
 	sort.Slice(candidates, func(i, j int) bool {
-		return CandidatesOrdering(preemptionCtx.log, candidates[i], candidates[j], preemptionCtx.preemptorCQ.Name, p.clock.Now())
+		return CandidatesOrdering(preemptionCtx.log, p.enabledAfs, candidates[i], candidates[j], preemptionCtx.preemptorCQ.Name, p.clock.Now())
 	})
 	if logV := preemptionCtx.log.V(5); logV.Enabled() {
 		logV.Info("Simulating fair preemption", "candidates", workload.References(candidates), "resourcesRequiringPreemption", preemptionCtx.frsNeedPreemption.UnsortedList(), "preemptingWorkload", klog.KObj(preemptionCtx.preemptor.Obj))
@@ -556,14 +559,14 @@ func resourceUsagePreemptionEnabled(a, b *workload.Info) bool {
 	return a.ClusterQueue == b.ClusterQueue && a.Obj.Spec.QueueName != b.Obj.Spec.QueueName && a.LocalQueueFSUsage != nil && b.LocalQueueFSUsage != nil
 }
 
-// candidatesOrdering criteria:
+// CandidatesOrdering criteria:
 // 0. Workloads already marked for preemption first.
 // 1. Workloads from other ClusterQueues in the cohort before the ones in the
 // same ClusterQueue as the preemptor.
 // 2. (AdmissionFairSharing only) Workloads with lower LocalQueue's usage first
 // 3. Workloads with lower priority first.
 // 4. Workloads admitted more recently first.
-func CandidatesOrdering(log logr.Logger, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) bool {
+func CandidatesOrdering(log logr.Logger, afsEnabled bool, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) bool {
 	aEvicted := meta.IsStatusConditionTrue(a.Obj.Status.Conditions, kueue.WorkloadEvicted)
 	bEvicted := meta.IsStatusConditionTrue(b.Obj.Status.Conditions, kueue.WorkloadEvicted)
 	if aEvicted != bEvicted {
@@ -575,7 +578,7 @@ func CandidatesOrdering(log logr.Logger, a, b *workload.Info, cq kueue.ClusterQu
 		return !aInCQ
 	}
 
-	if features.Enabled(features.AdmissionFairSharing) && resourceUsagePreemptionEnabled(a, b) {
+	if afsEnabled && resourceUsagePreemptionEnabled(a, b) {
 		if a.LocalQueueFSUsage != b.LocalQueueFSUsage {
 			log.V(3).Info("Comparing workloads by LocalQueue fair sharing usage",
 				"workloadA", klog.KObj(a.Obj), "queueA", a.Obj.Spec.QueueName, "usageA", a.LocalQueueFSUsage,
diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go
index 4c1d61087..9f0e1da8b 100644
--- a/pkg/scheduler/scheduler.go
+++ b/pkg/scheduler/scheduler.go
@@ -145,7 +145,7 @@ func New(queues *queue.Manager, cache *cache.Cache, cl client.Client, recorder r
 		cache:                   cache,
 		client:                  cl,
 		recorder:                recorder,
-		preemptor:               preemption.New(cl, wo, recorder, options.fairSharing, options.clock),
+		preemptor:               preemption.New(cl, wo, recorder, options.fairSharing, afs.Enabled(options.admissionFairSharing), options.clock),
 		admissionRoutineWrapper: routine.DefaultWrapper,
 		workloadOrdering:        wo,
 		clock:                   options.clock,
@@ -195,7 +195,7 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 	ctx = ctrl.LoggerInto(ctx, log)
 
 	var snapshotOpts []cache.SnapshotOption
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(s.admissionFairSharing) {
 		s.queues.RebuildClusterQueuesWithEntryPenalties()
 		snapshotOpts = append(snapshotOpts, cache.WithAfsEntryPenalties(s.queues.GetAfsEntryPenalties()))
 	}
@@ -623,7 +623,7 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *cache.ClusterQueueS
 	e.status = assumed
 	log.V(2).Info("Workload assumed in the cache")
 
-	if s.admissionFairSharing != nil && features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(s.admissionFairSharing) {
 		s.updateEntryPenalty(log, e, add)
 
 		// Trigger LocalQueue reconciler to apply any pending penalties
@@ -642,7 +642,7 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *cache.ClusterQueueS
 		// Ignore errors because the workload or clusterQueue could have been deleted
 		// by an event.
 		_ = s.cache.ForgetWorkload(log, newWorkload)
-		if s.admissionFairSharing != nil && features.Enabled(features.AdmissionFairSharing) {
+		if afs.Enabled(s.admissionFairSharing) {
 			s.updateEntryPenalty(log, e, subtract)
 		}
 		if apierrors.IsNotFound(err) {
diff --git a/pkg/util/admissionfairsharing/admission_fair_sharing.go b/pkg/util/admissionfairsharing/admission_fair_sharing.go
index ef2a873a4..b12298b68 100644
--- a/pkg/util/admissionfairsharing/admission_fair_sharing.go
+++ b/pkg/util/admissionfairsharing/admission_fair_sharing.go
@@ -29,7 +29,7 @@ import (
 
 func ResourceWeights(cqAdmissionScope *kueue.AdmissionScope, afsConfig *config.AdmissionFairSharing) (bool, map[corev1.ResourceName]float64) {
 	enableAdmissionFs, fsResWeights := false, make(map[corev1.ResourceName]float64)
-	if features.Enabled(features.AdmissionFairSharing) && afsConfig != nil && cqAdmissionScope != nil && cqAdmissionScope.AdmissionMode == kueue.UsageBasedAdmissionFairSharing {
+	if Enabled(afsConfig) && cqAdmissionScope != nil && cqAdmissionScope.AdmissionMode == kueue.UsageBasedAdmissionFairSharing {
 		enableAdmissionFs = true
 		fsResWeights = afsConfig.ResourceWeights
 	}
@@ -52,3 +52,7 @@ func CalculateEntryPenalty(totalRequests corev1.ResourceList, afs *config.Admiss
 
 	return resource.MulByFloat(totalRequests, alpha)
 }
+
+func Enabled(afsConfig *config.AdmissionFairSharing) bool {
+	return afsConfig != nil && features.Enabled(features.AdmissionFairSharing)
+}
diff --git a/pkg/workload/workload.go b/pkg/workload/workload.go
index 893c90020..a10c58369 100644
--- a/pkg/workload/workload.go
+++ b/pkg/workload/workload.go
@@ -1082,7 +1082,11 @@ func AdmissionChecksForWorkload(log logr.Logger, wl *kueue.Workload, admissionCh
 }
 
 func Evict(ctx context.Context, c client.Client, recorder record.EventRecorder, wl *kueue.Workload, reason, underlyingCause, msg string, clock clock.Clock) error {
-	prepareForEviction(wl, clock.Now(), reason, msg)
+	evictionReason := reason
+	if reason == kueue.WorkloadDeactivated && underlyingCause != "" {
+		evictionReason = fmt.Sprintf("%sDueTo%s", evictionReason, underlyingCause)
+	}
+	prepareForEviction(wl, clock.Now(), evictionReason, msg)
 	reportWorkloadEvictedOnce := workloadEvictionStateInc(wl, reason, underlyingCause)
 	if err := ApplyAdmissionStatus(ctx, c, wl, true, clock); err != nil {
 		return err
@@ -1095,7 +1099,7 @@ func Evict(ctx context.Context, c client.Client, recorder record.EventRecorder,
 		log.V(3).Info("WARNING: unexpected eviction of workload without status.Admission", "workload", klog.KObj(wl))
 		return nil
 	}
-	reportEvictedWorkload(recorder, wl, wl.Status.Admission.ClusterQueue, reason, msg)
+	reportEvictedWorkload(recorder, wl, wl.Status.Admission.ClusterQueue, reason, underlyingCause, msg)
 	if reportWorkloadEvictedOnce {
 		metrics.ReportEvictedWorkloadsOnce(wl.Status.Admission.ClusterQueue, reason, underlyingCause)
 	}
@@ -1113,15 +1117,19 @@ func resetClusterNomination(w *kueue.Workload) {
 	w.Status.NominatedClusterNames = nil
 }
 
-func reportEvictedWorkload(recorder record.EventRecorder, wl *kueue.Workload, cqName kueue.ClusterQueueReference, reason, message string) {
-	metrics.ReportEvictedWorkloads(cqName, reason)
+func reportEvictedWorkload(recorder record.EventRecorder, wl *kueue.Workload, cqName kueue.ClusterQueueReference, reason, underlyingCause, message string) {
+	metrics.ReportEvictedWorkloads(cqName, reason, underlyingCause)
 	if podsReadyToEvictionTime := workloadsWithPodsReadyToEvictedTime(wl); podsReadyToEvictionTime != nil {
-		metrics.PodsReadyToEvictedTimeSeconds.WithLabelValues(string(cqName), reason).Observe(podsReadyToEvictionTime.Seconds())
+		metrics.PodsReadyToEvictedTimeSeconds.WithLabelValues(string(cqName), reason, underlyingCause).Observe(podsReadyToEvictionTime.Seconds())
 	}
 	if features.Enabled(features.LocalQueueMetrics) {
-		metrics.ReportLocalQueueEvictedWorkloads(metrics.LQRefFromWorkload(wl), reason)
+		metrics.ReportLocalQueueEvictedWorkloads(metrics.LQRefFromWorkload(wl), reason, underlyingCause)
+	}
+	eventReason := fmt.Sprintf("%sDueTo%s", kueue.WorkloadEvicted, reason)
+	if reason == kueue.WorkloadDeactivated && underlyingCause != "" {
+		eventReason = fmt.Sprintf("%sDueTo%s", eventReason, underlyingCause)
 	}
-	recorder.Event(wl, corev1.EventTypeNormal, fmt.Sprintf("%sDueTo%s", kueue.WorkloadEvicted, reason), message)
+	recorder.Event(wl, corev1.EventTypeNormal, eventReason, message)
 }
 
 func ReportPreemption(preemptingCqName kueue.ClusterQueueReference, preemptingReason string, targetCqName kueue.ClusterQueueReference) {



Test patch:
diff --git a/pkg/controller/core/workload_controller_test.go b/pkg/controller/core/workload_controller_test.go
index b69709c23..f7d546e23 100644
--- a/pkg/controller/core/workload_controller_test.go
+++ b/pkg/controller/core/workload_controller_test.go
@@ -677,8 +677,9 @@ func TestReconcile(t *testing.T) {
 				).
 				SchedulingStatsEviction(
 					kueue.WorkloadSchedulingStatsEviction{
-						Reason: "DeactivatedDueToAdmissionCheck",
-						Count:  1,
+						Reason:          "Deactivated",
+						UnderlyingCause: "AdmissionCheck",
+						Count:           1,
 					},
 				).
 				Obj(),
@@ -1344,7 +1345,11 @@ func TestReconcile(t *testing.T) {
 					Message: "exceeding the maximum number of re-queuing retries",
 				}).
 				SchedulingStatsEviction(
-					kueue.WorkloadSchedulingStatsEviction{Reason: "DeactivatedDueToRequeuingLimitExceeded", Count: 1},
+					kueue.WorkloadSchedulingStatsEviction{
+						Reason:          "Deactivated",
+						UnderlyingCause: "RequeuingLimitExceeded",
+						Count:           1,
+					},
 				).
 				Obj(),
 			wantEvents: []utiltesting.EventRecord{
@@ -1408,8 +1413,9 @@ func TestReconcile(t *testing.T) {
 				}).
 				SchedulingStatsEviction(
 					kueue.WorkloadSchedulingStatsEviction{
-						Reason: "DeactivatedDueToRequeuingLimitExceeded",
-						Count:  1,
+						Reason:          "Deactivated",
+						UnderlyingCause: "RequeuingLimitExceeded",
+						Count:           1,
 					},
 				).
 				Obj(),
@@ -1477,8 +1483,9 @@ func TestReconcile(t *testing.T) {
 				RequeueState(ptr.To[int32](100), nil).
 				SchedulingStatsEviction(
 					kueue.WorkloadSchedulingStatsEviction{
-						Reason: "DeactivatedDueToRequeuingLimitExceeded",
-						Count:  1,
+						Reason:          "Deactivated",
+						UnderlyingCause: "RequeuingLimitExceeded",
+						Count:           1,
 					},
 				).
 				Obj(),
@@ -1546,8 +1553,9 @@ func TestReconcile(t *testing.T) {
 				RequeueState(ptr.To[int32](100), nil).
 				SchedulingStatsEviction(
 					kueue.WorkloadSchedulingStatsEviction{
-						Reason: "DeactivatedDueToRequeuingLimitExceeded",
-						Count:  1,
+						Reason:          "Deactivated",
+						UnderlyingCause: "RequeuingLimitExceeded",
+						Count:           1,
 					},
 				).
 				Obj(),
diff --git a/pkg/controller/jobframework/tas_test.go b/pkg/controller/jobframework/tas_test.go
new file mode 100644
index 000000000..9c7c85d2e
--- /dev/null
+++ b/pkg/controller/jobframework/tas_test.go
@@ -0,0 +1,214 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package jobframework
+
+import (
+	"strconv"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	batchv1 "k8s.io/api/batch/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	jobsetapi "sigs.k8s.io/jobset/api/jobset/v1alpha2"
+
+	kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1"
+	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
+)
+
+func TestPodSetTopologyRequestBuilder(t *testing.T) {
+	testCases := map[string]struct {
+		meta               *metav1.ObjectMeta
+		podIndexLabel      *string
+		subGroupIndexLabel *string
+		subGroupCount      *int32
+		wantReq            *kueue.PodSetTopologyRequest
+		wantErr            error
+	}{
+		"required annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetRequiredTopologyAnnotation: "cloud.com/block",
+				},
+			},
+			wantReq: &kueue.PodSetTopologyRequest{
+				Required: ptr.To("cloud.com/block"),
+			},
+		},
+		"required annotation with pod index label": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetRequiredTopologyAnnotation: "cloud.com/block",
+				},
+			},
+			podIndexLabel: ptr.To(batchv1.JobCompletionIndexAnnotation),
+			wantReq: &kueue.PodSetTopologyRequest{
+				Required:      ptr.To("cloud.com/block"),
+				PodIndexLabel: ptr.To(batchv1.JobCompletionIndexAnnotation),
+			},
+		},
+		"pod index label only": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{},
+			},
+			podIndexLabel: ptr.To(batchv1.JobCompletionIndexAnnotation),
+			wantReq: &kueue.PodSetTopologyRequest{
+				PodIndexLabel: ptr.To(batchv1.JobCompletionIndexAnnotation),
+			},
+		},
+		"required annotation with sub group": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetRequiredTopologyAnnotation: "cloud.com/block",
+					kueuealpha.PodSetGroupName:                  "block",
+				},
+			},
+			subGroupIndexLabel: ptr.To(jobsetapi.JobIndexKey),
+			subGroupCount:      ptr.To[int32](1),
+			wantReq: &kueue.PodSetTopologyRequest{
+				Required:           ptr.To("cloud.com/block"),
+				SubGroupIndexLabel: ptr.To(jobsetapi.JobIndexKey),
+				SubGroupCount:      ptr.To[int32](1),
+				PodSetGroupName:    ptr.To("block"),
+			},
+		},
+		"required annotation with sub group and pod set group name annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetRequiredTopologyAnnotation: "cloud.com/block",
+					kueuealpha.PodSetGroupName:                  "block",
+				},
+			},
+			subGroupIndexLabel: ptr.To(jobsetapi.JobIndexKey),
+			subGroupCount:      ptr.To[int32](1),
+			wantReq: &kueue.PodSetTopologyRequest{
+				Required:           ptr.To("cloud.com/block"),
+				SubGroupIndexLabel: ptr.To(jobsetapi.JobIndexKey),
+				SubGroupCount:      ptr.To[int32](1),
+				PodSetGroupName:    ptr.To("block"),
+			},
+		},
+		"preferred annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetPreferredTopologyAnnotation: "cloud.com/block",
+				},
+			},
+			wantReq: &kueue.PodSetTopologyRequest{
+				Preferred: ptr.To("cloud.com/block"),
+			},
+		},
+		"unconstrained annotation (true)": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetUnconstrainedTopologyAnnotation: "true",
+				},
+			},
+			wantReq: &kueue.PodSetTopologyRequest{
+				Unconstrained: ptr.To(true),
+			},
+		},
+		"unconstrained annotation (false)": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetUnconstrainedTopologyAnnotation: "false",
+				},
+			},
+			wantReq: &kueue.PodSetTopologyRequest{
+				Unconstrained: ptr.To(false),
+			},
+		},
+		"slice-only topology": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetSliceRequiredTopologyAnnotation: "cloud.com/block",
+					kueuealpha.PodSetSliceSizeAnnotation:             "1",
+				},
+			},
+			wantReq: &kueue.PodSetTopologyRequest{
+				PodSetSliceRequiredTopology: ptr.To("cloud.com/block"),
+				PodSetSliceSize:             ptr.To[int32](1),
+			},
+		},
+		"slice-only topology – only slice required annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetSliceRequiredTopologyAnnotation: "cloud.com/block",
+				},
+			},
+		},
+		"slice-only topology – only slice size annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetSliceSizeAnnotation: "1",
+				},
+			},
+		},
+		"slice-only topology with sub group and pod set group name annotation": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetSliceRequiredTopologyAnnotation: "cloud.com/block",
+					kueuealpha.PodSetSliceSizeAnnotation:             "1",
+					kueuealpha.PodSetGroupName:                       "block",
+				},
+			},
+			subGroupIndexLabel: ptr.To(jobsetapi.JobIndexKey),
+			subGroupCount:      ptr.To[int32](1),
+			wantReq: &kueue.PodSetTopologyRequest{
+				PodSetSliceRequiredTopology: ptr.To("cloud.com/block"),
+				PodSetSliceSize:             ptr.To[int32](1),
+				SubGroupIndexLabel:          ptr.To(jobsetapi.JobIndexKey),
+				SubGroupCount:               ptr.To[int32](1),
+			},
+		},
+		"invalid unconstrained topology annotation value": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetUnconstrainedTopologyAnnotation: "invalid",
+				},
+			},
+			wantErr: strconv.ErrSyntax,
+		},
+		"invalid podset slice size annotation value": {
+			meta: &metav1.ObjectMeta{
+				Annotations: map[string]string{
+					kueuealpha.PodSetSliceRequiredTopologyAnnotation: "cloud.com/block",
+					kueuealpha.PodSetSliceSizeAnnotation:             "invalid",
+				},
+			},
+			wantErr: strconv.ErrSyntax,
+		},
+	}
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			b := NewPodSetTopologyRequest(tc.meta)
+			b.PodIndexLabel(tc.podIndexLabel)
+			b.SubGroup(tc.subGroupIndexLabel, tc.subGroupCount)
+
+			gotReq, gotErr := b.Build()
+
+			if diff := cmp.Diff(tc.wantReq, gotReq); len(diff) != 0 {
+				t.Errorf("Unexpected request (-want,+got):\n%s", diff)
+			}
+
+			if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.EquateErrors()); len(diff) != 0 {
+				t.Errorf("Unexpected error (-want,+got):\n%s", diff)
+			}
+		})
+	}
+}
diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go
index 8cb5470e7..15a8ce3f7 100644
--- a/pkg/metrics/metrics_test.go
+++ b/pkg/metrics/metrics_test.go
@@ -146,8 +146,8 @@ func TestReportAndCleanupClusterQueueUsage(t *testing.T) {
 }
 
 func TestReportAndCleanupClusterQueueEvictedNumber(t *testing.T) {
-	ReportEvictedWorkloads("cluster_queue1", "Preempted")
-	ReportEvictedWorkloads("cluster_queue1", "Evicted")
+	ReportEvictedWorkloads("cluster_queue1", "Preempted", "")
+	ReportEvictedWorkloads("cluster_queue1", "Evicted", "")
 
 	expectFilteredMetricsCount(t, EvictedWorkloadsTotal, 2, "cluster_queue", "cluster_queue1")
 	expectFilteredMetricsCount(t, EvictedWorkloadsTotal, 1, "cluster_queue", "cluster_queue1", "reason", "Preempted")
diff --git a/pkg/scheduler/preemption/preemption_test.go b/pkg/scheduler/preemption/preemption_test.go
index 66dc406f5..8b323eead 100644
--- a/pkg/scheduler/preemption/preemption_test.go
+++ b/pkg/scheduler/preemption/preemption_test.go
@@ -33,7 +33,6 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/component-base/featuregate"
 	clocktesting "k8s.io/utils/clock/testing"
 	"k8s.io/utils/ptr"
 
@@ -1838,7 +1837,7 @@ func TestPreemption(t *testing.T) {
 				t.Fatalf("Failed adding kueue scheme: %v", err)
 			}
 			recorder := broadcaster.NewRecorder(scheme, corev1.EventSource{Component: constants.AdmissionName})
-			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, clocktesting.NewFakeClock(now))
+			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, false, clocktesting.NewFakeClock(now))
 			preemptor.applyPreemption = func(ctx context.Context, w *kueue.Workload, reason, _ string) error {
 				lock.Lock()
 				gotPreempted.Insert(targetKeyReason(workload.Key(w), reason))
@@ -2754,7 +2753,7 @@ func TestFairPreemptions(t *testing.T) {
 			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{
 				Enable:               true,
 				PreemptionStrategies: tc.strategies,
-			}, clocktesting.NewFakeClock(now))
+			}, false, clocktesting.NewFakeClock(now))
 
 			beforeSnapshot, err := cqCache.Snapshot(ctx)
 			if err != nil {
@@ -2817,9 +2816,9 @@ func TestCandidatesOrdering(t *testing.T) {
 	wlHighUsageLqDifCQ.LocalQueueFSUsage = ptr.To(1.0)
 
 	cases := map[string]struct {
-		candidates         []workload.Info
-		wantCandidates     []workload.Reference
-		enableFeatureGates []featuregate.Feature
+		candidates                  []workload.Info
+		wantCandidates              []workload.Reference
+		admissionFairSharingEnabled bool
 	}{
 		"workloads sorted by priority": {
 			candidates: []workload.Info{
@@ -2882,25 +2881,23 @@ func TestCandidatesOrdering(t *testing.T) {
 				*wlLowUsageLq,
 				*wlMidUsageLq,
 			},
-			wantCandidates:     []workload.Reference{"mid_lq_usage", "low_lq_usage"},
-			enableFeatureGates: []featuregate.Feature{features.AdmissionFairSharing},
+			wantCandidates:              []workload.Reference{"mid_lq_usage", "low_lq_usage"},
+			admissionFairSharingEnabled: true,
 		},
 		"workloads from different CQ are sorted based on priority and timestamp": {
 			candidates: []workload.Info{
 				*wlMidUsageLq,
 				*wlHighUsageLqDifCQ,
 			},
-			wantCandidates:     []workload.Reference{"high_lq_usage_different_cq", "mid_lq_usage"},
-			enableFeatureGates: []featuregate.Feature{features.AdmissionFairSharing},
+			wantCandidates:              []workload.Reference{"high_lq_usage_different_cq", "mid_lq_usage"},
+			admissionFairSharingEnabled: true,
 		}}
 
 	_, log := utiltesting.ContextWithLog(t)
 	for _, tc := range cases {
-		for _, gate := range tc.enableFeatureGates {
-			features.SetFeatureGateDuringTest(t, gate, true)
-		}
+		features.SetFeatureGateDuringTest(t, features.AdmissionFairSharing, tc.admissionFairSharingEnabled)
 		sort.Slice(tc.candidates, func(i int, j int) bool {
-			return CandidatesOrdering(log, &tc.candidates[i], &tc.candidates[j], kueue.ClusterQueueReference(preemptorCq), now)
+			return CandidatesOrdering(log, tc.admissionFairSharingEnabled, &tc.candidates[i], &tc.candidates[j], kueue.ClusterQueueReference(preemptorCq), now)
 		})
 		got := slices.Map(tc.candidates, func(c *workload.Info) workload.Reference {
 			return workload.Reference(c.Obj.Name)
@@ -4124,7 +4121,7 @@ func TestHierarchicalPreemptions(t *testing.T) {
 				t.Fatalf("Failed adding kueue scheme: %v", err)
 			}
 			recorder := broadcaster.NewRecorder(scheme, corev1.EventSource{Component: constants.AdmissionName})
-			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, clocktesting.NewFakeClock(now))
+			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, false, clocktesting.NewFakeClock(now))
 			preemptor.applyPreemption = func(ctx context.Context, w *kueue.Workload, reason, _ string) error {
 				lock.Lock()
 				gotPreempted.Insert(targetKeyReason(workload.Key(w), reason))
diff --git a/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go b/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
index fcb9e7900..ee9b3aa73 100644
--- a/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
+++ b/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
@@ -359,7 +359,7 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 				gomega.Eventually(func(g gomega.Gomega) {
 					g.Expect(k8sClient.Get(ctx, wlKey, &updatedWl)).To(gomega.Succeed())
 					g.Expect(workload.IsEvictedByDeactivation(&updatedWl)).To(gomega.BeTrue())
-					util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "DeactivatedDueToAdmissionCheck", 1)
+					util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "Deactivated", "AdmissionCheck", 1)
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			})
 		})
@@ -425,7 +425,7 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 					g.Expect(workload.IsEvictedByDeactivation(&updatedWl)).To(gomega.BeTrue())
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
-				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "DeactivatedDueToAdmissionCheck", 1)
+				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "Deactivated", "AdmissionCheck", 1)
 			})
 		})
 
@@ -477,7 +477,7 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 					g.Expect(workload.IsEvictedByDeactivation(&updatedWl)).To(gomega.BeTrue())
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
-				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "DeactivatedDueToAdmissionCheck", 1)
+				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "Deactivated", "AdmissionCheck", 1)
 			})
 		})
 
@@ -549,7 +549,7 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 					g.Expect(workload.IsEvictedByDeactivation(&updatedWl)).To(gomega.BeFalse())
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
-				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadDeactivated, 0)
+				util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadDeactivated, "", 0)
 			})
 		})
 
@@ -1130,7 +1130,7 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 					g.Expect(k8sClient.Get(ctx, wlKey, &updatedWl)).To(gomega.Succeed())
 
 					g.Expect(workload.IsEvictedByDeactivation(&updatedWl)).To(gomega.BeTrue())
-					util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "DeactivatedDueToAdmissionCheck", 1)
+					util.ExpectEvictedWorkloadsTotalMetric(cq.Name, "Deactivated", "AdmissionCheck", 1)
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			})
 		})
diff --git a/test/integration/singlecluster/controller/core/workload_controller_test.go b/test/integration/singlecluster/controller/core/workload_controller_test.go
index 47e01b4c6..ee1a9ddce 100644
--- a/test/integration/singlecluster/controller/core/workload_controller_test.go
+++ b/test/integration/singlecluster/controller/core/workload_controller_test.go
@@ -310,8 +310,8 @@ var _ = ginkgo.Describe("Workload controller", ginkgo.Ordered, ginkgo.ContinueOn
 				gomega.Eventually(func(g gomega.Gomega) {
 					g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed())
 					g.Expect(workload.IsEvictedByDeactivation(updatedWl)).To(gomega.BeTrue())
-					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, "DeactivatedDueToAdmissionCheck", 1)
-					util.ExpectEvictedWorkloadsOnceTotalMetric(clusterQueue.Name, "DeactivatedDueToAdmissionCheck", "", 1)
+					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, "Deactivated", "AdmissionCheck", 1)
+					util.ExpectEvictedWorkloadsOnceTotalMetric(clusterQueue.Name, "Deactivated", "AdmissionCheck", 1)
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			})
 		})
@@ -395,8 +395,8 @@ var _ = ginkgo.Describe("Workload controller", ginkgo.Ordered, ginkgo.ContinueOn
 					g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed())
 
 					g.Expect(workload.IsEvictedByDeactivation(updatedWl)).To(gomega.BeTrue())
-					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, "DeactivatedDueToAdmissionCheck", 1)
-					util.ExpectEvictedWorkloadsOnceTotalMetric(clusterQueue.Name, "DeactivatedDueToAdmissionCheck", "", 1)
+					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, "Deactivated", "AdmissionCheck", 1)
+					util.ExpectEvictedWorkloadsOnceTotalMetric(clusterQueue.Name, "Deactivated", "AdmissionCheck", 1)
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			})
 		})
diff --git a/test/integration/singlecluster/scheduler/podsready/scheduler_test.go b/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
index b4532b7b9..4fb160a3a 100644
--- a/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
+++ b/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
@@ -196,7 +196,7 @@ var _ = ginkgo.Describe("SchedulerWithWaitForPodsReady", func() {
 			util.FinishEvictionForWorkloads(ctx, k8sClient, prodWl)
 
 			ginkgo.By("check for PodsReadyToEvictedTimeSeconds metric existence")
-			util.ExpectPodsReadyToEvictedTimeSeconds(prodClusterQ.Name, kueue.WorkloadDeactivated, 1)
+			util.ExpectPodsReadyToEvictedTimeSeconds(prodClusterQ.Name, kueue.WorkloadDeactivated, "", 1)
 		})
 
 		ginkgo.It("Should unblock admission of new workloads once the admitted workload is deleted", func() {
@@ -283,7 +283,7 @@ var _ = ginkgo.Describe("SchedulerWithWaitForPodsReady", func() {
 			}, util.Timeout, util.Interval).Should(gomega.Succeed(), "the workload should be evicted after the timeout expires")
 
 			util.FinishEvictionForWorkloads(ctx, k8sClient, prodWl1)
-			util.ExpectEvictedWorkloadsTotalMetric(prodClusterQ.Name, kueue.WorkloadEvictedByPodsReadyTimeout, 1)
+			util.ExpectEvictedWorkloadsTotalMetric(prodClusterQ.Name, kueue.WorkloadEvictedByPodsReadyTimeout, kueue.WorkloadWaitForStart, 1)
 			util.ExpectEvictedWorkloadsOnceTotalMetric(prodClusterQ.Name, kueue.WorkloadEvictedByPodsReadyTimeout, kueue.WorkloadWaitForStart, 1)
 
 			ginkgo.By("verify the 'prod2' workload gets admitted and the 'prod1' is pending by backoff")
@@ -335,7 +335,7 @@ var _ = ginkgo.Describe("SchedulerWithWaitForPodsReady", func() {
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			util.FinishEvictionForWorkloads(ctx, k8sClient, prodWl)
 			// should observe a metrics of WorkloadEvictedByDeactivation
-			util.ExpectEvictedWorkloadsTotalMetric(prodClusterQ.Name, "DeactivatedDueToRequeuingLimitExceeded", 1)
+			util.ExpectEvictedWorkloadsTotalMetric(prodClusterQ.Name, "Deactivated", "RequeuingLimitExceeded", 1)
 			util.ExpectEvictedWorkloadsOnceTotalMetric(prodClusterQ.Name, kueue.WorkloadEvictedByPodsReadyTimeout, kueue.WorkloadWaitForStart, 1)
 
 			ginkgo.By("the reactivated workload should not be deactivated by the scheduler unless exceeding the backoffLimitCount")
diff --git a/test/integration/singlecluster/scheduler/preemption_test.go b/test/integration/singlecluster/scheduler/preemption_test.go
index 0ca164387..28b565e8b 100644
--- a/test/integration/singlecluster/scheduler/preemption_test.go
+++ b/test/integration/singlecluster/scheduler/preemption_test.go
@@ -132,7 +132,7 @@ var _ = ginkgo.Describe("Preemption", func() {
 			util.MustCreate(ctx, k8sClient, highWl2)
 
 			util.FinishEvictionForWorkloads(ctx, k8sClient, lowWl1, lowWl2)
-			util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadEvictedByPreemption, 2)
+			util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadEvictedByPreemption, "", 2)
 			util.ExpectPreemptedWorkloadsTotalMetric(cq.Name, kueue.InClusterQueueReason, 2)
 			util.ExpectEvictedWorkloadsOnceTotalMetric(cq.Name, kueue.WorkloadEvictedByPreemption, "", 2)
 
diff --git a/test/integration/singlecluster/scheduler/scheduler_test.go b/test/integration/singlecluster/scheduler/scheduler_test.go
index 2563adeb2..337f540a1 100644
--- a/test/integration/singlecluster/scheduler/scheduler_test.go
+++ b/test/integration/singlecluster/scheduler/scheduler_test.go
@@ -1966,7 +1966,7 @@ var _ = ginkgo.Describe("Scheduler", func() {
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
 			util.ExpectClusterQueueStatusMetric(cq, metrics.CQStatusPending)
-			util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, kueue.WorkloadEvictedByClusterQueueStopped, 1)
+			util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, kueue.WorkloadEvictedByClusterQueueStopped, "", 1)
 			util.ExpectEvictedWorkloadsOnceTotalMetric(cq.Name, kueue.WorkloadEvictedByClusterQueueStopped, "", 1)
 
 			ginkgo.By("Checking the condition of workload is evicted", func() {
@@ -2066,7 +2066,7 @@ var _ = ginkgo.Describe("Scheduler", func() {
 				g.Expect(k8sClient.Update(ctx, createdLq)).Should(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
-			util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadEvictedByLocalQueueStopped, 1)
+			util.ExpectEvictedWorkloadsTotalMetric(cq.Name, kueue.WorkloadEvictedByLocalQueueStopped, "", 1)
 			util.ExpectEvictedWorkloadsOnceTotalMetric(cq.Name, kueue.WorkloadEvictedByLocalQueueStopped, "", 1)
 
 			createdWl := kueue.Workload{}
diff --git a/test/integration/singlecluster/tas/tas_test.go b/test/integration/singlecluster/tas/tas_test.go
index 5fe44f97e..e9e89708d 100644
--- a/test/integration/singlecluster/tas/tas_test.go
+++ b/test/integration/singlecluster/tas/tas_test.go
@@ -1485,7 +1485,7 @@ var _ = ginkgo.Describe("Topology Aware Scheduling", ginkgo.Ordered, func() {
 
 				ginkgo.By("verify the workload is evicted due to no replacement possible", func() {
 					util.FinishEvictionForWorkloads(ctx, k8sClient, wl1)
-					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, kueue.WorkloadEvictedDueToNodeFailures, 1)
+					util.ExpectEvictedWorkloadsTotalMetric(clusterQueue.Name, kueue.WorkloadEvictedDueToNodeFailures, "", 1)
 					gomega.Eventually(func(g gomega.Gomega) {
 						updatedWl := &kueue.Workload{}
 						g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl1), updatedWl)).To(gomega.Succeed())
diff --git a/test/util/util.go b/test/util/util.go
index 4cdc154a8..70c922479 100644
--- a/test/util/util.go
+++ b/test/util/util.go
@@ -566,14 +566,14 @@ func ExpectAdmittedWorkloadsTotalMetric(cq *kueue.ClusterQueue, v int) {
 	expectCounterMetric(metric, v)
 }
 
-func ExpectEvictedWorkloadsTotalMetric(cqName, reason string, v int) {
-	metric := metrics.EvictedWorkloadsTotal.WithLabelValues(cqName, reason)
+func ExpectEvictedWorkloadsTotalMetric(cqName, reason, underlyingCause string, v int) {
+	metric := metrics.EvictedWorkloadsTotal.WithLabelValues(cqName, reason, underlyingCause)
 	expectCounterMetric(metric, v)
 }
 
-func ExpectPodsReadyToEvictedTimeSeconds(cqName, reason string, v int) {
+func ExpectPodsReadyToEvictedTimeSeconds(cqName, reason, underlyingCause string, v int) {
 	metric := metrics.PodsReadyToEvictedTimeSeconds
-	expectHistogramMetric(metric, cqName, reason, v)
+	expectHistogramMetric(metric, cqName, reason, underlyingCause, v)
 }
 
 func ExpectEvictedWorkloadsOnceTotalMetric(cqName string, reason, underlyingCause string, v int) {
@@ -599,9 +599,9 @@ func expectCounterMetric(metric prometheus.Counter, count int) {
 	}, Timeout, Interval).Should(gomega.Succeed())
 }
 
-func expectHistogramMetric(metric *prometheus.HistogramVec, cqName, preemptionReason string, count int) {
+func expectHistogramMetric(metric *prometheus.HistogramVec, cqName, preemptionReason, underlyingCause string, count int) {
 	gomega.EventuallyWithOffset(2, func(g gomega.Gomega) {
-		v, err := testutil.GetHistogramMetricCount(metric.WithLabelValues(cqName, preemptionReason))
+		v, err := testutil.GetHistogramMetricCount(metric.WithLabelValues(cqName, preemptionReason, underlyingCause))
 		g.Expect(err).ToNot(gomega.HaveOccurred())
 		g.Expect(int(v)).Should(gomega.Equal(count))
 	}, Timeout, Interval).Should(gomega.Succeed())