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