dcgmdevice_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package kubemodel
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "github.com/opencost/opencost/core/pkg/model/kubemodel"
  8. "github.com/opencost/opencost/core/pkg/source"
  9. )
  10. func TestComputeDCGMDevices(t *testing.T) {
  11. start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
  12. end := start.Add(time.Hour)
  13. tests := []struct {
  14. name string
  15. overrides map[string]any
  16. want map[string]*kubemodel.DCGMDevice
  17. }{
  18. {
  19. name: "no data returns empty dcgm device map",
  20. overrides: map[string]any{},
  21. want: map[string]*kubemodel.DCGMDevice{},
  22. },
  23. {
  24. name: "basic dcgm device info and uptime",
  25. overrides: map[string]any{
  26. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  27. {UUID: "GPU-abc123", Device: "nvidia0", ModelName: "A100"},
  28. },
  29. source.QueryDCGMDeviceUptime: []*source.DCGMDeviceUptimeResult{
  30. {UUID: "GPU-abc123", First: start, Last: end},
  31. },
  32. },
  33. want: map[string]*kubemodel.DCGMDevice{
  34. "GPU-abc123": {
  35. UUID: "GPU-abc123",
  36. Device: "nvidia0",
  37. ModelName: "A100",
  38. Start: start,
  39. End: end,
  40. PodUsages: map[string]kubemodel.DCGMPod{},
  41. },
  42. },
  43. },
  44. {
  45. name: "dcgm device without uptime is not registered",
  46. overrides: map[string]any{
  47. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  48. {UUID: "GPU-abc123", Device: "nvidia0", ModelName: "A100"},
  49. },
  50. },
  51. want: map[string]*kubemodel.DCGMDevice{},
  52. },
  53. {
  54. name: "dcgm device with empty uuid is skipped",
  55. overrides: map[string]any{
  56. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  57. {UUID: "", Device: "nvidia0", ModelName: "A100"},
  58. },
  59. source.QueryDCGMDeviceUptime: []*source.DCGMDeviceUptimeResult{
  60. {UUID: "GPU-abc123", First: start, Last: end},
  61. },
  62. },
  63. want: map[string]*kubemodel.DCGMDevice{},
  64. },
  65. {
  66. name: "dcgm container usage avg and max are populated",
  67. overrides: map[string]any{
  68. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  69. {UUID: "GPU-abc123", Device: "nvidia0", ModelName: "A100"},
  70. },
  71. source.QueryDCGMDeviceUptime: []*source.DCGMDeviceUptimeResult{
  72. {UUID: "GPU-abc123", First: start, Last: end},
  73. },
  74. source.QueryDCGMContainerUsageAvg: []*source.DCGMDeviceContainerUsageResult{
  75. {UUID: "GPU-abc123", PodUID: "pod-1", Container: "training", Value: 0.75},
  76. },
  77. source.QueryDCGMContainerUsageMax: []*source.DCGMDeviceContainerUsageResult{
  78. {UUID: "GPU-abc123", PodUID: "pod-1", Container: "training", Value: 0.95},
  79. },
  80. },
  81. want: map[string]*kubemodel.DCGMDevice{
  82. "GPU-abc123": {
  83. UUID: "GPU-abc123",
  84. Device: "nvidia0",
  85. ModelName: "A100",
  86. Start: start,
  87. End: end,
  88. PodUsages: map[string]kubemodel.DCGMPod{
  89. "pod-1": {
  90. ContainerUsages: map[string]kubemodel.DCGMContainer{
  91. "training": {UsageAvg: 0.75, UsageMax: 0.95},
  92. },
  93. },
  94. },
  95. },
  96. },
  97. },
  98. {
  99. name: "usage with empty pod uid or container is ignored",
  100. overrides: map[string]any{
  101. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  102. {UUID: "GPU-abc123", Device: "nvidia0", ModelName: "A100"},
  103. },
  104. source.QueryDCGMDeviceUptime: []*source.DCGMDeviceUptimeResult{
  105. {UUID: "GPU-abc123", First: start, Last: end},
  106. },
  107. source.QueryDCGMContainerUsageAvg: []*source.DCGMDeviceContainerUsageResult{
  108. {UUID: "GPU-abc123", PodUID: "", Container: "training", Value: 0.5},
  109. {UUID: "GPU-abc123", PodUID: "pod-1", Container: "", Value: 0.5},
  110. },
  111. },
  112. want: map[string]*kubemodel.DCGMDevice{
  113. "GPU-abc123": {
  114. UUID: "GPU-abc123",
  115. Device: "nvidia0",
  116. ModelName: "A100",
  117. Start: start,
  118. End: end,
  119. PodUsages: map[string]kubemodel.DCGMPod{},
  120. },
  121. },
  122. },
  123. {
  124. name: "duplicate device info entries use first occurrence",
  125. overrides: map[string]any{
  126. source.QueryDCGMDeviceInfo: []*source.DCGMDeviceInfoResult{
  127. {UUID: "GPU-abc123", Device: "nvidia0", ModelName: "A100"},
  128. {UUID: "GPU-abc123", Device: "nvidia0-dup", ModelName: "A100-dup"},
  129. },
  130. source.QueryDCGMDeviceUptime: []*source.DCGMDeviceUptimeResult{
  131. {UUID: "GPU-abc123", First: start, Last: end},
  132. },
  133. },
  134. want: map[string]*kubemodel.DCGMDevice{
  135. "GPU-abc123": {
  136. UUID: "GPU-abc123",
  137. Device: "nvidia0",
  138. ModelName: "A100",
  139. Start: start,
  140. End: end,
  141. PodUsages: map[string]kubemodel.DCGMPod{},
  142. },
  143. },
  144. },
  145. }
  146. for _, tt := range tests {
  147. t.Run(tt.name, func(t *testing.T) {
  148. ds := source.NewMockOpenCostDataSource()
  149. ds.ResolutionValue = 5 * time.Minute
  150. seedCluster(ds, start, end)
  151. for method, result := range tt.overrides {
  152. ds.Querier.SetOverride(method, result)
  153. }
  154. km, err := NewKubeModel(testClusterUID, false, ds)
  155. require.NoError(t, err)
  156. kms := kubemodel.NewKubeModelSet(start, end)
  157. err = km.computeDCGMDevices(kms, start, end)
  158. require.NoError(t, err)
  159. assert.Equal(t, tt.want, kms.DCGMDevices)
  160. })
  161. }
  162. }