@@ -5,23 +5,17 @@ SPDX-License-Identifier: Apache-2.0
55package hamicore
66
77import (
8- "bytes"
98 "context"
109 "encoding/json"
1110 "fmt"
1211 "strconv"
1312 "strings"
14- "time"
1513
1614 . "github.com/onsi/ginkgo/v2"
1715 . "github.com/onsi/gomega"
18- dto "github.com/prometheus/client_model/go"
19- "github.com/prometheus/common/expfmt"
20- "github.com/prometheus/common/model"
2116 v1 "k8s.io/api/core/v1"
2217 "k8s.io/apimachinery/pkg/api/resource"
2318 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24- "k8s.io/client-go/kubernetes"
2519 "k8s.io/utils/ptr"
2620
2721 kaiv1binder "github.com/kai-scheduler/KAI-scheduler/pkg/apis/kai/v1/binder"
@@ -38,51 +32,13 @@ import (
3832
3933const (
4034 kaiResourceIsolatorWebhookName = "kai-resource-isolator-mutating"
41- kaiResourceIsolatorNamespace = "kai-resource-isolator"
42- kaiVGPUMonitorDaemonSetName = "kai-resource-isolator-monitor"
43- kaiVGPUMonitorLabel = "app.kubernetes.io/component=kai-vgpu-monitor"
44- kaiVGPUMonitorMetricsPort = "9394"
4535 binderDeploymentName = "binder"
4636 binderDeploymentNamespace = "kai-scheduler"
4737 binderPluginsFlag = "--plugins"
4838 cudaImage = "nvidia/cuda:12.6.0-base-ubuntu22.04"
49- // devel image is needed so the metrics workload can nvcc a tiny cudaMalloc hold binary.
50- cudaDevelImage = "nvidia/cuda:12.6.0-devel-ubuntu22.04"
51- gpuMemoryRequestMiB = 2000
52- cudaAllocHoldMiB = 64
53- vgpuMetricsTimeout = 60 * time .Second
54- vgpuMetricsInterval = 5 * time .Second
55- hamiVGPUMemoryUsed = "hami_vgpu_memory_used_bytes"
39+ gpuMemoryRequestMiB = 2000
5640)
5741
58- // Compiles and runs a small CUDA alloc that holds memory so kai-vgpu-monitor can
59- // scrape hami_vgpu_memory_used_bytes > 0. Kept as a container entrypoint (not
60- // ExecInPod) so the allocation outlives the ready wait.
61- const cudaAllocHoldScript = `set -euo pipefail
62- cat >/tmp/hold.cu <<'EOF'
63- #include <cuda_runtime.h>
64- #include <stdio.h>
65- #include <unistd.h>
66- int main(void) {
67- void *p = NULL;
68- size_t bytes = (size_t)64 * 1024 * 1024;
69- cudaError_t err = cudaMalloc(&p, bytes);
70- if (err != cudaSuccess) {
71- fprintf(stderr, "cudaMalloc failed: %s\n", cudaGetErrorString(err));
72- return 1;
73- }
74- printf("allocated %zu bytes at %p\n", bytes, p);
75- fflush(stdout);
76- for (;;) {
77- sleep(3600);
78- }
79- return 0;
80- }
81- EOF
82- nvcc -O0 -o /tmp/hold /tmp/hold.cu
83- exec /tmp/hold
84- `
85-
8642var _ = Describe ("HAMi-core resource isolation" , Ordered , func () {
8743 var testCtx * testcontext.TestContext
8844
@@ -235,45 +191,6 @@ var _ = Describe("HAMi-core resource isolation", Ordered, func() {
235191 "nvidia-smi visible memory (%d MiB) should match CUDA_DEVICE_MEMORY_LIMIT (%d MiB)" ,
236192 visibleMemMiB , limitMiB )
237193 })
238-
239- It ("gpu-memory: kai-vgpu-monitor reports hami_vgpu_memory_used_bytes > 0" ,
240- Label (labels .ReservationPod ), func (ctx context.Context ) {
241- if ! isKaiVGPUMonitorInstalled (ctx , testCtx .KubeClientset ) {
242- Skip (fmt .Sprintf (
243- "kai-vgpu-monitor DaemonSet %q not found in namespace %q; " +
244- "install via hack/hami/deploy_isolator.sh (--test-hami)" ,
245- kaiVGPUMonitorDaemonSetName , kaiResourceIsolatorNamespace ,
246- ))
247- }
248-
249- pod := rd .CreatePodObject (testCtx .Queues [0 ], v1.ResourceRequirements {})
250- pod .Annotations [constants .GpuMemory ] = strconv .Itoa (gpuMemoryRequestMiB )
251- pod .Spec .Containers [0 ].Image = cudaDevelImage
252- pod .Spec .Containers [0 ].Command = []string {"bash" , "-c" }
253- pod .Spec .Containers [0 ].Args = []string {cudaAllocHoldScript }
254-
255- _ , err := rd .CreatePod (ctx , testCtx .KubeClientset , pod )
256- Expect (err ).NotTo (HaveOccurred ())
257- wait .ForPodReady (ctx , testCtx .ControllerClient , pod )
258-
259- pod , err = rd .GetPod (ctx , testCtx .KubeClientset , pod .Namespace , pod .Name )
260- Expect (err ).NotTo (HaveOccurred ())
261- Expect (pod .Spec .NodeName ).NotTo (BeEmpty (), "pod should be scheduled to a node" )
262- containerName := pod .Spec .Containers [0 ].Name
263-
264- By ("waiting for kai-vgpu-monitor to expose hami_vgpu_memory_used_bytes for the workload" )
265- Eventually (func (g Gomega ) {
266- used , scrapeErr := scrapeHamiVGPUMemoryUsedBytes (
267- ctx , testCtx .KubeClientset , pod .Spec .NodeName , pod .Namespace , pod .Name , containerName ,
268- )
269- g .Expect (scrapeErr ).NotTo (HaveOccurred ())
270- GinkgoLogr .Info ("hami_vgpu_memory_used_bytes" ,
271- "namespace" , pod .Namespace , "pod" , pod .Name , "container" , containerName , "bytes" , used )
272- g .Expect (used ).To (BeNumerically (">" , 0 ),
273- "expected hami_vgpu_memory_used_bytes > 0 for %s/%s container %s (held ~%d MiB)" ,
274- pod .Namespace , pod .Name , containerName , cudaAllocHoldMiB )
275- }, vgpuMetricsTimeout , vgpuMetricsInterval ).Should (Succeed ())
276- })
277194})
278195
279196func printNvidiaSmi (ctx context.Context , testCtx * testcontext.TestContext , pod * v1.Pod ) {
@@ -345,88 +262,3 @@ func hamiCoreEnabledInPluginsJSON(raw string) bool {
345262 }
346263 return ptr .Deref (cfg .Enabled , false )
347264}
348-
349- func isKaiVGPUMonitorInstalled (ctx context.Context , client kubernetes.Interface ) bool {
350- _ , err := client .AppsV1 ().DaemonSets (kaiResourceIsolatorNamespace ).
351- Get (ctx , kaiVGPUMonitorDaemonSetName , metav1.GetOptions {})
352- return err == nil
353- }
354-
355- func scrapeHamiVGPUMemoryUsedBytes (
356- ctx context.Context ,
357- client kubernetes.Interface ,
358- nodeName , namespace , podName , containerName string ,
359- ) (float64 , error ) {
360- monitorPod , err := findMonitorPodOnNode (ctx , client , nodeName )
361- if err != nil {
362- return 0 , err
363- }
364-
365- raw , err := client .CoreV1 ().Pods (monitorPod .Namespace ).
366- ProxyGet ("http" , monitorPod .Name , kaiVGPUMonitorMetricsPort , "/metrics" , nil ).
367- DoRaw (ctx )
368- if err != nil {
369- return 0 , fmt .Errorf ("proxy GET /metrics from monitor pod %s/%s: %w" ,
370- monitorPod .Namespace , monitorPod .Name , err )
371- }
372-
373- parser := expfmt .NewTextParser (model .UTF8Validation )
374- families , err := parser .TextToMetricFamilies (bytes .NewReader (raw ))
375- if err != nil {
376- return 0 , fmt .Errorf ("parse prometheus metrics: %w" , err )
377- }
378-
379- family , ok := families [hamiVGPUMemoryUsed ]
380- if ! ok {
381- return 0 , fmt .Errorf ("metric %s not present in monitor scrape" , hamiVGPUMemoryUsed )
382- }
383-
384- want := map [string ]string {
385- "namespace" : namespace ,
386- "pod" : podName ,
387- "container" : containerName ,
388- }
389- for _ , metric := range family .GetMetric () {
390- if ! metricHasLabels (metric , want ) {
391- continue
392- }
393- if metric .GetGauge () == nil {
394- return 0 , fmt .Errorf ("metric %s for %v is not a gauge" , hamiVGPUMemoryUsed , want )
395- }
396- return metric .GetGauge ().GetValue (), nil
397- }
398- return 0 , fmt .Errorf ("metric %s with labels %v not found" , hamiVGPUMemoryUsed , want )
399- }
400-
401- func findMonitorPodOnNode (ctx context.Context , client kubernetes.Interface , nodeName string ) (* v1.Pod , error ) {
402- pods , err := client .CoreV1 ().Pods (kaiResourceIsolatorNamespace ).List (ctx , metav1.ListOptions {
403- LabelSelector : kaiVGPUMonitorLabel ,
404- FieldSelector : "spec.nodeName=" + nodeName ,
405- })
406- if err != nil {
407- return nil , fmt .Errorf ("list kai-vgpu-monitor pods on node %s: %w" , nodeName , err )
408- }
409- for i := range pods .Items {
410- pod := & pods .Items [i ]
411- if rd .IsPodReady (pod ) {
412- return pod , nil
413- }
414- }
415- if len (pods .Items ) == 0 {
416- return nil , fmt .Errorf ("no kai-vgpu-monitor pods scheduled on node %s" , nodeName )
417- }
418- return nil , fmt .Errorf ("kai-vgpu-monitor pod(s) on node %s are not Ready" , nodeName )
419- }
420-
421- func metricHasLabels (metric * dto.Metric , want map [string ]string ) bool {
422- got := make (map [string ]string , len (metric .GetLabel ()))
423- for _ , label := range metric .GetLabel () {
424- got [label .GetName ()] = label .GetValue ()
425- }
426- for k , v := range want {
427- if got [k ] != v {
428- return false
429- }
430- }
431- return true
432- }
0 commit comments