master
c 956 lines 33.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "windows_plugin.h"
4 #include "windows-internals.h"
5 #include "libnetdata/os/windows-wmi/windows-wmi.h"
6
7 #include <windows.h>
8 #include <wchar.h>
9
10 #include <sensorsapi.h>
11 #include <sensors.h>
12 #include <propidl.h>
13
14 static ISensorManager *pSensorManager = NULL;
15 static ND_THREAD *sensors_thread_update = NULL;
16 static netdata_mutex_t sensors_mutex;
17
18 #define NETDATA_WIN_SENSOR_STATES (6)
19 #define NETDATA_WIN_VECTOR_POS (3)
20
21 enum netdata_win_sensor_monitored {
22 NETDATA_WIN_SENSOR_CELSIUS,
23 NETDATA_WIN_SENSOR_POWER_WATTS,
24 NETDATA_WIN_SENSOR_CURRENT_AMPS,
25 NETDATA_WIN_SENSOR_RELATIVITY_HUMIDITY,
26 NETDATA_WIN_SENSOR_LIGHT_LEVEL,
27 NETDATA_WIN_SENSOR_DATA_TYPE_LIGHT_TEMPERATURE,
28 NETDATA_WIN_SENSOR_VOLTAGE,
29 NETDATA_WIN_SENSOR_RESISTENCE,
30 NETDATA_WIN_SENSOR_CAPACITANCE_FARAD,
31 NETDATA_WIN_SENSOR_INDUCTANCE_HENRY,
32 NETDATA_WIN_SENSOR_ATMOSPHERE_PRESSURE,
33 NETDATA_WIN_SENSOR_LATITUDE_DEGREES,
34 NETDATA_WIN_SENSOR_LONGITUDE_DEGREES,
35 NETDATA_WIN_SENSOR_FORCE_NEWTONS,
36 NETDATA_WIN_SENSOR_GAUGE_PRESSURE,
37 NETDATA_WIN_SENSOR_HUMAN_PRESENCE,
38 NETDATA_WIN_SENSOR_HUMAN_PROXIMITY_METERS,
39
40 // Add only one vector axis here
41 NETDATA_WIN_SENSOR_TYPE_DISTANCE_X,
42 NETDATA_WIN_SENSOR_ACCELERATION_X_G,
43
44 NETDATA_WIN_SENSOR_LAST_WELL_DEFINED,
45
46 // Remaining axis should be added here
47 NETDATA_WIN_SENSOR_TYPE_DISTANCE_Y,
48 NETDATA_WIN_SENSOR_TYPE_DISTANCE_Z,
49 NETDATA_WIN_SENSOR_ACCELERATION_Y_G,
50 NETDATA_WIN_SENSOR_ACCELERATION_Z_G,
51
52 NETDATA_WIN_SENSOR_NEVER_USE_ME,
53
54 // Custom sensors
55 NETDATA_WIN_SENSOR_TYPE_CUSTOM_USAGE,
56 NETDATA_WIN_SENSOR_TYPE_CUSTOM_BOOLEAN_ARRAY,
57 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE1,
58 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE2,
59 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE3,
60 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE4,
61 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE5,
62 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE6,
63 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE7,
64 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE8,
65 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE9,
66 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE10,
67 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE11,
68 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE12,
69 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE13,
70 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE14,
71 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE15,
72 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE16,
73 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE17,
74 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE18,
75 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE19,
76 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE20,
77 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE21,
78 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE22,
79 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE23,
80 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE24,
81 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE25,
82 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE26,
83 NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE27
84 };
85
86 REFPROPERTYKEY sensor_keys[] = {
87 &SENSOR_DATA_TYPE_TEMPERATURE_CELSIUS,
88 &SENSOR_DATA_TYPE_ELECTRICAL_POWER_WATTS,
89 &SENSOR_DATA_TYPE_CURRENT_AMPS,
90 &SENSOR_DATA_TYPE_RELATIVE_HUMIDITY_PERCENT,
91 &SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX,
92 &SENSOR_DATA_TYPE_LIGHT_TEMPERATURE_KELVIN,
93 &SENSOR_DATA_TYPE_VOLTAGE_VOLTS,
94 &SENSOR_DATA_TYPE_RESISTANCE_OHMS,
95 &SENSOR_DATA_TYPE_CAPACITANCE_FARAD,
96 &SENSOR_DATA_TYPE_INDUCTANCE_HENRY,
97 &SENSOR_DATA_TYPE_ATMOSPHERIC_PRESSURE_BAR,
98 &SENSOR_DATA_TYPE_LATITUDE_DEGREES,
99 &SENSOR_DATA_TYPE_LONGITUDE_DEGREES,
100 &SENSOR_DATA_TYPE_FORCE_NEWTONS,
101 &SENSOR_DATA_TYPE_GAUGE_PRESSURE_PASCAL,
102 &SENSOR_DATA_TYPE_HUMAN_PRESENCE,
103 &SENSOR_DATA_TYPE_HUMAN_PROXIMITY_METERS,
104
105 // Add only one vector axis here
106 &SENSOR_DATA_TYPE_DISTANCE_X_METERS,
107 &SENSOR_DATA_TYPE_ACCELERATION_X_G,
108
109 // Main loop stop
110 NULL,
111
112 // Remaining axis should be added here
113 &SENSOR_DATA_TYPE_DISTANCE_Y_METERS,
114 &SENSOR_DATA_TYPE_DISTANCE_Z_METERS,
115 &SENSOR_DATA_TYPE_ACCELERATION_Y_G,
116 &SENSOR_DATA_TYPE_ACCELERATION_Z_G,
117
118 // Stop additional
119 NULL,
120
121 &SENSOR_DATA_TYPE_CUSTOM_USAGE,
122 &SENSOR_DATA_TYPE_CUSTOM_BOOLEAN_ARRAY,
123 &SENSOR_DATA_TYPE_CUSTOM_VALUE1,
124 &SENSOR_DATA_TYPE_CUSTOM_VALUE2,
125 &SENSOR_DATA_TYPE_CUSTOM_VALUE3,
126 &SENSOR_DATA_TYPE_CUSTOM_VALUE4,
127 &SENSOR_DATA_TYPE_CUSTOM_VALUE5,
128 &SENSOR_DATA_TYPE_CUSTOM_VALUE6,
129 &SENSOR_DATA_TYPE_CUSTOM_VALUE7,
130 &SENSOR_DATA_TYPE_CUSTOM_VALUE8,
131 &SENSOR_DATA_TYPE_CUSTOM_VALUE9,
132 &SENSOR_DATA_TYPE_CUSTOM_VALUE10,
133 &SENSOR_DATA_TYPE_CUSTOM_VALUE11,
134 &SENSOR_DATA_TYPE_CUSTOM_VALUE12,
135 &SENSOR_DATA_TYPE_CUSTOM_VALUE13,
136 &SENSOR_DATA_TYPE_CUSTOM_VALUE14,
137 &SENSOR_DATA_TYPE_CUSTOM_VALUE15,
138 &SENSOR_DATA_TYPE_CUSTOM_VALUE16,
139 &SENSOR_DATA_TYPE_CUSTOM_VALUE17,
140 &SENSOR_DATA_TYPE_CUSTOM_VALUE18,
141 &SENSOR_DATA_TYPE_CUSTOM_VALUE19,
142 &SENSOR_DATA_TYPE_CUSTOM_VALUE20,
143 &SENSOR_DATA_TYPE_CUSTOM_VALUE21,
144 &SENSOR_DATA_TYPE_CUSTOM_VALUE22,
145 &SENSOR_DATA_TYPE_CUSTOM_VALUE23,
146 &SENSOR_DATA_TYPE_CUSTOM_VALUE24,
147 &SENSOR_DATA_TYPE_CUSTOM_VALUE25,
148 &SENSOR_DATA_TYPE_CUSTOM_VALUE26,
149 &SENSOR_DATA_TYPE_CUSTOM_VALUE27};
150
151 static struct win_sensor_config {
152 const char *title;
153 const char *units;
154 const char *context;
155 const char *family;
156
157 int priority;
158 } configs[] = {
159 {
160 .title = "Sensor Temperature",
161 .units = "Cel",
162 .context = "system.hw.sensor.temperature.input",
163 .family = "Temperature",
164 .priority = NETDATA_CHART_PRIO_SENSORS,
165 },
166 {
167 .title = "Sensor Power",
168 .units = "W",
169 .context = "system.hw.sensor.power.input",
170 .family = "Power",
171 .priority = NETDATA_CHART_PRIO_SENSOR_POWER,
172 },
173 {
174 .title = "Sensor Current",
175 .units = "A",
176 .context = "system.hw.sensor.current.input",
177 .family = "Current",
178 .priority = NETDATA_CHART_PRIO_SENSOR_CURRENT,
179 },
180 {
181 .title = "Sensor Humidity",
182 .units = "%",
183 .context = "system.hw.sensor.humidity.input",
184 .family = "Humidity",
185 .priority = NETDATA_CHART_PRIO_SENSOR_HUMIDITY,
186 },
187 {
188 .title = "Ambient light level",
189 .units = "lx",
190 .context = "system.hw.sensor.lux.input",
191 .family = "illuminance",
192 .priority = NETDATA_CHART_PRIO_SENSOR_LUX,
193 },
194 {
195 .title = "Color temperature of light",
196 .units = "Cel",
197 .context = "system.hw.sensor.color.input",
198 .family = "Temperature",
199 .priority = NETDATA_CHART_PRIO_SENSOR_TEMPERATURE,
200 },
201 {
202 .title = "Electrical potential",
203 .units = "V",
204 .context = "system.hw.sensor.voltage.input",
205 .family = "Potential",
206 .priority = NETDATA_CHART_PRIO_SENSOR_VOLTAGE,
207 },
208 {
209 .title = "Electrical resistence",
210 .units = "Ohms",
211 .context = "system.hw.sensor.resistance.input",
212 .family = "Resistance",
213 .priority = NETDATA_CHART_PRIO_SENSOR_RESISTANCE,
214 },
215 {
216 .title = "Electrical capacitance",
217 .units = "F",
218 .context = "system.hw.sensor.capacitance.input",
219 .family = "Capacitance",
220 .priority = NETDATA_CHART_PRIO_SENSOR_CAPACITANCE,
221 },
222 {
223 .title = "Electrical inductance",
224 .units = "H",
225 .context = "system.hw.sensor.inductance.input",
226 .family = "Inductance",
227 .priority = NETDATA_CHART_PRIO_SENSOR_INDUCTANCE,
228 },
229 {
230 .title = "Ambient atmospheric pressure",
231 .units = "Pa",
232 .context = "system.hw.sensor.pressure.input",
233 .family = "Pressure",
234 .priority = NETDATA_CHART_PRIO_SENSOR_AMBIENT_PRESSURE,
235 },
236 {
237 .title = "Geographic latitude",
238 .units = "Degrees",
239 .context = "system.hw.sensor.latitude.input",
240 .family = "Location",
241 .priority = NETDATA_CHART_PRIO_SENSOR_LATITUDE,
242 },
243 {
244 .title = "Geographic longitude",
245 .units = "Degrees",
246 .context = "system.hw.sensor.longitude.input",
247 .family = "Location",
248 .priority = NETDATA_CHART_PRIO_SENSOR_LONGITUDE,
249 },
250 {
251 .title = "Force",
252 .units = "N",
253 .context = "system.hw.sensor.force.input",
254 .family = "Force",
255 .priority = NETDATA_CHART_PRIO_SENSOR_FORCE,
256 },
257 {
258 .title = "Gauge Pressure",
259 .units = "Pa",
260 .context = "system.hw.sensor.gauge_pressure.input",
261 .family = "Pressure",
262 .priority = NETDATA_CHART_PRIO_SENSOR_GAUGE_PRESSURE,
263 },
264 {
265 .title = "Human presence",
266 .units = "presence",
267 .context = "system.hw.sensor.human_presence.input",
268 .family = "Presence",
269 .priority = NETDATA_CHART_PRIO_SENSOR_HUMAN,
270 },
271 {
272 .title = "Human proximity",
273 .units = "m",
274 .context = "system.hw.sensor.human_proximity.input",
275 .family = "Proximity",
276 .priority = NETDATA_CHART_PRIO_SENSOR_PROXIMITY,
277 },
278 {
279 .title = "Distance",
280 .units = "m",
281 .context = "system.hw.sensor.distance.input",
282 .family = "Distance",
283 .priority = NETDATA_CHART_PRIO_SENSOR_DISTANCE,
284 },
285 {
286 .title = "Acceleration.",
287 .units = "g",
288 .context = "system.hw.sensor.acceleration.input",
289 .family = "Acceleration",
290 .priority = NETDATA_CHART_PRIO_SENSOR_ACCELERATION,
291 },
292 {
293 .title = "Custom Chart",
294 .units = "nd",
295 .context = "system.hw.sensor.custom",
296 .family = "sensors",
297 .priority = NETDATA_CHART_PRIO_SENSOR_MIN_CUSTOM,
298 }};
299
300 struct netdata_sensors_extra_values {
301 collected_number value;
302 enum netdata_win_sensor_monitored sensor_data_type;
303 RRDDIM *rd_value;
304
305 struct netdata_sensors_extra_values *next;
306 };
307
308 struct netdata_sensors_extra_config {
309 const char *units;
310 const char *title;
311
312 int multiplier;
313 };
314
315 struct sensor_data {
316 bool initialized;
317 bool first_time;
318 bool enabled;
319 enum netdata_win_sensor_monitored sensor_data_type;
320 struct win_sensor_config *config;
321 struct netdata_sensors_extra_values *values;
322
323 const char *type;
324 const char *category;
325 const char *name;
326 const char *manufacturer;
327 const char *model;
328 struct netdata_sensors_extra_config *external_config;
329
330 SensorState current_state;
331
332 RRDSET *st_sensor_state;
333 RRDDIM *rd_sensor_state[NETDATA_WIN_SENSOR_STATES];
334
335 RRDSET *st_sensor_data;
336 RRDDIM *rd_sensor_data_0;
337 RRDDIM *rd_sensor_data_1;
338 RRDDIM *rd_sensor_data_2;
339
340 // Some sensors are vectors, this means they have three different values (Time is ignored here)
341 collected_number current_data_value[NETDATA_WIN_VECTOR_POS];
342 NETDATA_DOUBLE mult_factor;
343 NETDATA_DOUBLE div_factor;
344 NETDATA_DOUBLE add_factor;
345 };
346
347 DICTIONARY *sensors;
348
349 static void netdata_sensors_free_extra_values(struct netdata_sensors_extra_values *values)
350 {
351 while (values) {
352 struct netdata_sensors_extra_values *next = values->next;
353 freez(values);
354 values = next;
355 }
356 }
357
358 static void netdata_sensor_cleanup(struct sensor_data *sd)
359 {
360 if (sd->st_sensor_state)
361 rrdset_is_obsolete___safe_from_collector_thread(sd->st_sensor_state);
362 if (sd->st_sensor_data)
363 rrdset_is_obsolete___safe_from_collector_thread(sd->st_sensor_data);
364
365 freez((void *)sd->type);
366 freez((void *)sd->category);
367 freez((void *)sd->name);
368 freez((void *)sd->manufacturer);
369 freez((void *)sd->model);
370 freez(sd->external_config);
371 netdata_sensors_free_extra_values(sd->values);
372
373 sd->type = NULL;
374 sd->category = NULL;
375 sd->name = NULL;
376 sd->manufacturer = NULL;
377 sd->model = NULL;
378 sd->external_config = NULL;
379 sd->values = NULL;
380 }
381
382 // Microsoft appends additional data
383 #define ADDTIONAL_UUID_STR_LEN (UUID_STR_LEN + 17)
384
385 static bool netdata_clsid_to_char(char *output, size_t output_len, const GUID *pguid)
386 {
387 if (unlikely(!output))
388 return false;
389
390 LPWSTR wguid = NULL;
391 HRESULT hr = StringFromCLSID(pguid, &wguid);
392 output[0] = '\0';
393 if (FAILED(hr) || unlikely(!wguid))
394 return false;
395
396 size_t converted = wcstombs(output, wguid, output_len - 1);
397 CoTaskMemFree(wguid);
398
399 if (unlikely(converted == (size_t)-1)) {
400 output[0] = '\0';
401 return false;
402 }
403
404 output[converted] = '\0';
405
406 return true;
407 }
408
409 static inline char *netdata_convert_guid_to_string(HRESULT hr, GUID *value)
410 {
411 if (SUCCEEDED(hr)) {
412 char cguid[ADDTIONAL_UUID_STR_LEN];
413 netdata_clsid_to_char(cguid, ADDTIONAL_UUID_STR_LEN, value);
414 return strdupz(cguid);
415 }
416 return NULL;
417 }
418
419 static inline void netdata_fill_sensor_type(struct sensor_data *sd, ISensor *pSensor)
420 {
421 GUID type = {0};
422 HRESULT hr = pSensor->lpVtbl->GetType(pSensor, &type);
423 sd->type = netdata_convert_guid_to_string(hr, &type);
424 }
425
426 static inline void netdata_fill_sensor_category(struct sensor_data *sd, ISensor *pSensor)
427 {
428 GUID category = {0};
429 HRESULT hr = pSensor->lpVtbl->GetCategory(pSensor, &category);
430 sd->category = netdata_convert_guid_to_string(hr, &category);
431 }
432
433 static inline char *netdata_pvar_to_char(const PROPERTYKEY *key, ISensor *pSensor)
434 {
435 PROPVARIANT pv;
436 PropVariantInit(&pv);
437 HRESULT hr = pSensor->lpVtbl->GetProperty(pSensor, key, &pv);
438 if (SUCCEEDED(hr) && pv.vt == VT_LPWSTR) {
439 char value[8192];
440 int len = wcslen(pv.pwszVal);
441 len = wcstombs(value, pv.pwszVal, len);
442 if (len < 0) {
443 return NULL;
444 }
445 value[len] = '\0';
446 PropVariantClear(&pv);
447 return strdupz(value);
448 }
449 PropVariantClear(&pv);
450 return NULL;
451 }
452
453 static void netdata_initialize_sensor_dict(struct sensor_data *sd, ISensor *pSensor)
454 {
455 netdata_fill_sensor_type(sd, pSensor);
456 netdata_fill_sensor_category(sd, pSensor);
457 sd->name = netdata_pvar_to_char(&SENSOR_PROPERTY_FRIENDLY_NAME, pSensor);
458 sd->model = netdata_pvar_to_char(&SENSOR_PROPERTY_MODEL, pSensor);
459 sd->manufacturer = netdata_pvar_to_char(&SENSOR_PROPERTY_MANUFACTURER, pSensor);
460 }
461
462 static int netdata_collect_sensor_data(collected_number *value, ISensor *pSensor, REFPROPERTYKEY key, NETDATA_DOUBLE div_factor)
463 {
464 ISensorDataReport *pReport = NULL;
465 PROPVARIANT pv = {};
466 HRESULT hr;
467
468 hr = pSensor->lpVtbl->GetData(pSensor, &pReport);
469 if (SUCCEEDED(hr) && pReport) {
470 PropVariantInit(&pv);
471 hr = pReport->lpVtbl->GetSensorValue(pReport, key, &pv);
472 if (SUCCEEDED(hr)) {
473 switch (pv.vt) {
474 case VT_UI4:
475 *value = (collected_number)(pv.ulVal * 100);
476 break;
477 case VT_R4:
478 *value = (collected_number)(pv.fltVal * div_factor);
479 break;
480 case VT_R8:
481 *value = (collected_number)(pv.dblVal * div_factor);
482 break;
483 case VT_BOOL:
484 *value = (pv.boolVal == VARIANT_TRUE) ? 100 : 0;
485 break;
486 default:
487 goto error_collect_sensor_data;
488 }
489 pReport->lpVtbl->Release(pReport);
490 } else {
491 pReport->lpVtbl->Release(pReport);
492 goto error_collect_sensor_data;
493 }
494 PropVariantClear(&pv);
495 } else
496 goto error_collect_sensor_data;
497
498 return 1;
499 error_collect_sensor_data:
500 if (pv.vt != VT_EMPTY)
501 PropVariantClear(&pv);
502
503 *value = 0;
504 return 0;
505 }
506
507 static void netdata_sensors_get_data(struct sensor_data *sd, ISensor *pSensor)
508 {
509 for (int i = 0; sensor_keys[i]; i++) {
510 if (netdata_collect_sensor_data(&sd->current_data_value[0], pSensor, sensor_keys[i], sd->div_factor)) {
511 sd->sensor_data_type = i;
512 sd->config = &configs[i];
513 sd->enabled = true;
514
515 if (i == NETDATA_WIN_SENSOR_TYPE_DISTANCE_X) {
516 netdata_collect_sensor_data(&sd->current_data_value[1],
517 pSensor,
518 sensor_keys[NETDATA_WIN_SENSOR_TYPE_DISTANCE_Y],
519 sd->div_factor);
520 netdata_collect_sensor_data(&sd->current_data_value[2],
521 pSensor,
522 sensor_keys[NETDATA_WIN_SENSOR_TYPE_DISTANCE_Z],
523 sd->div_factor);
524 } else if (i == NETDATA_WIN_SENSOR_ACCELERATION_X_G) {
525 netdata_collect_sensor_data(&sd->current_data_value[1],
526 pSensor,
527 sensor_keys[NETDATA_WIN_SENSOR_ACCELERATION_Y_G],
528 sd->div_factor);
529 netdata_collect_sensor_data(&sd->current_data_value[2],
530 pSensor,
531 sensor_keys[NETDATA_WIN_SENSOR_ACCELERATION_Z_G],
532 sd->div_factor);
533 } else if (i == NETDATA_WIN_SENSOR_DATA_TYPE_LIGHT_TEMPERATURE) {
534 sd->div_factor = 100.0;
535 sd->add_factor = -27315.0; // 273.15 * 100.0
536 }
537 break;
538 }
539 }
540
541 sd->first_time = false;
542 }
543
544 static void netdata_sensors_get_custom_data(struct sensor_data *sd, ISensor *pSensor)
545 {
546 collected_number current;
547 // Last two values have been constant through all collections. One of the values are always negative.
548 for (int i = NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE1; i < NETDATA_WIN_SENSOR_TYPE_CUSTOM_VALUE25; i++) {
549 if (netdata_collect_sensor_data(&current, pSensor, sensor_keys[i], sd->div_factor)) {
550 if (unlikely(!sd->enabled)) {
551 sd->sensor_data_type = i;
552 sd->config = &configs[NETDATA_WIN_SENSOR_LAST_WELL_DEFINED];
553 sd->enabled = true;
554 sd->current_data_value[0] = current;
555 } else {
556 if (unlikely(!sd->values)) {
557 sd->values = callocz(sizeof(struct netdata_sensors_extra_values), 1);
558 sd->values->sensor_data_type = i;
559 sd->values->value = current;
560 } else {
561 struct netdata_sensors_extra_values *move;
562 for (move = sd->values; move->next; move = move->next);
563 move->next = callocz(sizeof(struct netdata_sensors_extra_values), 1);
564 move->next->sensor_data_type = i;
565 move->next->value = current;
566 }
567 }
568 }
569 }
570 }
571
572 static struct netdata_sensors_extra_config *netdata_sensors_fill_configuration(const char *name)
573 {
574 #define NETDATA_DEFAULT_SENSOR_SECTION "plugin:windows:GetSensors"
575 char section_name[CONFIG_MAX_NAME + 1];
576 snprintfz(section_name, CONFIG_MAX_NAME, "%s:%s", NETDATA_DEFAULT_SENSOR_SECTION, name);
577
578 const char *units = inicfg_get(&netdata_config, section_name, "units", NULL);
579 if (unlikely(!units)) {
580 return NULL;
581 }
582
583 struct netdata_sensors_extra_config *sec = callocz(sizeof(struct netdata_sensors_extra_config), 1);
584 sec->units = units;
585 sec->title = inicfg_get(&netdata_config, section_name, "title", NULL);;
586 sec->multiplier = (int)inicfg_get_number(&netdata_config, section_name, "multiplier", 1);
587
588 return sec;
589 }
590
591 static void netdata_get_sensors()
592 {
593 ISensorCollection *pSensorCollection = NULL;
594 HRESULT hr = pSensorManager->lpVtbl->GetSensorsByCategory(pSensorManager, &SENSOR_CATEGORY_ALL, &pSensorCollection);
595 if (FAILED(hr)) {
596 return;
597 }
598
599 ULONG count = 0;
600 hr = pSensorCollection->lpVtbl->GetCount(pSensorCollection, &count);
601 if (FAILED(hr)) {
602 pSensorCollection->lpVtbl->Release(pSensorCollection);
603 return;
604 }
605
606 // the same size of windows_shared_buffer
607 char thread_values[8192];
608 for (ULONG i = 0; i < count; ++i) {
609 ISensor *pSensor = NULL;
610 hr = pSensorCollection->lpVtbl->GetAt(pSensorCollection, i, &pSensor);
611 if (FAILED(hr) || !pSensor) {
612 if (pSensor)
613 pSensor->lpVtbl->Release(pSensor);
614 continue;
615 }
616
617 GUID id = {0};
618 hr = pSensor->lpVtbl->GetID(pSensor, &id);
619 if (FAILED(hr)) {
620 pSensor->lpVtbl->Release(pSensor);
621 continue;
622 }
623
624 if (!netdata_clsid_to_char(thread_values, sizeof(thread_values), &id)) {
625 pSensor->lpVtbl->Release(pSensor);
626 continue;
627 }
628
629 __netdata_mutex_lock(&sensors_mutex);
630 struct sensor_data *sd = dictionary_set(sensors, thread_values, NULL, sizeof(*sd));
631
632 if (unlikely(!sd->initialized)) {
633 netdata_initialize_sensor_dict(sd, pSensor);
634 sd->initialized = true;
635 }
636
637 sd->current_state = SENSOR_STATE_MIN;
638 (void)pSensor->lpVtbl->GetState(pSensor, &sd->current_state);
639
640 if (sd->first_time) {
641 netdata_sensors_get_data(sd, pSensor);
642 sd->external_config = netdata_sensors_fill_configuration(sd->name);
643 if (unlikely(!sd->enabled))
644 netdata_sensors_get_custom_data(sd, pSensor);
645 } else if (likely(sd->enabled)) {
646 netdata_collect_sensor_data(&sd->current_data_value[0],
647 pSensor,
648 sensor_keys[sd->sensor_data_type],
649 sd->div_factor);
650 if (sd->sensor_data_type == NETDATA_WIN_SENSOR_TYPE_DISTANCE_X) {
651 netdata_collect_sensor_data(&sd->current_data_value[1],
652 pSensor,
653 sensor_keys[NETDATA_WIN_SENSOR_TYPE_DISTANCE_Y],
654 sd->div_factor);
655 netdata_collect_sensor_data(&sd->current_data_value[2],
656 pSensor,
657 sensor_keys[NETDATA_WIN_SENSOR_TYPE_DISTANCE_Z],
658 sd->div_factor);
659 } else if (sd->sensor_data_type == NETDATA_WIN_SENSOR_ACCELERATION_X_G) {
660 netdata_collect_sensor_data(&sd->current_data_value[1],
661 pSensor,
662 sensor_keys[NETDATA_WIN_SENSOR_ACCELERATION_Y_G],
663 sd->div_factor);
664 netdata_collect_sensor_data(&sd->current_data_value[2],
665 pSensor,
666 sensor_keys[NETDATA_WIN_SENSOR_ACCELERATION_Z_G],
667 sd->div_factor);
668 }
669 if (likely(sd->values)) {
670 struct netdata_sensors_extra_values *move;
671 for (move = sd->values; move; move = move->next) {
672 netdata_collect_sensor_data(&move->value,
673 pSensor,
674 sensor_keys[move->sensor_data_type],
675 sd->div_factor);
676 }
677 }
678 }
679 __netdata_mutex_unlock(&sensors_mutex);
680
681 pSensor->lpVtbl->Release(pSensor);
682 }
683
684 pSensorCollection->lpVtbl->Release(pSensorCollection);
685 }
686
687 static void netdata_sensors_monitor(void *ptr __maybe_unused)
688 {
689 // Initialize COM for this thread - sensor thread only needs COM for Sensor API, not WMI
690 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
691 bool com_initialized = SUCCEEDED(hr);
692
693 // RPC_E_CHANGED_MODE means COM was already initialized in a different mode (e.g., COINIT_APARTMENTTHREADED)
694 // In this case, COM is available but we didn't increment the reference count
695 if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
696 nd_log(NDLS_COLLECTORS, NDLP_ERR,
697 "Sensor thread: cannot initialize COM interface (error 0x%lX)", (unsigned long)hr);
698 return;
699 }
700
701 // Create sensor manager instance for this thread
702 hr = CoCreateInstance(
703 &CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, &IID_ISensorManager, (void **)&pSensorManager);
704 if (FAILED(hr)) {
705 nd_log(NDLS_COLLECTORS, NDLP_ERR,
706 "Sensor thread: cannot create ISensorManager (error 0x%lX)", (unsigned long)hr);
707 // Only uninitialize if we successfully initialized COM ourselves
708 if (com_initialized)
709 CoUninitialize();
710 return;
711 }
712
713 heartbeat_t hb;
714 heartbeat_init(&hb, USEC_PER_SEC);
715
716 while (service_running(SERVICE_COLLECTORS)) {
717 (void)heartbeat_next(&hb);
718
719 if (unlikely(!service_running(SERVICE_COLLECTORS)))
720 break;
721
722 netdata_get_sensors();
723 }
724
725 // Thread cleanup - release sensor manager and uninitialize COM
726 if (pSensorManager) {
727 pSensorManager->lpVtbl->Release(pSensorManager);
728 pSensorManager = NULL;
729 }
730
731 // Only uninitialize if we successfully initialized COM ourselves
732 if (com_initialized)
733 CoUninitialize();
734 }
735
736 void dict_sensor_insert(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused)
737 {
738 struct sensor_data *sd = value;
739
740 sd->first_time = true;
741 sd->sensor_data_type = 0;
742 sd->config = NULL;
743 sd->mult_factor = 1.0;
744 sd->div_factor = 100.0;
745 sd->add_factor = 0.0;
746 }
747
748 void dict_sensor_delete(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused)
749 {
750 struct sensor_data *sd = value;
751 netdata_sensor_cleanup(sd);
752 }
753
754 static int initialize(int update_every)
755 {
756 // Note: COM and Sensor API initialization is now done in the sensor thread
757 // (netdata_sensors_monitor) because COM must be initialized per-thread.
758 // The sensor thread owns the pSensorManager instance and handles its cleanup.
759
760 if (unlikely(__netdata_mutex_init(&sensors_mutex)))
761 return -1;
762
763 sensors = dictionary_create_advanced(
764 DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct sensor_data));
765 dictionary_register_insert_callback(sensors, dict_sensor_insert, NULL);
766 dictionary_register_delete_callback(sensors, dict_sensor_delete, NULL);
767
768 sensors_thread_update =
769 nd_thread_create("sensors_upd", NETDATA_THREAD_OPTION_DEFAULT, netdata_sensors_monitor, &update_every);
770
771 return 0;
772 }
773
774 static void sensors_states_chart(struct sensor_data *sd, int update_every)
775 {
776 if (!sd->st_sensor_state) {
777 char id[RRD_ID_LENGTH_MAX + 1];
778 snprintfz(id, RRD_ID_LENGTH_MAX, "%s_state", sd->name);
779 netdata_fix_chart_name(id);
780 sd->st_sensor_state = rrdset_create_localhost(
781 "sensors",
782 id,
783 NULL,
784 "sensors",
785 "system.hw.sensor.state",
786 "Current sensor state.",
787 "status",
788 PLUGIN_WINDOWS_NAME,
789 "GetSensors",
790 69999,
791 update_every,
792 RRDSET_TYPE_LINE);
793
794 rrdlabels_add(sd->st_sensor_state->rrdlabels, "name", sd->name, RRDLABEL_SRC_AUTO);
795 rrdlabels_add(sd->st_sensor_state->rrdlabels, "manufacturer", sd->manufacturer, RRDLABEL_SRC_AUTO);
796 rrdlabels_add(sd->st_sensor_state->rrdlabels, "model", sd->model, RRDLABEL_SRC_AUTO);
797
798 sd->rd_sensor_state[0] = rrddim_add(sd->st_sensor_state, "ready", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
799 sd->rd_sensor_state[1] = rrddim_add(sd->st_sensor_state, "not_available", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
800 sd->rd_sensor_state[2] = rrddim_add(sd->st_sensor_state, "no_data", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
801 sd->rd_sensor_state[3] = rrddim_add(sd->st_sensor_state, "initializing", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
802 sd->rd_sensor_state[4] = rrddim_add(sd->st_sensor_state, "access_denied", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
803 sd->rd_sensor_state[5] = rrddim_add(sd->st_sensor_state, "error", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
804 }
805 }
806
807 static inline void sensors_state_chart_loop(struct sensor_data *sd, int update_every)
808 {
809 sensors_states_chart(sd, update_every);
810 collected_number set_value = (collected_number)sd->current_state;
811 for (collected_number i = 0; i < NETDATA_WIN_SENSOR_STATES; i++) {
812 rrddim_set_by_pointer(sd->st_sensor_state, sd->rd_sensor_state[i], i == set_value);
813 }
814 rrdset_done(sd->st_sensor_state);
815 }
816
817 static inline RRDDIM *netdata_add_sensor_dimension(struct sensor_data *sd, char *label, int mult_factor)
818 {
819 return rrddim_add(
820 sd->st_sensor_data,
821 label,
822 NULL,
823 (collected_number)mult_factor,
824 (collected_number)sd->div_factor,
825 RRD_ALGORITHM_ABSOLUTE);
826 }
827
828 static inline void netdata_state_chart_set_value(RRDDIM *dim, struct sensor_data *sd, int value_idx)
829 {
830 collected_number value = sd->current_data_value[value_idx] + (collected_number)sd->add_factor;
831 rrddim_set_by_pointer(sd->st_sensor_data, dim, value);
832 }
833
834 static void sensors_data_chart(struct sensor_data *sd, int update_every)
835 {
836 if (!sd->st_sensor_data) {
837 char id[RRD_ID_LENGTH_MAX + 1];
838 char ctx[RRD_ID_LENGTH_MAX + 1];
839 int priority = sd->config->priority;
840
841 snprintfz(id, RRD_ID_LENGTH_MAX, "sensors.%s_input", sd->name);
842 netdata_fix_chart_name(id);
843 struct netdata_sensors_extra_config *cfg = sd->external_config;
844 if (sd->sensor_data_type > NETDATA_WIN_SENSOR_TYPE_CUSTOM_USAGE) {
845 static int min_custom = NETDATA_CHART_PRIO_SENSOR_MIN_CUSTOM;
846 static int custom_input = 1;
847
848 snprintfz(ctx, RRD_ID_LENGTH_MAX, "%s%d.input", sd->config->context, custom_input++);
849 priority = min_custom++;
850 } else {
851 ctx[0] = '\0';
852 }
853 sd->st_sensor_data = rrdset_create_localhost(
854 "sensors",
855 id,
856 NULL,
857 sd->config->family,
858 (unlikely(!ctx[0])) ? sd->config->context: ctx,
859 (cfg && cfg->title) ? cfg->title : sd->config->title,
860 (cfg && cfg->units) ? cfg->units : sd->config->units,
861 PLUGIN_WINDOWS_NAME,
862 "GetSensors",
863 priority,
864 update_every,
865 RRDSET_TYPE_LINE);
866
867 rrdlabels_add(sd->st_sensor_data->rrdlabels, "name", sd->name, RRDLABEL_SRC_AUTO);
868 rrdlabels_add(sd->st_sensor_data->rrdlabels, "manufacturer", sd->manufacturer, RRDLABEL_SRC_AUTO);
869 rrdlabels_add(sd->st_sensor_data->rrdlabels, "model", sd->model, RRDLABEL_SRC_AUTO);
870
871 if (sd->sensor_data_type != NETDATA_WIN_SENSOR_TYPE_DISTANCE_X &&
872 sd->sensor_data_type != NETDATA_WIN_SENSOR_ACCELERATION_X_G) {
873 sd->rd_sensor_data_0 = netdata_add_sensor_dimension(sd,
874 (unlikely(!ctx[0])) ? "input" : "input0",
875 (cfg) ? cfg->multiplier : sd->mult_factor);
876 if (likely(sd->values)) {
877 int i;
878 struct netdata_sensors_extra_values *move;
879 for (move = sd->values, i = 1; move; move = move->next, i++) {
880 snprintfz(id, RRD_ID_LENGTH_MAX, "input%d", i);
881 move->rd_value = netdata_add_sensor_dimension(sd, id, (cfg) ? cfg->multiplier : sd->mult_factor);
882 }
883 }
884 } else {
885 sd->rd_sensor_data_0 =
886 netdata_add_sensor_dimension(sd, "inputX", (cfg) ? cfg->multiplier : sd->mult_factor);
887 sd->rd_sensor_data_1 =
888 netdata_add_sensor_dimension(sd, "inputY", (cfg) ? cfg->multiplier : sd->mult_factor);
889 sd->rd_sensor_data_2 =
890 netdata_add_sensor_dimension(sd, "inputZ", (cfg) ? cfg->multiplier : sd->mult_factor);
891 }
892 }
893
894 netdata_state_chart_set_value(sd->rd_sensor_data_0, sd, 0);
895 if (sd->sensor_data_type == NETDATA_WIN_SENSOR_TYPE_DISTANCE_X ||
896 sd->sensor_data_type == NETDATA_WIN_SENSOR_ACCELERATION_X_G) {
897 netdata_state_chart_set_value(sd->rd_sensor_data_1, sd, 1);
898 netdata_state_chart_set_value(sd->rd_sensor_data_2, sd, 2);
899 } else if (likely(sd->values)) {
900 struct netdata_sensors_extra_values *move;
901 for (move = sd->values; move; move = move->next) {
902 rrddim_set_by_pointer(sd->st_sensor_data, move->rd_value, move->value);
903 }
904 }
905 rrdset_done(sd->st_sensor_data);
906 }
907
908 int dict_sensors_charts_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused)
909 {
910 int *update_every = data;
911 struct sensor_data *sd = value;
912
913 if (unlikely(!sd->name))
914 return 1;
915
916 sensors_state_chart_loop(sd, *update_every);
917
918 if (unlikely(!sd->enabled))
919 return 1;
920
921 sensors_data_chart(sd, *update_every);
922
923 return 1;
924 }
925
926 int do_GetSensors(int update_every, usec_t dt __maybe_unused)
927 {
928 static bool initialized = false;
929 if (unlikely(!initialized)) {
930 if (likely(initialize(update_every))) {
931 return -1;
932 }
933
934 initialized = true;
935 }
936
937 __netdata_mutex_lock(&sensors_mutex);
938 dictionary_sorted_walkthrough_read(sensors, dict_sensors_charts_cb, &update_every);
939 __netdata_mutex_unlock(&sensors_mutex);
940 return 0;
941 }
942
943 void do_Sensors_cleanup()
944 {
945 // Wait for sensor thread to finish
946 // The thread handles its own COM cleanup (CoUninitialize) and pSensorManager release
947 if (nd_thread_join(sensors_thread_update))
948 nd_log_daemon(NDLP_ERR, "Failed to join sensors thread update");
949 sensors_thread_update = NULL;
950
951 __netdata_mutex_destroy(&sensors_mutex);
952 dictionary_destroy(sensors);
953 sensors = NULL;
954 // Note: pSensorManager is owned and cleaned up by the sensor thread itself
955 // No additional cleanup needed here since the thread has already released resources
956 }