master
go 208 lines 6.33 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nvidia_smi
4
5 import (
6 "encoding/xml"
7 "errors"
8 "fmt"
9 "strconv"
10 "strings"
11
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/oldmetrix"
13 )
14
15 func (c *Collector) collect() (map[string]int64, error) {
16 if c.exec == nil {
17 return nil, errors.New("nvidia-smi exec is not initialized")
18 }
19
20 mx := make(map[string]int64)
21
22 if err := c.collectGPUInfo(mx); err != nil {
23 return nil, err
24 }
25
26 return mx, nil
27 }
28
29 func (c *Collector) collectGPUInfo(mx map[string]int64) error {
30 bs, err := c.exec.queryGPUInfo()
31 if err != nil {
32 return fmt.Errorf("error on quering XML GPU info: %v", err)
33 }
34
35 info := &gpusInfo{}
36 if err := xml.Unmarshal(bs, info); err != nil {
37 return fmt.Errorf("error on unmarshaling XML GPU info response: %v", err)
38 }
39
40 seenGPU := make(map[string]bool)
41 seenMIG := make(map[string]bool)
42
43 for i, gpu := range info.GPUs {
44 if !isValidValue(gpu.UUID) {
45 continue
46 }
47
48 px := "gpu_" + gpu.UUID + "_"
49
50 seenGPU[px] = true
51
52 if !c.gpus[px] {
53 c.gpus[px] = true
54 c.addGpuCharts(gpu, i)
55 }
56
57 addMetric(mx, px+"pcie_bandwidth_usage_rx", gpu.PCI.RxUtil, 1024) // KB => bytes
58 addMetric(mx, px+"pcie_bandwidth_usage_tx", gpu.PCI.TxUtil, 1024) // KB => bytes
59 if maxBw := calcMaxPCIEBandwidth(gpu); maxBw > 0 {
60 rx := parseFloat(gpu.PCI.RxUtil) * 1024 // KB => bytes
61 tx := parseFloat(gpu.PCI.TxUtil) * 1024 // KB => bytes
62 mx[px+"pcie_bandwidth_utilization_rx"] = int64((rx * 100 / maxBw) * 100)
63 mx[px+"pcie_bandwidth_utilization_tx"] = int64((tx * 100 / maxBw) * 100)
64 }
65 addMetric(mx, px+"fan_speed_perc", gpu.FanSpeed, 0)
66 addMetric(mx, px+"gpu_utilization", gpu.Utilization.GpuUtil, 0)
67 addMetric(mx, px+"mem_utilization", gpu.Utilization.MemoryUtil, 0)
68 addMetric(mx, px+"decoder_utilization", gpu.Utilization.DecoderUtil, 0)
69 addMetric(mx, px+"encoder_utilization", gpu.Utilization.EncoderUtil, 0)
70 addMetric(mx, px+"frame_buffer_memory_usage_free", gpu.FBMemoryUsage.Free, 1024*1024) // MiB => bytes
71 addMetric(mx, px+"frame_buffer_memory_usage_used", gpu.FBMemoryUsage.Used, 1024*1024) // MiB => bytes
72 addMetric(mx, px+"frame_buffer_memory_usage_reserved", gpu.FBMemoryUsage.Reserved, 1024*1024) // MiB => bytes
73 addMetric(mx, px+"bar1_memory_usage_free", gpu.Bar1MemoryUsage.Free, 1024*1024) // MiB => bytes
74 addMetric(mx, px+"bar1_memory_usage_used", gpu.Bar1MemoryUsage.Used, 1024*1024) // MiB => bytes
75 addMetric(mx, px+"temperature", gpu.Temperature.GpuTemp, 0)
76 addMetric(mx, px+"graphics_clock", gpu.Clocks.GraphicsClock, 0)
77 addMetric(mx, px+"video_clock", gpu.Clocks.VideoClock, 0)
78 addMetric(mx, px+"sm_clock", gpu.Clocks.SmClock, 0)
79 addMetric(mx, px+"mem_clock", gpu.Clocks.MemClock, 0)
80 addGPUPowerMetricsSwitch(mx, px, gpu)
81 addMetric(mx, px+"voltage", gpu.Voltage.GraphicsVolt, 0)
82 for i := range 16 {
83 s := "P" + strconv.Itoa(i)
84 mx[px+"performance_state_"+s] = oldmetrix.Bool(gpu.PerformanceState == s)
85 }
86 if isValidValue(gpu.MIGMode.CurrentMIG) {
87 mode := strings.ToLower(gpu.MIGMode.CurrentMIG)
88 mx[px+"mig_current_mode_enabled"] = oldmetrix.Bool(mode == "enabled")
89 mx[px+"mig_current_mode_disabled"] = oldmetrix.Bool(mode == "disabled")
90 mx[px+"mig_devices_count"] = int64(len(gpu.MIGDevices.MIGDevice))
91 }
92
93 for _, mig := range gpu.MIGDevices.MIGDevice {
94 if !isValidValue(mig.GPUInstanceID) {
95 continue
96 }
97
98 px := "mig_instance_" + mig.GPUInstanceID + "_" + px
99
100 seenMIG[px] = true
101
102 if !c.migs[px] {
103 c.migs[px] = true
104 c.addMIGDeviceCharts(gpu, mig)
105 }
106
107 addMetric(mx, px+"ecc_error_sram_uncorrectable", mig.ECCErrorCount.VolatileCount.SRAMUncorrectable, 0)
108 addMetric(mx, px+"frame_buffer_memory_usage_free", mig.FBMemoryUsage.Free, 1024*1024) // MiB => bytes
109 addMetric(mx, px+"frame_buffer_memory_usage_used", mig.FBMemoryUsage.Used, 1024*1024) // MiB => bytes
110 addMetric(mx, px+"frame_buffer_memory_usage_reserved", mig.FBMemoryUsage.Reserved, 1024*1024) // MiB => bytes
111 addMetric(mx, px+"bar1_memory_usage_free", mig.BAR1MemoryUsage.Free, 1024*1024) // MiB => bytes
112 addMetric(mx, px+"bar1_memory_usage_used", mig.BAR1MemoryUsage.Used, 1024*1024) // MiB => bytes
113 }
114 }
115
116 for px := range c.gpus {
117 if !seenGPU[px] {
118 delete(c.gpus, px)
119 c.removeCharts(px)
120 }
121 }
122
123 for px := range c.migs {
124 if !seenMIG[px] {
125 delete(c.migs, px)
126 c.removeCharts(px)
127 }
128 }
129
130 return nil
131 }
132
133 func addGPUPowerMetricsSwitch(mx map[string]int64, px string, gpu gpuInfo) {
134 switch true {
135 case gpu.PowerReadings != nil && gpu.PowerReadings.PowerDraw != nil:
136 addMetric(mx, px+"power_draw", *gpu.PowerReadings.PowerDraw, 0)
137 case gpu.GPUPowerReadings != nil && gpu.GPUPowerReadings.PowerDraw != nil:
138 addMetric(mx, px+"power_draw", *gpu.GPUPowerReadings.PowerDraw, 0)
139 case gpu.GPUPowerReadings != nil && gpu.GPUPowerReadings.InstantPowerDraw != nil:
140 addMetric(mx, px+"power_draw", *gpu.GPUPowerReadings.InstantPowerDraw, 0)
141 case gpu.GPUPowerReadings != nil && gpu.GPUPowerReadings.AveragePowerDraw != nil:
142 addMetric(mx, px+"power_draw", *gpu.GPUPowerReadings.AveragePowerDraw, 0)
143 }
144 }
145
146 func calcMaxPCIEBandwidth(gpu gpuInfo) float64 {
147 gen := gpu.PCI.PCIGPULinkInfo.PCIEGen.MaxLinkGen
148 width := strings.TrimSuffix(gpu.PCI.PCIGPULinkInfo.LinkWidths.MaxLinkWidth, "x")
149
150 if !isValidValue(gen) || !isValidValue(width) {
151 return 0
152 }
153
154 // https://enterprise-support.nvidia.com/s/article/understanding-pcie-configuration-for-maximum-performance
155 var speed, enc float64
156 switch gen {
157 case "1":
158 speed, enc = 2.5, 1.0/5.0
159 case "2":
160 speed, enc = 5, 1.0/5.0
161 case "3":
162 speed, enc = 8, 2.0/130.0
163 case "4":
164 speed, enc = 16, 2.0/130.0
165 case "5":
166 speed, enc = 32, 2.0/130.0
167 default:
168 return 0
169 }
170
171 // Maximum PCIe Bandwidth = SPEED * WIDTH * (1 - ENCODING) - 1Gb/s
172 return (speed*parseFloat(width)*(1-enc) - 1) * 1e9 / 8 // Gb/s => bytes
173 }
174
175 func addMetric(mx map[string]int64, key, value string, mul int) {
176 if !isValidValue(value) {
177 return
178 }
179
180 value = removeUnits(value)
181
182 v, err := strconv.ParseFloat(value, 64)
183 if err != nil {
184 return
185 }
186
187 if mul > 0 {
188 v *= float64(mul)
189 }
190
191 mx[key] = int64(v)
192 }
193
194 func isValidValue(v string) bool {
195 return v != "" && v != "N/A" && v != "[N/A]"
196 }
197
198 func parseFloat(s string) float64 {
199 v, _ := strconv.ParseFloat(removeUnits(s), 64)
200 return v
201 }
202
203 func removeUnits(s string) string {
204 if i := strings.IndexByte(s, ' '); i != -1 {
205 s = s[:i]
206 }
207 return s
208 }