nvidia.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package accelerators
  15. import (
  16. "bufio"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path/filepath"
  21. "strconv"
  22. "strings"
  23. "sync"
  24. "time"
  25. info "github.com/google/cadvisor/info/v1"
  26. "github.com/mindprince/gonvml"
  27. "k8s.io/klog"
  28. )
  29. type NvidiaManager struct {
  30. sync.Mutex
  31. // true if there are NVIDIA devices present on the node
  32. devicesPresent bool
  33. // true if the NVML library (libnvidia-ml.so.1) was loaded successfully
  34. nvmlInitialized bool
  35. // nvidiaDevices is a map from device minor number to a handle that can be used to get metrics about the device
  36. nvidiaDevices map[int]gonvml.Device
  37. }
  38. var sysFsPCIDevicesPath = "/sys/bus/pci/devices/"
  39. const nvidiaVendorId = "0x10de"
  40. // Setup initializes NVML if nvidia devices are present on the node.
  41. func (nm *NvidiaManager) Setup() {
  42. if !detectDevices(nvidiaVendorId) {
  43. klog.V(4).Info("No NVIDIA devices found.")
  44. return
  45. }
  46. nm.devicesPresent = true
  47. initializeNVML(nm)
  48. }
  49. // detectDevices returns true if a device with given pci id is present on the node.
  50. func detectDevices(vendorId string) bool {
  51. devices, err := ioutil.ReadDir(sysFsPCIDevicesPath)
  52. if err != nil {
  53. klog.Warningf("Error reading %q: %v", sysFsPCIDevicesPath, err)
  54. return false
  55. }
  56. for _, device := range devices {
  57. vendorPath := filepath.Join(sysFsPCIDevicesPath, device.Name(), "vendor")
  58. content, err := ioutil.ReadFile(vendorPath)
  59. if err != nil {
  60. klog.V(4).Infof("Error while reading %q: %v", vendorPath, err)
  61. continue
  62. }
  63. if strings.EqualFold(strings.TrimSpace(string(content)), vendorId) {
  64. klog.V(3).Infof("Found device with vendorId %q", vendorId)
  65. return true
  66. }
  67. }
  68. return false
  69. }
  70. // initializeNVML initializes the NVML library and sets up the nvmlDevices map.
  71. // This is defined as a variable to help in testing.
  72. var initializeNVML = func(nm *NvidiaManager) {
  73. if err := gonvml.Initialize(); err != nil {
  74. // This is under a logging level because otherwise we may cause
  75. // log spam if the drivers/nvml is not installed on the system.
  76. klog.V(4).Infof("Could not initialize NVML: %v", err)
  77. return
  78. }
  79. nm.nvmlInitialized = true
  80. numDevices, err := gonvml.DeviceCount()
  81. if err != nil {
  82. klog.Warningf("GPU metrics would not be available. Failed to get the number of nvidia devices: %v", err)
  83. return
  84. }
  85. klog.V(1).Infof("NVML initialized. Number of nvidia devices: %v", numDevices)
  86. nm.nvidiaDevices = make(map[int]gonvml.Device, numDevices)
  87. for i := 0; i < int(numDevices); i++ {
  88. device, err := gonvml.DeviceHandleByIndex(uint(i))
  89. if err != nil {
  90. klog.Warningf("Failed to get nvidia device handle %d: %v", i, err)
  91. continue
  92. }
  93. minorNumber, err := device.MinorNumber()
  94. if err != nil {
  95. klog.Warningf("Failed to get nvidia device minor number: %v", err)
  96. continue
  97. }
  98. nm.nvidiaDevices[int(minorNumber)] = device
  99. }
  100. }
  101. // Destroy shuts down NVML.
  102. func (nm *NvidiaManager) Destroy() {
  103. if nm.nvmlInitialized {
  104. gonvml.Shutdown()
  105. }
  106. }
  107. // GetCollector returns a collector that can fetch nvidia gpu metrics for nvidia devices
  108. // present in the devices.list file in the given devicesCgroupPath.
  109. func (nm *NvidiaManager) GetCollector(devicesCgroupPath string) (AcceleratorCollector, error) {
  110. nc := &NvidiaCollector{}
  111. if !nm.devicesPresent {
  112. return nc, nil
  113. }
  114. // Makes sure that we don't call initializeNVML() concurrently and
  115. // that we only call initializeNVML() when it's not initialized.
  116. nm.Lock()
  117. if !nm.nvmlInitialized {
  118. initializeNVML(nm)
  119. }
  120. if !nm.nvmlInitialized || len(nm.nvidiaDevices) == 0 {
  121. nm.Unlock()
  122. return nc, nil
  123. }
  124. nm.Unlock()
  125. nvidiaMinorNumbers, err := parseDevicesCgroup(devicesCgroupPath)
  126. if err != nil {
  127. return nc, err
  128. }
  129. for _, minor := range nvidiaMinorNumbers {
  130. device, ok := nm.nvidiaDevices[minor]
  131. if !ok {
  132. return nc, fmt.Errorf("nvidia device minor number %d not found in cached devices", minor)
  133. }
  134. nc.Devices = append(nc.Devices, device)
  135. }
  136. return nc, nil
  137. }
  138. // parseDevicesCgroup parses the devices cgroup devices.list file for the container
  139. // and returns a list of minor numbers corresponding to NVIDIA GPU devices that the
  140. // container is allowed to access. In cases where the container has access to all
  141. // devices or all NVIDIA devices but the devices are not enumerated separately in
  142. // the devices.list file, we return an empty list.
  143. // This is defined as a variable to help in testing.
  144. var parseDevicesCgroup = func(devicesCgroupPath string) ([]int, error) {
  145. // Always return a non-nil slice
  146. nvidiaMinorNumbers := []int{}
  147. devicesList := filepath.Join(devicesCgroupPath, "devices.list")
  148. f, err := os.Open(devicesList)
  149. if err != nil {
  150. return nvidiaMinorNumbers, fmt.Errorf("error while opening devices cgroup file %q: %v", devicesList, err)
  151. }
  152. defer f.Close()
  153. s := bufio.NewScanner(f)
  154. // See https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt for the file format
  155. for s.Scan() {
  156. text := s.Text()
  157. fields := strings.Fields(text)
  158. if len(fields) != 3 {
  159. return nvidiaMinorNumbers, fmt.Errorf("invalid devices cgroup entry %q: must contain three whitespace-separated fields", text)
  160. }
  161. // Split the second field to find out major:minor numbers
  162. majorMinor := strings.Split(fields[1], ":")
  163. if len(majorMinor) != 2 {
  164. return nvidiaMinorNumbers, fmt.Errorf("invalid devices cgroup entry %q: second field should have one colon", text)
  165. }
  166. // NVIDIA graphics devices are character devices with major number 195.
  167. // https://github.com/torvalds/linux/blob/v4.13/Documentation/admin-guide/devices.txt#L2583
  168. if fields[0] == "c" && majorMinor[0] == "195" {
  169. minorNumber, err := strconv.Atoi(majorMinor[1])
  170. if err != nil {
  171. return nvidiaMinorNumbers, fmt.Errorf("invalid devices cgroup entry %q: minor number is not integer", text)
  172. }
  173. // We don't want devices like nvidiactl (195:255) and nvidia-modeset (195:254)
  174. if minorNumber < 128 {
  175. nvidiaMinorNumbers = append(nvidiaMinorNumbers, minorNumber)
  176. }
  177. // We are ignoring the "195:*" case
  178. // where the container has access to all NVIDIA devices on the machine.
  179. }
  180. // We are ignoring the "*:*" case
  181. // where the container has access to all devices on the machine.
  182. }
  183. return nvidiaMinorNumbers, nil
  184. }
  185. type NvidiaCollector struct {
  186. // Exposed for testing
  187. Devices []gonvml.Device
  188. }
  189. // UpdateStats updates the stats for NVIDIA GPUs (if any) attached to the container.
  190. func (nc *NvidiaCollector) UpdateStats(stats *info.ContainerStats) error {
  191. for _, device := range nc.Devices {
  192. model, err := device.Name()
  193. if err != nil {
  194. return fmt.Errorf("error while getting gpu name: %v", err)
  195. }
  196. uuid, err := device.UUID()
  197. if err != nil {
  198. return fmt.Errorf("error while getting gpu uuid: %v", err)
  199. }
  200. memoryTotal, memoryUsed, err := device.MemoryInfo()
  201. if err != nil {
  202. return fmt.Errorf("error while getting gpu memory info: %v", err)
  203. }
  204. //TODO: Use housekeepingInterval
  205. utilizationGPU, err := device.AverageGPUUtilization(10 * time.Second)
  206. if err != nil {
  207. return fmt.Errorf("error while getting gpu utilization: %v", err)
  208. }
  209. stats.Accelerators = append(stats.Accelerators, info.AcceleratorStats{
  210. Make: "nvidia",
  211. Model: model,
  212. ID: uuid,
  213. MemoryTotal: memoryTotal,
  214. MemoryUsed: memoryUsed,
  215. DutyCycle: uint64(utilizationGPU),
  216. })
  217. }
  218. return nil
  219. }