@cryptotaxi247 / netdata-1 / commits / 9764e7637

go.d/sensors refactor (#18581)

go.d/sensors refactor wip

Ilya Mashchenko committed Sep 21, 2024 at 20:47 UTC 9764e763720f3de0747dd553eb60970fd27c3e38
25 files changed +2115 -2282
src/go/plugin/go.d/modules/sensors/charts.go
+479 -179
@@ -11,207 +11,502 @@ import (
11 )
12
13 const (
14 - prioSensorTemperature = module.Priority + iota
15 - prioSensorVoltage
16 - prioSensorCurrent
17 - prioSensorPower
18 - prioSensorFan
19 - prioSensorEnergy
20 - prioSensorHumidity
21 - prioSensorIntrusion
14 + prioTemperatureSensorInput = module.Priority + iota
15 + prioTemperatureSensorAlarm
16 +
17 + prioVoltageSensorInput
18 + prioVoltageSensorAverage
19 + prioVoltageSensorAlarm
20 +
21 + prioFanSensorInput
22 + prioFanSensorAlarm
23 +
24 + prioCurrentSensorInput
25 + prioCurrentSensorAverage
26 + prioCurrentSensorAlarm
27 +
28 + prioPowerSensorInput
29 + prioPowerSensorAverage
30 + prioPowerSensorAlarm
31 +
32 + prioEnergySensorInput
33 +
34 + prioHumiditySensorInput
35 +
36 + prioIntrusionSensorAlarm
37 )
38
24 -var sensorTemperatureChartTmpl = module.Chart{
25 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_temperature",
26 - Title: "Sensor temperature",
27 - Units: "Celsius",
28 - Fam: "temperature",
29 - Ctx: "sensors.sensor_temperature",
30 - Type: module.Line,
31 - Priority: prioSensorTemperature,
32 - Dims: module.Dims{
33 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "temperature", Div: precision},
34 - },
35 -}
36 -
37 -var sensorVoltageChartTmpl = module.Chart{
38 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_voltage",
39 - Title: "Sensor voltage",
40 - Units: "Volts",
41 - Fam: "voltage",
42 - Ctx: "sensors.sensor_voltage",
43 - Type: module.Line,
44 - Priority: prioSensorVoltage,
45 - Dims: module.Dims{
46 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "voltage", Div: precision},
47 - },
48 -}
49 -
50 -var sensorCurrentChartTmpl = module.Chart{
51 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_current",
52 - Title: "Sensor current",
53 - Units: "Amperes",
54 - Fam: "current",
55 - Ctx: "sensors.sensor_current",
56 - Type: module.Line,
57 - Priority: prioSensorCurrent,
58 - Dims: module.Dims{
59 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "current", Div: precision},
60 - },
61 -}
62 -
63 -var sensorPowerChartTmpl = module.Chart{
64 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_power",
65 - Title: "Sensor power",
66 - Units: "Watts",
67 - Fam: "power",
68 - Ctx: "sensors.sensor_power",
69 - Type: module.Line,
70 - Priority: prioSensorPower,
71 - Dims: module.Dims{
72 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "power", Div: precision},
73 - },
74 -}
75 -
76 -var sensorFanChartTmpl = module.Chart{
77 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_fan",
78 - Title: "Sensor fan speed",
79 - Units: "RPM",
80 - Fam: "fan",
81 - Ctx: "sensors.sensor_fan_speed",
82 - Type: module.Line,
83 - Priority: prioSensorFan,
84 - Dims: module.Dims{
85 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "fan", Div: precision},
86 - },
87 -}
88 -
89 -var sensorEnergyChartTmpl = module.Chart{
90 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_energy",
91 - Title: "Sensor energy",
92 - Units: "Joules",
93 - Fam: "energy",
94 - Ctx: "sensors.sensor_energy",
95 - Type: module.Line,
96 - Priority: prioSensorEnergy,
97 - Dims: module.Dims{
98 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "energy", Div: precision},
99 - },
100 -}
101 -
102 -var sensorHumidityChartTmpl = module.Chart{
103 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_humidity",
104 - Title: "Sensor humidity",
105 - Units: "percent",
106 - Fam: "humidity",
107 - Ctx: "sensors.sensor_humidity",
108 - Type: module.Area,
109 - Priority: prioSensorHumidity,
110 - Dims: module.Dims{
111 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s", Name: "humidity", Div: precision},
112 - },
113 -}
114 -
115 -var sensorIntrusionChartTmpl = module.Chart{
116 - ID: "sensor_chip_%s_feature_%s_subfeature_%s_intrusion",
117 - Title: "Sensor intrusion",
118 - Units: "status",
119 - Fam: "intrusion",
120 - Ctx: "sensors.sensor_intrusion",
121 - Type: module.Line,
122 - Priority: prioSensorIntrusion,
123 - Dims: module.Dims{
124 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s_clear", Name: "alarm_clear"},
125 - {ID: "sensor_chip_%s_feature_%s_subfeature_%s_triggered", Name: "alarm_triggered"},
126 - },
127 -}
128 -
129 -func (s *Sensors) addExecSensorChart(sn execSensor) {
130 - var chart *module.Chart
131 -
132 - switch sn.sensorType() {
133 - case sensorTypeTemp:
134 - chart = sensorTemperatureChartTmpl.Copy()
135 - case sensorTypeVoltage:
136 - chart = sensorVoltageChartTmpl.Copy()
137 - case sensorTypePower:
138 - chart = sensorPowerChartTmpl.Copy()
139 - case sensorTypeHumidity:
140 - chart = sensorHumidityChartTmpl.Copy()
141 - case sensorTypeFan:
142 - chart = sensorFanChartTmpl.Copy()
143 - case sensorTypeCurrent:
144 - chart = sensorCurrentChartTmpl.Copy()
145 - case sensorTypeEnergy:
146 - chart = sensorEnergyChartTmpl.Copy()
147 - case sensorTypeIntrusion:
148 - chart = sensorIntrusionChartTmpl.Copy()
149 - default:
150 - return
39 +var temperatureSensorChartsTmpl = module.Charts{
40 + temperatureSensorInputChartTmpl.Copy(),
41 + temperatureSensorAlarmChartTmpl.Copy(),
42 +}
43 +
44 +var voltageSensorChartsTmpl = module.Charts{
45 + voltageSensorInputChartTmpl.Copy(),
46 + voltageSensorAverageChartTmpl.Copy(),
47 + voltageSensorAlarmChartTmpl.Copy(),
48 +}
49 +
50 +var fanSensorChartsTmpl = module.Charts{
51 + fanSensorInputChartTmpl.Copy(),
52 + fanSensorAlarmChartTmpl.Copy(),
53 +}
54 +
55 +var currentSensorChartsTmpl = module.Charts{
56 + currentSensorInputChartTmpl.Copy(),
57 + currentSensorAverageChartTmpl.Copy(),
58 + currentSensorAlarmChartTmpl.Copy(),
59 +}
60 +
61 +var powerSensorChartsTmpl = module.Charts{
62 + powerSensorInputChartTmpl.Copy(),
63 + powerSensorAverageChartTmpl.Copy(),
64 + powerSensorAlarmChartTmpl.Copy(),
65 +}
66 +
67 +var energySensorChartsTmpl = module.Charts{
68 + energySensorInputChartTmpl.Copy(),
69 +}
70 +
71 +var humiditySensorChartsTmpl = module.Charts{
72 + humiditySensorInputChartTmpl.Copy(),
73 +}
74 +
75 +var intrusionSensorChartsTmpl = module.Charts{
76 + intrusionSensorAlarmChartTmpl.Copy(),
77 +}
78 +
79 +var (
80 + temperatureSensorInputChartTmpl = module.Chart{
81 + ID: "%s_%s_temperature",
82 + Title: "Sensor Temperature",
83 + Units: "Celsius",
84 + Fam: "temperature",
85 + Ctx: "sensors.chip_sensor_temperature",
86 + Type: module.Line,
87 + Priority: prioTemperatureSensorInput,
88 + Dims: module.Dims{
89 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
90 + },
91 + }
92 + temperatureSensorAlarmChartTmpl = module.Chart{
93 + ID: "%s_%s_temperature_alarm",
94 + Title: "Temperature Sensor Alarm",
95 + Units: "status",
96 + Fam: "temperature",
97 + Ctx: "sensors.chip_sensor_temperature_alarm",
98 + Type: module.Line,
99 + Priority: prioTemperatureSensorAlarm,
100 + Dims: module.Dims{
101 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
102 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
103 + },
104 + }
105 +)
106 +
107 +var (
108 + voltageSensorInputChartTmpl = module.Chart{
109 + ID: "%s_%s_voltage",
110 + Title: "Sensor Voltage",
111 + Units: "Volts",
112 + Fam: "voltage",
113 + Ctx: "sensors.chip_sensor_voltage",
114 + Type: module.Line,
115 + Priority: prioVoltageSensorInput,
116 + Dims: module.Dims{
117 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
118 + },
119 + }
120 + voltageSensorAverageChartTmpl = module.Chart{
121 + ID: "%s_%s_voltage_average",
122 + Title: "Sensor Voltage Average",
123 + Units: "Volts",
124 + Fam: "voltage",
125 + Ctx: "sensors.chip_sensor_voltage_average",
126 + Type: module.Line,
127 + Priority: prioVoltageSensorAverage,
128 + Dims: module.Dims{
129 + {ID: "chip_%s_sensor_%s_average", Name: "average", Div: precision},
130 + },
131 }
132 + voltageSensorAlarmChartTmpl = module.Chart{
133 + ID: "%s_%s_voltage_alarm",
134 + Title: "Voltage Sensor Alarm",
135 + Units: "status",
136 + Fam: "voltage",
137 + Ctx: "sensors.chip_sensor_voltage_alarm",
138 + Type: module.Line,
139 + Priority: prioVoltageSensorAlarm,
140 + Dims: module.Dims{
141 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
142 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
143 + },
144 + }
145 +)
146
153 - chip, feat, subfeat := snakeCase(sn.chip), snakeCase(sn.feature), snakeCase(sn.subfeature)
147 +var (
148 + fanSensorInputChartTmpl = module.Chart{
149 + ID: "%s_%s_fan",
150 + Title: "Sensor Fan",
151 + Units: "RPM",
152 + Fam: "fan",
153 + Ctx: "sensors.chip_sensor_fan",
154 + Type: module.Line,
155 + Priority: prioFanSensorInput,
156 + Dims: module.Dims{
157 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
158 + },
159 + }
160 + fanSensorAlarmChartTmpl = module.Chart{
161 + ID: "%s_%s_fan_alarm",
162 + Title: "Fan Sensor Alarm",
163 + Units: "status",
164 + Fam: "fan",
165 + Ctx: "sensors.chip_sensor_fan_alarm",
166 + Type: module.Line,
167 + Priority: prioFanSensorAlarm,
168 + Dims: module.Dims{
169 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
170 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
171 + },
172 + }
173 +)
174
155 - chart.ID = fmt.Sprintf(chart.ID, chip, feat, subfeat)
156 - chart.Labels = []module.Label{
157 - {Key: "chip", Value: sn.chip},
158 - {Key: "feature", Value: sn.feature},
175 +var (
176 + currentSensorInputChartTmpl = module.Chart{
177 + ID: "%s_%s_current",
178 + Title: "Sensor Current",
179 + Units: "Amperes",
180 + Fam: "current",
181 + Ctx: "sensors.chip_sensor_current",
182 + Type: module.Line,
183 + Priority: prioCurrentSensorInput,
184 + Dims: module.Dims{
185 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
186 + },
187 + }
188 + currentSensorAverageChartTmpl = module.Chart{
189 + ID: "%s_%s_current_average",
190 + Title: "Sensor Current Average",
191 + Units: "Amperes",
192 + Fam: "current",
193 + Ctx: "sensors.chip_sensor_current_average",
194 + Type: module.Line,
195 + Priority: prioCurrentSensorAverage,
196 + Dims: module.Dims{
197 + {ID: "chip_%s_sensor_%s_average", Name: "average", Div: precision},
198 + },
199 }
160 - for _, dim := range chart.Dims {
161 - dim.ID = fmt.Sprintf(dim.ID, chip, feat, subfeat)
200 + currentSensorAlarmChartTmpl = module.Chart{
201 + ID: "%s_%s_current_alarm",
202 + Title: "Sensor Alarm",
203 + Units: "status",
204 + Fam: "current",
205 + Ctx: "sensors.chip_sensor_current_alarm",
206 + Type: module.Line,
207 + Priority: prioCurrentSensorAlarm,
208 + Dims: module.Dims{
209 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
210 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
211 + },
212 }
213 +)
214
164 - if err := s.Charts().Add(chart); err != nil {
165 - s.Warning(err)
215 +var (
216 + powerSensorInputChartTmpl = module.Chart{
217 + ID: "%s_%s_power",
218 + Title: "Sensor Power",
219 + Units: "Watts",
220 + Fam: "power",
221 + Ctx: "sensors.chip_sensor_power",
222 + Type: module.Line,
223 + Priority: prioPowerSensorInput,
224 + Dims: module.Dims{
225 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
226 + },
227 + }
228 + powerSensorAverageChartTmpl = module.Chart{
229 + ID: "%s_%s_power_average",
230 + Title: "Sensor Power Average",
231 + Units: "Watts",
232 + Fam: "power",
233 + Ctx: "sensors.chip_sensor_power_average",
234 + Type: module.Line,
235 + Priority: prioPowerSensorAverage,
236 + Dims: module.Dims{
237 + {ID: "chip_%s_sensor_%s_average", Name: "average", Div: precision},
238 + },
239 + }
240 + powerSensorAlarmChartTmpl = module.Chart{
241 + ID: "%s_%s_power_alarm",
242 + Title: "Power Sensor Alarm",
243 + Units: "status",
244 + Fam: "current",
245 + Ctx: "sensors.chip_sensor_power_alarm",
246 + Type: module.Line,
247 + Priority: prioPowerSensorAlarm,
248 + Dims: module.Dims{
249 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
250 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
251 + },
252 + }
253 +)
254 +
255 +var (
256 + energySensorInputChartTmpl = module.Chart{
257 + ID: "%s_%s_energy",
258 + Title: "Sensor Energy",
259 + Units: "Joules",
260 + Fam: "energy",
261 + Ctx: "sensors.chip_sensor_energy",
262 + Type: module.Line,
263 + Priority: prioEnergySensorInput,
264 + Dims: module.Dims{
265 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
266 + },
267 + }
268 +)
269 +
270 +var (
271 + humiditySensorInputChartTmpl = module.Chart{
272 + ID: "%s_%s_humidity",
273 + Title: "Sensor Humidity",
274 + Units: "percent",
275 + Fam: "humidity",
276 + Ctx: "sensors.chip_sensor_humidity",
277 + Type: module.Line,
278 + Priority: prioHumiditySensorInput,
279 + Dims: module.Dims{
280 + {ID: "chip_%s_sensor_%s_input", Name: "input", Div: precision},
281 + },
282 + }
283 +)
284 +
285 +var (
286 + intrusionSensorAlarmChartTmpl = module.Chart{
287 + ID: "%s_%s_intrusion_alarm",
288 + Title: "Sensor Intrusion Alarm",
289 + Units: "status",
290 + Fam: "intrusion",
291 + Ctx: "sensors.chip_sensor_intrusion_alarm",
292 + Type: module.Line,
293 + Priority: prioIntrusionSensorAlarm,
294 + Dims: module.Dims{
295 + {ID: "chip_%s_sensor_%s_alarm_clear", Name: "clear"},
296 + {ID: "chip_%s_sensor_%s_alarm_triggered", Name: "triggered"},
297 + },
298 + }
299 +)
300 +
301 +func (s *Sensors) updateCharts(chips []*lmsensors.Chip) {
302 + seen := make(map[string]bool)
303 +
304 + for _, chip := range chips {
305 + for _, sn := range chip.Sensors.Voltage {
306 + key := chip.UniqueName + "_" + sn.Name
307 + seen[key] = true
308 + if !s.seenSensors[key] {
309 + s.seenSensors[key] = true
310 + s.addVoltageCharts(chip, sn)
311 + }
312 + }
313 + for _, sn := range chip.Sensors.Fan {
314 + key := chip.UniqueName + "_" + sn.Name
315 + seen[key] = true
316 + if !s.seenSensors[key] {
317 + s.seenSensors[key] = true
318 + s.addFanCharts(chip, sn)
319 + }
320 + }
321 + for _, sn := range chip.Sensors.Temperature {
322 + key := chip.UniqueName + "_" + sn.Name
323 + seen[key] = true
324 + if !s.seenSensors[key] {
325 + s.seenSensors[key] = true
326 + s.addTemperatureCharts(chip, sn)
327 + }
328 + }
329 + for _, sn := range chip.Sensors.Current {
330 + key := chip.UniqueName + "_" + sn.Name
331 + seen[key] = true
332 + if !s.seenSensors[key] {
333 + s.seenSensors[key] = true
334 + s.addCurrentCharts(chip, sn)
335 + }
336 + }
337 + for _, sn := range chip.Sensors.Power {
338 + key := chip.UniqueName + "_" + sn.Name
339 + seen[key] = true
340 + if !s.seenSensors[key] {
341 + s.seenSensors[key] = true
342 + s.addPowerCharts(chip, sn)
343 + }
344 + }
345 + for _, sn := range chip.Sensors.Energy {
346 + key := chip.UniqueName + "_" + sn.Name
347 + seen[key] = true
348 + if !s.seenSensors[key] {
349 + s.seenSensors[key] = true
350 + s.addEnergyCharts(chip, sn)
351 + }
352 + }
353 + for _, sn := range chip.Sensors.Humidity {
354 + key := chip.UniqueName + "_" + sn.Name
355 + seen[key] = true
356 + if !s.seenSensors[key] {
357 + s.seenSensors[key] = true
358 + s.addHumidityCharts(chip, sn)
359 + }
360 + }
361 + for _, sn := range chip.Sensors.Intrusion {
362 + key := chip.UniqueName + "_" + sn.Name
363 + seen[key] = true
364 + if !s.seenSensors[key] {
365 + s.seenSensors[key] = true
366 + s.addIntrusionCharts(chip, sn)
367 + }
368 + }
369 + }
370 +
371 + for key := range s.seenSensors {
372 + if !seen[key] {
373 + delete(s.seenSensors, key)
374 + s.removeSensorChart(key)
375 + }
376 }
377 }
378
169 -func (s *Sensors) addSysfsSensorChart(devName string, sn lmsensors.Sensor) {
170 - var chart *module.Chart
171 - var feat, subfeat string
172 - devName = snakeCase(devName)
173 -
174 - switch v := sn.(type) {
175 - case *lmsensors.TemperatureSensor:
176 - chart = sensorTemperatureChartTmpl.Copy()
177 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_input"
178 - case *lmsensors.VoltageSensor:
179 - chart = sensorVoltageChartTmpl.Copy()
180 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_input"
181 - case *lmsensors.CurrentSensor:
182 - chart = sensorCurrentChartTmpl.Copy()
183 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_input"
184 - case *lmsensors.PowerSensor:
185 - chart = sensorPowerChartTmpl.Copy()
186 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_average"
187 - case *lmsensors.FanSensor:
188 - chart = sensorFanChartTmpl.Copy()
189 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_input"
190 - case *lmsensors.IntrusionSensor:
191 - chart = sensorIntrusionChartTmpl.Copy()
192 - feat, subfeat = firstNotEmpty(v.Label, v.Name), v.Name+"_alarm"
193 - default:
194 - return
379 +func (s *Sensors) addTemperatureCharts(chip *lmsensors.Chip, sn *lmsensors.TemperatureSensor) {
380 + charts := temperatureSensorChartsTmpl.Copy()
381 +
382 + if sn.Input == nil {
383 + _ = charts.Remove(temperatureSensorInputChartTmpl.ID)
384 + }
385 + if sn.Alarm == nil {
386 + _ = charts.Remove(temperatureSensorAlarmChartTmpl.ID)
387 + }
388 +
389 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
390 +}
391 +
392 +func (s *Sensors) addVoltageCharts(chip *lmsensors.Chip, sn *lmsensors.VoltageSensor) {
393 + charts := voltageSensorChartsTmpl.Copy()
394 +
395 + if sn.Input == nil {
396 + _ = charts.Remove(voltageSensorInputChartTmpl.ID)
397 + }
398 + if sn.Average == nil {
399 + _ = charts.Remove(voltageSensorAverageChartTmpl.ID)
400 + }
401 + if sn.Alarm == nil {
402 + _ = charts.Remove(voltageSensorAlarmChartTmpl.ID)
403 + }
404 +
405 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
406 +}
407 +
408 +func (s *Sensors) addFanCharts(chip *lmsensors.Chip, sn *lmsensors.FanSensor) {
409 + charts := fanSensorChartsTmpl.Copy()
410 +
411 + if sn.Input == nil {
412 + _ = charts.Remove(fanSensorInputChartTmpl.ID)
413 + }
414 + if sn.Alarm == nil {
415 + _ = charts.Remove(fanSensorAlarmChartTmpl.ID)
416 + }
417 +
418 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
419 +}
420 +
421 +func (s *Sensors) addCurrentCharts(chip *lmsensors.Chip, sn *lmsensors.CurrentSensor) {
422 + charts := currentSensorChartsTmpl.Copy()
423 +
424 + if sn.Input == nil {
425 + _ = charts.Remove(currentSensorInputChartTmpl.ID)
426 + }
427 + if sn.Average == nil {
428 + _ = charts.Remove(currentSensorAverageChartTmpl.ID)
429 + }
430 + if sn.Alarm == nil {
431 + _ = charts.Remove(currentSensorAlarmChartTmpl.ID)
432 + }
433 +
434 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
435 +}
436 +
437 +func (s *Sensors) addPowerCharts(chip *lmsensors.Chip, sn *lmsensors.PowerSensor) {
438 + charts := powerSensorChartsTmpl.Copy()
439 +
440 + if sn.Input == nil {
441 + _ = charts.Remove(powerSensorInputChartTmpl.ID)
442 }
443 + if sn.Average == nil {
444 + _ = charts.Remove(powerSensorAverageChartTmpl.ID)
445 + }
446 + if sn.Alarm == nil {
447 + _ = charts.Remove(powerSensorAlarmChartTmpl.ID)
448 + }
449 +
450 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
451 +}
452
197 - origFeat := feat
198 - feat, subfeat = snakeCase(feat), snakeCase(subfeat)
453 +func (s *Sensors) addEnergyCharts(chip *lmsensors.Chip, sn *lmsensors.EnergySensor) {
454 + charts := energySensorChartsTmpl.Copy()
455
200 - chart.ID = fmt.Sprintf(chart.ID, devName, feat, subfeat)
201 - chart.Labels = []module.Label{
202 - {Key: "chip", Value: devName},
203 - {Key: "feature", Value: origFeat},
456 + if sn.Input == nil {
457 + _ = charts.Remove(energySensorInputChartTmpl.ID)
458 }
205 - for _, dim := range chart.Dims {
206 - dim.ID = fmt.Sprintf(dim.ID, devName, feat, subfeat)
459 +
460 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
461 +}
462 +
463 +func (s *Sensors) addHumidityCharts(chip *lmsensors.Chip, sn *lmsensors.HumiditySensor) {
464 + charts := humiditySensorChartsTmpl.Copy()
465 +
466 + if sn.Input == nil {
467 + _ = charts.Remove(humiditySensorInputChartTmpl.ID)
468 }
469
209 - if err := s.Charts().Add(chart); err != nil {
470 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
471 +}
472 +
473 +func (s *Sensors) addIntrusionCharts(chip *lmsensors.Chip, sn *lmsensors.IntrusionSensor) {
474 + charts := intrusionSensorChartsTmpl.Copy()
475 +
476 + if sn.Alarm == nil {
477 + _ = charts.Remove(intrusionSensorAlarmChartTmpl.ID)
478 + }
479 +
480 + s.addCharts(charts, chip.UniqueName, chip.SysDevice, sn.Name, sn.Label)
481 +}
482 +
483 +func (s *Sensors) addCharts(charts *module.Charts, chipUniqueName, chipSysDevice, snName, snLabel string) {
484 + if len(*charts) == 0 {
485 + return
486 + }
487 +
488 + for _, chart := range *charts {
489 + chart.ID = fmt.Sprintf(chart.ID, chipUniqueName, snName)
490 + chart.ID = cleanChartId(chart.ID)
491 + chart.Labels = []module.Label{
492 + {Key: "chip", Value: chipSysDevice},
493 + {Key: "chip_id", Value: chipUniqueName},
494 + {Key: "sensor", Value: snName},
495 + {Key: "label", Value: snLabel},
496 + }
497 + for _, dim := range chart.Dims {
498 + dim.ID = fmt.Sprintf(dim.ID, chipUniqueName, snName)
499 + }
500 + }
501 +
502 + if err := s.Charts().Add(*charts...); err != nil {
503 s.Warning(err)
504 }
505 }
506
507 func (s *Sensors) removeSensorChart(px string) {
508 + px = cleanChartId(px)
509 +
510 for _, chart := range *s.Charts() {
511 if strings.HasPrefix(chart.ID, px) {
512 chart.MarkRemove()
@@ -220,3 +515,8 @@ func (s *Sensors) removeSensorChart(px string) {
515 }
516 }
517 }
518 +
519 +func cleanChartId(id string) string {
520 + r := strings.NewReplacer(" ", "_", ".", "_")
521 + return strings.ToLower(r.Replace(id))
522 +}
src/go/plugin/go.d/modules/sensors/collect.go
+175 -3
@@ -2,11 +2,183 @@
2
3 package sensors
4
5 +import (
6 + "errors"
7 + "fmt"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/sensors/lmsensors"
10 +)
11 +
12 const precision = 1000
13
14 +func ptr[T any](v T) *T { return &v }
15 +
16 func (s *Sensors) collect() (map[string]int64, error) {
8 - if s.exec != nil {
9 - return s.collectExec()
17 + if s.sc == nil {
18 + return nil, errors.New("sysfs scanner is not initialized")
19 + }
20 +
21 + chips, err := s.sc.Scan()
22 + if err != nil {
23 + return nil, err
24 + }
25 +
26 + if len(chips) == 0 {
27 + return nil, errors.New("no chips found on the system")
28 + }
29 +
30 + mx := make(map[string]int64)
31 +
32 + for _, chip := range chips {
33 + for _, sn := range chip.Sensors.Voltage {
34 + writeVoltage(mx, chip, sn)
35 + }
36 + for _, sn := range chip.Sensors.Fan {
37 + writeFan(mx, chip, sn)
38 + }
39 + for _, sn := range chip.Sensors.Temperature {
40 + writeTemperature(mx, chip, sn)
41 + }
42 + for _, sn := range chip.Sensors.Current {
43 + writeCurrent(mx, chip, sn)
44 + }
45 + for _, sn := range chip.Sensors.Power {
46 + writePower(mx, chip, sn)
47 + }
48 + for _, sn := range chip.Sensors.Energy {
49 + writeEnergy(mx, chip, sn)
50 + }
51 + for _, sn := range chip.Sensors.Humidity {
52 + writeHumidity(mx, chip, sn)
53 + }
54 + for _, sn := range chip.Sensors.Intrusion {
55 + writeIntrusion(mx, chip, sn)
56 + }
57 + }
58 +
59 + s.updateCharts(chips)
60 +
61 + return mx, nil
62 +}
63 +
64 +func writeVoltage(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.VoltageSensor) {
65 + px := sensorPrefix(chip.UniqueName, sn.Name)
66 +
67 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
68 + writeMetricAlarm(mx, px, sn.Alarm)
69 + writeMetric(mx, px+"input", sn.Input)
70 + writeMetric(mx, px+"average", sn.Average)
71 + writeMetric(mx, px+"min", sn.Min)
72 + writeMetric(mx, px+"max", sn.Max)
73 + writeMetric(mx, px+"lcrit", sn.CritMin)
74 + writeMetric(mx, px+"crit", sn.CritMax)
75 + writeMetric(mx, px+"lowest", sn.Lowest)
76 + writeMetric(mx, px+"highest", sn.Highest)
77 +}
78 +
79 +func writeFan(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.FanSensor) {
80 + px := sensorPrefix(chip.UniqueName, sn.Name)
81 +
82 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
83 + writeMetricAlarm(mx, px, sn.Alarm)
84 + writeMetric(mx, px+"input", sn.Input)
85 + writeMetric(mx, px+"min", sn.Min)
86 + writeMetric(mx, px+"max", sn.Max)
87 + writeMetric(mx, px+"target", sn.Target)
88 +}
89 +
90 +func writeTemperature(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.TemperatureSensor) {
91 + px := sensorPrefix(chip.UniqueName, sn.Name)
92 +
93 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
94 + writeMetricAlarm(mx, px, sn.Alarm)
95 + writeMetric(mx, px+"input", sn.Input)
96 + writeMetric(mx, px+"min", sn.Min)
97 + writeMetric(mx, px+"max", sn.Max)
98 + writeMetric(mx, px+"lcrit", sn.CritMin)
99 + writeMetric(mx, px+"crit", sn.CritMax)
100 + writeMetric(mx, px+"emergency", sn.Emergency)
101 + writeMetric(mx, px+"lowest", sn.Lowest)
102 + writeMetric(mx, px+"highest", sn.Highest)
103 +}
104 +
105 +func writeCurrent(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.CurrentSensor) {
106 + px := sensorPrefix(chip.UniqueName, sn.Name)
107 +
108 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
109 + writeMetricAlarm(mx, px, sn.Alarm)
110 + writeMetric(mx, px+"max", sn.Max)
111 + writeMetric(mx, px+"min", sn.Min)
112 + writeMetric(mx, px+"lcrit", sn.CritMin)
113 + writeMetric(mx, px+"crit", sn.CritMax)
114 + writeMetric(mx, px+"input", sn.Input)
115 + writeMetric(mx, px+"average", sn.Average)
116 + writeMetric(mx, px+"lowest", sn.Lowest)
117 + writeMetric(mx, px+"highest", sn.Highest)
118 +}
119 +
120 +func writePower(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.PowerSensor) {
121 + px := sensorPrefix(chip.UniqueName, sn.Name)
122 +
123 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
124 + writeMetricAlarm(mx, px, sn.Alarm)
125 + writeMetric(mx, px+"average", sn.Average)
126 + writeMetric(mx, px+"average_highest", sn.AverageHighest)
127 + writeMetric(mx, px+"average_lowest", sn.AverageLowest)
128 + writeMetric(mx, px+"average_max", sn.AverageMax)
129 + writeMetric(mx, px+"average_min", sn.AverageMin)
130 + writeMetric(mx, px+"input", sn.Input)
131 + writeMetric(mx, px+"input_highest", sn.InputHighest)
132 + writeMetric(mx, px+"input_lowest", sn.InputLowest)
133 + writeMetric(mx, px+"accuracy", sn.Accuracy)
134 + writeMetric(mx, px+"cap", sn.Cap)
135 + writeMetric(mx, px+"cap_max", sn.CapMax)
136 + writeMetric(mx, px+"cap_min", sn.CapMin)
137 + writeMetric(mx, px+"max", sn.Max)
138 + writeMetric(mx, px+"crit", sn.CritMax)
139 +}
140 +
141 +func writeEnergy(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.EnergySensor) {
142 + px := sensorPrefix(chip.UniqueName, sn.Name)
143 +
144 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
145 + writeMetric(mx, px+"input", sn.Input)
146 +}
147 +
148 +func writeHumidity(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.HumiditySensor) {
149 + px := sensorPrefix(chip.UniqueName, sn.Name)
150 +
151 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
152 + writeMetric(mx, px+"input", sn.Input)
153 +}
154 +
155 +func writeIntrusion(mx map[string]int64, chip *lmsensors.Chip, sn *lmsensors.IntrusionSensor) {
156 + px := sensorPrefix(chip.UniqueName, sn.Name)
157 +
158 + mx[px+"read_time"] = sn.ReadTime.Milliseconds()
159 + writeMetricAlarm(mx, px, sn.Alarm)
160 +}
161 +
162 +func writeMetric(mx map[string]int64, key string, value *float64) {
163 + if value != nil {
164 + mx[key] = int64(*value * precision)
165 + }
166 +}
167 +
168 +func writeMetricAlarm(mx map[string]int64, px string, value *bool) {
169 + if value != nil {
170 + mx[px+"alarm_clear"] = boolToInt(!*value)
171 + mx[px+"alarm_triggered"] = boolToInt(*value)
172 + }
173 +}
174 +
175 +func sensorPrefix(chip, sensor string) string {
176 + return fmt.Sprintf("chip_%s_sensor_%s_", chip, sensor)
177 +}
178 +
179 +func boolToInt(b bool) int64 {
180 + if b {
181 + return 1
182 }
11 - return s.collectSysfs()
183 + return 0
184 }
src/go/plugin/go.d/modules/sensors/collect_exec.go deleted
-200
@@ -1,200 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package sensors
4 -
5 -import (
6 - "bufio"
7 - "bytes"
8 - "errors"
9 - "fmt"
10 - "strconv"
11 - "strings"
12 -)
13 -
14 -const (
15 - sensorTypeTemp = "temperature"
16 - sensorTypeVoltage = "voltage"
17 - sensorTypePower = "power"
18 - sensorTypeHumidity = "humidity"
19 - sensorTypeFan = "fan"
20 - sensorTypeCurrent = "current"
21 - sensorTypeEnergy = "energy"
22 - sensorTypeIntrusion = "intrusion"
23 -)
24 -
25 -type execSensor struct {
26 - chip string
27 - feature string
28 - subfeature string
29 - value string
30 -}
31 -
32 -func (s *execSensor) String() string {
33 - return fmt.Sprintf("chip:%s feat:%s subfeat:%s value:%s", s.chip, s.feature, s.subfeature, s.value)
34 -}
35 -
36 -func (s *execSensor) sensorType() string {
37 - switch {
38 - case strings.HasPrefix(s.subfeature, "temp"):
39 - return sensorTypeTemp
40 - case strings.HasPrefix(s.subfeature, "intrusion"):
41 - return sensorTypeIntrusion
42 - case strings.HasPrefix(s.subfeature, "in"):
43 - return sensorTypeVoltage
44 - case strings.HasPrefix(s.subfeature, "power"):
45 - return sensorTypePower
46 - case strings.HasPrefix(s.subfeature, "humidity"):
47 - return sensorTypeHumidity
48 - case strings.HasPrefix(s.subfeature, "fan"):
49 - return sensorTypeFan
50 - case strings.HasPrefix(s.subfeature, "curr"):
51 - return sensorTypeCurrent
52 - case strings.HasPrefix(s.subfeature, "energy"):
53 - return sensorTypeEnergy
54 - default:
55 - return ""
56 - }
57 -}
58 -
59 -func (s *execSensor) limits() (minVal float64, maxVal float64, ok bool) {
60 - switch s.sensorType() {
61 - case sensorTypeTemp:
62 - return -127, 1000, true
63 - case sensorTypeVoltage:
64 - return -400, 400, true
65 - case sensorTypeCurrent:
66 - return -127, 127, true
67 - case sensorTypeFan:
68 - return 0, 65535, true
69 - default:
70 - return 0, 0, false
71 - }
72 -}
73 -
74 -func (s *Sensors) collectExec() (map[string]int64, error) {
75 - if s.exec == nil {
76 - return nil, errors.New("exec sensor is not initialized")
77 - }
78 -
79 - s.Debugf("using sensors binary to collect metrics")
80 -
81 - bs, err := s.exec.sensorsInfo()
82 - if err != nil {
83 - return nil, err
84 - }
85 -
86 - if len(bs) == 0 {
87 - return nil, errors.New("empty response from sensors")
88 - }
89 -
90 - sensors, err := parseExecSensors(bs)
91 - if err != nil {
92 - return nil, err
93 - }
94 - if len(sensors) == 0 {
95 - return nil, errors.New("no sensors found")
96 - }
97 -
98 - mx := make(map[string]int64)
99 - seen := make(map[string]bool)
100 -
101 - for _, sn := range sensors {
102 - var sx string
103 -
104 - switch sn.sensorType() {
105 - case "":
106 - s.Debugf("can not find type for sensor '%s'", sn)
107 - continue
108 - case sensorTypePower:
109 - sx = "_average"
110 - case sensorTypeIntrusion:
111 - sx = "_alarm"
112 - default:
113 - sx = "_input"
114 - }
115 -
116 - if !strings.HasSuffix(sn.subfeature, sx) {
117 - s.Debugf("skipping sensor: '%s'", sn)
118 - continue
119 - }
120 -
121 - v, err := strconv.ParseFloat(sn.value, 64)
122 - if err != nil {
123 - s.Debugf("parsing value for sensor '%s': %v", sn, err)
124 - continue
125 - }
126 -
127 - if minVal, maxVal, ok := sn.limits(); ok && (v < minVal || v > maxVal) {
128 - s.Debugf("value outside limits [%d/%d] for sensor '%s'", int64(minVal), int64(maxVal), sn)
129 - continue
130 - }
131 -
132 - key := fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s", sn.chip, sn.feature, sn.subfeature)
133 - key = snakeCase(key)
134 -
135 - if !s.sensors[key] {
136 - s.sensors[key] = true
137 - s.addExecSensorChart(sn)
138 - }
139 -
140 - seen[key] = true
141 -
142 - if sn.sensorType() == sensorTypeIntrusion {
143 - mx[key+"_triggered"] = boolToInt(v != 0)
144 - mx[key+"_clear"] = boolToInt(v == 0)
145 - } else {
146 - mx[key] = int64(v * precision)
147 - }
148 - }
149 -
150 - for k := range s.sensors {
151 - if !seen[k] {
152 - delete(s.sensors, k)
153 - s.removeSensorChart(k)
154 - }
155 - }
156 -
157 - return mx, nil
158 -}
159 -
160 -func snakeCase(n string) string {
161 - return strings.ToLower(strings.ReplaceAll(n, " ", "_"))
162 -}
163 -
164 -func parseExecSensors(output []byte) ([]execSensor, error) {
165 - var sensors []execSensor
166 -
167 - sc := bufio.NewScanner(bytes.NewReader(output))
168 -
169 - var chip, feat string
170 -
171 - for sc.Scan() {
172 - text := sc.Text()
173 - if text == "" {
174 - chip, feat = "", ""
175 - continue
176 - }
177 -
178 - switch {
179 - case strings.HasPrefix(text, " ") && chip != "" && feat != "":
180 - parts := strings.Split(text, ":")
181 - if len(parts) != 2 {
182 - continue
183 - }
184 - subfeat, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
185 - sensors = append(sensors, execSensor{
186 - chip: chip,
187 - feature: feat,
188 - subfeature: subfeat,
189 - value: value,
190 - })
191 - case strings.HasSuffix(text, ":") && chip != "":
192 - feat = strings.TrimSpace(strings.TrimSuffix(text, ":"))
193 - default:
194 - chip = text
195 - feat = ""
196 - }
197 - }
198 -
199 - return sensors, nil
200 -}
src/go/plugin/go.d/modules/sensors/collect_sysfs.go deleted
-97
@@ -1,97 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package sensors
4 -
5 -import (
6 - "errors"
7 - "fmt"
8 -
9 - "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/sensors/lmsensors"
10 -)
11 -
12 -func (s *Sensors) collectSysfs() (map[string]int64, error) {
13 - if s.sc == nil {
14 - return nil, errors.New("sysfs scanner is not initialized")
15 - }
16 -
17 - s.Debugf("using sysfs scan to collect metrics")
18 -
19 - devices, err := s.sc.Scan()
20 - if err != nil {
21 - return nil, err
22 - }
23 -
24 - if len(devices) == 0 {
25 - return nil, errors.New("sysfs scanner: devices found")
26 - }
27 -
28 - seen := make(map[string]bool)
29 - mx := make(map[string]int64)
30 -
31 - for _, dev := range devices {
32 - for _, sn := range dev.Sensors {
33 - var key string
34 -
35 - switch v := sn.(type) {
36 - case *lmsensors.TemperatureSensor:
37 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_input", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
38 - mx[key] = int64(v.Input * precision)
39 - case *lmsensors.VoltageSensor:
40 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_input", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
41 - mx[key] = int64(v.Input * precision)
42 - case *lmsensors.CurrentSensor:
43 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_input", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
44 - mx[key] = int64(v.Input * precision)
45 - case *lmsensors.PowerSensor:
46 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_average", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
47 - mx[key] = int64(v.Average * precision)
48 - case *lmsensors.FanSensor:
49 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_input", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
50 - mx[key] = int64(v.Input * precision)
51 - case *lmsensors.IntrusionSensor:
52 - key = snakeCase(fmt.Sprintf("sensor_chip_%s_feature_%s_subfeature_%s_alarm", dev.Name, firstNotEmpty(v.Label, v.Name), v.Name))
53 - mx[key+"_triggered"] = boolToInt(v.Alarm)
54 - mx[key+"_clear"] = boolToInt(!v.Alarm)
55 - default:
56 - s.Debugf("unexpected sensor type: %T", v)
57 - continue
58 - }
59 -
60 - seen[key] = true
61 -
62 - if !s.sensors[key] {
63 - s.sensors[key] = true
64 - s.addSysfsSensorChart(dev.Name, sn)
65 - }
66 - }
67 - }
68 -
69 - if len(mx) == 0 {
70 - return nil, errors.New("sysfs scanner: no metrics collected")
71 - }
72 -
73 - for k := range s.sensors {
74 - if !seen[k] {
75 - delete(s.sensors, k)
76 - s.removeSensorChart(k)
77 - }
78 - }
79 -
80 - return mx, nil
81 -}
82 -
83 -func firstNotEmpty(s ...string) string {
84 - for _, v := range s {
85 - if v != "" {
86 - return v
87 - }
88 - }
89 - return ""
90 -}
91 -
92 -func boolToInt(b bool) int64 {
93 - if b {
94 - return 1
95 - }
96 - return 0
97 -}
src/go/plugin/go.d/modules/sensors/config_schema.json
-19
@@ -10,19 +10,6 @@
10 "type": "integer",
11 "minimum": 1,
12 "default": 10
13 - },
14 - "binary_path": {
15 - "title": "Binary path",
16 - "description": "Path to the `sensors` binary. If left empty or if the binary is not found, [**sysfs**](https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface) will be used to collect sensor statistics.",
17 - "type": "string",
18 - "default": "/usr/bin/sensors"
19 - },
20 - "timeout": {
21 - "title": "Timeout",
22 - "description": "Timeout for executing the binary, specified in seconds.",
23 - "type": "number",
24 - "minimum": 0.5,
25 - "default": 2
13 }
14 },
15 "required": [
@@ -35,12 +22,6 @@
22 "uiSchema": {
23 "uiOptions": {
24 "fullPage": true
38 - },
39 - "binary_path": {
40 - "ui:help": "If an absolute path is provided, the collector will use it directly; otherwise, it will search for the binary in directories specified in the PATH environment variable."
41 - },
42 - "timeout": {
43 - "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
25 }
26 }
27 }
src/go/plugin/go.d/modules/sensors/exec.go deleted
-41
@@ -1,41 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package sensors
4 -
5 -import (
6 - "context"
7 - "fmt"
8 - "os/exec"
9 - "time"
10 -
11 - "github.com/netdata/netdata/go/plugins/logger"
12 -)
13 -
14 -func newSensorsCliExec(binPath string, timeout time.Duration) *sensorsCliExec {
15 - return &sensorsCliExec{
16 - binPath: binPath,
17 - timeout: timeout,
18 - }
19 -}
20 -
21 -type sensorsCliExec struct {
22 - *logger.Logger
23 -
24 - binPath string
25 - timeout time.Duration
26 -}
27 -
28 -func (e *sensorsCliExec) sensorsInfo() ([]byte, error) {
29 - ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
30 - defer cancel()
31 -
32 - cmd := exec.CommandContext(ctx, e.binPath, "-A", "-u")
33 - e.Debugf("executing '%s'", cmd)
34 -
35 - bs, err := cmd.Output()
36 - if err != nil {
37 - return nil, fmt.Errorf("error on '%s': %v", cmd, err)
38 - }
39 -
40 - return bs, nil
41 -}
src/go/plugin/go.d/modules/sensors/init.go deleted
-34
@@ -1,34 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package sensors
4 -
5 -import (
6 - "os"
7 - "os/exec"
8 - "strings"
9 -)
10 -
11 -func (s *Sensors) initSensorsBinary() (sensorsBinary, error) {
12 - if s.BinaryPath == "" {
13 - return nil, nil
14 - }
15 -
16 - binPath := s.BinaryPath
17 -
18 - if !strings.HasPrefix(binPath, "/") {
19 - path, err := exec.LookPath(binPath)
20 - if err != nil {
21 - return nil, err
22 - }
23 - binPath = path
24 - }
25 -
26 - if _, err := os.Stat(binPath); err != nil {
27 - return nil, err
28 - }
29 -
30 - sensorsExec := newSensorsCliExec(binPath, s.Timeout.Duration())
31 - sensorsExec.Logger = s.Logger
32 -
33 - return sensorsExec, nil
34 -}
src/go/plugin/go.d/modules/sensors/lmsensors/currentsensor.go deleted
-62
@@ -1,62 +0,0 @@
1 -package lmsensors
2 -
3 -import (
4 - "strconv"
5 -)
6 -
7 -var _ Sensor = &CurrentSensor{}
8 -
9 -// A CurrentSensor is a Sensor that detects current in Amperes.
10 -type CurrentSensor struct {
11 - // The name of the sensor.
12 - Name string
13 -
14 - // A label that describes what the sensor is monitoring. Label may be empty.
15 - Label string
16 -
17 - // Whether the sensor has an alarm triggered.
18 - Alarm bool
19 -
20 - // The input current, in Amperes, indicated by the sensor.
21 - Input float64
22 -
23 - // The maximum current threshold, in Amperes, indicated by the sensor.
24 - Maximum float64
25 -
26 - // The critical current threshold, in Amperes, indicated by the sensor.
27 - Critical float64
28 -}
29 -
30 -func (s *CurrentSensor) Type() SensorType { return SensorTypeCurrent }
31 -
32 -func (s *CurrentSensor) parse(raw map[string]string) error {
33 - for k, v := range raw {
34 - switch k {
35 - case "crit", "input", "max":
36 - f, err := strconv.ParseFloat(v, 64)
37 - if err != nil {
38 - return err
39 - }
40 -
41 - // Raw current values are scaled by 1000
42 - f /= 1000
43 -
44 - switch k {
45 - case "crit":
46 - s.Critical = f
47 - case "input":
48 - s.Input = f
49 - case "max":
50 - s.Maximum = f
51 - }
52 - case "alarm":
53 - s.Alarm = v != "0"
54 - case "label":
55 - s.Label = v
56 - }
57 - }
58 -
59 - return nil
60 -}
61 -
62 -func (s *CurrentSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/lmsensors/doc.go
+1 -2
@@ -1,3 +1,2 @@
1 -// Package lmsensors provides access to Linux monitoring sensors data, such
2 -// as temperatures, voltage, and fan speeds.
1 +// Package lmsensors provides access to Linux monitoring sensors data, such as temperatures, voltage, and fan speeds.
2 package lmsensors
src/go/plugin/go.d/modules/sensors/lmsensors/fansensor.go deleted
-64
@@ -1,64 +0,0 @@
1 -package lmsensors
2 -
3 -import (
4 - "strconv"
5 -)
6 -
7 -var _ Sensor = &FanSensor{}
8 -
9 -// A FanSensor is a Sensor that detects fan speeds in rotations per minute.
10 -type FanSensor struct {
11 - // The name of the sensor.
12 - Name string
13 -
14 - // A label that describes what the sensor is monitoring. Label may be empty.
15 - Label string
16 -
17 - // Whether the fan speed is below the minimum threshold.
18 - Alarm bool
19 -
20 - // Whether the fan will sound an audible alarm when fan speed is below the minimum threshold.
21 - Beep bool
22 -
23 - // The input fan speed, in rotations per minute, indicated by the sensor.
24 - Input float64
25 -
26 - // The low threshold fan speed, in rotations per minute, indicated by the sensor.
27 - Minimum float64
28 -
29 - // The high threshold fan speed, in rotations per minute, indicated by the sensor.
30 - Maximum float64
31 -}
32 -
33 -func (s *FanSensor) Type() SensorType { return SensorTypeFan }
34 -
35 -func (s *FanSensor) parse(raw map[string]string) error {
36 - for k, v := range raw {
37 - switch k {
38 - case "input", "min", "max":
39 - f, err := strconv.ParseFloat(v, 64)
40 - if err != nil {
41 - return err
42 - }
43 -
44 - switch k {
45 - case "input":
46 - s.Input = f
47 - case "min":
48 - s.Minimum = f
49 - case "max":
50 - s.Maximum = f
51 - }
52 - case "alarm":
53 - s.Alarm = v != "0"
54 - case "beep":
55 - s.Beep = v != "0"
56 - case "label":
57 - s.Label = v
58 - }
59 - }
60 -
61 - return nil
62 -}
63 -
64 -func (s *FanSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/lmsensors/fs.go new
+44
@@ -0,0 +1,44 @@
1 +package lmsensors
2 +
3 +import (
4 + "io/fs"
5 + "os"
6 + "path/filepath"
7 + "strings"
8 +)
9 +
10 +// A filesystem is an interface to a filesystem, used for testing.
11 +type filesystem interface {
12 + ReadDir(name string) ([]fs.DirEntry, error)
13 + ReadFile(filename string) (string, error)
14 + Readlink(name string) (string, error)
15 + Stat(name string) (os.FileInfo, error)
16 + WalkDir(root string, walkFn fs.WalkDirFunc) error
17 +}
18 +
19 +// A systemFilesystem is a filesystem which uses operations on the host filesystem.
20 +type systemFilesystem struct{}
21 +
22 +func (s *systemFilesystem) ReadDir(name string) ([]fs.DirEntry, error) {
23 + return os.ReadDir(name)
24 +}
25 +
26 +func (s *systemFilesystem) ReadFile(filename string) (string, error) {
27 + b, err := os.ReadFile(filename)
28 + if err != nil {
29 + return "", err
30 + }
31 + return strings.TrimSpace(string(b)), nil
32 +}
33 +
34 +func (s *systemFilesystem) Readlink(name string) (string, error) {
35 + return os.Readlink(name)
36 +}
37 +
38 +func (s *systemFilesystem) Stat(name string) (os.FileInfo, error) {
39 + return os.Stat(name)
40 +}
41 +
42 +func (s *systemFilesystem) WalkDir(root string, walkFn fs.WalkDirFunc) error {
43 + return filepath.WalkDir(root, walkFn)
44 +}
src/go/plugin/go.d/modules/sensors/lmsensors/intrusionsensor.go deleted
-32
@@ -1,32 +0,0 @@
1 -package lmsensors
2 -
3 -var _ Sensor = &IntrusionSensor{}
4 -
5 -// An IntrusionSensor is a Sensor that detects when the machine's chassis has been opened.
6 -type IntrusionSensor struct {
7 - // The name of the sensor.
8 - Name string
9 -
10 - // A label that describes what the sensor is monitoring. Label may be empty.
11 - Label string
12 -
13 - // Whether the machine's chassis has been opened, and the alarm has been triggered.
14 - Alarm bool
15 -}
16 -
17 -func (s *IntrusionSensor) Type() SensorType { return SensorTypeIntrusion }
18 -
19 -func (s *IntrusionSensor) parse(raw map[string]string) error {
20 - for k, v := range raw {
21 - switch k {
22 - case "alarm":
23 - s.Alarm = v != "0"
24 - case "label":
25 - s.Label = v
26 - }
27 - }
28 -
29 - return nil
30 -}
31 -
32 -func (s *IntrusionSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/lmsensors/parse.go new
+363
@@ -0,0 +1,363 @@
1 +package lmsensors
2 +
3 +import (
4 + "fmt"
5 + "strconv"
6 + "strings"
7 + "time"
8 + "unicode"
9 +)
10 +
11 +type (
12 + rawSensors map[string]map[string]rawValue // e.g. [temp1][input]
13 +
14 + rawValue struct {
15 + value string
16 + readTime time.Duration
17 + }
18 +)
19 +
20 +// parseSensors parses all Sensors from an input raw data slice, produced during a filesystem walk.
21 +func parseSensors(rawSns rawSensors) (*Sensors, error) {
22 + var sensors Sensors
23 +
24 + for name, values := range rawSns {
25 + typ := name
26 + if i := strings.IndexFunc(name, unicode.IsDigit); i > 0 {
27 + typ = name[:i]
28 + }
29 +
30 + switch typ {
31 + case "in":
32 + sn := &VoltageSensor{Name: name}
33 + if err := parseVoltage(sn, values); err != nil {
34 + return nil, fmt.Errorf("voltage sensor '%s': %w", name, err)
35 + }
36 + sensors.Voltage = append(sensors.Voltage, sn)
37 + case "fan":
38 + sn := &FanSensor{Name: name}
39 + if err := parseFan(sn, values); err != nil {
40 + return nil, fmt.Errorf("fan sensor '%s': %w", name, err)
41 + }
42 + sensors.Fan = append(sensors.Fan, sn)
43 + case "temp":
44 + sn := &TemperatureSensor{Name: name}
45 + if err := parseTemperature(sn, values); err != nil {
46 + return nil, fmt.Errorf("temperature sensor '%s': %w", name, err)
47 + }
48 + sensors.Temperature = append(sensors.Temperature, sn)
49 + case "curr":
50 + sn := &CurrentSensor{Name: name}
51 + if err := parseCurrent(sn, values); err != nil {
52 + return nil, fmt.Errorf("current sensor '%s': %w", name, err)
53 + }
54 + sensors.Current = append(sensors.Current, sn)
55 + case "power":
56 + sn := &PowerSensor{Name: name}
57 + if err := parsePower(sn, values); err != nil {
58 + return nil, fmt.Errorf("power sensor '%s': %w", name, err)
59 + }
60 + sensors.Power = append(sensors.Power, sn)
61 + case "energy":
62 + sn := &EnergySensor{Name: name}
63 + if err := parseEnergy(sn, values); err != nil {
64 + return nil, fmt.Errorf("energy sensor '%s': %w", name, err)
65 + }
66 + sensors.Energy = append(sensors.Energy, sn)
67 + case "humidity":
68 + sn := &HumiditySensor{Name: name}
69 + if err := parseHumidity(sn, values); err != nil {
70 + return nil, fmt.Errorf("humidity sensor '%s': %w", name, err)
71 + }
72 + sensors.Humidity = append(sensors.Humidity, sn)
73 + case "intrusion":
74 + sn := &IntrusionSensor{Name: name}
75 + if err := parseIntrusion(sn, values); err != nil {
76 + return nil, fmt.Errorf("intrusion sensor '%s': %w", name, err)
77 + }
78 + sensors.Intrusion = append(sensors.Intrusion, sn)
79 + default:
80 + continue
81 + }
82 + }
83 +
84 + return &sensors, nil
85 +}
86 +
87 +func parseVoltage(s *VoltageSensor, values map[string]rawValue) error {
88 + const div = 1e3 // raw in milli degree Celsius
89 +
90 + for name, val := range values {
91 + var err error
92 +
93 + s.ReadTime += val.readTime
94 + v := val.value
95 +
96 + switch name {
97 + case "label":
98 + s.Label = v
99 + case "alarm":
100 + s.Alarm = ptr(v != "0")
101 + case "min":
102 + s.Min, err = parseFloat(v, div)
103 + case "lcrit":
104 + s.CritMin, err = parseFloat(v, div)
105 + case "max":
106 + s.Max, err = parseFloat(v, div)
107 + case "crit":
108 + s.CritMax, err = parseFloat(v, div)
109 + case "input":
110 + s.Input, err = parseFloat(v, div)
111 + case "average":
112 + s.Average, err = parseFloat(v, div)
113 + case "lowest":
114 + s.Lowest, err = parseFloat(v, div)
115 + case "highest":
116 + s.Highest, err = parseFloat(v, div)
117 + }
118 + if err != nil {
119 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
120 + }
121 + }
122 +
123 + return nil
124 +}
125 +
126 +func parseFan(s *FanSensor, values map[string]rawValue) error {
127 + const div = 1 // raw in revolution/min (RPM)
128 +
129 + for name, val := range values {
130 + var err error
131 +
132 + s.ReadTime += val.readTime
133 + v := val.value
134 +
135 + switch name {
136 + case "input":
137 + s.Input, err = parseFloat(v, div)
138 + case "min":
139 + s.Min, err = parseFloat(v, div)
140 + case "max":
141 + s.Max, err = parseFloat(v, div)
142 + case "target":
143 + s.Target, err = parseFloat(v, div)
144 + case "alarm":
145 + s.Alarm = ptr(v != "0")
146 + case "label":
147 + s.Label = v
148 + }
149 + if err != nil {
150 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
151 + }
152 + }
153 +
154 + return nil
155 +}
156 +
157 +func parseTemperature(s *TemperatureSensor, values map[string]rawValue) error {
158 + const div = 1000 // raw in milli degree Celsius
159 +
160 + for name, val := range values {
161 + var err error
162 +
163 + s.ReadTime += val.readTime
164 + v := val.value
165 +
166 + switch name {
167 + case "max":
168 + s.Max, err = parseFloat(v, div)
169 + case "min":
170 + s.Min, err = parseFloat(v, div)
171 + case "input":
172 + s.Input, err = parseFloat(v, div)
173 + case "crit":
174 + s.CritMax, err = parseFloat(v, div)
175 + case "emergency":
176 + s.Emergency, err = parseFloat(v, div)
177 + case "lcrit":
178 + s.CritMin, err = parseFloat(v, div)
179 + case "lowest":
180 + s.Lowest, err = parseFloat(v, div)
181 + case "highest":
182 + s.Highest, err = parseFloat(v, div)
183 + case "alarm":
184 + s.Alarm = ptr(v != "0")
185 + case "type":
186 + t, err := strconv.Atoi(v)
187 + if err != nil {
188 + return err
189 + }
190 + s.TempTypeRaw = t
191 + case "label":
192 + s.Label = v
193 + }
194 + if err != nil {
195 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
196 + }
197 + }
198 +
199 + return nil
200 +}
201 +
202 +func parseCurrent(s *CurrentSensor, values map[string]rawValue) error {
203 + const div = 1e3 // raw in milli ampere
204 +
205 + for name, val := range values {
206 + var err error
207 +
208 + s.ReadTime += val.readTime
209 + v := val.value
210 +
211 + switch name {
212 + case "max":
213 + s.Max, err = parseFloat(v, div)
214 + case "min":
215 + s.Min, err = parseFloat(v, div)
216 + case "lcrit":
217 + s.CritMin, err = parseFloat(v, div)
218 + case "crit":
219 + s.CritMax, err = parseFloat(v, div)
220 + case "input":
221 + s.Input, err = parseFloat(v, div)
222 + case "average":
223 + s.Average, err = parseFloat(v, div)
224 + case "lowest":
225 + s.Lowest, err = parseFloat(v, div)
226 + case "highest":
227 + s.Highest, err = parseFloat(v, div)
228 + case "alarm":
229 + s.Alarm = ptr(v != "0")
230 + case "label":
231 + s.Label = v
232 + }
233 + if err != nil {
234 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
235 + }
236 + }
237 +
238 + return nil
239 +}
240 +
241 +func parsePower(s *PowerSensor, values map[string]rawValue) error {
242 + const div = 1e6 // raw in microWatt
243 +
244 + for name, val := range values {
245 + var err error
246 +
247 + s.ReadTime += val.readTime
248 + v := val.value
249 +
250 + switch name {
251 + case "label":
252 + s.Label = v
253 + case "alarm":
254 + s.Alarm = ptr(v != "0")
255 + case "average":
256 + s.Average, err = parseFloat(v, div)
257 + case "average_highest":
258 + s.AverageHighest, err = parseFloat(v, div)
259 + case "average_lowest":
260 + s.AverageLowest, err = parseFloat(v, div)
261 + case "average_max":
262 + s.AverageMax, err = parseFloat(v, div)
263 + case "average_min":
264 + s.AverageMin, err = parseFloat(v, div)
265 + case "input":
266 + s.Input, err = parseFloat(v, div)
267 + case "input_highest":
268 + s.InputHighest, err = parseFloat(v, div)
269 + case "input_lowest":
270 + s.InputLowest, err = parseFloat(v, div)
271 + case "accuracy":
272 + s.Accuracy, err = parseFloat(v, 1)
273 + case "cap":
274 + s.Cap, err = parseFloat(v, div)
275 + case "cap_max":
276 + s.CapMax, err = parseFloat(v, div)
277 + case "cap_min":
278 + s.CapMin, err = parseFloat(v, div)
279 + case "max":
280 + s.Max, err = parseFloat(v, div)
281 + case "crit":
282 + s.CritMax, err = parseFloat(v, div)
283 + }
284 + if err != nil {
285 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
286 + }
287 + }
288 +
289 + return nil
290 +}
291 +
292 +func parseEnergy(s *EnergySensor, values map[string]rawValue) error {
293 + const div = 1e6 // raw in microJoule
294 +
295 + for name, val := range values {
296 + var err error
297 +
298 + s.ReadTime += val.readTime
299 + v := val.value
300 +
301 + switch name {
302 + case "label":
303 + s.Label = v
304 + case "input":
305 + s.Input, err = parseFloat(v, div)
306 + }
307 + if err != nil {
308 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
309 + }
310 + }
311 +
312 + return nil
313 +}
314 +
315 +func parseHumidity(s *HumiditySensor, values map[string]rawValue) error {
316 + const div = 1e3 // raw in milli percent
317 +
318 + for name, val := range values {
319 + var err error
320 +
321 + s.ReadTime += val.readTime
322 + v := val.value
323 +
324 + switch name {
325 + case "label":
326 + s.Label = v
327 + case "input":
328 + s.Input, err = parseFloat(v, div)
329 + }
330 + if err != nil {
331 + return fmt.Errorf("subfeature '%s' value '%s': %v", name, v, err)
332 + }
333 + }
334 +
335 + return nil
336 +}
337 +
338 +func parseIntrusion(s *IntrusionSensor, values map[string]rawValue) error {
339 + for name, val := range values {
340 + s.ReadTime += val.readTime
341 + v := val.value
342 +
343 + switch name {
344 + case "label":
345 + s.Label = v
346 + case "alarm":
347 + s.Alarm = ptr(v != "0")
348 + }
349 + }
350 +
351 + return nil
352 +}
353 +
354 +func parseFloat(s string, div float64) (*float64, error) {
355 + f, err := strconv.ParseFloat(s, 64)
356 + if err != nil {
357 + return nil, err
358 + }
359 +
360 + return ptr(f / div), nil
361 +}
362 +
363 +func ptr[T any](v T) *T { return &v }
src/go/plugin/go.d/modules/sensors/lmsensors/powersensor.go deleted
-75
@@ -1,75 +0,0 @@
1 -package lmsensors
2 -
3 -import (
4 - "strconv"
5 - "time"
6 -)
7 -
8 -var _ Sensor = &PowerSensor{}
9 -
10 -// A PowerSensor is a Sensor that detects average electrical power consumption in watts.
11 -type PowerSensor struct {
12 - // The name of the sensor.
13 - Name string
14 -
15 - // A label that describes what the sensor is monitoring. Label may be empty.
16 - Label string
17 -
18 - // The average electrical power consumption, in watts, indicated by the sensor.
19 - Average float64
20 -
21 - // The interval of time over which the average electrical power consumption is collected.
22 - AverageInterval time.Duration
23 -
24 - // Whether this sensor has a battery.
25 - Battery bool
26 -
27 - // The model number of the sensor.
28 - ModelNumber string
29 -
30 - // Miscellaneous OEM information about the sensor.
31 - OEMInfo string
32 -
33 - // The serial number of the sensor.
34 - SerialNumber string
35 -}
36 -
37 -func (s *PowerSensor) Type() SensorType { return SensorTypePower }
38 -
39 -func (s *PowerSensor) parse(raw map[string]string) error {
40 - for k, v := range raw {
41 - switch k {
42 - case "average":
43 - f, err := strconv.ParseFloat(v, 64)
44 - if err != nil {
45 - return err
46 - }
47 -
48 - // Raw temperature values are scaled by one million
49 - f /= 1000000
50 - s.Average = f
51 - case "average_interval":
52 - // Time values in milliseconds
53 - d, err := time.ParseDuration(v + "ms")
54 - if err != nil {
55 - return err
56 - }
57 -
58 - s.AverageInterval = d
59 - case "is_battery":
60 - s.Battery = v != "0"
61 - case "model_number":
62 - s.ModelNumber = v
63 - case "oem_info":
64 - s.OEMInfo = v
65 - case "serial_number":
66 - s.SerialNumber = v
67 - case "label":
68 - s.Label = v
69 - }
70 - }
71 -
72 - return nil
73 -}
74 -
75 -func (s *PowerSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/lmsensors/scanner.go
+80 -102
@@ -1,6 +1,8 @@
1 package lmsensors
2
3 import (
4 + "encoding/hex"
5 + "errors"
6 "fmt"
7 "io/fs"
8 "os"
@@ -8,15 +10,16 @@ import (
10 "strings"
11 "time"
12
13 + "github.com/cloudflare/cfssl/scan/crypto/sha1"
14 +
15 "github.com/netdata/netdata/go/plugins/logger"
16 )
17
14 -// A filesystem is an interface to a filesystem, used for testing.
15 -type filesystem interface {
16 - ReadFile(filename string) (string, error)
17 - Readlink(name string) (string, error)
18 - Stat(name string) (os.FileInfo, error)
19 - WalkDir(root string, walkFn fs.WalkDirFunc) error
18 +// New creates a new Scanner.
19 +func New() *Scanner {
20 + return &Scanner{
21 + fs: &systemFilesystem{},
22 + }
23 }
24
25 // A Scanner scans for Devices, so data can be read from their Sensors.
@@ -26,15 +29,8 @@ type Scanner struct {
29 fs filesystem
30 }
31
29 -// New creates a new Scanner.
30 -func New() *Scanner {
31 - return &Scanner{
32 - fs: &systemFilesystem{},
33 - }
34 -}
35 -
32 // Scan scans for Devices and their Sensors.
37 -func (sc *Scanner) Scan() ([]*Device, error) {
33 +func (sc *Scanner) Scan() ([]*Chip, error) {
34 paths, err := sc.detectDevicePaths()
35 if err != nil {
36 return nil, err
@@ -42,93 +38,71 @@ func (sc *Scanner) Scan() ([]*Device, error) {
38
39 sc.Debugf("sysfs scanner: found %d paths", len(paths))
40
45 - var devices []*Device
41 + var chips []*Chip
42
47 - for _, rootPath := range paths {
48 - sc.Debugf("sysfs scanner: scanning %s", rootPath)
43 + for _, path := range paths {
44 + sc.Debugf("sysfs scanner: scanning %s", path)
45
50 - dev := &Device{}
51 - raw := make(map[string]map[string]string)
46 + var chip Chip
47
53 - // Walk filesystem paths to fetch devices and sensors
54 - err := sc.fs.WalkDir(rootPath, func(path string, de fs.DirEntry, err error) error {
55 - if err != nil {
56 - return err
57 - }
48 + rawSns := make(rawSensors)
49
59 - if de.IsDir() || !de.Type().IsRegular() {
60 - if de.IsDir() && path != rootPath {
61 - return fs.SkipDir
62 - }
63 - return nil
64 - }
50 + des, err := sc.fs.ReadDir(path)
51 + if err != nil {
52 + return nil, err
53 + }
54
66 - // Skip some files that can't be read or don't provide useful sensor information
67 - file := filepath.Base(path)
68 - if shouldSkip(file) {
69 - return nil
55 + for _, de := range des {
56 + if !de.Type().IsRegular() || shouldSkip(de.Name()) {
57 + continue
58 }
59
60 + filePath := filepath.Join(path, de.Name())
61 +
62 now := time.Now()
73 - s, err := sc.fs.ReadFile(path)
63 + content, err := sc.fs.ReadFile(filePath)
64 if err != nil {
75 - return nil
65 + sc.Debugf("sysfs scanner: failed to read '%s': %v", filePath, err)
66 + continue
67 }
77 - sc.Debugf("sysfs scanner: reading file '%s' took %s", path, time.Since(now))
68 + since := time.Since(now)
69 +
70 + sc.Debugf("sysfs scanner: reading file '%s' took %s", filePath, since)
71
79 - if file == "name" {
80 - dev.Name = s
81 - return nil
72 + if de.Name() == "name" {
73 + chip.Name = content
74 + continue
75 }
76
77 // Sensor names in format "sensor#_foo", e.g. "temp1_input"
85 - parts := strings.SplitN(file, "_", 2)
86 - if len(parts) != 2 {
87 - return nil
78 + feat, subfeat, ok := strings.Cut(de.Name(), "_")
79 + if !ok {
80 + continue
81 }
82
90 - if _, ok := raw[parts[0]]; !ok {
91 - raw[parts[0]] = make(map[string]string)
83 + if _, ok := rawSns[feat]; !ok {
84 + rawSns[feat] = make(map[string]rawValue)
85 }
86
94 - raw[parts[0]][parts[1]] = s
95 -
96 - return nil
97 - })
98 - if err != nil {
99 - return nil, err
87 + rawSns[feat][subfeat] = rawValue{value: content, readTime: since}
88 }
89
102 - sensors, err := parseSensors(raw)
90 + sensors, err := parseSensors(rawSns)
91 if err != nil {
104 - return nil, err
92 + return nil, fmt.Errorf("sysfs scanner: failed to parse (device '%s', path '%s'): %v", chip.Name, path, err)
93 }
94
107 - for _, sn := range sensors {
108 - sc.Debugf("sysfs scanner: found sensor %+v", sn)
95 + if sensors != nil {
96 + chip.Sensors = *sensors
97 }
98
111 - dev.Sensors = sensors
112 - devices = append(devices, dev)
113 - }
114 -
115 - renameDevices(devices)
99 + chip.SysDevice = getDevicePath(path)
100 + chip.UniqueName = fmt.Sprintf("%s-%s-%s", chip.Name, getBusType(chip.SysDevice), getHash(chip.SysDevice))
101
117 - return devices, nil
118 -}
119 -
120 -// renameDevices renames devices in place to prevent duplicate device names, and to number each device.
121 -func renameDevices(devices []*Device) {
122 - nameCount := make(map[string]int)
123 -
124 - for i := range devices {
125 - name := devices[i].Name
126 - devices[i].Name = fmt.Sprintf("%s-%02d",
127 - name,
128 - nameCount[name],
129 - )
130 - nameCount[name]++
102 + chips = append(chips, &chip)
103 }
104 +
105 + return chips, nil
106 }
107
108 // detectDevicePaths performs a filesystem walk to paths where devices may reside on Linux.
@@ -136,6 +110,7 @@ func (sc *Scanner) detectDevicePaths() ([]string, error) {
110 const lookPath = "/sys/class/hwmon"
111
112 var paths []string
113 +
114 err := sc.fs.WalkDir(lookPath, func(path string, de os.DirEntry, err error) error {
115 if err != nil {
116 return err
@@ -154,7 +129,7 @@ func (sc *Scanner) detectDevicePaths() ([]string, error) {
129
130 // Symlink destination has a file called name, meaning a sensor exists here and data can be retrieved
131 fi, err := sc.fs.Stat(filepath.Join(dest, "name"))
157 - if err != nil && !os.IsNotExist(err) {
132 + if err != nil && !errors.Is(err, fs.ErrNotExist) {
133 return err
134 }
135 if err == nil && fi.Mode().IsRegular() {
@@ -166,7 +141,7 @@ func (sc *Scanner) detectDevicePaths() ([]string, error) {
141 device := filepath.Join(dest, "device")
142 fi, err = sc.fs.Stat(device)
143 if err != nil {
169 - if !os.IsNotExist(err) {
144 + if !errors.Is(err, fs.ErrNotExist) {
145 return err
146 }
147 return nil
@@ -185,7 +160,7 @@ func (sc *Scanner) detectDevicePaths() ([]string, error) {
160
161 // Symlink destination has a file called name, meaning a sensor exists here and data can be retrieved
162 if _, err := sc.fs.Stat(filepath.Join(dest, "name")); err != nil {
188 - if !os.IsNotExist(err) {
163 + if !errors.Is(err, fs.ErrNotExist) {
164 return err
165 }
166 return nil
@@ -199,6 +174,34 @@ func (sc *Scanner) detectDevicePaths() ([]string, error) {
174 return paths, err
175 }
176
177 +func getDevicePath(path string) string {
178 + devPath, err := filepath.EvalSymlinks(filepath.Join(path, "device"))
179 + if err != nil {
180 + devPath = path
181 + if i := strings.Index(devPath, "/hwmon"); i > 0 {
182 + devPath = devPath[:i]
183 + }
184 + }
185 + return strings.TrimPrefix(devPath, "/sys/devices/")
186 +}
187 +
188 +func getHash(devPath string) string {
189 + hash := sha1.Sum([]byte(devPath))
190 + return hex.EncodeToString(hash[:])[:8]
191 +}
192 +
193 +func getBusType(devPath string) string {
194 + devPath = filepath.Join("/", devPath)
195 + devPath = strings.ToLower(devPath)
196 +
197 + for _, v := range []string{"i2c", "isa", "pci", "spi", "virtual", "acpi", "hid", "mdio", "scsi"} {
198 + if strings.Contains(devPath, "/"+v) {
199 + return v
200 + }
201 + }
202 + return "unk"
203 +}
204 +
205 // shouldSkip indicates if a given filename should be skipped during the filesystem walk operation.
206 func shouldSkip(file string) bool {
207 if strings.HasPrefix(file, "runtime_") {
@@ -218,28 +221,3 @@ func shouldSkip(file string) bool {
221
222 return true
223 }
221 -
222 -var _ filesystem = &systemFilesystem{}
223 -
224 -// A systemFilesystem is a filesystem which uses operations on the host filesystem.
225 -type systemFilesystem struct{}
226 -
227 -func (fs *systemFilesystem) ReadFile(filename string) (string, error) {
228 - b, err := os.ReadFile(filename)
229 - if err != nil {
230 - return "", err
231 - }
232 - return strings.TrimSpace(string(b)), nil
233 -}
234 -
235 -func (fs *systemFilesystem) Readlink(name string) (string, error) {
236 - return os.Readlink(name)
237 -}
238 -
239 -func (fs *systemFilesystem) Stat(name string) (os.FileInfo, error) {
240 - return os.Stat(name)
241 -}
242 -
243 -func (fs *systemFilesystem) WalkDir(root string, walkFn fs.WalkDirFunc) error {
244 - return filepath.WalkDir(root, walkFn)
245 -}
src/go/plugin/go.d/modules/sensors/lmsensors/scanner_test.go
+386 -672
@@ -4,750 +4,469 @@ import (
4 "fmt"
5 "io/fs"
6 "os"
7 - "reflect"
7 + "path/filepath"
8 + "slices"
9 "strings"
10 "testing"
11 "time"
11 -)
12
13 -// TODO(mdlayher): why does scanning work if device file isn't a symlink,
14 -// even though it is in the actual filesystem (and the actual filesystem
15 -// exhibits the same behavior)?
13 + "github.com/stretchr/testify/require"
14 +)
15
17 -func TestScannerScan(t *testing.T) {
18 - tests := []struct {
19 - name string
20 - fs filesystem
21 - devices []*Device
16 +func TestScanner_Scan(t *testing.T) {
17 + var tests = map[string]struct {
18 + fs filesystem
19 + wantDevices []*Chip
20 }{
23 - {
24 - name: "power_meter device",
21 + "Power sensor": {
22 + wantDevices: []*Chip{
23 + {
24 + Name: "power_meter",
25 + UniqueName: "power_meter-acpi-b37a4ed3",
26 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
27 + Sensors: Sensors{
28 + Power: []*PowerSensor{
29 + {
30 + Name: "power1",
31 + Label: "some_label",
32 + Alarm: ptr(false),
33 + Average: ptr(345.0),
34 + AverageHighest: ptr(345.0),
35 + AverageLowest: ptr(345.0),
36 + AverageMax: ptr(345.0),
37 + AverageMin: ptr(345.0),
38 + Input: ptr(345.0),
39 + InputHighest: ptr(345.0),
40 + InputLowest: ptr(345.0),
41 + Accuracy: ptr(34.5),
42 + Cap: ptr(345.0),
43 + CapMax: ptr(345.0),
44 + CapMin: ptr(345.0),
45 + Max: ptr(345.0),
46 + CritMax: ptr(345.0),
47 + },
48 + },
49 + }},
50 + },
51 fs: &memoryFilesystem{
52 symlinks: map[string]string{
53 "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
54 "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
55 },
56 files: []memoryFile{
31 - {
32 - name: "/sys/class/hwmon",
33 - dirEntry: &memoryDirEntry{
34 - isDir: true,
35 - },
36 - },
37 - {
38 - name: "/sys/class/hwmon/hwmon0",
39 - dirEntry: &memoryDirEntry{
40 - mode: os.ModeSymlink,
41 - },
42 - },
43 - {
44 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00",
45 - dirEntry: &memoryDirEntry{
46 - isDir: true,
47 - },
48 - },
49 - {
50 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name",
51 - err: os.ErrNotExist,
52 - },
53 - {
54 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device",
55 - dirEntry: &memoryDirEntry{
56 - // mode: os.ModeSymlink,
57 - },
58 - },
59 - {
60 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name",
61 - contents: "power_meter",
62 - },
63 - {
64 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average",
65 - contents: "345000000",
66 - },
67 - {
68 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average_interval",
69 - contents: "1000",
70 - },
71 - {
72 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_is_battery",
73 - contents: "0",
74 - },
75 - {
76 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_model_number",
77 - contents: "Intel(R) Node Manager",
78 - },
79 - {
80 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_oem_info",
81 - contents: "Meter measures total domain",
82 - },
83 - {
84 - name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_serial_number",
85 - contents: "",
86 - },
57 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
58 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
59 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
60 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
61 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
62 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "power_meter"},
63 +
64 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_label", val: "some_label"},
65 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_alarm", val: "0"},
66 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average", val: "345000000"},
67 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average_highest", val: "345000000"},
68 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average_lowest", val: "345000000"},
69 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average_max", val: "345000000"},
70 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_average_min", val: "345000000"},
71 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_input", val: "345000000"},
72 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_input_highest", val: "345000000"},
73 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_input_lowest", val: "345000000"},
74 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_accuracy", val: "34.5"},
75 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_cap", val: "345000000"},
76 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_cap_max", val: "345000000"},
77 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_cap_min", val: "345000000"},
78 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_max", val: "345000000"},
79 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/power1_crit", val: "345000000"},
80 },
81 },
89 - devices: []*Device{{
90 - Name: "power_meter-00",
91 - Sensors: []Sensor{
92 - &PowerSensor{
93 - Name: "power1",
94 - Average: 345.0,
95 - AverageInterval: 1 * time.Second,
96 - Battery: false,
97 - ModelNumber: "Intel(R) Node Manager",
98 - OEMInfo: "Meter measures total domain",
99 - SerialNumber: "",
100 - },
101 - },
102 - }},
82 },
104 - {
105 - name: "acpitz device",
83 + "Temperature sensor": {
84 + wantDevices: []*Chip{
85 + {
86 + Name: "temp_meter",
87 + UniqueName: "temp_meter-pci-e3b89088",
88 + SysDevice: "pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0",
89 + Sensors: Sensors{
90 + Temperature: []*TemperatureSensor{
91 + {
92 + Name: "temp1",
93 + Label: "some_label",
94 + Alarm: ptr(false),
95 + TempTypeRaw: 1,
96 + Input: ptr(42.0),
97 + Max: ptr(42.0),
98 + Min: ptr(42.0),
99 + CritMin: ptr(42.0),
100 + CritMax: ptr(42.0),
101 + Emergency: ptr(42.0),
102 + Lowest: ptr(42.0),
103 + Highest: ptr(42.0),
104 + },
105 + },
106 + }},
107 + },
108 fs: &memoryFilesystem{
109 symlinks: map[string]string{
108 - "/sys/class/hwmon/hwmon0": "../../devices/virtual/hwmon/hwmon0",
110 + "/sys/class/hwmon/hwmon0": "../../devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0",
111 + "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/device": "../../../0000:81:00.0",
112 },
113 files: []memoryFile{
111 - {
112 - name: "/sys/class/hwmon",
113 - dirEntry: &memoryDirEntry{
114 - isDir: true,
115 - },
116 - },
117 - {
118 - name: "/sys/class/hwmon/hwmon0",
119 - dirEntry: &memoryDirEntry{
120 - mode: os.ModeSymlink,
121 - },
122 - },
123 - {
124 - name: "/sys/devices/virtual/hwmon/hwmon0",
125 - dirEntry: &memoryDirEntry{
126 - isDir: true,
127 - },
128 - },
129 - {
130 - name: "/sys/devices/virtual/hwmon/hwmon0/name",
131 - contents: "acpitz",
132 - },
133 - {
134 - name: "/sys/devices/virtual/hwmon/hwmon0/temp1_crit",
135 - contents: "105000",
136 - },
137 - {
138 - name: "/sys/devices/virtual/hwmon/hwmon0/temp1_input",
139 - contents: "27800",
140 - },
114 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
115 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
116 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0", de: &memoryDirEntry{isDir: true}},
117 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/hwmon/hwmon0/name", err: os.ErrNotExist},
118 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/hwmon/hwmon0/device", de: &memoryDirEntry{}},
119 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/name", val: "temp_meter"},
120 +
121 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_label", val: "some_label"},
122 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_alarm", val: "0"},
123 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_type", val: "1"},
124 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_max", val: "42000"},
125 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_min", val: "42000"},
126 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_input", val: "42000"},
127 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_crit", val: "42000"},
128 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_emergency", val: "42000"},
129 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_lcrit", val: "42000"},
130 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_lowest", val: "42000"},
131 + {name: "/sys/devices/pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0/hwmon0/temp1_highest", val: "42000"},
132 },
133 },
143 - devices: []*Device{{
144 - Name: "acpitz-00",
145 - Sensors: []Sensor{
146 - &TemperatureSensor{
147 - Name: "temp1",
148 - Input: 27.8,
149 - Critical: 105.0,
150 - CriticalAlarm: false,
151 - },
152 - },
153 - }},
134 },
155 - {
156 - name: "coretemp device",
135 + "Voltage sensor": {
136 + wantDevices: []*Chip{
137 + {
138 + Name: "voltage_meter",
139 + UniqueName: "voltage_meter-acpi-b37a4ed3",
140 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
141 + Sensors: Sensors{
142 + Voltage: []*VoltageSensor{
143 + {
144 + Name: "in1",
145 + Label: "some_label",
146 + Alarm: ptr(false),
147 + Input: ptr(42.0),
148 + Average: ptr(42.0),
149 + Min: ptr(42.0),
150 + Max: ptr(42.0),
151 + CritMin: ptr(42.0),
152 + CritMax: ptr(42.0),
153 + Lowest: ptr(42.0),
154 + Highest: ptr(42.0),
155 + },
156 + },
157 + }},
158 + },
159 fs: &memoryFilesystem{
160 symlinks: map[string]string{
159 - "/sys/class/hwmon/hwmon1": "../../devices/platform/coretemp.0/hwmon/hwmon1",
160 - "/sys/devices/platform/coretemp.0/hwmon/hwmon1/device": "../../../coretemp.0",
161 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
162 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
163 },
164 files: []memoryFile{
163 - {
164 - name: "/sys/class/hwmon",
165 - dirEntry: &memoryDirEntry{
166 - isDir: true,
167 - },
168 - },
169 - {
170 - name: "/sys/class/hwmon/hwmon1",
171 - dirEntry: &memoryDirEntry{
172 - mode: os.ModeSymlink,
173 - },
174 - },
175 - {
176 - name: "/sys/devices/platform/coretemp.0",
177 - dirEntry: &memoryDirEntry{
178 - isDir: true,
179 - },
180 - },
181 - {
182 - name: "/sys/devices/platform/coretemp.0/hwmon/hwmon1/name",
183 - err: os.ErrNotExist,
184 - },
185 - {
186 - name: "/sys/devices/platform/coretemp.0/hwmon/hwmon1/device",
187 - dirEntry: &memoryDirEntry{
188 - // mode: os.ModeSymlink,
189 - },
190 - },
191 - {
192 - name: "/sys/devices/platform/coretemp.0/name",
193 - contents: "coretemp",
194 - },
195 - {
196 - name: "/sys/devices/platform/coretemp.0/temp1_crit",
197 - contents: "100000",
198 - },
199 - {
200 - name: "/sys/devices/platform/coretemp.0/temp1_crit_alarm",
201 - contents: "0",
202 - },
203 - {
204 - name: "/sys/devices/platform/coretemp.0/temp1_input",
205 - contents: "40000",
206 - },
207 - {
208 - name: "/sys/devices/platform/coretemp.0/temp1_label",
209 - contents: "Core 0",
210 - },
211 - {
212 - name: "/sys/devices/platform/coretemp.0/temp1_max",
213 - contents: "80000",
214 - },
215 - {
216 - name: "/sys/devices/platform/coretemp.0/temp2_crit",
217 - contents: "100000",
218 - },
219 - {
220 - name: "/sys/devices/platform/coretemp.0/temp2_crit_alarm",
221 - contents: "0",
222 - },
223 - {
224 - name: "/sys/devices/platform/coretemp.0/temp2_input",
225 - contents: "42000",
226 - },
227 - {
228 - name: "/sys/devices/platform/coretemp.0/temp2_label",
229 - contents: "Core 1",
230 - },
231 - {
232 - name: "/sys/devices/platform/coretemp.0/temp2_max",
233 - contents: "80000",
234 - },
165 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
166 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
167 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
168 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
169 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
170 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "voltage_meter"},
171 +
172 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_label", val: "some_label"},
173 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_alarm", val: "0"},
174 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_input", val: "42000"},
175 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_average", val: "42000"},
176 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_min", val: "42000"},
177 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_max", val: "42000"},
178 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_lcrit", val: "42000"},
179 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_crit", val: "42000"},
180 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_lowest", val: "42000"},
181 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/in1_highest", val: "42000"},
182 },
183 },
237 - devices: []*Device{{
238 - Name: "coretemp-00",
239 - Sensors: []Sensor{
240 - &TemperatureSensor{
241 - Name: "temp1",
242 - Label: "Core 0",
243 - Input: 40.0,
244 - Maximum: 80.0,
245 - Critical: 100.0,
246 - CriticalAlarm: false,
247 - },
248 - &TemperatureSensor{
249 - Name: "temp2",
250 - Label: "Core 1",
251 - Input: 42.0,
252 - Maximum: 80.0,
253 - Critical: 100.0,
254 - CriticalAlarm: false,
255 - },
256 - },
257 - }},
184 },
259 - {
260 - name: "it8728 device",
185 + "Fan sensor": {
186 + wantDevices: []*Chip{
187 + {
188 + Name: "fan_meter",
189 + UniqueName: "fan_meter-acpi-b37a4ed3",
190 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
191 + Sensors: Sensors{
192 + Fan: []*FanSensor{
193 + {
194 + Name: "fan1",
195 + Label: "some_label",
196 + Alarm: ptr(false),
197 + Input: ptr(42.0),
198 + Min: ptr(42.0),
199 + Max: ptr(42.0),
200 + Target: ptr(42.0),
201 + },
202 + },
203 + }},
204 + },
205 fs: &memoryFilesystem{
206 symlinks: map[string]string{
263 - "/sys/class/hwmon/hwmon2": "../../devices/platform/it87.2608/hwmon/hwmon2",
264 - "/sys/devices/platform/it87.2608/hwmon/hwmon2/device": "../../../it87.2608",
207 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
208 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
209 },
210 files: []memoryFile{
267 - {
268 - name: "/sys/class/hwmon",
269 - dirEntry: &memoryDirEntry{
270 - isDir: true,
271 - },
272 - },
273 - {
274 - name: "/sys/class/hwmon/hwmon2",
275 - dirEntry: &memoryDirEntry{
276 - mode: os.ModeSymlink,
277 - },
278 - },
279 - {
280 - name: "/sys/devices/platform/it87.2608",
281 - dirEntry: &memoryDirEntry{
282 - isDir: true,
283 - },
284 - },
285 - {
286 - name: "/sys/devices/platform/it87.2608/hwmon/hwmon2/name",
287 - err: os.ErrNotExist,
288 - },
289 - {
290 - name: "/sys/devices/platform/it87.2608/hwmon/hwmon2/device",
291 - dirEntry: &memoryDirEntry{
292 - // mode: os.ModeSymlink,
293 - },
294 - },
295 - {
296 - name: "/sys/devices/platform/it87.2608/name",
297 - contents: "it8728",
298 - },
299 - {
300 - name: "/sys/devices/platform/it87.2608/fan1_alarm",
301 - contents: "0",
302 - },
303 - {
304 - name: "/sys/devices/platform/it87.2608/fan1_beep",
305 - contents: "1",
306 - },
307 - {
308 - name: "/sys/devices/platform/it87.2608/fan1_input",
309 - contents: "1010",
310 - },
311 - {
312 - name: "/sys/devices/platform/it87.2608/fan1_min",
313 - contents: "10",
314 - },
315 - {
316 - name: "/sys/devices/platform/it87.2608/in0_alarm",
317 - contents: "0",
318 - },
319 - {
320 - name: "/sys/devices/platform/it87.2608/in0_beep",
321 - contents: "0",
322 - },
323 - {
324 - name: "/sys/devices/platform/it87.2608/in0_input",
325 - contents: "1056",
326 - },
327 - {
328 - name: "/sys/devices/platform/it87.2608/in0_max",
329 - contents: "3060",
330 - },
331 - {
332 - name: "/sys/devices/platform/it87.2608/in1_alarm",
333 - contents: "0",
334 - },
335 - {
336 - name: "/sys/devices/platform/it87.2608/in1_beep",
337 - contents: "0",
338 - },
339 - {
340 - name: "/sys/devices/platform/it87.2608/in1_input",
341 - contents: "3384",
342 - },
343 - {
344 - name: "/sys/devices/platform/it87.2608/in1_label",
345 - contents: "3VSB",
346 - },
347 - {
348 - name: "/sys/devices/platform/it87.2608/in1_max",
349 - contents: "6120",
350 - },
351 - {
352 - name: "/sys/devices/platform/it87.2608/intrusion0_alarm",
353 - contents: "1",
354 - },
355 - {
356 - name: "/sys/devices/platform/it87.2608/temp1_alarm",
357 - contents: "0",
358 - },
359 - {
360 - name: "/sys/devices/platform/it87.2608/temp1_beep",
361 - contents: "1",
362 - },
363 - {
364 - name: "/sys/devices/platform/it87.2608/temp1_input",
365 - contents: "43000",
366 - },
367 - {
368 - name: "/sys/devices/platform/it87.2608/temp1_max",
369 - contents: "127000",
370 - },
371 - {
372 - name: "/sys/devices/platform/it87.2608/temp1_type",
373 - contents: "4",
374 - },
211 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
212 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
213 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
214 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
215 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
216 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "fan_meter"},
217 +
218 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_label", val: "some_label"},
219 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_alarm", val: "0"},
220 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_input", val: "42"},
221 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_min", val: "42"},
222 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_max", val: "42"},
223 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/fan1_target", val: "42"},
224 },
225 },
377 - devices: []*Device{{
378 - Name: "it8728-00",
379 - Sensors: []Sensor{
380 - &FanSensor{
381 - Name: "fan1",
382 - Alarm: false,
383 - Beep: true,
384 - Input: 1010,
385 - Minimum: 10,
386 - },
387 - &VoltageSensor{
388 - Name: "in0",
389 - Alarm: false,
390 - Beep: false,
391 - Input: 1.056,
392 - Maximum: 3.060,
393 - },
394 - &VoltageSensor{
395 - Name: "in1",
396 - Label: "3VSB",
397 - Alarm: false,
398 - Beep: false,
399 - Input: 3.384,
400 - Maximum: 6.120,
401 - },
402 - &IntrusionSensor{
403 - Name: "intrusion0",
404 - Alarm: true,
405 - },
406 - &TemperatureSensor{
407 - Name: "temp1",
408 - Alarm: false,
409 - Beep: true,
410 - TempType: TemperatureSensorTypeThermistor,
411 - Input: 43.0,
412 - Maximum: 127.0,
413 - },
414 - },
415 - }},
226 },
417 - {
418 - name: "multiple coretemp devices",
227 + "Energy sensor": {
228 + wantDevices: []*Chip{
229 + {
230 + Name: "energy_meter",
231 + UniqueName: "energy_meter-acpi-b37a4ed3",
232 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
233 + Sensors: Sensors{
234 + Energy: []*EnergySensor{
235 + {
236 + Name: "energy1",
237 + Label: "some_label",
238 + Input: ptr(42.0),
239 + },
240 + },
241 + }},
242 + },
243 fs: &memoryFilesystem{
244 symlinks: map[string]string{
421 - "/sys/class/hwmon/hwmon1": "../../devices/platform/coretemp.0/hwmon/hwmon1",
422 - "/sys/class/hwmon/hwmon2": "../../devices/platform/coretemp.1/hwmon/hwmon2",
423 - "/sys/devices/platform/coretemp.0/hwmon/hwmon1/device": "../../../coretemp.0",
424 - "/sys/devices/platform/coretemp.1/hwmon/hwmon2/device": "../../../coretemp.1",
245 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
246 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
247 },
248 files: []memoryFile{
427 - {
428 - name: "/sys/class/hwmon",
429 - dirEntry: &memoryDirEntry{
430 - isDir: true,
431 - },
432 - },
433 - {
434 - name: "/sys/class/hwmon/hwmon1",
435 - dirEntry: &memoryDirEntry{
436 - mode: os.ModeSymlink,
437 - },
438 - },
439 - {
440 - name: "/sys/class/hwmon/hwmon2",
441 - dirEntry: &memoryDirEntry{
442 - mode: os.ModeSymlink,
443 - },
444 - },
445 - {
446 - name: "/sys/devices/platform/coretemp.0",
447 - dirEntry: &memoryDirEntry{
448 - isDir: true,
449 - },
450 - },
451 - {
452 - name: "/sys/devices/platform/coretemp.0/hwmon/hwmon1/name",
453 - err: os.ErrNotExist,
454 - },
455 - {
456 - name: "/sys/devices/platform/coretemp.0/hwmon/hwmon1/device",
457 - dirEntry: &memoryDirEntry{
458 - // mode: os.ModeSymlink,
459 - },
460 - },
461 - {
462 - name: "/sys/devices/platform/coretemp.0/name",
463 - contents: "coretemp",
464 - },
465 - {
466 - name: "/sys/devices/platform/coretemp.0/temp1_crit",
467 - contents: "100000",
468 - },
469 - {
470 - name: "/sys/devices/platform/coretemp.0/temp1_crit_alarm",
471 - contents: "0",
472 - },
473 - {
474 - name: "/sys/devices/platform/coretemp.0/temp1_input",
475 - contents: "40000",
476 - },
477 - {
478 - name: "/sys/devices/platform/coretemp.0/temp1_label",
479 - contents: "Core 0",
480 - },
481 - {
482 - name: "/sys/devices/platform/coretemp.0/temp1_max",
483 - contents: "80000",
484 - },
485 - {
486 - name: "/sys/devices/platform/coretemp.0/temp2_crit",
487 - contents: "100000",
488 - },
489 - {
490 - name: "/sys/devices/platform/coretemp.0/temp2_crit_alarm",
491 - contents: "0",
492 - },
493 - {
494 - name: "/sys/devices/platform/coretemp.0/temp2_input",
495 - contents: "42000",
496 - },
497 - {
498 - name: "/sys/devices/platform/coretemp.0/temp2_label",
499 - contents: "Core 1",
500 - },
501 - {
502 - name: "/sys/devices/platform/coretemp.0/temp2_max",
503 - contents: "80000",
504 - },
505 - {
506 - name: "/sys/devices/platform/coretemp.1",
507 - dirEntry: &memoryDirEntry{
508 - isDir: true,
509 - },
510 - },
511 - {
512 - name: "/sys/devices/platform/coretemp.1/hwmon/hwmon2/name",
513 - err: os.ErrNotExist,
514 - },
515 - {
516 - name: "/sys/devices/platform/coretemp.1/hwmon/hwmon2/device",
517 - dirEntry: &memoryDirEntry{
518 - // mode: os.ModeSymlink,
519 - },
520 - },
521 - {
522 - name: "/sys/devices/platform/coretemp.1/name",
523 - contents: "coretemp",
524 - },
525 - {
526 - name: "/sys/devices/platform/coretemp.1/temp1_crit",
527 - contents: "100000",
528 - },
529 - {
530 - name: "/sys/devices/platform/coretemp.1/temp1_crit_alarm",
531 - contents: "0",
532 - },
533 - {
534 - name: "/sys/devices/platform/coretemp.1/temp1_input",
535 - contents: "38000",
536 - },
537 - {
538 - name: "/sys/devices/platform/coretemp.1/temp1_label",
539 - contents: "Core 0",
540 - },
541 - {
542 - name: "/sys/devices/platform/coretemp.1/temp1_max",
543 - contents: "80000",
544 - },
545 - {
546 - name: "/sys/devices/platform/coretemp.1/temp2_crit",
547 - contents: "100000",
548 - },
549 - {
550 - name: "/sys/devices/platform/coretemp.1/temp2_crit_alarm",
551 - contents: "0",
552 - },
553 - {
554 - name: "/sys/devices/platform/coretemp.1/temp2_input",
555 - contents: "37000",
556 - },
557 - {
558 - name: "/sys/devices/platform/coretemp.1/temp2_label",
559 - contents: "Core 1",
560 - },
561 - {
562 - name: "/sys/devices/platform/coretemp.1/temp2_max",
563 - contents: "80000",
564 - },
249 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
250 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
251 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
252 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
253 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
254 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "energy_meter"},
255 +
256 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/energy1_label", val: "some_label"},
257 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/energy1_alarm", val: "0"},
258 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/energy1_input", val: "42000000"},
259 },
260 },
567 - devices: []*Device{
261 + },
262 + "Current sensor": {
263 + wantDevices: []*Chip{
264 {
569 - Name: "coretemp-00",
570 - Sensors: []Sensor{
571 - &TemperatureSensor{
572 - Name: "temp1",
573 - Label: "Core 0",
574 - Input: 40.0,
575 - Maximum: 80.0,
576 - Critical: 100.0,
577 - CriticalAlarm: false,
578 - },
579 - &TemperatureSensor{
580 - Name: "temp2",
581 - Label: "Core 1",
582 - Input: 42.0,
583 - Maximum: 80.0,
584 - Critical: 100.0,
585 - CriticalAlarm: false,
265 + Name: "current_meter",
266 + UniqueName: "current_meter-acpi-b37a4ed3",
267 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
268 + Sensors: Sensors{
269 + Current: []*CurrentSensor{
270 + {
271 + Name: "curr1",
272 + Label: "some_label",
273 + Alarm: ptr(false),
274 + Max: ptr(42.0),
275 + Min: ptr(42.0),
276 + CritMin: ptr(42.0),
277 + CritMax: ptr(42.0),
278 + Input: ptr(42.0),
279 + Average: ptr(42.0),
280 + Lowest: ptr(42.0),
281 + Highest: ptr(42.0),
282 + },
283 },
587 - },
284 + }},
285 + },
286 + fs: &memoryFilesystem{
287 + symlinks: map[string]string{
288 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
289 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
290 },
291 + files: []memoryFile{
292 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
293 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
294 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
295 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
296 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
297 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "current_meter"},
298 +
299 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_label", val: "some_label"},
300 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_alarm", val: "0"},
301 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_max", val: "42000"},
302 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_min", val: "42000"},
303 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_lcrit", val: "42000"},
304 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_crit", val: "42000"},
305 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_input", val: "42000"},
306 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_average", val: "42000"},
307 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_lowest", val: "42000"},
308 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/curr1_highest", val: "42000"},
309 + },
310 + },
311 + },
312 + "Humidity sensor": {
313 + wantDevices: []*Chip{
314 {
590 - Name: "coretemp-01",
591 - Sensors: []Sensor{
592 - &TemperatureSensor{
593 - Name: "temp1",
594 - Label: "Core 0",
595 - Input: 38.0,
596 - Maximum: 80.0,
597 - Critical: 100.0,
598 - CriticalAlarm: false,
315 + Name: "humidity_meter",
316 + UniqueName: "humidity_meter-acpi-b37a4ed3",
317 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
318 + Sensors: Sensors{
319 + Humidity: []*HumiditySensor{
320 + {
321 + Name: "humidity1",
322 + Label: "some_label",
323 + Input: ptr(42.0),
324 + },
325 },
600 - &TemperatureSensor{
601 - Name: "temp2",
602 - Label: "Core 1",
603 - Input: 37.0,
604 - Maximum: 80.0,
605 - Critical: 100.0,
606 - CriticalAlarm: false,
607 - },
608 - },
326 + }},
327 + },
328 + fs: &memoryFilesystem{
329 + symlinks: map[string]string{
330 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
331 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
332 + },
333 + files: []memoryFile{
334 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
335 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
336 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
337 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
338 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
339 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "humidity_meter"},
340 +
341 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/humidity1_label", val: "some_label"},
342 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/humidity1_alarm", val: "0"},
343 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/humidity1_input", val: "42000"},
344 },
345 },
346 },
612 - {
613 - name: "sfc device",
347 + "Intrusion sensor": {
348 + wantDevices: []*Chip{
349 + {
350 + Name: "intrusion_meter",
351 + UniqueName: "intrusion_meter-acpi-b37a4ed3",
352 + SysDevice: "LNXSYSTM:00/device:00/ACPI0000:00",
353 + Sensors: Sensors{
354 + Intrusion: []*IntrusionSensor{
355 + {
356 + Name: "intrusion1",
357 + Label: "some_label",
358 + Alarm: ptr(false),
359 + },
360 + },
361 + }},
362 + },
363 fs: &memoryFilesystem{
364 symlinks: map[string]string{
616 - "/sys/class/hwmon/hwmon0": "../../devices/pci0000:00/0000:00:02.0/0000:03:00.0/hwmon/hwmon0",
617 - "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/hwmon/hwmon0/device": "../../../0000:03:00.0",
365 + "/sys/class/hwmon/hwmon0": "../../devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0",
366 + "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device": "../../../ACPI0000:00",
367 },
368 files: []memoryFile{
620 - {
621 - name: "/sys/class/hwmon",
622 - dirEntry: &memoryDirEntry{
623 - isDir: true,
624 - },
625 - },
626 - {
627 - name: "/sys/class/hwmon/hwmon0",
628 - dirEntry: &memoryDirEntry{
629 - mode: os.ModeSymlink,
630 - },
631 - },
632 - {
633 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0",
634 - dirEntry: &memoryDirEntry{
635 - isDir: true,
636 - },
637 - },
638 - {
639 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/hwmon/hwmon0/name",
640 - err: os.ErrNotExist,
641 - },
642 - {
643 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/hwmon/hwmon0/device",
644 - dirEntry: &memoryDirEntry{
645 - // mode: os.ModeSymlink,
646 - },
647 - },
648 - {
649 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/name",
650 - contents: "sfc",
651 - },
652 - {
653 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/curr1_alarm",
654 - contents: "0",
655 - },
656 - {
657 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/curr1_crit",
658 - contents: "18000",
659 - },
660 - {
661 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/curr1_input",
662 - contents: "7624",
663 - },
664 - {
665 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/curr1_label",
666 - contents: "0.9V supply current",
667 - },
668 - {
669 - name: "/sys/devices/pci0000:00/0000:00:02.0/0000:03:00.0/curr1_max",
670 - contents: "16000",
671 - },
369 + {name: "/sys/class/hwmon", de: &memoryDirEntry{isDir: true}},
370 + {name: "/sys/class/hwmon/hwmon0", de: &memoryDirEntry{mode: os.ModeSymlink}},
371 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00", de: &memoryDirEntry{isDir: true}},
372 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/name", err: os.ErrNotExist},
373 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/hwmon/hwmon0/device", de: &memoryDirEntry{}},
374 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/name", val: "intrusion_meter"},
375 +
376 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/intrusion1_label", val: "some_label"},
377 + {name: "/sys/devices/LNXSYSTM:00/device:00/ACPI0000:00/intrusion1_alarm", val: "0"},
378 },
379 },
674 - devices: []*Device{{
675 - Name: "sfc-00",
676 - Sensors: []Sensor{
677 - &CurrentSensor{
678 - Name: "curr1",
679 - Label: "0.9V supply current",
680 - Alarm: false,
681 - Input: 7.624,
682 - Maximum: 16.0,
683 - Critical: 18.0,
684 - },
685 - },
686 - }},
380 },
381 }
382 + for name, test := range tests {
383 + t.Run(name, func(t *testing.T) {
384 + sc := New()
385 + sc.fs = test.fs
386
690 - for _, tt := range tests {
691 - t.Run(tt.name, func(t *testing.T) {
692 - s := &Scanner{fs: tt.fs}
387 + devices, err := sc.Scan()
388 + require.NoError(t, err)
389
694 - devices, err := s.Scan()
695 - if err != nil {
696 - t.Fatalf("unexpected error: %v", err)
390 + for _, dev := range devices {
391 + for _, sn := range dev.Sensors.Voltage {
392 + require.NotZerof(t, sn.ReadTime, "zero read time: [voltage] dev '%s' sensor '%s'", dev.Name, sn.Name)
393 + sn.ReadTime = 0
394 + }
395 + for _, sn := range dev.Sensors.Fan {
396 + require.NotZerof(t, sn.ReadTime, "zero read time: [fan] dev '%s' sensor '%s'", dev.Name, sn.Name)
397 + sn.ReadTime = 0
398 + }
399 + for _, sn := range dev.Sensors.Temperature {
400 + require.NotZerof(t, sn.ReadTime, "zero read time: [temp] dev '%s' sensor '%s'", dev.Name, sn.Name)
401 + sn.ReadTime = 0
402 + }
403 + for _, sn := range dev.Sensors.Current {
404 + require.NotZerof(t, sn.ReadTime, "zero read time: [curr] dev '%s' sensor '%s'", dev.Name, sn.Name)
405 + sn.ReadTime = 0
406 + }
407 + for _, sn := range dev.Sensors.Power {
408 + require.NotZerof(t, sn.ReadTime, "zero read time: [power] dev '%s' sensor '%s'", dev.Name, sn.Name)
409 + sn.ReadTime = 0
410 + }
411 + for _, sn := range dev.Sensors.Energy {
412 + require.NotZerof(t, sn.ReadTime, "zero read time: [energy] dev '%s' sensor '%s'", dev.Name, sn.Name)
413 + sn.ReadTime = 0
414 + }
415 + for _, sn := range dev.Sensors.Humidity {
416 + require.NotZerof(t, sn.ReadTime, "zero read time: [humidity] dev '%s' sensor '%s'", dev.Name, sn.Name)
417 + sn.ReadTime = 0
418 + }
419 + for _, sn := range dev.Sensors.Intrusion {
420 + require.NotZerof(t, sn.ReadTime, "zero read time: [intrusion] dev '%s' sensor '%s'", dev.Name, sn.Name)
421 + sn.ReadTime = 0
422 + }
423 }
424
699 - if want, got := tt.devices, devices; !reflect.DeepEqual(want, got) {
700 - t.Fatalf("unexpected Devices:\n- want:\n%v\n- got:\n%v",
701 - devicesStr(want), devicesStr(got))
702 - }
425 + require.Equal(t, test.wantDevices, devices)
426 })
427 }
428 }
429
707 -func devicesStr(ds []*Device) string {
708 - var out string
709 - for _, d := range ds {
710 - out += fmt.Sprintf("device: %q [%d sensors]\n", d.Name, len(d.Sensors))
711 -
712 - for _, s := range d.Sensors {
713 - out += fmt.Sprintf(" - sensor: %#v\n", s)
714 - }
715 - }
716 -
717 - return out
718 -}
719 -
720 -var _ filesystem = &memoryFilesystem{}
721 -
722 -// A memoryFilesystem is an in-memory implementation of filesystem, used for
723 -// tests.
430 type memoryFilesystem struct {
431 symlinks map[string]string
432 files []memoryFile
433 }
434
729 -func (fs *memoryFilesystem) ReadFile(filename string) (string, error) {
730 - for _, f := range fs.files {
435 +func (m *memoryFilesystem) ReadDir(name string) ([]fs.DirEntry, error) {
436 + if !slices.ContainsFunc(m.files, func(file memoryFile) bool { return file.name == name }) {
437 + return nil, fmt.Errorf("readdir: dir %s not in memory", name)
438 + }
439 + var des []fs.DirEntry
440 + for _, v := range m.files {
441 + if strings.HasPrefix(v.name, name) {
442 + des = append(des, &memoryDirEntry{name: filepath.Base(v.name), isDir: false})
443 + }
444 + }
445 + return des, nil
446 +}
447 +
448 +func (m *memoryFilesystem) ReadFile(filename string) (string, error) {
449 + for _, f := range m.files {
450 if f.name == filename {
732 - return f.contents, nil
451 + return f.val, nil
452 }
453 }
454
455 return "", fmt.Errorf("readfile: file %q not in memory", filename)
456 }
457
739 -func (fs *memoryFilesystem) Readlink(name string) (string, error) {
740 - if l, ok := fs.symlinks[name]; ok {
458 +func (m *memoryFilesystem) Readlink(name string) (string, error) {
459 + if l, ok := m.symlinks[name]; ok {
460 return l, nil
461 }
462
463 return "", fmt.Errorf("readlink: symlink %q not in memory", name)
464 }
465
747 -func (fs *memoryFilesystem) Stat(name string) (os.FileInfo, error) {
748 - for _, f := range fs.files {
466 +func (m *memoryFilesystem) Stat(name string) (os.FileInfo, error) {
467 + for _, f := range m.files {
468 if f.name == name {
750 - de := f.dirEntry
469 + de := f.de
470 if de == nil {
471 de = &memoryDirEntry{}
472 }
@@ -759,18 +478,17 @@ func (fs *memoryFilesystem) Stat(name string) (os.FileInfo, error) {
478 return nil, fmt.Errorf("stat: file %q not in memory", name)
479 }
480
762 -func (fs *memoryFilesystem) WalkDir(root string, walkFn fs.WalkDirFunc) error {
763 - if _, err := fs.Stat(root); err != nil {
481 +func (m *memoryFilesystem) WalkDir(root string, walkFn fs.WalkDirFunc) error {
482 + if _, err := m.Stat(root); err != nil {
483 return err
484 }
485
767 - for _, f := range fs.files {
768 - // Only walk paths under the specified root
486 + for _, f := range m.files {
487 if !strings.HasPrefix(f.name, root) {
488 continue
489 }
490
773 - de := f.dirEntry
491 + de := f.de
492 if de == nil {
493 de = &memoryDirEntry{}
494 }
@@ -783,17 +501,13 @@ func (fs *memoryFilesystem) WalkDir(root string, walkFn fs.WalkDirFunc) error {
501 return nil
502 }
503
786 -// A memoryFile is an in-memory file used by memoryFilesystem.
504 type memoryFile struct {
788 - name string
789 - contents string
790 - dirEntry fs.DirEntry
791 - err error
505 + name string
506 + val string
507 + de fs.DirEntry
508 + err error
509 }
510
794 -var _ fs.DirEntry = &memoryDirEntry{}
795 -
796 -// A memoryDirEntry is a fs.DirEntry used by memoryFiles.
511 type memoryDirEntry struct {
512 name string
513 mode os.FileMode
src/go/plugin/go.d/modules/sensors/lmsensors/sensor.go
+160 -75
@@ -1,92 +1,177 @@
1 package lmsensors
2
3 import (
4 - "sort"
5 - "strings"
4 + "time"
5 )
6
8 -// A Device is a physical or virtual device which may have zero or
9 -// more Sensors.
10 -type Device struct {
11 - // The name of the device.
12 - Name string
7 +// A Chip is a physical or virtual device which may have zero or more Sensors.
8 +type Chip struct {
9 + Name string
10 + UniqueName string
11 + SysDevice string
12 + Sensors Sensors
13 +}
14
14 - // Any Sensors that belong to this Device. Use type assertions to
15 - // check for specific Sensor types and fetch their data.
16 - Sensors []Sensor
15 +type Sensors struct {
16 + // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
17 + Voltage []*VoltageSensor
18 + Fan []*FanSensor
19 + Temperature []*TemperatureSensor
20 + Current []*CurrentSensor
21 + Power []*PowerSensor
22 + Energy []*EnergySensor
23 + Humidity []*HumiditySensor
24 + Intrusion []*IntrusionSensor
25 }
26
19 -type SensorType string
27 +// A VoltageSensor is a Sensor that detects voltage.
28 +type VoltageSensor struct {
29 + ReadTime time.Duration
30
21 -const (
22 - SensorTypeCurrent SensorType = "current"
23 - SensorTypeFan SensorType = "fan"
24 - SensorTypeIntrusion SensorType = "intrusion"
25 - SensorTypePower SensorType = "power"
26 - SensorTypeTemperature SensorType = "temperature"
27 - SensorTypeVoltage SensorType = "voltage"
28 -)
31 + Name string
32 + Label string
33 +
34 + Alarm *bool
35 +
36 + Input *float64
37 + Average *float64
38 +
39 + Lowest *float64
40 + Highest *float64
41 +
42 + Min *float64
43 + Max *float64
44 + CritMin *float64
45 + CritMax *float64
46 +}
47 +
48 +// A FanSensor is a Sensor that detects fan speeds in rotations per minute.
49 +type FanSensor struct {
50 + ReadTime time.Duration
51 +
52 + Name string
53 + Label string
54 +
55 + Alarm *bool
56
30 -// A Sensor is a hardware sensor, used to retrieve device temperatures, fan speeds, voltages, etc.
31 -// Use type assertions to check for specific
32 -// Sensor types and fetch their data.
33 -type Sensor interface {
34 - Type() SensorType
57 + Input *float64
58 + Min *float64
59 + Max *float64
60 + Target *float64
61 }
62
37 -// parseSensors parses all Sensors from an input raw data slice, produced during a filesystem walk.
38 -func parseSensors(raw map[string]map[string]string) ([]Sensor, error) {
39 - sensors := make([]Sensor, 0, len(raw))
40 - for k, v := range raw {
41 - var sn Sensor
42 - var err error
43 -
44 - switch {
45 - case strings.HasPrefix(k, "curr"):
46 - s := &CurrentSensor{Name: k}
47 - sn = s
48 - err = s.parse(v)
49 - case strings.HasPrefix(k, "intrusion"):
50 - s := &IntrusionSensor{Name: k}
51 - sn = s
52 - err = s.parse(v)
53 - case strings.HasPrefix(k, "in"):
54 - s := &VoltageSensor{Name: k}
55 - sn = s
56 - err = s.parse(v)
57 - case strings.HasPrefix(k, "fan"):
58 - s := &FanSensor{Name: k}
59 - sn = s
60 - err = s.parse(v)
61 - case strings.HasPrefix(k, "power"):
62 - s := &PowerSensor{Name: k}
63 - sn = s
64 - err = s.parse(v)
65 - case strings.HasPrefix(k, "temp"):
66 - s := &TemperatureSensor{Name: k}
67 - sn = s
68 - err = s.parse(v)
69 - default:
70 - continue
71 - }
72 - if err != nil {
73 - return nil, err
74 - }
75 -
76 - if sn == nil {
77 - continue
78 - }
79 -
80 - sensors = append(sensors, sn)
63 +// A TemperatureSensor is a Sensor that detects temperatures in degrees Celsius.
64 +type TemperatureSensor struct {
65 + ReadTime time.Duration
66 +
67 + Name string
68 + Label string
69 + TempTypeRaw int
70 +
71 + Alarm *bool
72 +
73 + Input *float64
74 + Lowest *float64
75 + Highest *float64
76 + Min *float64
77 + Max *float64
78 + CritMin *float64
79 + CritMax *float64
80 + Emergency *float64
81 +}
82 +
83 +func (s TemperatureSensor) TempType() string {
84 + switch s.TempTypeRaw {
85 + case 1:
86 + return "CPU embedded diode"
87 + case 2:
88 + return "3904 transistor"
89 + case 3:
90 + return "thermal diode"
91 + case 4:
92 + return "thermistor"
93 + case 5:
94 + return "AMD AMDSI"
95 + case 6:
96 + return "Intel PECI"
97 + default:
98 + return ""
99 }
100 +}
101 +
102 +// A CurrentSensor is a Sensor that detects current in Amperes.
103 +type CurrentSensor struct {
104 + ReadTime time.Duration
105 +
106 + Name string
107 + Label string
108 +
109 + Alarm *bool
110 +
111 + Input *float64
112 + Lowest *float64
113 + Highest *float64
114 + Min *float64
115 + Max *float64
116 + CritMin *float64
117 + CritMax *float64
118 +
119 + Average *float64
120 +}
121 +
122 +// A PowerSensor is a Sensor that detects average electrical power consumption in watts.
123 +type PowerSensor struct {
124 + ReadTime time.Duration
125 +
126 + Name string
127 + Label string
128 +
129 + Alarm *bool
130 +
131 + Input *float64
132 + InputLowest *float64
133 + InputHighest *float64
134 + Cap *float64
135 + CapMin *float64
136 + CapMax *float64
137 + Max *float64
138 + CritMax *float64
139 +
140 + Average *float64
141 + AverageMin *float64
142 + AverageMax *float64
143 + AverageLowest *float64
144 + AverageHighest *float64
145 +
146 + Accuracy *float64
147 +}
148 +
149 +// A EnergySensor is a Sensor that detects energy in microJoule.
150 +type EnergySensor struct {
151 + ReadTime time.Duration
152 +
153 + Name string
154 + Label string
155 +
156 + Input *float64
157 +}
158 +
159 +// A HumiditySensor is a Sensor that detects humidity in milli-percent.
160 +type HumiditySensor struct {
161 + ReadTime time.Duration
162 +
163 + Name string
164 + Label string
165 +
166 + Input *float64
167 +}
168
83 - type namer interface{ name() string }
169 +// An IntrusionSensor is a Sensor that detects when the machine's chassis has been opened.
170 +type IntrusionSensor struct {
171 + ReadTime time.Duration
172
85 - sort.Slice(sensors, func(i, j int) bool {
86 - v1, ok1 := sensors[i].(namer)
87 - v2, ok2 := sensors[j].(namer)
88 - return ok1 && ok2 && v1.name() < v2.name()
89 - })
173 + Name string
174 + Label string
175
91 - return sensors, nil
176 + Alarm *bool
177 }
src/go/plugin/go.d/modules/sensors/lmsensors/temperaturesensor.go deleted
-108
@@ -1,108 +0,0 @@
1 -package lmsensors
2 -
3 -import (
4 - "strconv"
5 -)
6 -
7 -// A TemperatureSensorType is value that indicates the type of TemperatureSensor.
8 -type TemperatureSensorType int
9 -
10 -// All possible TemperatureSensorType constants.
11 -const (
12 - TemperatureSensorUnknown TemperatureSensorType = 0
13 - TemperatureSensorTypePIICeleronDiode TemperatureSensorType = 1
14 - TemperatureSensorType3904Transistor TemperatureSensorType = 2
15 - TemperatureSensorTypeThermalDiode TemperatureSensorType = 3
16 - TemperatureSensorTypeThermistor TemperatureSensorType = 4
17 - TemperatureSensorTypeAMDAMDSI TemperatureSensorType = 5
18 - TemperatureSensorTypeIntelPECI TemperatureSensorType = 6
19 -)
20 -
21 -var _ Sensor = &TemperatureSensor{}
22 -
23 -// A TemperatureSensor is a Sensor that detects temperatures in degrees
24 -// Celsius.
25 -type TemperatureSensor struct {
26 - // The name of the sensor.
27 - Name string
28 -
29 - // A label that describes what the sensor is monitoring. Label may be empty.
30 - Label string
31 -
32 - // Whether the sensor has an alarm triggered.
33 - Alarm bool
34 -
35 - // Whether the sensor will sound an audible alarm if an alarm
36 - // is triggered.
37 - Beep bool
38 -
39 - // The type of sensor used to report temperatures.
40 - TempType TemperatureSensorType
41 -
42 - // The input temperature, in degrees Celsius, indicated by the sensor.
43 - Input float64
44 -
45 - // A low threshold temperature, in degrees Celsius, indicated by the sensor.
46 - Minimum float64
47 -
48 - // A high threshold temperature, in degrees Celsius, indicated by the sensor.
49 - Maximum float64
50 -
51 - // A critical threshold temperature, in degrees Celsius, indicated by the sensor.
52 - Critical float64
53 -
54 - // An emergency threshold temperature, in degrees Celsius, indicated by the sensor.
55 - Emergency float64
56 -
57 - // Whether the temperature is past the critical threshold.
58 - CriticalAlarm bool
59 -}
60 -
61 -func (s *TemperatureSensor) Type() SensorType { return SensorTypeTemperature }
62 -
63 -func (s *TemperatureSensor) parse(raw map[string]string) error {
64 - for k, v := range raw {
65 - switch k {
66 - case "input", "min", "max", "crit", "emergency":
67 - f, err := strconv.ParseFloat(v, 64)
68 - if err != nil {
69 - return err
70 - }
71 -
72 - // Raw temperature values are scaled by 1000
73 - f /= 1000
74 -
75 - switch k {
76 - case "input":
77 - s.Input = f
78 - case "min":
79 - s.Minimum = f
80 - case "max":
81 - s.Maximum = f
82 - case "crit":
83 - s.Critical = f
84 - case "emergency":
85 - s.Emergency = f
86 - }
87 - case "alarm":
88 - s.Alarm = v != "0"
89 - case "beep":
90 - s.Beep = v != "0"
91 - case "type":
92 - t, err := strconv.Atoi(v)
93 - if err != nil {
94 - return err
95 - }
96 -
97 - s.TempType = TemperatureSensorType(t)
98 - case "crit_alarm":
99 - s.CriticalAlarm = v != "0"
100 - case "label":
101 - s.Label = v
102 - }
103 - }
104 -
105 - return nil
106 -}
107 -
108 -func (s *TemperatureSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/lmsensors/voltagesensor.go deleted
-68
@@ -1,68 +0,0 @@
1 -package lmsensors
2 -
3 -import (
4 - "strconv"
5 -)
6 -
7 -var _ Sensor = &VoltageSensor{}
8 -
9 -// A VoltageSensor is a Sensor that detects voltage.
10 -type VoltageSensor struct {
11 - // The name of the sensor.
12 - Name string
13 -
14 - // A label that describes what the sensor is monitoring. Label may be empty.
15 - Label string
16 -
17 - // Whether the sensor has an alarm triggered.
18 - Alarm bool
19 -
20 - // Whether the sensor will sound an audible alarm when an alarm
21 - // is triggered.
22 - Beep bool
23 -
24 - // The input voltage indicated by the sensor.
25 - Input float64
26 -
27 - // The minimum voltage threshold indicated by the sensor.
28 - Min float64
29 -
30 - // The maximum voltage threshold indicated by the sensor.
31 - Maximum float64
32 -}
33 -
34 -func (s *VoltageSensor) Type() SensorType { return SensorTypeVoltage }
35 -
36 -func (s *VoltageSensor) parse(raw map[string]string) error {
37 - for k, v := range raw {
38 - switch k {
39 - case "input", "min", "max":
40 - f, err := strconv.ParseFloat(v, 64)
41 - if err != nil {
42 - return err
43 - }
44 -
45 - // Raw temperature values are scaled by 1000
46 - f /= 1000
47 -
48 - switch k {
49 - case "input":
50 - s.Input = f
51 - case "min":
52 - s.Min = f
53 - case "max":
54 - s.Maximum = f
55 - }
56 - case "alarm":
57 - s.Alarm = v != "0"
58 - case "beep":
59 - s.Beep = v != "0"
60 - case "label":
61 - s.Label = v
62 - }
63 - }
64 -
65 - return nil
66 -}
67 -
68 -func (s *VoltageSensor) name() string { return s.Name }
src/go/plugin/go.d/modules/sensors/metadata.yaml
+97 -51
@@ -5,7 +5,7 @@ modules:
5 plugin_name: go.d.plugin
6 module_name: sensors
7 monitored_instance:
8 - name: Linux Sensors (lm-sensors)
8 + name: Linux Sensors
9 link: https://hwmon.wiki.kernel.org/lm_sensors
10 icon_filename: "microchip.svg"
11 categories:
@@ -19,6 +19,7 @@ modules:
19 - fan
20 - energy
21 - humidity
22 + - intrusion
23 related_resources:
24 integrations:
25 list: []
@@ -30,7 +31,7 @@ modules:
31 metrics_description: >
32 This collector gathers real-time system sensor statistics,
33 including temperature, voltage, current, power, fan speed, energy consumption, and humidity,
33 - utilizing the [sensors](https://linux.die.net/man/1/sensors) binary or [sysfs](https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface).
34 + utilizing the [sysfs](https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface) interface.
35 method_description: ""
36 supported_platforms:
37 include: []
@@ -44,12 +45,13 @@ modules:
45 The following type of sensors are auto-detected:
46
47 - temperature
47 - - fan
48 - voltage
49 + - fan
50 - current
51 - power
52 - energy
53 - humidity
54 + - intrusion
55 limits:
56 description: ""
57 performance_impact:
@@ -71,31 +73,17 @@ modules:
73 description: Data collection frequency.
74 default_value: 10
75 required: false
74 - - name: binary_path
75 - description: Path to the `sensors` binary. If left empty or if the binary is not found, [sysfs](https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface) will be used to collect sensor statistics.
76 - default_value: /usr/bin/sensors
77 - required: true
78 - - name: timeout
79 - description: Timeout for executing the binary, specified in seconds.
80 - default_value: 2
81 - required: false
76 examples:
77 folding:
78 title: Config
79 enabled: true
80 list:
87 - - name: Custom binary path
88 - description: The executable is not in the directories specified in the PATH environment variable.
81 + - name: Custom update_every
82 + description: Allows you to override the default data collection interval.
83 config: |
84 jobs:
85 - name: sensors
92 - binary_path: /usr/local/sbin/sensors
93 - - name: Use sysfs instead of sensors
94 - description: Set `binary_path` to an empty string to use sysfs.
95 - config: |
96 - jobs:
97 - - name: sensors
98 - binary_path: ""
86 + update_every: 5 # Collect sensors statistics every 5 seconds
87 troubleshooting:
88 problems:
89 list: []
@@ -108,59 +96,117 @@ modules:
96 availability: []
97 scopes:
98 - name: sensor
111 - description: These metrics refer to the sensor.
99 + description: These metrics refer to the system sensor.
100 labels:
101 - name: chip
114 - description: The hardware component responsible for the sensor monitoring.
115 - - name: feature
116 - description: The specific sensor or monitoring point provided by the chip.
102 + description: The path to the sensor's chip device, excluding the /sys/devices prefix. This provides a unique identifier for the physical hardware component.
103 + - name: chip_id
104 + description: A unique identifier for the sensor's chip, formatted as `chipName-busType-hash`.
105 + - name: sensor
106 + description: The name of the specific sensor within the chip device. This provides a direct identifier for the individual measurement point.
107 + - name: label
108 + description: A label provided by the kernel driver to indicate the intended use or purpose of the sensor.
109 +
110 metrics:
118 - - name: sensors.sensor_temperature
119 - description: Sensor temperature
111 + - name: sensors.chip_sensor_temperature
112 + description: Sensor Temperature
113 unit: Celsius
114 chart_type: line
115 dimensions:
123 - - name: temperature
124 - - name: sensors.sensor_voltage
125 - description: Sensor voltage
116 + - name: input
117 + - name: sensors.chip_sensor_temperature_alarm
118 + description: Temperature Sensor Alarm
119 + unit: status
120 + chart_type: line
121 + dimensions:
122 + - name: clear
123 + - name: triggered
124 + - name: sensors.chip_sensor_voltage
125 + description: Sensor Voltage
126 + unit: Volts
127 + chart_type: line
128 + dimensions:
129 + - name: input
130 + - name: sensors.chip_sensor_voltage_average
131 + description: Sensor Voltage Average
132 unit: Volts
133 chart_type: line
134 dimensions:
129 - - name: voltage
130 - - name: sensors.sensor_current
131 - description: Sensor current
135 + - name: average
136 + - name: sensors.chip_sensor_voltage_alarm
137 + description: Voltage Sensor Alarm
138 + unit: status
139 + chart_type: line
140 + dimensions:
141 + - name: clear
142 + - name: triggered
143 + - name: sensors.chip_sensor_fan
144 + description: Sensor Fan
145 + unit: RPM
146 + chart_type: line
147 + dimensions:
148 + - name: input
149 + - name: sensors.chip_sensor_fan_alarm
150 + description: Fan Sensor Alarm
151 + unit: status
152 + chart_type: line
153 + dimensions:
154 + - name: clear
155 + - name: triggered
156 + - name: sensors.chip_sensor_current
157 + description: Sensor Current
158 unit: Amperes
159 chart_type: line
160 dimensions:
135 - - name: current
136 - - name: sensors.sensor_power
137 - description: Sensor power
161 + - name: input
162 + - name: sensors.chip_sensor_current_average
163 + description: Sensor Current Average
164 + unit: Amperes
165 + chart_type: line
166 + dimensions:
167 + - name: average
168 + - name: sensors.chip_sensor_current_alarm
169 + description: Current Sensor Alarm
170 + unit: status
171 + chart_type: line
172 + dimensions:
173 + - name: clear
174 + - name: triggered
175 + - name: sensors.chip_sensor_power
176 + description: Sensor Power
177 unit: Watts
178 chart_type: line
179 dimensions:
141 - - name: power
142 - - name: sensors.sensor_fan_speed
143 - description: Sensor fan speed
144 - unit: RPM
180 + - name: input
181 + - name: sensors.chip_sensor_power_average
182 + description: Sensor Power Average
183 + unit: Watts
184 chart_type: line
185 dimensions:
147 - - name: fan
148 - - name: sensors.sensor_energy
149 - description: Sensor energy
186 + - name: average
187 + - name: sensors.chip_sensor_power_alarm
188 + description: Power Sensor Alarm
189 + unit: status
190 + chart_type: line
191 + dimensions:
192 + - name: clear
193 + - name: triggered
194 + - name: sensors.chip_sensor_energy
195 + description: Sensor Energy
196 unit: Joules
197 chart_type: line
198 dimensions:
153 - - name: energy
154 - - name: sensors.sensor_humidity
155 - description: Sensor humidity
199 + - name: input
200 + - name: sensors.chip_sensor_humidity
201 + description: Sensor Humidity
202 unit: percent
157 - chart_type: area
203 + chart_type: line
204 dimensions:
159 - - name: humidity
160 - - name: sensors.sensor_intrusion
161 - description: Sensor intrusion
205 + - name: input
206 + - name: sensors.chip_sensor_intrusion_alarm
207 + description: Intrusion Sensor Alarm
208 unit: status
209 chart_type: line
210 dimensions:
165 - - name: alarm_clear
166 - - name: alarm_triggered
211 + - name: clear
212 + - name: triggered
src/go/plugin/go.d/modules/sensors/sensors.go
+7 -24
@@ -5,11 +5,9 @@ package sensors
5 import (
6 _ "embed"
7 "errors"
8 - "time"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
10 "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/sensors/lmsensors"
12 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
11 )
12
13 //go:embed "config_schema.json"
@@ -28,19 +26,14 @@ func init() {
26
27 func New() *Sensors {
28 return &Sensors{
31 - Config: Config{
32 - BinaryPath: "/usr/bin/sensors",
33 - Timeout: confopt.Duration(time.Second * 2),
34 - },
35 - charts: &module.Charts{},
36 - sensors: make(map[string]bool),
29 + Config: Config{},
30 + charts: &module.Charts{},
31 + seenSensors: make(map[string]bool),
32 }
33 }
34
35 type Config struct {
41 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
42 - Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
43 - BinaryPath string `yaml:"binary_path" json:"binary_path"`
36 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
37 }
38
39 type (
@@ -50,16 +43,12 @@ type (
43
44 charts *module.Charts
45
53 - exec sensorsBinary
54 - sc sysfsScanner
46 + sc sysfsScanner
47
56 - sensors map[string]bool
57 - }
58 - sensorsBinary interface {
59 - sensorsInfo() ([]byte, error)
48 + seenSensors map[string]bool
49 }
50 sysfsScanner interface {
62 - Scan() ([]*lmsensors.Device, error)
51 + Scan() ([]*lmsensors.Chip, error)
52 }
53 )
54
@@ -68,12 +57,6 @@ func (s *Sensors) Configuration() any {
57 }
58
59 func (s *Sensors) Init() error {
71 - if sb, err := s.initSensorsBinary(); err != nil {
72 - s.Infof("sensors exec initialization: %v", err)
73 - } else if sb != nil {
74 - s.exec = sb
75 - }
76 -
60 sc := lmsensors.New()
61 sc.Logger = s.Logger
62 s.sc = sc
src/go/plugin/go.d/modules/sensors/sensors_test.go
+322 -290
@@ -17,16 +17,12 @@ import (
17 var (
18 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
19 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
20 -
21 - dataSensors, _ = os.ReadFile("testdata/sensors.txt")
20 )
21
22 func Test_testDataIsValid(t *testing.T) {
23 for name, data := range map[string][]byte{
24 "dataConfigJSON": dataConfigJSON,
25 "dataConfigYAML": dataConfigYAML,
28 -
29 - "dataSensors": dataSensors,
26 } {
27 require.NotNil(t, data, name)
28
@@ -42,17 +38,9 @@ func TestSensors_Init(t *testing.T) {
38 config Config
39 wantFail bool
40 }{
45 - "success if 'binary_path' is not set": {
46 - wantFail: false,
47 - config: Config{
48 - BinaryPath: "",
49 - },
50 - },
51 - "success if failed to find binary": {
41 + "success with default config": {
42 wantFail: false,
53 - config: Config{
54 - BinaryPath: "sensors!!!",
55 - },
43 + config: New().Config,
44 },
45 }
46
@@ -82,7 +70,7 @@ func TestSensors_Cleanup(t *testing.T) {
70 "after check": {
71 prepare: func() *Sensors {
72 sensors := New()
85 - sensors.exec = prepareMockExecOk()
73 + sensors.sc = prepareMockScannerOk()
74 _ = sensors.Check()
75 return sensors
76 },
@@ -90,7 +78,7 @@ func TestSensors_Cleanup(t *testing.T) {
78 "after collect": {
79 prepare: func() *Sensors {
80 sensors := New()
93 - sensors.exec = prepareMockExecOk()
81 + sensors.sc = prepareMockScannerOk()
82 _ = sensors.Collect()
83 return sensors
84 },
@@ -112,32 +100,23 @@ func TestSensors_Charts(t *testing.T) {
100
101 func TestSensors_Check(t *testing.T) {
102 tests := map[string]struct {
115 - prepareMock func() *mockSensorsBinary
103 + prepareMock func() *mockScanner
104 wantFail bool
105 }{
118 - "exec: only temperature": {
106 + "multiple sensors": {
107 wantFail: false,
120 - prepareMock: prepareMockExecOk,
121 - },
122 - "exec: error on sensors info call": {
123 - wantFail: true,
124 - prepareMock: prepareMockExecErr,
108 + prepareMock: prepareMockScannerOk,
109 },
126 - "exec: empty response": {
110 + "error on scan": {
111 wantFail: true,
128 - prepareMock: prepareMockExecEmptyResponse,
129 - },
130 - "exec: unexpected response": {
131 - wantFail: true,
132 - prepareMock: prepareMockExecUnexpectedResponse,
112 + prepareMock: prepareMockScannerErr,
113 },
114 }
115
116 for name, test := range tests {
117 t.Run(name, func(t *testing.T) {
118 sensors := New()
139 - mock := test.prepareMock()
140 - sensors.exec = mock
119 + sensors.sc = test.prepareMock()
120
121 if test.wantFail {
122 assert.Error(t, sensors.Check())
@@ -150,99 +129,141 @@ func TestSensors_Check(t *testing.T) {
129
130 func TestSensors_Collect(t *testing.T) {
131 tests := map[string]struct {
153 - prepareExecMock func() *mockSensorsBinary
154 - prepareSysfsMock func() *mockSysfsScanner
155 - wantMetrics map[string]int64
156 - wantCharts int
132 + prepareScanner func() *mockScanner
133 + wantMetrics map[string]int64
134 + wantCharts int
135 }{
158 - "exec: multiple sensors": {
159 - prepareExecMock: prepareMockExecOk,
160 - wantCharts: 22,
136 + "multiple sensors": {
137 + prepareScanner: prepareMockScannerOk,
138 + wantCharts: 24,
139 wantMetrics: map[string]int64{
162 - "sensor_chip_acpitz-acpi-0_feature_temp1_subfeature_temp1_input": 88000,
163 - "sensor_chip_amdgpu-pci-0300_feature_edge_subfeature_temp1_input": 53000,
164 - "sensor_chip_amdgpu-pci-0300_feature_fan1_subfeature_fan1_input": 0,
165 - "sensor_chip_amdgpu-pci-0300_feature_junction_subfeature_temp2_input": 58000,
166 - "sensor_chip_amdgpu-pci-0300_feature_mem_subfeature_temp3_input": 57000,
167 - "sensor_chip_amdgpu-pci-0300_feature_ppt_subfeature_power1_average": 29000,
168 - "sensor_chip_amdgpu-pci-0300_feature_vddgfx_subfeature_in0_input": 787,
169 - "sensor_chip_amdgpu-pci-6700_feature_edge_subfeature_temp1_input": 60000,
170 - "sensor_chip_amdgpu-pci-6700_feature_ppt_subfeature_power1_average": 5088,
171 - "sensor_chip_amdgpu-pci-6700_feature_vddgfx_subfeature_in0_input": 1335,
172 - "sensor_chip_amdgpu-pci-6700_feature_vddnb_subfeature_in1_input": 973,
173 - "sensor_chip_asus-isa-0000_feature_cpu_fan_subfeature_fan1_input": 5700000,
174 - "sensor_chip_asus-isa-0000_feature_gpu_fan_subfeature_fan2_input": 6600000,
175 - "sensor_chip_bat0-acpi-0_feature_in0_subfeature_in0_input": 17365,
176 - "sensor_chip_k10temp-pci-00c3_feature_tctl_subfeature_temp1_input": 90000,
177 - "sensor_chip_nct6779-isa-0290_feature_intrusion0_subfeature_intrusion0_alarm_clear": 0,
178 - "sensor_chip_nct6779-isa-0290_feature_intrusion0_subfeature_intrusion0_alarm_triggered": 1,
179 - "sensor_chip_nct6779-isa-0290_feature_intrusion1_subfeature_intrusion1_alarm_clear": 0,
180 - "sensor_chip_nct6779-isa-0290_feature_intrusion1_subfeature_intrusion1_alarm_triggered": 1,
181 - "sensor_chip_nvme-pci-0600_feature_composite_subfeature_temp1_input": 33850,
182 - "sensor_chip_nvme-pci-0600_feature_sensor_1_subfeature_temp2_input": 48850,
183 - "sensor_chip_nvme-pci-0600_feature_sensor_2_subfeature_temp3_input": 33850,
184 - "sensor_chip_ucsi_source_psy_usbc000:001-isa-0000_feature_curr1_subfeature_curr1_input": 0,
185 - "sensor_chip_ucsi_source_psy_usbc000:001-isa-0000_feature_in0_subfeature_in0_input": 0,
140 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_alarm_clear": 1,
141 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_alarm_triggered": 0,
142 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_average": 42000,
143 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_crit": 42000,
144 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_highest": 42000,
145 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_input": 42000,
146 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_lcrit": 42000,
147 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_lowest": 42000,
148 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_max": 42000,
149 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_min": 42000,
150 + "chip_chip0-pci-xxxxxxxx_sensor_curr1_read_time": 0,
151 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_crit": 42000,
152 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_highest": 42000,
153 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_input": 42000,
154 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_lcrit": 42000,
155 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_lowest": 42000,
156 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_max": 42000,
157 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_min": 42000,
158 + "chip_chip0-pci-xxxxxxxx_sensor_curr2_read_time": 0,
159 + "chip_chip0-pci-xxxxxxxx_sensor_energy1_input": 42000,
160 + "chip_chip0-pci-xxxxxxxx_sensor_energy1_read_time": 0,
161 + "chip_chip0-pci-xxxxxxxx_sensor_energy2_input": 42000,
162 + "chip_chip0-pci-xxxxxxxx_sensor_energy2_read_time": 0,
163 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_alarm_clear": 1,
164 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_alarm_triggered": 0,
165 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_input": 42000,
166 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_max": 42000,
167 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_min": 42000,
168 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_read_time": 0,
169 + "chip_chip0-pci-xxxxxxxx_sensor_fan1_target": 42000,
170 + "chip_chip0-pci-xxxxxxxx_sensor_fan2_input": 42000,
171 + "chip_chip0-pci-xxxxxxxx_sensor_fan2_max": 42000,
172 + "chip_chip0-pci-xxxxxxxx_sensor_fan2_min": 42000,
173 + "chip_chip0-pci-xxxxxxxx_sensor_fan2_read_time": 0,
174 + "chip_chip0-pci-xxxxxxxx_sensor_fan2_target": 42000,
175 + "chip_chip0-pci-xxxxxxxx_sensor_humidity1_input": 42000,
176 + "chip_chip0-pci-xxxxxxxx_sensor_humidity1_read_time": 0,
177 + "chip_chip0-pci-xxxxxxxx_sensor_humidity2_input": 42000,
178 + "chip_chip0-pci-xxxxxxxx_sensor_humidity2_read_time": 0,
179 + "chip_chip0-pci-xxxxxxxx_sensor_in1_alarm_clear": 1,
180 + "chip_chip0-pci-xxxxxxxx_sensor_in1_alarm_triggered": 0,
181 + "chip_chip0-pci-xxxxxxxx_sensor_in1_average": 42000,
182 + "chip_chip0-pci-xxxxxxxx_sensor_in1_crit": 42000,
183 + "chip_chip0-pci-xxxxxxxx_sensor_in1_highest": 42000,
184 + "chip_chip0-pci-xxxxxxxx_sensor_in1_input": 42000,
185 + "chip_chip0-pci-xxxxxxxx_sensor_in1_lcrit": 42000,
186 + "chip_chip0-pci-xxxxxxxx_sensor_in1_lowest": 42000,
187 + "chip_chip0-pci-xxxxxxxx_sensor_in1_max": 42000,
188 + "chip_chip0-pci-xxxxxxxx_sensor_in1_min": 42000,
189 + "chip_chip0-pci-xxxxxxxx_sensor_in1_read_time": 0,
190 + "chip_chip0-pci-xxxxxxxx_sensor_in2_crit": 42000,
191 + "chip_chip0-pci-xxxxxxxx_sensor_in2_highest": 42000,
192 + "chip_chip0-pci-xxxxxxxx_sensor_in2_input": 42000,
193 + "chip_chip0-pci-xxxxxxxx_sensor_in2_lcrit": 42000,
194 + "chip_chip0-pci-xxxxxxxx_sensor_in2_lowest": 42000,
195 + "chip_chip0-pci-xxxxxxxx_sensor_in2_max": 42000,
196 + "chip_chip0-pci-xxxxxxxx_sensor_in2_min": 42000,
197 + "chip_chip0-pci-xxxxxxxx_sensor_in2_read_time": 0,
198 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion1_alarm_clear": 1,
199 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion1_alarm_triggered": 0,
200 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion1_read_time": 0,
201 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion2_alarm_clear": 0,
202 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion2_alarm_triggered": 1,
203 + "chip_chip0-pci-xxxxxxxx_sensor_intrusion2_read_time": 0,
204 + "chip_chip0-pci-xxxxxxxx_sensor_power1_accuracy": 34500,
205 + "chip_chip0-pci-xxxxxxxx_sensor_power1_alarm_clear": 1,
206 + "chip_chip0-pci-xxxxxxxx_sensor_power1_alarm_triggered": 0,
207 + "chip_chip0-pci-xxxxxxxx_sensor_power1_average": 345000,
208 + "chip_chip0-pci-xxxxxxxx_sensor_power1_average_highest": 345000,
209 + "chip_chip0-pci-xxxxxxxx_sensor_power1_average_lowest": 345000,
210 + "chip_chip0-pci-xxxxxxxx_sensor_power1_average_max": 345000,
211 + "chip_chip0-pci-xxxxxxxx_sensor_power1_average_min": 345000,
212 + "chip_chip0-pci-xxxxxxxx_sensor_power1_cap": 345000,
213 + "chip_chip0-pci-xxxxxxxx_sensor_power1_cap_max": 345000,
214 + "chip_chip0-pci-xxxxxxxx_sensor_power1_cap_min": 345000,
215 + "chip_chip0-pci-xxxxxxxx_sensor_power1_crit": 345000,
216 + "chip_chip0-pci-xxxxxxxx_sensor_power1_input": 345000,
217 + "chip_chip0-pci-xxxxxxxx_sensor_power1_input_highest": 345000,
218 + "chip_chip0-pci-xxxxxxxx_sensor_power1_input_lowest": 345000,
219 + "chip_chip0-pci-xxxxxxxx_sensor_power1_max": 345000,
220 + "chip_chip0-pci-xxxxxxxx_sensor_power1_read_time": 0,
221 + "chip_chip0-pci-xxxxxxxx_sensor_power2_accuracy": 34500,
222 + "chip_chip0-pci-xxxxxxxx_sensor_power2_average_highest": 345000,
223 + "chip_chip0-pci-xxxxxxxx_sensor_power2_average_lowest": 345000,
224 + "chip_chip0-pci-xxxxxxxx_sensor_power2_average_max": 345000,
225 + "chip_chip0-pci-xxxxxxxx_sensor_power2_average_min": 345000,
226 + "chip_chip0-pci-xxxxxxxx_sensor_power2_cap": 345000,
227 + "chip_chip0-pci-xxxxxxxx_sensor_power2_cap_max": 345000,
228 + "chip_chip0-pci-xxxxxxxx_sensor_power2_cap_min": 345000,
229 + "chip_chip0-pci-xxxxxxxx_sensor_power2_crit": 345000,
230 + "chip_chip0-pci-xxxxxxxx_sensor_power2_input": 345000,
231 + "chip_chip0-pci-xxxxxxxx_sensor_power2_input_highest": 345000,
232 + "chip_chip0-pci-xxxxxxxx_sensor_power2_input_lowest": 345000,
233 + "chip_chip0-pci-xxxxxxxx_sensor_power2_max": 345000,
234 + "chip_chip0-pci-xxxxxxxx_sensor_power2_read_time": 0,
235 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_alarm_clear": 1,
236 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_alarm_triggered": 0,
237 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_crit": 42000,
238 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_emergency": 42000,
239 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_highest": 42000,
240 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_input": 42000,
241 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_lcrit": 42000,
242 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_lowest": 42000,
243 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_max": 42000,
244 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_min": 42000,
245 + "chip_chip0-pci-xxxxxxxx_sensor_temp1_read_time": 0,
246 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_crit": 42000,
247 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_emergency": 42000,
248 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_highest": 42000,
249 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_input": 42000,
250 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_lcrit": 42000,
251 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_lowest": 42000,
252 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_max": 42000,
253 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_min": 42000,
254 + "chip_chip0-pci-xxxxxxxx_sensor_temp2_read_time": 0,
255 },
256 },
188 - "exec: error on sensors info call": {
189 - prepareExecMock: prepareMockExecErr,
190 - wantMetrics: nil,
191 - },
192 - "exec: empty response": {
193 - prepareExecMock: prepareMockExecEmptyResponse,
194 - wantMetrics: nil,
195 - },
196 - "exec: unexpected response": {
197 - prepareExecMock: prepareMockExecUnexpectedResponse,
198 - wantMetrics: nil,
199 - },
200 -
201 - "sysfs: multiple sensors": {
202 - prepareSysfsMock: prepareMockSysfsScannerOk,
203 - wantCharts: 21,
204 - wantMetrics: map[string]int64{
205 - "sensor_chip_acpitz-acpi-0_feature_temp1_subfeature_temp1_input": 88000,
206 - "sensor_chip_amdgpu-pci-0300_feature_edge_subfeature_temp1_input": 53000,
207 - "sensor_chip_amdgpu-pci-0300_feature_fan1_subfeature_fan1_input": 0,
208 - "sensor_chip_amdgpu-pci-0300_feature_junction_subfeature_temp2_input": 58000,
209 - "sensor_chip_amdgpu-pci-0300_feature_mem_subfeature_temp3_input": 57000,
210 - "sensor_chip_amdgpu-pci-0300_feature_ppt_subfeature_power1_average": 29000,
211 - "sensor_chip_amdgpu-pci-0300_feature_vddgfx_subfeature_in0_input": 787,
212 - "sensor_chip_amdgpu-pci-6700_feature_edge_subfeature_temp1_input": 60000,
213 - "sensor_chip_amdgpu-pci-6700_feature_ppt_subfeature_power1_average": 5088,
214 - "sensor_chip_amdgpu-pci-6700_feature_vddgfx_subfeature_in0_input": 1335,
215 - "sensor_chip_amdgpu-pci-6700_feature_vddnb_subfeature_in1_input": 973,
216 - "sensor_chip_asus-isa-0000_feature_cpu_fan_subfeature_fan1_input": 5700000,
217 - "sensor_chip_asus-isa-0000_feature_gpu_fan_subfeature_fan2_input": 6600000,
218 - "sensor_chip_asus-isa-0000_feature_intrusion0_subfeature_intrusion0_alarm_clear": 0,
219 - "sensor_chip_asus-isa-0000_feature_intrusion0_subfeature_intrusion0_alarm_triggered": 1,
220 - "sensor_chip_bat0-acpi-0_feature_in0_subfeature_in0_input": 17365,
221 - "sensor_chip_k10temp-pci-00c3_feature_tctl_subfeature_temp1_input": 90000,
222 - "sensor_chip_nvme-pci-0600_feature_composite_subfeature_temp1_input": 33850,
223 - "sensor_chip_nvme-pci-0600_feature_sensor_1_subfeature_temp2_input": 48850,
224 - "sensor_chip_nvme-pci-0600_feature_sensor_2_subfeature_temp3_input": 33850,
225 - "sensor_chip_ucsi_source_psy_usbc000:001-isa-0000_feature_curr1_subfeature_curr1_input": 0,
226 - "sensor_chip_ucsi_source_psy_usbc000:001-isa-0000_feature_in0_subfeature_in0_input": 0,
227 - },
228 - },
229 - "sysfs: error on scan": {
230 - prepareSysfsMock: prepareMockSysfsScannerErr,
231 - wantMetrics: nil,
257 + "error on scan": {
258 + prepareScanner: prepareMockScannerErr,
259 + wantMetrics: nil,
260 },
261 }
262
263 for name, test := range tests {
264 t.Run(name, func(t *testing.T) {
265 sensors := New()
238 -
239 - if test.prepareExecMock != nil {
240 - sensors.exec = test.prepareExecMock()
241 - } else if test.prepareSysfsMock != nil {
242 - sensors.sc = test.prepareSysfsMock()
243 - } else {
244 - t.Fail()
245 - }
266 + sensors.sc = test.prepareScanner()
267
268 var mx map[string]int64
269
@@ -261,201 +282,212 @@ func TestSensors_Collect(t *testing.T) {
282 }
283 }
284
264 -func prepareMockExecOk() *mockSensorsBinary {
265 - return &mockSensorsBinary{
266 - sensorsInfoData: dataSensors,
267 - }
268 -}
269 -
270 -func prepareMockExecErr() *mockSensorsBinary {
271 - return &mockSensorsBinary{
272 - errOnSensorsInfo: true,
285 +func prepareMockScannerOk() *mockScanner {
286 + return &mockScanner{
287 + scanData: mockChips(),
288 }
289 }
290
276 -func prepareMockExecUnexpectedResponse() *mockSensorsBinary {
277 - return &mockSensorsBinary{
278 - sensorsInfoData: []byte(`
279 -Lorem ipsum dolor sit amet, consectetur adipiscing elit.
280 -Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus.
281 -Fusce et felis pulvinar, posuere sem non, porttitor eros.
282 -`),
291 +func prepareMockScannerErr() *mockScanner {
292 + return &mockScanner{
293 + errOnScan: true,
294 }
295 }
296
286 -func prepareMockExecEmptyResponse() *mockSensorsBinary {
287 - return &mockSensorsBinary{}
288 -}
289 -
290 -type mockSensorsBinary struct {
291 - errOnSensorsInfo bool
292 - sensorsInfoData []byte
297 +type mockScanner struct {
298 + errOnScan bool
299 + scanData []*lmsensors.Chip
300 }
301
295 -func (m *mockSensorsBinary) sensorsInfo() ([]byte, error) {
296 - if m.errOnSensorsInfo {
297 - return nil, errors.New("mock.sensorsInfo() error")
302 +func (m *mockScanner) Scan() ([]*lmsensors.Chip, error) {
303 + if m.errOnScan {
304 + return nil, errors.New("mock.scan() error")
305 }
299 -
300 - return m.sensorsInfoData, nil
306 + return m.scanData, nil
307 }
308
303 -func prepareMockSysfsScannerOk() *mockSysfsScanner {
304 - return &mockSysfsScanner{
305 - scanData: []*lmsensors.Device{
306 - {Name: "asus-isa-0000", Sensors: []lmsensors.Sensor{
307 - &lmsensors.FanSensor{
308 - Name: "fan1",
309 - Label: "cpu_fan",
310 - Input: 5700,
311 - },
312 - &lmsensors.FanSensor{
313 - Name: "fan2",
314 - Label: "gpu_fan",
315 - Input: 6600,
316 - },
317 - &lmsensors.IntrusionSensor{
318 - Name: "intrusion0",
319 - Label: "intrusion0",
320 - Alarm: true,
321 - },
322 - }},
323 - {Name: "nvme-pci-0600", Sensors: []lmsensors.Sensor{
324 - &lmsensors.TemperatureSensor{
325 - Name: "temp1",
326 - Label: "Composite",
327 - Input: 33.85,
328 - Maximum: 83.85,
329 - Minimum: -40.15,
330 - Critical: 87.85,
331 - Alarm: false,
332 - },
333 - &lmsensors.TemperatureSensor{
334 - Name: "temp2",
335 - Label: "Sensor 1",
336 - Input: 48.85,
337 - Maximum: 65261.85,
338 - Minimum: -273.15,
339 - },
340 - &lmsensors.TemperatureSensor{
341 - Name: "temp3",
342 - Label: "Sensor 2",
343 - Input: 33.85,
344 - Maximum: 65261.85,
345 - Minimum: -273.15,
346 - },
347 - }},
348 - {Name: "amdgpu-pci-6700", Sensors: []lmsensors.Sensor{
349 - &lmsensors.VoltageSensor{
350 - Name: "in0",
351 - Label: "vddgfx",
352 - Input: 1.335,
353 - },
354 - &lmsensors.VoltageSensor{
355 - Name: "in1",
356 - Label: "vddnb",
357 - Input: 0.973,
358 - },
359 - &lmsensors.TemperatureSensor{
360 - Name: "temp1",
361 - Label: "edge",
362 - Input: 60.000,
363 - },
364 - &lmsensors.PowerSensor{
365 - Name: "power1",
366 - Label: "PPT",
367 - Average: 5.088,
368 - },
369 - }},
370 - {Name: "BAT0-acpi-0", Sensors: []lmsensors.Sensor{
371 - &lmsensors.VoltageSensor{
372 - Name: "in0",
373 - Label: "in0",
374 - Input: 17.365,
375 - },
376 - }},
377 - {Name: "ucsi_source_psy_USBC000:001-isa-0000", Sensors: []lmsensors.Sensor{
378 - &lmsensors.VoltageSensor{
379 - Name: "in0",
380 - Label: "in0",
381 - Input: 0.000,
382 - },
383 - &lmsensors.CurrentSensor{
384 - Name: "curr1",
385 - Label: "curr1",
386 - Input: 0.000,
387 - },
388 - }},
389 - {Name: "k10temp-pci-00c3", Sensors: []lmsensors.Sensor{
390 - &lmsensors.TemperatureSensor{
391 - Name: "temp1",
392 - Label: "Tctl",
393 - Input: 90,
309 +func mockChips() []*lmsensors.Chip {
310 + return []*lmsensors.Chip{
311 + {
312 + Name: "chip0",
313 + UniqueName: "chip0-pci-xxxxxxxx",
314 + SysDevice: "pci0000:80/0000:80:01.4/0000:81:00.0/nvme/nvme0",
315 + Sensors: lmsensors.Sensors{
316 + Voltage: []*lmsensors.VoltageSensor{
317 + {
318 + Name: "in1",
319 + Label: "some_label1",
320 + Alarm: ptr(false),
321 + Input: ptr(42.0),
322 + Average: ptr(42.0),
323 + Min: ptr(42.0),
324 + Max: ptr(42.0),
325 + CritMin: ptr(42.0),
326 + CritMax: ptr(42.0),
327 + Lowest: ptr(42.0),
328 + Highest: ptr(42.0),
329 + },
330 + {
331 + Name: "in2",
332 + Label: "some_label2",
333 + Input: ptr(42.0),
334 + Min: ptr(42.0),
335 + Max: ptr(42.0),
336 + CritMin: ptr(42.0),
337 + CritMax: ptr(42.0),
338 + Lowest: ptr(42.0),
339 + Highest: ptr(42.0),
340 + },
341 },
395 - }},
396 - {Name: "amdgpu-pci-0300", Sensors: []lmsensors.Sensor{
397 - &lmsensors.VoltageSensor{
398 - Name: "in0",
399 - Label: "vddgfx",
400 - Input: 0.787,
342 + Fan: []*lmsensors.FanSensor{
343 + {
344 + Name: "fan1",
345 + Label: "some_label1",
346 + Alarm: ptr(false),
347 + Input: ptr(42.0),
348 + Min: ptr(42.0),
349 + Max: ptr(42.0),
350 + Target: ptr(42.0),
351 + },
352 + {
353 + Name: "fan2",
354 + Label: "some_label2",
355 + Input: ptr(42.0),
356 + Min: ptr(42.0),
357 + Max: ptr(42.0),
358 + Target: ptr(42.0),
359 + },
360 },
402 - &lmsensors.FanSensor{
403 - Name: "fan1",
404 - Label: "fan1",
405 - Maximum: 4900,
361 + Temperature: []*lmsensors.TemperatureSensor{
362 + {
363 + Name: "temp1",
364 + Label: "some_label1",
365 + Alarm: ptr(false),
366 + TempTypeRaw: 1,
367 + Input: ptr(42.0),
368 + Max: ptr(42.0),
369 + Min: ptr(42.0),
370 + CritMin: ptr(42.0),
371 + CritMax: ptr(42.0),
372 + Emergency: ptr(42.0),
373 + Lowest: ptr(42.0),
374 + Highest: ptr(42.0),
375 + },
376 + {
377 + Name: "temp2",
378 + Label: "some_label2",
379 + TempTypeRaw: 1,
380 + Input: ptr(42.0),
381 + Max: ptr(42.0),
382 + Min: ptr(42.0),
383 + CritMin: ptr(42.0),
384 + CritMax: ptr(42.0),
385 + Emergency: ptr(42.0),
386 + Lowest: ptr(42.0),
387 + Highest: ptr(42.0),
388 + },
389 },
407 - &lmsensors.TemperatureSensor{
408 - Name: "temp1",
409 - Label: "edge",
410 - Input: 53,
411 - Critical: 100,
412 - Emergency: 105,
390 + Current: []*lmsensors.CurrentSensor{
391 + {
392 + Name: "curr1",
393 + Label: "some_label1",
394 + Alarm: ptr(false),
395 + Max: ptr(42.0),
396 + Min: ptr(42.0),
397 + CritMin: ptr(42.0),
398 + CritMax: ptr(42.0),
399 + Input: ptr(42.0),
400 + Average: ptr(42.0),
401 + Lowest: ptr(42.0),
402 + Highest: ptr(42.0),
403 + },
404 + {
405 + Name: "curr2",
406 + Label: "some_label2",
407 + Max: ptr(42.0),
408 + Min: ptr(42.0),
409 + CritMin: ptr(42.0),
410 + CritMax: ptr(42.0),
411 + Input: ptr(42.0),
412 + Lowest: ptr(42.0),
413 + Highest: ptr(42.0),
414 + },
415 },
414 - &lmsensors.TemperatureSensor{
415 - Name: "temp2",
416 - Label: "junction",
417 - Input: 58,
418 - Critical: 100,
419 - Emergency: 105,
416 + Power: []*lmsensors.PowerSensor{
417 + {
418 + Name: "power1",
419 + Label: "some_label1",
420 + Alarm: ptr(false),
421 + Average: ptr(345.0),
422 + AverageHighest: ptr(345.0),
423 + AverageLowest: ptr(345.0),
424 + AverageMax: ptr(345.0),
425 + AverageMin: ptr(345.0),
426 + Input: ptr(345.0),
427 + InputHighest: ptr(345.0),
428 + InputLowest: ptr(345.0),
429 + Accuracy: ptr(34.5),
430 + Cap: ptr(345.0),
431 + CapMax: ptr(345.0),
432 + CapMin: ptr(345.0),
433 + Max: ptr(345.0),
434 + CritMax: ptr(345.0),
435 + },
436 + {
437 + Name: "power2",
438 + Label: "some_label2",
439 + AverageHighest: ptr(345.0),
440 + AverageLowest: ptr(345.0),
441 + AverageMax: ptr(345.0),
442 + AverageMin: ptr(345.0),
443 + Input: ptr(345.0),
444 + InputHighest: ptr(345.0),
445 + InputLowest: ptr(345.0),
446 + Accuracy: ptr(34.5),
447 + Cap: ptr(345.0),
448 + CapMax: ptr(345.0),
449 + CapMin: ptr(345.0),
450 + Max: ptr(345.0),
451 + CritMax: ptr(345.0),
452 + },
453 },
421 - &lmsensors.TemperatureSensor{
422 - Name: "temp3",
423 - Label: "mem",
424 - Input: 57,
425 - Critical: 106,
426 - Emergency: 110,
454 + Energy: []*lmsensors.EnergySensor{
455 + {
456 + Name: "energy1",
457 + Label: "some_label1",
458 + Input: ptr(42.0),
459 + },
460 + {
461 + Name: "energy2",
462 + Label: "some_label2",
463 + Input: ptr(42.0),
464 + },
465 },
428 - &lmsensors.PowerSensor{
429 - Name: "power1",
430 - Label: "PPT",
431 - Average: 29,
466 + Humidity: []*lmsensors.HumiditySensor{
467 + {
468 + Name: "humidity1",
469 + Label: "some_label1",
470 + Input: ptr(42.0),
471 + },
472 + {
473 + Name: "humidity2",
474 + Label: "some_label2",
475 + Input: ptr(42.0),
476 + },
477 },
433 - }},
434 - {Name: "acpitz-acpi-0", Sensors: []lmsensors.Sensor{
435 - &lmsensors.FanSensor{
436 - Name: "temp1",
437 - Label: "temp1",
438 - Input: 88,
478 + Intrusion: []*lmsensors.IntrusionSensor{
479 + {
480 + Name: "intrusion1",
481 + Label: "some_label1",
482 + Alarm: ptr(false),
483 + },
484 + {
485 + Name: "intrusion2",
486 + Label: "some_label2",
487 + Alarm: ptr(true),
488 + },
489 },
440 - }},
490 + },
491 },
492 }
493 }
444 -
445 -func prepareMockSysfsScannerErr() *mockSysfsScanner {
446 - return &mockSysfsScanner{
447 - errOnScan: true,
448 - }
449 -}
450 -
451 -type mockSysfsScanner struct {
452 - errOnScan bool
453 - scanData []*lmsensors.Device
454 -}
455 -
456 -func (m *mockSysfsScanner) Scan() ([]*lmsensors.Device, error) {
457 - if m.errOnScan {
458 - return nil, errors.New("mock.scan() error")
459 - }
460 - return m.scanData, nil
461 -}
src/go/plugin/go.d/modules/sensors/testdata/config.json
+1 -3
@@ -1,5 +1,3 @@
1 {
2 - "update_every": 123,
3 - "timeout": 123.123,
4 - "binary_path": "ok"
2 + "update_every": 123
3 }
src/go/plugin/go.d/modules/sensors/testdata/config.yaml
-2
@@ -1,3 +1 @@
1 update_every: 123
2 -timeout: 123.123
3 -binary_path: "ok"
src/go/plugin/go.d/modules/sensors/testdata/sensors.txt deleted
-79
@@ -1,79 +0,0 @@
1 -asus-isa-0000
2 -cpu_fan:
3 - fan1_input: 5700.000
4 -gpu_fan:
5 - fan2_input: 6600.000
6 -nvme-pci-0600
7 -Composite:
8 - temp1_input: 33.850
9 - temp1_max: 83.850
10 - temp1_min: -40.150
11 - temp1_crit: 87.850
12 - temp1_alarm: 0.000
13 -Sensor 1:
14 - temp2_input: 48.850
15 - temp2_max: 65261.850
16 - temp2_min: -273.150
17 -Sensor 2:
18 - temp3_input: 33.850
19 - temp3_max: 65261.850
20 - temp3_min: -273.150
21 -amdgpu-pci-6700
22 -vddgfx:
23 - in0_input: 1.335
24 -vddnb:
25 - in1_input: 0.973
26 -edge:
27 - temp1_input: 60.000
28 -PPT:
29 - power1_average: 5.088
30 - power1_input: 8.144
31 -BAT0-acpi-0
32 -in0:
33 - in0_input: 17.365
34 -ucsi_source_psy_USBC000:001-isa-0000
35 -in0:
36 - in0_input: 0.000
37 - in0_min: 0.000
38 - in0_max: 0.000
39 -curr1:
40 - curr1_input: 0.000
41 - curr1_max: 0.000
42 -k10temp-pci-00c3
43 -Tctl:
44 - temp1_input: 90.000
45 -amdgpu-pci-0300
46 -vddgfx:
47 - in0_input: 0.787
48 -fan1:
49 - fan1_input: 0.000
50 - fan1_min: 0.000
51 - fan1_max: 4900.000
52 -edge:
53 - temp1_input: 53.000
54 - temp1_crit: 100.000
55 - temp1_crit_hyst: -273.150
56 - temp1_emergency: 105.000
57 -junction:
58 - temp2_input: 58.000
59 - temp2_crit: 100.000
60 - temp2_crit_hyst: -273.150
61 - temp2_emergency: 105.000
62 -mem:
63 - temp3_input: 57.000
64 - temp3_crit: 105.000
65 - temp3_crit_hyst: -273.150
66 - temp3_emergency: 110.000
67 -PPT:
68 - power1_average: 29.000
69 - power1_cap: 120.000
70 -acpitz-acpi-0
71 -temp1:
72 - temp1_input: 88.000
73 -nct6779-isa-0290
74 -intrusion0:
75 - intrusion0_alarm: 1.000
76 - intrusion0_beep: 0.000
77 -intrusion1:
78 - intrusion1_alarm: 1.000
79 - intrusion1_beep: 0.000