1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "stats.h"
22 #include "perfmon.h"
23 #include "log.h"
24
25 ZBX_THREAD_LOCAL static zbx_perf_counter_id_t *PerfCounterList = NULL;
26
27 /* This struct contains mapping between built-in English counter names and PDH indexes. */
28 /* If you change it then you also need to add enum values to zbx_builtin_counter_ref_t. */
29 static struct builtin_counter_ref
30 {
31 unsigned long pdhIndex;
32 wchar_t eng_name[PDH_MAX_COUNTER_NAME];
33 }
34 builtin_counter_map[] =
35 {
36 { 0, L"System" },
37 { 0, L"Processor" },
38 { 0, L"Processor Information" },
39 { 0, L"% Processor Time" },
40 { 0, L"Processor Queue Length" },
41 { 0, L"System Up Time" },
42 { 0, L"Terminal Services" },
43 { 0, L"Total Sessions" }
44 };
45
zbx_PdhMakeCounterPath(const char * function,PDH_COUNTER_PATH_ELEMENTS * cpe,char * counterpath)46 PDH_STATUS zbx_PdhMakeCounterPath(const char *function, PDH_COUNTER_PATH_ELEMENTS *cpe, char *counterpath)
47 {
48 DWORD dwSize = PDH_MAX_COUNTER_PATH;
49 wchar_t *wcounterPath = NULL;
50 PDH_STATUS pdh_status;
51
52 wcounterPath = zbx_malloc(wcounterPath, sizeof(wchar_t) * PDH_MAX_COUNTER_PATH);
53
54 if (ERROR_SUCCESS != (pdh_status = PdhMakeCounterPath(cpe, wcounterPath, &dwSize, 0)))
55 {
56 char *object, *counter;
57
58 object = zbx_unicode_to_utf8(cpe->szObjectName);
59 counter = zbx_unicode_to_utf8(cpe->szCounterName);
60
61 zabbix_log(LOG_LEVEL_ERR, "%s(): cannot make counterpath for \"\\%s\\%s\": %s",
62 function, object, counter, strerror_from_module(pdh_status, L"PDH.DLL"));
63
64 zbx_free(counter);
65 zbx_free(object);
66 }
67 else
68 zbx_unicode_to_utf8_static(wcounterPath, counterpath, PDH_MAX_COUNTER_PATH);
69
70 zbx_free(wcounterPath);
71
72 return pdh_status;
73 }
74
zbx_PdhOpenQuery(const char * function,PDH_HQUERY query)75 PDH_STATUS zbx_PdhOpenQuery(const char *function, PDH_HQUERY query)
76 {
77 PDH_STATUS pdh_status;
78
79 if (ERROR_SUCCESS != (pdh_status = PdhOpenQuery(NULL, 0, query)))
80 {
81 zabbix_log(LOG_LEVEL_ERR, "%s(): call to PdhOpenQuery() failed: %s",
82 function, strerror_from_module(pdh_status, L"PDH.DLL"));
83 }
84
85 return pdh_status;
86 }
87
88 /******************************************************************************
89 * *
90 * Comments: counter is NULL if it is not in the collector, *
91 * do not call it for PERF_COUNTER_ACTIVE counters *
92 * *
93 ******************************************************************************/
zbx_PdhAddCounter(const char * function,zbx_perf_counter_data_t * counter,PDH_HQUERY query,const char * counterpath,zbx_perf_counter_lang_t lang,PDH_HCOUNTER * handle)94 PDH_STATUS zbx_PdhAddCounter(const char *function, zbx_perf_counter_data_t *counter, PDH_HQUERY query,
95 const char *counterpath, zbx_perf_counter_lang_t lang, PDH_HCOUNTER *handle)
96 {
97 /* pointer type to PdhAddEnglishCounterW() */
98 typedef PDH_STATUS (WINAPI *ADD_ENG_COUNTER)(PDH_HQUERY, LPCWSTR, DWORD_PTR, PDH_HCOUNTER);
99
100 PDH_STATUS pdh_status = ERROR_SUCCESS;
101 wchar_t *wcounterPath = NULL;
102 int need_english;
103
104 ZBX_THREAD_LOCAL static ADD_ENG_COUNTER add_eng_counter;
105 ZBX_THREAD_LOCAL static int first_call = 1;
106
107 need_english = PERF_COUNTER_LANG_DEFAULT != lang ||
108 (NULL != counter && PERF_COUNTER_LANG_DEFAULT != counter->lang);
109
110 /* PdhAddEnglishCounterW() is only available on Windows 2008/Vista and onwards, */
111 /* so we need to resolve it dynamically and fail if it's not available */
112 if (0 != first_call && 0 != need_english)
113 {
114 if (NULL == (add_eng_counter = (ADD_ENG_COUNTER)GetProcAddress(GetModuleHandle(L"PDH.DLL"),
115 "PdhAddEnglishCounterW")))
116 {
117 zabbix_log(LOG_LEVEL_WARNING, "PdhAddEnglishCounter() is not available, "
118 "perf_counter_en[] is not supported");
119 }
120
121 first_call = 0;
122 }
123
124 if (0 != need_english && NULL == add_eng_counter)
125 {
126 pdh_status = PDH_NOT_IMPLEMENTED;
127 }
128
129 if (ERROR_SUCCESS == pdh_status)
130 {
131 wcounterPath = zbx_utf8_to_unicode(counterpath);
132 }
133
134 if (ERROR_SUCCESS == pdh_status && NULL == *handle)
135 {
136 pdh_status = need_english ?
137 add_eng_counter(query, wcounterPath, 0, handle) :
138 PdhAddCounter(query, wcounterPath, 0, handle);
139 }
140
141 if (ERROR_SUCCESS != pdh_status && NULL != *handle)
142 {
143 if (ERROR_SUCCESS == PdhRemoveCounter(*handle))
144 *handle = NULL;
145 }
146
147 if (ERROR_SUCCESS == pdh_status)
148 {
149 if (NULL != counter)
150 counter->status = PERF_COUNTER_INITIALIZED;
151
152 zabbix_log(LOG_LEVEL_DEBUG, "%s(): PerfCounter '%s' successfully added", function, counterpath);
153 }
154 else
155 {
156 if (NULL != counter)
157 counter->status = PERF_COUNTER_NOTSUPPORTED;
158
159 zabbix_log(LOG_LEVEL_DEBUG, "%s(): unable to add PerfCounter '%s': %s",
160 function, counterpath, strerror_from_module(pdh_status, L"PDH.DLL"));
161 }
162
163 zbx_free(wcounterPath);
164
165 return pdh_status;
166 }
167
zbx_PdhCollectQueryData(const char * function,const char * counterpath,PDH_HQUERY query)168 PDH_STATUS zbx_PdhCollectQueryData(const char *function, const char *counterpath, PDH_HQUERY query)
169 {
170 PDH_STATUS pdh_status;
171
172 if (ERROR_SUCCESS != (pdh_status = PdhCollectQueryData(query)))
173 {
174 zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot collect data '%s': %s",
175 function, counterpath, strerror_from_module(pdh_status, L"PDH.DLL"));
176 }
177
178 return pdh_status;
179 }
180
zbx_PdhGetRawCounterValue(const char * function,const char * counterpath,PDH_HCOUNTER handle,PPDH_RAW_COUNTER value)181 PDH_STATUS zbx_PdhGetRawCounterValue(const char *function, const char *counterpath, PDH_HCOUNTER handle, PPDH_RAW_COUNTER value)
182 {
183 PDH_STATUS pdh_status;
184
185 if (ERROR_SUCCESS != (pdh_status = PdhGetRawCounterValue(handle, NULL, value)) ||
186 (PDH_CSTATUS_VALID_DATA != value->CStatus && PDH_CSTATUS_NEW_DATA != value->CStatus))
187 {
188 if (ERROR_SUCCESS == pdh_status)
189 pdh_status = value->CStatus;
190
191 zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot get counter value '%s': %s",
192 function, counterpath, strerror_from_module(pdh_status, L"PDH.DLL"));
193 }
194
195 return pdh_status;
196 }
197
198 /******************************************************************************
199 * *
200 * Comments: Get the value of a counter. If it is a rate counter, *
201 * sleep 1 second to get the second raw value. *
202 * *
203 ******************************************************************************/
calculate_counter_value(const char * function,const char * counterpath,zbx_perf_counter_lang_t lang,double * value)204 PDH_STATUS calculate_counter_value(const char *function, const char *counterpath,
205 zbx_perf_counter_lang_t lang, double *value)
206 {
207 PDH_HQUERY query;
208 PDH_HCOUNTER handle = NULL;
209 PDH_STATUS pdh_status;
210 PDH_RAW_COUNTER rawData, rawData2;
211 PDH_FMT_COUNTERVALUE counterValue;
212
213 if (ERROR_SUCCESS != (pdh_status = zbx_PdhOpenQuery(function, &query)))
214 return pdh_status;
215
216 if (ERROR_SUCCESS != (pdh_status = zbx_PdhAddCounter(function, NULL, query, counterpath, lang, &handle)))
217 goto close_query;
218
219 if (ERROR_SUCCESS != (pdh_status = zbx_PdhCollectQueryData(function, counterpath, query)))
220 goto remove_counter;
221
222 if (ERROR_SUCCESS != (pdh_status = zbx_PdhGetRawCounterValue(function, counterpath, handle, &rawData)))
223 goto remove_counter;
224
225 if (PDH_CSTATUS_INVALID_DATA == (pdh_status = PdhCalculateCounterFromRawValue(handle, PDH_FMT_DOUBLE |
226 PDH_FMT_NOCAP100, &rawData, NULL, &counterValue)))
227 {
228 /* some (e.g., rate) counters require two raw values, MSDN lacks documentation */
229 /* about what happens but tests show that PDH_CSTATUS_INVALID_DATA is returned */
230
231 zbx_sleep(1);
232
233 if (ERROR_SUCCESS == (pdh_status = zbx_PdhCollectQueryData(function, counterpath, query)) &&
234 ERROR_SUCCESS == (pdh_status = zbx_PdhGetRawCounterValue(function, counterpath,
235 handle, &rawData2)))
236 {
237 pdh_status = PdhCalculateCounterFromRawValue(handle, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100,
238 &rawData2, &rawData, &counterValue);
239 }
240 }
241
242 if (ERROR_SUCCESS != pdh_status || (PDH_CSTATUS_VALID_DATA != counterValue.CStatus &&
243 PDH_CSTATUS_NEW_DATA != counterValue.CStatus))
244 {
245 if (ERROR_SUCCESS == pdh_status)
246 pdh_status = counterValue.CStatus;
247
248 zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot calculate counter value '%s': %s",
249 function, counterpath, strerror_from_module(pdh_status, L"PDH.DLL"));
250 }
251 else
252 {
253 *value = counterValue.doubleValue;
254 }
255 remove_counter:
256 PdhRemoveCounter(handle);
257 close_query:
258 PdhCloseQuery(query);
259
260 return pdh_status;
261 }
262
263 /******************************************************************************
264 * *
265 * Function: get_builtin_counter_index *
266 * *
267 * Purpose: get performance counter index by reference value described by *
268 * zbx_builtin_counter_ref_t enum *
269 * *
270 * Parameters: counter_ref - [IN] built-in performance counter *
271 * *
272 * Return value: PDH performance counter index or 0 on failure *
273 * *
274 * Comments: Performance counter index values can differ across Windows *
275 * installations for the same names *
276 * *
277 ******************************************************************************/
get_builtin_counter_index(zbx_builtin_counter_ref_t counter_ref)278 DWORD get_builtin_counter_index(zbx_builtin_counter_ref_t counter_ref)
279 {
280 if (PCI_MAX_INDEX < counter_ref)
281 {
282 static int first_error = 1;
283
284 if (0 != first_error)
285 {
286 THIS_SHOULD_NEVER_HAPPEN;
287 first_error = 0;
288 }
289
290 return 0;
291 }
292
293 return builtin_counter_map[counter_ref].pdhIndex;
294 }
295
296 /******************************************************************************
297 * *
298 * Function: get_all_counter_eng_names *
299 * *
300 * Purpose: helper function for init_builtin_counter_indexes() *
301 * *
302 * Parameters: reg_value_name - [IN] name of the registry value *
303 * *
304 * Return value: wchar_t* buffer with list of strings on success, *
305 * NULL on failure *
306 * *
307 * Comments: This function should be normally called with L"Counter" *
308 * parameter. It returns a list of null-terminated string pairs. *
309 * Last string is followed by an additional null-terminator. *
310 * The return buffer must be freed by the caller. *
311 * *
312 ******************************************************************************/
get_all_counter_eng_names(wchar_t * reg_value_name)313 static wchar_t *get_all_counter_eng_names(wchar_t *reg_value_name)
314 {
315 const char *__function_name = "get_all_counter_eng_names";
316 wchar_t *buffer = NULL;
317 DWORD buffer_size = 0;
318 LSTATUS status = ERROR_SUCCESS;
319 /* this registry key guaranteed to hold english counter texts even in localized Win versions */
320 static HKEY reg_key = HKEY_PERFORMANCE_TEXT;
321
322 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
323
324 /* query the size of the text data for further buffer allocation */
325 if (ERROR_SUCCESS != (status = RegQueryValueEx(reg_key, reg_value_name, NULL, NULL, NULL, &buffer_size)))
326 {
327 zabbix_log(LOG_LEVEL_ERR, "RegQueryValueEx() failed at getting buffer size, 0x%lx",
328 (unsigned long)status);
329 goto finish;
330 }
331
332 buffer = (wchar_t*)zbx_malloc(buffer, (size_t)buffer_size);
333
334 if (ERROR_SUCCESS != (status = RegQueryValueEx(reg_key, reg_value_name, NULL, NULL, (LPBYTE)buffer,
335 &buffer_size)))
336 {
337 zabbix_log(LOG_LEVEL_ERR, "RegQueryValueEx() failed with 0x%lx", (unsigned long)status);
338 zbx_free(buffer);
339 goto finish;
340 }
341 finish:
342 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
343
344 return buffer;
345 }
346
347 /******************************************************************************
348 * *
349 * Function: init_builtin_counter_indexes *
350 * *
351 * Purpose: scans registry key with all performance counter English names *
352 * and obtains system-dependent PDH counter indexes for further *
353 * use by corresponding items *
354 * *
355 * Return value: SUCCEED/FAIL *
356 * *
357 * Comments: This function should be normally called during agent *
358 * initialization from init_perf_collector(). *
359 * *
360 ******************************************************************************/
init_builtin_counter_indexes(void)361 int init_builtin_counter_indexes(void)
362 {
363 const char *__function_name = "init_builtin_counter_indexes";
364 int ret = FAIL, i;
365 wchar_t *counter_text, *saved_ptr;
366
367 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
368
369 /* Get buffer holding a list of performance counter indexes and English counter names. */
370 /* L"Counter" stores names, L"Help" stores descriptions ("Help" is not used). */
371 if (NULL == (counter_text = saved_ptr = get_all_counter_eng_names(L"Counter")))
372 goto out;
373
374 /* bypass first pair of counter data elements - these contain number of records */
375 counter_text += wcslen(counter_text) + 1;
376 counter_text += wcslen(counter_text) + 1;
377
378 for (; 0 != *counter_text; counter_text += wcslen(counter_text) + 1)
379 {
380 DWORD counter_index = (DWORD)_wtoi(counter_text);
381 counter_text += wcslen(counter_text) + 1;
382
383 for (i = 0; i < ARRSIZE(builtin_counter_map); i++)
384 {
385 if (0 == wcscmp(builtin_counter_map[i].eng_name, counter_text))
386 {
387 builtin_counter_map[i].pdhIndex = counter_index;
388 break;
389 }
390 }
391 }
392
393 ret = SUCCEED;
394 zbx_free(saved_ptr);
395 out:
396 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
397
398 return ret;
399 }
400
get_counter_name(DWORD pdhIndex)401 wchar_t *get_counter_name(DWORD pdhIndex)
402 {
403 const char *__function_name = "get_counter_name";
404 zbx_perf_counter_id_t *counterName;
405 DWORD dwSize;
406 PDH_STATUS pdh_status;
407
408 zabbix_log(LOG_LEVEL_DEBUG, "In %s() pdhIndex:%u", __function_name, pdhIndex);
409
410 counterName = PerfCounterList;
411 while (NULL != counterName)
412 {
413 if (counterName->pdhIndex == pdhIndex)
414 break;
415 counterName = counterName->next;
416 }
417
418 if (NULL == counterName)
419 {
420 counterName = (zbx_perf_counter_id_t *)zbx_malloc(counterName, sizeof(zbx_perf_counter_id_t));
421
422 memset(counterName, 0, sizeof(zbx_perf_counter_id_t));
423 counterName->pdhIndex = pdhIndex;
424 counterName->next = PerfCounterList;
425
426 dwSize = PDH_MAX_COUNTER_NAME;
427 if (ERROR_SUCCESS == (pdh_status = PdhLookupPerfNameByIndex(NULL, pdhIndex, counterName->name, &dwSize)))
428 PerfCounterList = counterName;
429 else
430 {
431 zabbix_log(LOG_LEVEL_ERR, "PdhLookupPerfNameByIndex() failed: %s",
432 strerror_from_module(pdh_status, L"PDH.DLL"));
433 zbx_free(counterName);
434 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():FAIL", __function_name);
435 return L"UnknownPerformanceCounter";
436 }
437 }
438
439 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():SUCCEED", __function_name);
440
441 return counterName->name;
442 }
443
check_counter_path(char * counterPath,int convert_from_numeric)444 int check_counter_path(char *counterPath, int convert_from_numeric)
445 {
446 const char *__function_name = "check_counter_path";
447 PDH_COUNTER_PATH_ELEMENTS *cpe = NULL;
448 PDH_STATUS status;
449 int ret = FAIL;
450 DWORD dwSize = 0;
451 wchar_t *wcounterPath;
452
453 wcounterPath = zbx_utf8_to_unicode(counterPath);
454
455 status = PdhParseCounterPath(wcounterPath, NULL, &dwSize, 0);
456 if (PDH_MORE_DATA == status || ERROR_SUCCESS == status)
457 {
458 cpe = (PDH_COUNTER_PATH_ELEMENTS *)zbx_malloc(cpe, dwSize);
459 }
460 else
461 {
462 zabbix_log(LOG_LEVEL_ERR, "cannot get required buffer size for counter path '%s': %s",
463 counterPath, strerror_from_module(status, L"PDH.DLL"));
464 goto clean;
465 }
466
467 if (ERROR_SUCCESS != (status = PdhParseCounterPath(wcounterPath, cpe, &dwSize, 0)))
468 {
469 zabbix_log(LOG_LEVEL_ERR, "cannot parse counter path '%s': %s",
470 counterPath, strerror_from_module(status, L"PDH.DLL"));
471 goto clean;
472 }
473
474 if (0 != convert_from_numeric)
475 {
476 int is_numeric = (SUCCEED == _wis_uint(cpe->szObjectName) ? 0x01 : 0);
477 is_numeric |= (SUCCEED == _wis_uint(cpe->szCounterName) ? 0x02 : 0);
478
479 if (0 != is_numeric)
480 {
481 if (0x01 & is_numeric)
482 cpe->szObjectName = get_counter_name(_wtoi(cpe->szObjectName));
483 if (0x02 & is_numeric)
484 cpe->szCounterName = get_counter_name(_wtoi(cpe->szCounterName));
485
486 if (ERROR_SUCCESS != zbx_PdhMakeCounterPath(__function_name, cpe, counterPath))
487 goto clean;
488
489 zabbix_log(LOG_LEVEL_DEBUG, "counter path converted to '%s'", counterPath);
490 }
491 }
492
493 ret = SUCCEED;
494 clean:
495 zbx_free(cpe);
496 zbx_free(wcounterPath);
497
498 return ret;
499 }
500