@@ -34,6 +34,7 @@ import (
34
34
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
35
35
"k8s.io/kubernetes/pkg/kubelet/apis/podresources"
36
36
"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager"
37
+ "k8s.io/kubernetes/pkg/kubelet/metrics"
37
38
"k8s.io/kubernetes/pkg/kubelet/util"
38
39
"k8s.io/kubernetes/test/e2e/feature"
39
40
"k8s.io/kubernetes/test/e2e/framework"
@@ -389,6 +390,129 @@ var _ = SIGDescribe("CPU Manager Metrics", framework.WithSerial(), feature.CPUMa
389
390
ginkgo .By ("Ensuring the metrics match the expectations about alignment metrics a few more times" )
390
391
gomega .Consistently (ctx , getKubeletMetrics , 1 * time .Minute , 15 * time .Second ).Should (matchAlignmentMetrics )
391
392
})
393
+ ginkgo .It ("should report zero counters for allocation per NUMA after a fresh restart" , func (ctx context.Context ) {
394
+
395
+ cpuPolicyOptions := map [string ]string {
396
+ cpumanager .DistributeCPUsAcrossNUMAOption : "true" ,
397
+ cpumanager .FullPCPUsOnlyOption : "true" ,
398
+ }
399
+ newCfg := configureCPUManagerInKubelet (oldCfg ,
400
+ & cpuManagerKubeletArguments {
401
+ policyName : string (cpumanager .PolicyStatic ),
402
+ reservedSystemCPUs : cpuset .New (0 ),
403
+ enableCPUManagerOptions : true ,
404
+ options : cpuPolicyOptions ,
405
+ },
406
+ )
407
+
408
+ updateKubeletConfig (ctx , f , newCfg , true )
409
+
410
+ ginkgo .By ("Checking the cpumanager allocation per NUMA metric right after the kubelet restart, with no pods running" )
411
+ numaNodes , _ , _ := hostCheck ()
412
+
413
+ framework .Logf ("numaNodes on the system %d" , numaNodes )
414
+
415
+ keys := make (map [interface {}]types.GomegaMatcher )
416
+ idFn := makeCustomLabelID (metrics .AlignedNUMANode )
417
+
418
+ for i := 0 ; i < numaNodes ; i ++ {
419
+ keys ["kubelet_cpu_manager_allocation_per_numa" ] = gstruct .MatchAllElements (idFn , gstruct.Elements {
420
+ fmt .Sprintf ("%d" , i ): timelessSample (0 ),
421
+ })
422
+
423
+ }
424
+
425
+ matchSpreadMetrics := gstruct .MatchKeys (gstruct .IgnoreExtras , keys )
426
+
427
+ ginkgo .By ("Giving the Kubelet time to start up and produce metrics" )
428
+ gomega .Eventually (ctx , getKubeletMetrics , 1 * time .Minute , 15 * time .Second ).Should (matchSpreadMetrics )
429
+ ginkgo .By ("Ensuring the metrics match the expectations a few more times" )
430
+ gomega .Consistently (ctx , getKubeletMetrics , 1 * time .Minute , 15 * time .Second ).Should (matchSpreadMetrics )
431
+
432
+ })
433
+
434
+ ginkgo .It ("should report allocation per NUMA metric when handling guaranteed pods" , func (ctx context.Context ) {
435
+ var cpusNumPerNUMA , coresNumPerNUMA , numaNodes , threadsPerCore int
436
+ cpuPolicyOptions := map [string ]string {
437
+ cpumanager .DistributeCPUsAcrossNUMAOption : "true" ,
438
+ cpumanager .FullPCPUsOnlyOption : "true" ,
439
+ }
440
+ newCfg := configureCPUManagerInKubelet (oldCfg ,
441
+ & cpuManagerKubeletArguments {
442
+ policyName : string (cpumanager .PolicyStatic ),
443
+ reservedSystemCPUs : cpuset .New (0 ),
444
+ enableCPUManagerOptions : true ,
445
+ options : cpuPolicyOptions ,
446
+ },
447
+ )
448
+
449
+ updateKubeletConfig (ctx , f , newCfg , true )
450
+
451
+ numaNodes , coresNumPerNUMA , threadsPerCore = hostCheck ()
452
+ cpusNumPerNUMA = coresNumPerNUMA * threadsPerCore
453
+
454
+ framework .Logf ("numaNodes on the system %d" , numaNodes )
455
+ framework .Logf ("Cores per NUMA on the system %d" , coresNumPerNUMA )
456
+ framework .Logf ("Threads per Core on the system %d" , threadsPerCore )
457
+ framework .Logf ("CPUs per NUMA on the system %d" , cpusNumPerNUMA )
458
+
459
+ smtLevel = getSMTLevel ()
460
+ framework .Logf ("SMT Level on the system %d" , smtLevel )
461
+
462
+ ginkgo .By ("Querying the podresources endpoint to get the baseline" )
463
+ endpoint , err := util .LocalEndpoint (defaultPodResourcesPath , podresources .Socket )
464
+ framework .ExpectNoError (err , "LocalEndpoint() failed err: %v" , err )
465
+
466
+ cli , conn , err := podresources .GetV1Client (endpoint , defaultPodResourcesTimeout , defaultPodResourcesMaxSize )
467
+ framework .ExpectNoError (err , "GetV1Client() failed err: %v" , err )
468
+ defer func () {
469
+ framework .ExpectNoError (conn .Close ())
470
+ }()
471
+
472
+ ginkgo .By ("Checking the pool allocatable resources from the kubelet" )
473
+ resp , err := cli .GetAllocatableResources (ctx , & kubeletpodresourcesv1.AllocatableResourcesRequest {})
474
+ framework .ExpectNoError (err , "failed to get the kubelet allocatable resources" )
475
+ allocatableCPUs , _ := demuxCPUsAndDevicesFromGetAllocatableResources (resp )
476
+
477
+ // 'distribute-cpus-across-numa' policy option ensures that CPU allocations are evenly distributed
478
+ // across NUMA nodes in cases where more than one NUMA node is required to satisfy the allocation.
479
+ // So, we want to ensure that the CPU Request exceeds the number of CPUs that can fit within a single
480
+ // NUMA node. We have to pick cpuRequest such that:
481
+ // 1. CPURequest > cpusNumPerNUMA
482
+ // 2. Not occupy all the CPUs on the node ande leave room for reserved CPU
483
+ // 3. CPURequest is a multiple if number of NUMA nodes to allow equal CPU distribution across NUMA nodes
484
+ //
485
+ // In summary: cpusNumPerNUMA < CPURequest < ((cpusNumPerNuma * numaNodes) - reservedCPUscount)
486
+ // Considering all these constraints we select: CPURequest= (cpusNumPerNUMA-smtLevel)*numaNodes
487
+ cpuRequest := (cpusNumPerNUMA - smtLevel ) * numaNodes
488
+ if cpuRequest > allocatableCPUs .Size () {
489
+ e2eskipper .Skipf ("Pod requesting %d CPUs which is more than allocatable CPUs:%d" , cpuRequest , allocatableCPUs .Size ())
490
+ }
491
+
492
+ ginkgo .By ("Creating the test pod" )
493
+ testPod = e2epod .NewPodClient (f ).Create (ctx , makeGuaranteedCPUExclusiveSleeperPod ("test-pod-allocation-per-numa" , cpuRequest ))
494
+
495
+ ginkgo .By ("Checking the cpumanager metrics after pod creation" )
496
+
497
+ keys := make (map [interface {}]types.GomegaMatcher )
498
+ idFn := makeCustomLabelID (metrics .AlignedNUMANode )
499
+
500
+ // On a clean environment with no other pods running if distribute-across-numa policy option is enabled
501
+ for i := 0 ; i < numaNodes ; i ++ {
502
+ keys ["kubelet_cpu_manager_allocation_per_numa" ] = gstruct .MatchAllElements (idFn , gstruct.Elements {
503
+ fmt .Sprintf ("%d" , i ): timelessSample (2 ),
504
+ })
505
+
506
+ }
507
+
508
+ matchSpreadMetrics := gstruct .MatchKeys (gstruct .IgnoreExtras , keys )
509
+
510
+ ginkgo .By ("Giving the Kubelet time to start up and produce metrics" )
511
+ gomega .Eventually (ctx , getKubeletMetrics , 1 * time .Minute , 15 * time .Second ).Should (matchSpreadMetrics )
512
+ ginkgo .By ("Ensuring the metrics match the expectations a few more times" )
513
+ gomega .Consistently (ctx , getKubeletMetrics , 1 * time .Minute , 15 * time .Second ).Should (matchSpreadMetrics )
514
+ })
515
+
392
516
})
393
517
})
394
518
0 commit comments