Repository: kubernetes-sigs/kueue

Issue:
TAS: allow to dump the usage of domains for TAS ResourceFlavors
**What would you like to be added**:

Extend the existing mechanism which allows to dump the state of cache. We could extend [this function](https://github.com/kubernetes-sigs/kueue/blob/0f54466b991a7d198eeca306dc75f2aee518c482/pkg/cache/snapshot.go#L57-L79) to dump the free capacity of TAS snapshots per domain.

**Why is this needed**:

To improve debuggability of TAS.



Discussion:
@mimowo: cc @mbobrovskyi @vladikkuzn: /assign


Solution patch:
diff --git a/pkg/cache/snapshot.go b/pkg/cache/snapshot.go
index e4c4c74b0..c74df1bfc 100644
--- a/pkg/cache/snapshot.go
+++ b/pkg/cache/snapshot.go
@@ -77,6 +77,28 @@ func (s *Snapshot) Log(log logr.Logger) {
 			"usage", cohort.ResourceNode.Usage,
 		)
 	}
+
+	// Dump TAS snapshots if the feature is enabled
+	if features.Enabled(features.TopologyAwareScheduling) {
+		for cqName, cq := range s.ClusterQueues() {
+			for tasFlavor, tasSnapshot := range cq.TASFlavors {
+				freeCapacityPerDomain, err := tasSnapshot.SerializeFreeCapacityPerDomain()
+				if err != nil {
+					log.Error(err, "Failed to serialize TAS snapshot free capacity",
+						"clusterQueue", cqName,
+						"resourceFlavor", tasFlavor,
+					)
+					continue
+				}
+
+				log.Info("TAS Snapshot Free Capacity",
+					"clusterQueue", cqName,
+					"resourceFlavor", tasFlavor,
+					"freeCapacityPerDomain", freeCapacityPerDomain,
+				)
+			}
+		}
+	}
 }
 
 func (c *Cache) Snapshot(ctx context.Context) (*Snapshot, error) {
diff --git a/pkg/cache/tas_flavor_snapshot.go b/pkg/cache/tas_flavor_snapshot.go
index f361efb08..096dbda41 100644
--- a/pkg/cache/tas_flavor_snapshot.go
+++ b/pkg/cache/tas_flavor_snapshot.go
@@ -18,6 +18,7 @@ package cache
 
 import (
 	"cmp"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"maps"
@@ -244,6 +245,67 @@ func (s *TASFlavorSnapshot) removeTASUsage(domainID utiltas.TopologyDomainID, us
 	s.leaves[domainID].tasUsage.Sub(usage)
 }
 
+func (s *TASFlavorSnapshot) freeCapacityPerDomain() map[utiltas.TopologyDomainID]resources.Requests {
+	freeCapacityPerDomain := make(map[utiltas.TopologyDomainID]resources.Requests, len(s.leaves))
+
+	for domainID, leaf := range s.leaves {
+		freeCapacityPerDomain[domainID] = leaf.freeCapacity.Clone()
+	}
+
+	return freeCapacityPerDomain
+}
+
+func (s *TASFlavorSnapshot) tasUsagePerDomain() map[utiltas.TopologyDomainID]resources.Requests {
+	tasUsagePerDomain := make(map[utiltas.TopologyDomainID]resources.Requests, len(s.leaves))
+
+	for domainID, leaf := range s.leaves {
+		tasUsagePerDomain[domainID] = leaf.tasUsage.Clone()
+	}
+
+	return tasUsagePerDomain
+}
+
+type domainCapacityDetails struct {
+	FreeCapacity map[corev1.ResourceName]string `json:"freeCapacity"`
+	TasUsage     map[corev1.ResourceName]string `json:"tasUsage"`
+}
+
+func (s *TASFlavorSnapshot) SerializeFreeCapacityPerDomain() (string, error) {
+	freeCapacityPerDomain := s.freeCapacityPerDomain()
+	tasUsagePerDomain := s.tasUsagePerDomain()
+
+	details := make(map[utiltas.TopologyDomainID]domainCapacityDetails, len(s.leaves))
+
+	for _, domain := range slices.Sorted(maps.Keys(freeCapacityPerDomain)) {
+		freeCapacity := freeCapacityPerDomain[domain]
+		tasUsage := tasUsagePerDomain[domain]
+
+		freeCapacityDetails := make(map[corev1.ResourceName]string, len(freeCapacity))
+		for _, resourceName := range slices.Sorted(maps.Keys(freeCapacity)) {
+			value := freeCapacity[resourceName]
+			freeCapacityDetails[resourceName] = resources.ResourceQuantityString(resourceName, value)
+		}
+
+		tasUsageDetails := make(map[corev1.ResourceName]string, len(freeCapacity))
+		for _, resourceName := range slices.Sorted(maps.Keys(tasUsage)) {
+			value := tasUsage[resourceName]
+			tasUsageDetails[resourceName] = resources.ResourceQuantityString(resourceName, value)
+		}
+
+		details[domain] = domainCapacityDetails{
+			FreeCapacity: freeCapacityDetails,
+			TasUsage:     tasUsageDetails,
+		}
+	}
+
+	jsonBytes, err := json.Marshal(details)
+	if err != nil {
+		return "", err
+	}
+
+	return string(jsonBytes), nil
+}
+
 type TASPodSetRequests struct {
 	PodSet            *kueue.PodSet
 	SinglePodRequests resources.Requests
diff --git a/pkg/scheduler/preemption/preemption.go b/pkg/scheduler/preemption/preemption.go
index fa1177516..0a2d41123 100644
--- a/pkg/scheduler/preemption/preemption.go
+++ b/pkg/scheduler/preemption/preemption.go
@@ -38,6 +38,7 @@ import (
 	config "sigs.k8s.io/kueue/apis/config/v1beta1"
 	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/metrics"
 	"sigs.k8s.io/kueue/pkg/resources"
 	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
@@ -204,6 +205,24 @@ var HumanReadablePreemptionReasons = map[string]string{
 	kueue.InCohortReclamationReason:           "reclamation within the cohort",
 	kueue.InCohortFairSharingReason:           "fair sharing within the cohort",
 	kueue.InCohortReclaimWhileBorrowingReason: "reclamation within the cohort while borrowing",
+	"": "UNKNOWN",
+}
+
+func preemptionMessage(preemptor *kueue.Workload, reason string) string {
+	var wUID, jUID string
+	if preemptor == nil || preemptor.UID == "" {
+		wUID = "UNKNOWN"
+	} else {
+		wUID = string(preemptor.UID)
+	}
+	uid, ok := preemptor.Labels[constants.JobUIDLabel]
+	if !ok || uid == "" {
+		jUID = "UNKNOWN"
+	} else {
+		jUID = uid
+	}
+
+	return fmt.Sprintf("Preempted to accommodate a workload (UID: %s, JobUID: %s) due to %s", wUID, jUID, HumanReadablePreemptionReasons[reason])
 }
 
 // IssuePreemptions marks the target workloads as evicted.
@@ -216,7 +235,7 @@ func (p *Preemptor) IssuePreemptions(ctx context.Context, preemptor *workload.In
 	workqueue.ParallelizeUntil(ctx, parallelPreemptions, len(targets), func(i int) {
 		target := targets[i]
 		if !meta.IsStatusConditionTrue(target.WorkloadInfo.Obj.Status.Conditions, kueue.WorkloadEvicted) {
-			message := fmt.Sprintf("Preempted to accommodate a workload (UID: %s) due to %s", preemptor.Obj.UID, HumanReadablePreemptionReasons[target.Reason])
+			message := preemptionMessage(preemptor.Obj, target.Reason)
 			err := p.applyPreemption(ctx, target.WorkloadInfo.Obj, target.Reason, message)
 			if err != nil {
 				errCh.SendErrorWithCancel(err, cancel)
diff --git a/site/content/en/docs/concepts/preemption.md b/site/content/en/docs/concepts/preemption.md
index cc78d5b20..4dbfbb485 100644
--- a/site/content/en/docs/concepts/preemption.md
+++ b/site/content/en/docs/concepts/preemption.md
@@ -29,17 +29,16 @@ that is similar to the following:
 
 ```yaml
 status:
-  conditions:
-  - lastTransitionTime: "2024-05-31T18:42:33Z"
-    message: 'Preempted to accommodate a workload (UID: 5515f7da-d2ea-4851-9e9c-6b8b3333734d)
-      in the ClusterQueue'
+  - lastTransitionTime: "2025-03-07T21:19:54Z"
+    message: 'Preempted to accommodate a workload (UID: 5c023c28-8533-4927-b266-56bca5e310c1,
+      JobUID: 4548c8bd-c399-4027-bb02-6114f3a8cdeb) due to prioritization in the ClusterQueue'
     observedGeneration: 1
     reason: Preempted
     status: "True"
     type: Evicted
-  - lastTransitionTime: "2024-05-31T18:42:33Z"
-    message: 'Preempted to accommodate a workload (UID: 5515f7da-d2ea-4851-9e9c-6b8b3333734d)
-      in the ClusterQueue'
+  - lastTransitionTime: "2025-03-07T21:19:54Z"
+    message: 'Preempted to accommodate a workload (UID: 5c023c28-8533-4927-b266-56bca5e310c1,
+      JobUID: 4548c8bd-c399-4027-bb02-6114f3a8cdeb) due to prioritization in the ClusterQueue'
     reason: InClusterQueue
     status: "True"
     type: Preempted
@@ -48,6 +47,8 @@ status:
 The `Evicted` condition indicates that the Workload was evicted with a reason `Preempted`,
 whereas the `Preempted` condition gives more details about the preemption reason.
 
+The preempting workload can be found by running `kubectl get workloads --selector=kueue.x-k8s.io/job-uid=<JobUID> --all-namespaces`.
+
 ## Preemption algorithms
 
 Kueue offers two preemption algorithms. The main difference between them is the criteria to allow



Test patch:
diff --git a/pkg/cache/tas_flavor_snapshot_test.go b/pkg/cache/tas_flavor_snapshot_test.go
new file mode 100644
index 000000000..c107dcc98
--- /dev/null
+++ b/pkg/cache/tas_flavor_snapshot_test.go
@@ -0,0 +1,67 @@
+/*
+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 cache
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	corev1 "k8s.io/api/core/v1"
+
+	"sigs.k8s.io/kueue/pkg/resources"
+)
+
+func TestFreeCapacityPerDomain(t *testing.T) {
+	snapshot := &TASFlavorSnapshot{
+		leaves: leafDomainByID{
+			"domain2": &leafDomain{
+				freeCapacity: resources.Requests{
+					corev1.ResourceCPU:    1000,
+					corev1.ResourceMemory: 2 * 1024 * 1024 * 1024, // 2 GiB
+				},
+				tasUsage: resources.Requests{
+					corev1.ResourceMemory: 1 * 1024 * 1024 * 1024, // 1 GiB
+					corev1.ResourceCPU:    500,
+				},
+			},
+			"domain1": &leafDomain{
+				freeCapacity: resources.Requests{
+					corev1.ResourceMemory: 4 * 1024 * 1024 * 1024, // 4 GiB
+					corev1.ResourceCPU:    2000,
+					"nvidia.com/gpu":      1,
+				},
+				tasUsage: resources.Requests{
+					corev1.ResourceCPU:    500,
+					"nvidia.com/gpu":      1,
+					corev1.ResourceMemory: 2 * 1024 * 1024 * 1024, // 1 GiB
+				},
+			},
+		},
+	}
+
+	expected := `{"domain1":{"freeCapacity":{"cpu":"2","memory":"4Gi","nvidia.com/gpu":"1"},"tasUsage":{"cpu":"500m","memory":"2Gi","nvidia.com/gpu":"1"}},"domain2":{"freeCapacity":{"cpu":"1","memory":"2Gi"},"tasUsage":{"cpu":"500m","memory":"1Gi"}}}`
+	var wantErr error
+
+	got, gotErr := snapshot.SerializeFreeCapacityPerDomain()
+	if diff := cmp.Diff(wantErr, gotErr, cmpopts.EquateErrors()); len(diff) != 0 {
+		t.Errorf("Unexpected error (-want,+got):\n%s", diff)
+	}
+	if diff := cmp.Diff(expected, got); diff != "" {
+		t.Errorf("SerializeFreeCapacityPerDomain() mismatch (-expected +got):\n%s", diff)
+	}
+}
diff --git a/pkg/scheduler/preemption/preemption_test.go b/pkg/scheduler/preemption/preemption_test.go
index 9f22dbe7a..364f323d5 100644
--- a/pkg/scheduler/preemption/preemption_test.go
+++ b/pkg/scheduler/preemption/preemption_test.go
@@ -41,6 +41,7 @@ import (
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
 	"sigs.k8s.io/kueue/pkg/cache"
 	"sigs.k8s.io/kueue/pkg/constants"
+	controllerconstants "sigs.k8s.io/kueue/pkg/controller/constants"
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/hierarchy"
 	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
@@ -2451,3 +2452,35 @@ func singlePodSetAssignment(assignments flavorassigner.ResourceAssignment) flavo
 		}},
 	}
 }
+
+func TestPreemptionMessage(t *testing.T) {
+	cases := []struct {
+		preemptor *kueue.Workload
+		reason    string
+		want      string
+	}{
+		{
+			preemptor: &kueue.Workload{},
+			want:      "Preempted to accommodate a workload (UID: UNKNOWN, JobUID: UNKNOWN) due to UNKNOWN",
+		},
+		{
+			preemptor: &kueue.Workload{ObjectMeta: metav1.ObjectMeta{UID: "uid"}},
+			want:      "Preempted to accommodate a workload (UID: uid, JobUID: UNKNOWN) due to UNKNOWN",
+		},
+		{
+			preemptor: &kueue.Workload{ObjectMeta: metav1.ObjectMeta{UID: "uid", Labels: map[string]string{controllerconstants.JobUIDLabel: "juid"}}},
+			want:      "Preempted to accommodate a workload (UID: uid, JobUID: juid) due to UNKNOWN",
+		},
+		{
+			preemptor: &kueue.Workload{ObjectMeta: metav1.ObjectMeta{UID: "uid", Labels: map[string]string{controllerconstants.JobUIDLabel: "juid"}}},
+			reason:    kueue.InClusterQueueReason,
+			want:      "Preempted to accommodate a workload (UID: uid, JobUID: juid) due to prioritization in the ClusterQueue",
+		},
+	}
+	for _, tc := range cases {
+		got := preemptionMessage(tc.preemptor, tc.reason)
+		if got != tc.want {
+			t.Errorf("preemptionMessage(preemptor=kueue.Workload{UID:%v, Labels:%v}, reason=%q) returned %q, want %q", tc.preemptor.UID, tc.preemptor.Labels, tc.reason, got, tc.want)
+		}
+	}
+}
diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go
index 905b70772..8d652d52e 100644
--- a/pkg/scheduler/scheduler_test.go
+++ b/pkg/scheduler/scheduler_test.go
@@ -5630,7 +5630,7 @@ func TestScheduleForTASPreemption(t *testing.T) {
 					Key:       types.NamespacedName{Namespace: "default", Name: "low-priority-admitted"},
 					EventType: "Normal",
 					Reason:    "Preempted",
-					Message:   "Preempted to accommodate a workload (UID: ) due to prioritization in the ClusterQueue",
+					Message:   "Preempted to accommodate a workload (UID: UNKNOWN, JobUID: UNKNOWN) due to prioritization in the ClusterQueue",
 				},
 				{
 					Key:       types.NamespacedName{Namespace: "default", Name: "foo"},
@@ -5695,7 +5695,7 @@ func TestScheduleForTASPreemption(t *testing.T) {
 					Key:       types.NamespacedName{Namespace: "default", Name: "low-priority-admitted"},
 					EventType: "Normal",
 					Reason:    "Preempted",
-					Message:   "Preempted to accommodate a workload (UID: ) due to prioritization in the ClusterQueue",
+					Message:   "Preempted to accommodate a workload (UID: UNKNOWN, JobUID: UNKNOWN) due to prioritization in the ClusterQueue",
 				},
 				{
 					Key:       types.NamespacedName{Namespace: "default", Name: "high-priority-waiting"},
@@ -5781,7 +5781,7 @@ func TestScheduleForTASPreemption(t *testing.T) {
 					Key:       types.NamespacedName{Namespace: "default", Name: "low-priority-admitted"},
 					EventType: "Normal",
 					Reason:    "Preempted",
-					Message:   "Preempted to accommodate a workload (UID: ) due to prioritization in the ClusterQueue",
+					Message:   "Preempted to accommodate a workload (UID: UNKNOWN, JobUID: UNKNOWN) due to prioritization in the ClusterQueue",
 				},
 				{
 					Key:       types.NamespacedName{Namespace: "default", Name: "foo"},
@@ -5869,7 +5869,7 @@ func TestScheduleForTASPreemption(t *testing.T) {
 					Key:       types.NamespacedName{Namespace: "default", Name: "low-priority-admitted"},
 					EventType: "Normal",
 					Reason:    "Preempted",
-					Message:   "Preempted to accommodate a workload (UID: ) due to prioritization in the ClusterQueue",
+					Message:   "Preempted to accommodate a workload (UID: UNKNOWN, JobUID: UNKNOWN) due to prioritization in the ClusterQueue",
 				},
 				{
 					Key:       types.NamespacedName{Namespace: "default", Name: "foo"},
diff --git a/test/integration/singlecluster/scheduler/preemption_test.go b/test/integration/singlecluster/scheduler/preemption_test.go
index 70a615232..4295c1a8a 100644
--- a/test/integration/singlecluster/scheduler/preemption_test.go
+++ b/test/integration/singlecluster/scheduler/preemption_test.go
@@ -29,6 +29,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
+	"sigs.k8s.io/kueue/pkg/controller/constants"
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/scheduler/preemption"
 	"sigs.k8s.io/kueue/pkg/util/testing"
@@ -187,6 +188,29 @@ var _ = ginkgo.Describe("Preemption", func() {
 			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, wl3)
 			util.ExpectWorkloadsToBePending(ctx, k8sClient, wl4)
 		})
+
+		ginkgo.It("Should include job UID in preemption condition when the label is set", func() {
+			ginkgo.By("Creating a low priority Workload")
+			lowWl := testing.MakeWorkload("low", ns.Name).
+				Queue(q.Name).
+				Priority(lowPriority).
+				Request(corev1.ResourceCPU, "4").
+				Obj()
+			gomega.Expect(k8sClient.Create(ctx, lowWl)).To(gomega.Succeed())
+
+			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, lowWl)
+
+			ginkgo.By("Creating a high priority Workload with JobUID label")
+			highWl := testing.MakeWorkload("high", ns.Name).
+				Label(constants.JobUIDLabel, "job-uid").
+				Queue(q.Name).
+				Priority(highPriority).
+				Request(corev1.ResourceCPU, "4").
+				Obj()
+			gomega.Expect(k8sClient.Create(ctx, highWl)).To(gomega.Succeed())
+
+			util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InClusterQueueReason, metav1.ConditionTrue, lowWl, highWl, string(highWl.UID), "job-uid")
+		})
 	})
 
 	ginkgo.Context("In a ClusterQueue that is part of a cohort", func() {
@@ -278,8 +302,8 @@ var _ = ginkgo.Describe("Preemption", func() {
 
 			conditionCmpOpts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")
 			ginkgo.By("Verify the Preempted condition", func() {
-				util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InClusterQueueReason, metav1.ConditionTrue, alphaLowWl, alphaMidWl)
-				util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InCohortReclamationReason, metav1.ConditionTrue, betaMidWl, alphaMidWl)
+				util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InClusterQueueReason, metav1.ConditionTrue, alphaLowWl, alphaMidWl, string(alphaMidWl.UID), "UNKNOWN")
+				util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InCohortReclamationReason, metav1.ConditionTrue, betaMidWl, alphaMidWl, string(alphaMidWl.UID), "UNKNOWN")
 				util.ExpectPreemptedWorkloadsTotalMetric(alphaCQ.Name, kueue.InClusterQueueReason, 1)
 				util.ExpectPreemptedWorkloadsTotalMetric(alphaCQ.Name, kueue.InCohortReclamationReason, 1)
 				util.ExpectPreemptedWorkloadsTotalMetric(betaCQ.Name, kueue.InClusterQueueReason, 0)
@@ -297,7 +321,7 @@ var _ = ginkgo.Describe("Preemption", func() {
 						Type:    kueue.WorkloadPreempted,
 						Status:  metav1.ConditionFalse,
 						Reason:  "QuotaReserved",
-						Message: fmt.Sprintf("Previously: Preempted to accommodate a workload (UID: %s) due to %s", alphaMidWl.UID, preemption.HumanReadablePreemptionReasons[kueue.InClusterQueueReason]),
+						Message: fmt.Sprintf("Previously: Preempted to accommodate a workload (UID: %s, JobUID: UNKNOWN) due to %s", alphaMidWl.UID, preemption.HumanReadablePreemptionReasons[kueue.InClusterQueueReason]),
 					}, conditionCmpOpts))
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
@@ -307,7 +331,7 @@ var _ = ginkgo.Describe("Preemption", func() {
 						Type:    kueue.WorkloadPreempted,
 						Status:  metav1.ConditionFalse,
 						Reason:  "QuotaReserved",
-						Message: fmt.Sprintf("Previously: Preempted to accommodate a workload (UID: %s) due to %s", alphaMidWl.UID, preemption.HumanReadablePreemptionReasons[kueue.InCohortReclamationReason]),
+						Message: fmt.Sprintf("Previously: Preempted to accommodate a workload (UID: %s, JobUID: UNKNOWN) due to %s", alphaMidWl.UID, preemption.HumanReadablePreemptionReasons[kueue.InCohortReclamationReason]),
 					}, conditionCmpOpts))
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			})
@@ -689,7 +713,7 @@ var _ = ginkgo.Describe("Preemption", func() {
 				Obj()
 			gomega.Expect(k8sClient.Create(ctx, aStandardVeryHighWl)).To(gomega.Succeed())
 
-			util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InCohortReclaimWhileBorrowingReason, metav1.ConditionTrue, aBestEffortLowWl, aStandardVeryHighWl)
+			util.ExpectPreemptedCondition(ctx, k8sClient, kueue.InCohortReclaimWhileBorrowingReason, metav1.ConditionTrue, aBestEffortLowWl, aStandardVeryHighWl, string(aStandardVeryHighWl.UID), "UNKNOWN")
 			util.ExpectPreemptedWorkloadsTotalMetric(aStandardCQ.Name, kueue.InCohortReclaimWhileBorrowingReason, 1)
 			util.ExpectPreemptedWorkloadsTotalMetric(aBestEffortCQ.Name, kueue.InCohortReclaimWhileBorrowingReason, 0)
 
diff --git a/test/util/util.go b/test/util/util.go
index 29716ed28..8054d8e22 100644
--- a/test/util/util.go
+++ b/test/util/util.go
@@ -828,7 +828,7 @@ readCh:
 	gomega.ExpectWithOffset(1, gotObjs).To(gomega.Equal(objs))
 }
 
-func ExpectPreemptedCondition(ctx context.Context, k8sClient client.Client, reason string, status metav1.ConditionStatus, preemptedWl, preempteeWl *kueue.Workload) {
+func ExpectPreemptedCondition(ctx context.Context, k8sClient client.Client, reason string, status metav1.ConditionStatus, preemptedWl, preempteeWl *kueue.Workload, preemteeWorkloadUID, preempteeJobUID string) {
 	conditionCmpOpts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime", "ObservedGeneration")
 	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
 		g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(preemptedWl), preemptedWl)).To(gomega.Succeed())
@@ -836,7 +836,7 @@ func ExpectPreemptedCondition(ctx context.Context, k8sClient client.Client, reas
 			Type:    kueue.WorkloadPreempted,
 			Status:  status,
 			Reason:  reason,
-			Message: fmt.Sprintf("Preempted to accommodate a workload (UID: %s) due to %s", preempteeWl.UID, preemption.HumanReadablePreemptionReasons[reason]),
+			Message: fmt.Sprintf("Preempted to accommodate a workload (UID: %s, JobUID: %s) due to %s", preemteeWorkloadUID, preempteeJobUID, preemption.HumanReadablePreemptionReasons[reason]),
 		}, conditionCmpOpts)))
 	}, Timeout, Interval).Should(gomega.Succeed())
 }