/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "perfstat.h"

#include "stats.h"
#include "log.h"
#include "zbxmutexs.h"
#include "zbxsysinfo.h"
#include "zbxstr.h"

#define OBJECT_CACHE_REFRESH_INTERVAL	60
#define NAMES_UPDATE_INTERVAL		60

struct object_name_ref
{
	char		*eng_name;
	wchar_t		*loc_name;
};

typedef struct
{
	zbx_perf_counter_data_t	*pPerfCounterList;
	PDH_HQUERY		pdh_query;
	time_t			lastrefresh_objects;	/* last refresh time of object cache */
	time_t			lastupdate_names;	/* last update time of object names */
}
ZBX_PERF_STAT_DATA;

static ZBX_PERF_STAT_DATA	ppsd;
static zbx_mutex_t		perfstat_access = ZBX_MUTEX_NULL;

static struct object_name_ref	*object_names = NULL;
static int			object_num = 0;

#define LOCK_PERFCOUNTERS	zbx_mutex_lock(perfstat_access)
#define UNLOCK_PERFCOUNTERS	zbx_mutex_unlock(perfstat_access)

static int	perf_collector_started(void)
{
	return (NULL != ppsd.pdh_query ? SUCCEED : FAIL);
}

/******************************************************************************
 *                                                                            *
 * Comments: counter failed or disappeared, dismiss all previous values       *
 *                                                                            *
 ******************************************************************************/
static void	deactivate_perf_counter(zbx_perf_counter_data_t *counter)
{
	zabbix_log(LOG_LEVEL_DEBUG, "deactivate_perf_counter() counterpath:'%s'", counter->counterpath);

	counter->status = PERF_COUNTER_NOTSUPPORTED;
	counter->value_count = 0;
	counter->value_current = -1;
	counter->olderRawValue = 0;
	counter->sum = 0;
}

/******************************************************************************
 *                                                                            *
 * Comments: if the specified counter exists or the new one is successfully   *
 *           added, a pointer to that counter is returned, NULL otherwise     *
 *                                                                            *
 ******************************************************************************/
zbx_perf_counter_data_t	*add_perf_counter(const char *name, const char *counterpath, int interval,
		zbx_perf_counter_lang_t lang, char **error)
{
	zbx_perf_counter_data_t	*cptr = NULL;
	PDH_STATUS		pdh_status;
	int			added = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() counter:'%s' interval:%d", __func__, counterpath, interval);

	LOCK_PERFCOUNTERS;

	if (SUCCEED != perf_collector_started())
	{
		*error = zbx_strdup(*error, "Performance collector is not started.");
		goto out;
	}

	for (cptr = ppsd.pPerfCounterList; ; cptr = cptr->next)
	{
		/* add new parameters */
		if (NULL == cptr)
		{
			cptr = (zbx_perf_counter_data_t *)zbx_malloc(cptr, sizeof(zbx_perf_counter_data_t));

			/* initialize the counter */
			memset(cptr, 0, sizeof(zbx_perf_counter_data_t));
			if (NULL != name)
				cptr->name = zbx_strdup(NULL, name);
			cptr->counterpath = zbx_strdup(NULL, counterpath);
			cptr->interval = interval;
			cptr->lang = lang;
			cptr->value_current = -1;
			cptr->value_array = (double *)zbx_malloc(cptr->value_array, sizeof(double) * interval);

			/* add the counter to the query */
			pdh_status = zbx_PdhAddCounter(__func__, cptr, ppsd.pdh_query, counterpath,
					lang, &cptr->handle);

			cptr->next = ppsd.pPerfCounterList;
			ppsd.pPerfCounterList = cptr;

			if (ERROR_SUCCESS != pdh_status && PDH_CSTATUS_NO_INSTANCE != pdh_status)
			{
				*error = zbx_dsprintf(*error, "Invalid performance counter format.");
				cptr = NULL;	/* indicate a failure */
			}

			added = SUCCEED;
			break;
		}

		if (NULL != name)
		{
			if (0 == strcmp(cptr->name, name))
				break;
		}
		else if (0 == strcmp(cptr->counterpath, counterpath) &&
				cptr->interval == interval && cptr->lang == lang)
		{
			break;
		}
	}

	if (FAIL == added)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() counter '%s' already exists", __func__, counterpath);
	}
	else if (NULL != name && NULL != cptr)
	{
		char	*alias_name;

		alias_name = zbx_dsprintf(NULL, "__UserPerfCounter[%s]", name);
		zbx_add_alias(name, alias_name);
		zbx_free(alias_name);
	}
out:
	UNLOCK_PERFCOUNTERS;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s(): %s", __func__, NULL == cptr ? "FAIL" : "SUCCEED");

	return cptr;
}

/******************************************************************************
 *                                                                            *
 * Purpose: extends the performance counter buffer to store the new data      *
 *          interval                                                          *
 *                                                                            *
 * Parameters: result    - [IN] the performance counter                       *
 *             interval  - [IN] the new data collection interval in seconds   *
 *                                                                            *
 ******************************************************************************/
static void	extend_perf_counter_interval(zbx_perf_counter_data_t *counter, int interval)
{
	if (interval <= counter->interval)
		return;

	counter->value_array = (double *)zbx_realloc(counter->value_array, sizeof(double) * interval);

	/* move the data to the end to keep the ring buffer intact */
	if (counter->value_current < counter->value_count)
	{
		int	i;
		double	*src, *dst;

		src = &counter->value_array[counter->interval - 1];
		dst = &counter->value_array[interval - 1];

		for (i = 0; i < counter->value_count - counter->value_current; i++)
			*dst-- = *src--;
	}

	counter->interval = interval;
}

static void	free_object_names(void)
{
	int	i;

	for (i = 0; i < object_num; i++)
	{
		zbx_free(object_names[i].eng_name);
		zbx_free(object_names[i].loc_name);
	}

	zbx_free(object_names);
	object_num = 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: obtains PDH object localized names and associates them with       *
 *          English names, to be used by perf_instance_en.discovery           *
 *                                                                            *
 * Return value: SUCCEED/FAIL                                                 *
 *                                                                            *
 ******************************************************************************/
static int	set_object_names(void)
{
	wchar_t		*names_eng, *names_loc, *eng_name, *loc_name, *objects, *object, *p_eng = NULL, *p_loc = NULL;
	DWORD		sz = 0;
	PDH_STATUS	pdh_status;
	BOOL		refresh;
	int		ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	LOCK_PERFCOUNTERS;

	if (ppsd.lastupdate_names + NAMES_UPDATE_INTERVAL >= time(NULL))
	{
		ret = SUCCEED;
		goto out;
	}

	if (ppsd.lastrefresh_objects + OBJECT_CACHE_REFRESH_INTERVAL > time(NULL))
		refresh = FALSE;
	else
		refresh = TRUE;

	if (PDH_MORE_DATA != (pdh_status = PdhEnumObjects(NULL, NULL, NULL, &sz, PERF_DETAIL_WIZARD, refresh)))
	{
		zabbix_log(LOG_LEVEL_ERR, "cannot obtain required buffer size: %s",
				strerror_from_module(pdh_status, L"PDH.DLL"));
		goto out;
	}

	if (TRUE == refresh)
		ppsd.lastrefresh_objects = time(NULL);

	if (NULL == (p_eng = names_eng = zbx_get_all_counter_names(HKEY_PERFORMANCE_TEXT, L"Counter")) ||
			NULL == (p_loc = names_loc = zbx_get_all_counter_names(HKEY_PERFORMANCE_NLSTEXT, L"Counter")))
	{
		goto out;
	}

	/* skip fields with number of records */
	names_eng += wcslen(names_eng) + 1;
	names_eng += wcslen(names_eng) + 1;
	names_loc += wcslen(names_loc) + 1;
	names_loc += wcslen(names_loc) + 1;

	objects = zbx_malloc(NULL, (++sz) * sizeof(wchar_t));

	if (ERROR_SUCCESS != (pdh_status = PdhEnumObjects(NULL, NULL, objects, &sz, PERF_DETAIL_WIZARD, FALSE)))
	{
		zabbix_log(LOG_LEVEL_ERR, "cannot obtain objects list: %s",
				strerror_from_module(pdh_status, L"PDH.DLL"));
		zbx_free(objects);
		goto out;
	}

	free_object_names();

	for (object = objects; L'\0' != *object; object += sz)
	{
		DWORD	idx_eng, idx_loc;

		sz = (DWORD)wcslen(object) + 1;
		object_names = zbx_realloc(object_names, sizeof(struct object_name_ref) * (object_num + 1));

		object_names[object_num].eng_name = NULL;
		object_names[object_num].loc_name = zbx_malloc(NULL, sizeof(wchar_t) * sz);
		memcpy(object_names[object_num].loc_name, object, sizeof(wchar_t) * sz);

		/* For some objects the localized name might be missing and PdhEnumObjects() will return English    */
		/* name instead. In that case for localized name use name returned by PdhEnumObjects() if such name */
		/* exists in English names registry (HKEY_PERFORMANCE_TEXT).                                        */

		idx_loc = 0;

		for (loc_name = names_loc; L'\0' != *loc_name; loc_name += wcslen(loc_name) + 1)
		{
			DWORD	idx;

			idx = (DWORD)_wtoi(loc_name);
			loc_name += wcslen(loc_name) + 1;

			if (0 == wcscmp(object, loc_name))
			{
				idx_loc = idx;
				break;
			}
		}

		for (eng_name = names_eng; L'\0' != *eng_name; eng_name += wcslen(eng_name) + 1)
		{
			idx_eng = (DWORD)_wtoi(eng_name);
			eng_name += wcslen(eng_name) + 1;

			if (idx_loc == idx_eng ||
					(0 == idx_loc && 0 == wcscmp(object_names[object_num].loc_name, eng_name)))
			{
				object_names[object_num].eng_name = zbx_unicode_to_utf8(eng_name);
				break;
			}
		}

		object_num++;
	}

	zbx_free(objects);
	ppsd.lastupdate_names = time(NULL);
	ret = SUCCEED;
out:
	zbx_free(p_eng);
	zbx_free(p_loc);

	UNLOCK_PERFCOUNTERS;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Comments: counter is removed from the collector and                        *
 *           the memory is freed - do not use it again                        *
 *                                                                            *
 ******************************************************************************/
void	remove_perf_counter(zbx_perf_counter_data_t *counter)
{
	zbx_perf_counter_data_t	*cptr;

	LOCK_PERFCOUNTERS;

	if (NULL == counter || NULL == ppsd.pPerfCounterList)
		goto out;

	if (counter == ppsd.pPerfCounterList)
	{
		ppsd.pPerfCounterList = counter->next;
	}
	else
	{
		for (cptr = ppsd.pPerfCounterList; ; cptr = cptr->next)
		{
			if (cptr->next == counter)
			{
				cptr->next = counter->next;
				break;
			}
		}
	}

	PdhRemoveCounter(counter->handle);
	zbx_free(counter->name);
	zbx_free(counter->counterpath);
	zbx_free(counter->value_array);
	zbx_free(counter);
out:
	UNLOCK_PERFCOUNTERS;

}

static void	free_perf_counter_list(void)
{
	zbx_perf_counter_data_t	*cptr;

	while (NULL != ppsd.pPerfCounterList)
	{
		cptr = ppsd.pPerfCounterList;
		ppsd.pPerfCounterList = cptr->next;

		zbx_free(cptr->name);
		zbx_free(cptr->counterpath);
		zbx_free(cptr->value_array);
		zbx_free(cptr);
	}
}

/******************************************************************************
 *                                                                            *
 * Comments: must be called only for PERF_COUNTER_ACTIVE counters,            *
 *           interval must be less than or equal to counter->interval         *
 *                                                                            *
 ******************************************************************************/
static double	compute_average_value(zbx_perf_counter_data_t *counter, int interval)
{
	double	sum = 0;
	int	i, j, count;

	if (PERF_COUNTER_ACTIVE != counter->status || interval > counter->interval)
		return 0;

	if (counter->interval == interval)
		return counter->sum / (double)counter->value_count;

	/* compute the average manually for custom intervals */
	i = counter->value_current;
	count = (counter->value_count < interval ? counter->value_count : interval);

	/* cycle backwards through the circular buffer of values */
	for (j = 0; j < count; j++, i = (0 < i ? i - 1 : counter->interval - 1))
		sum += counter->value_array[i];

	return sum / (double)count;
}

int	init_perf_collector(zbx_threadedness_t threadedness, char **error)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	switch (threadedness)
	{
		case ZBX_SINGLE_THREADED:
			break;
		case ZBX_MULTI_THREADED:
			if (SUCCEED != zbx_mutex_create(&perfstat_access, ZBX_MUTEX_PERFSTAT, error))
				goto out;
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			*error = zbx_strdup(*error, "internal error");
			goto out;
	}

	if (ERROR_SUCCESS != zbx_PdhOpenQuery(__func__, &ppsd.pdh_query))
	{
		*error = zbx_strdup(*error, "cannot open performance data query");
		goto out;
	}

	ppsd.lastrefresh_objects = 0;
	ppsd.lastupdate_names = 0;

	if (SUCCEED != zbx_init_builtin_counter_indexes())
	{
		*error = zbx_strdup(*error, "cannot initialize built-in counter indexes");
		goto out;
	}

	if (SUCCEED != set_object_names())
		zabbix_log(LOG_LEVEL_WARNING, "%s(): cannot initialize object names", __func__);

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

void	free_perf_collector(void)
{
	zbx_perf_counter_data_t	*cptr;

	if (SUCCEED != perf_collector_started())
		return;

	for (cptr = ppsd.pPerfCounterList; cptr != NULL; cptr = cptr->next)
	{
		if (NULL != cptr->handle)
		{
			PdhRemoveCounter(cptr->handle);
			cptr->handle = NULL;
		}
	}

	PdhCloseQuery(ppsd.pdh_query);
	ppsd.pdh_query = NULL;

	LOCK_PERFCOUNTERS;

	free_perf_counter_list();
	free_object_names();

	UNLOCK_PERFCOUNTERS;

	zbx_mutex_destroy(&perfstat_access);
}

void	collect_perfstat(void)
{
	zbx_perf_counter_data_t	*cptr;
	PDH_STATUS		pdh_status;
	time_t			now;
	PDH_FMT_COUNTERVALUE	value;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	LOCK_PERFCOUNTERS;

	if (SUCCEED != perf_collector_started())
		goto out;

	if (NULL == ppsd.pPerfCounterList)	/* no counters */
		goto out;

	now = time(NULL);

	/* refresh unsupported counters */
	for (cptr = ppsd.pPerfCounterList; NULL != cptr; cptr = cptr->next)
	{
		if (PERF_COUNTER_NOTSUPPORTED != cptr->status)
			continue;

		zbx_PdhAddCounter(__func__, cptr, ppsd.pdh_query, cptr->counterpath,
				cptr->lang, &cptr->handle);
	}

	/* query for new data */
	if (ERROR_SUCCESS != (pdh_status = PdhCollectQueryData(ppsd.pdh_query)))
	{
		for (cptr = ppsd.pPerfCounterList; NULL != cptr; cptr = cptr->next)
		{
			if (PERF_COUNTER_NOTSUPPORTED != cptr->status)
				deactivate_perf_counter(cptr);
		}

		zabbix_log(LOG_LEVEL_DEBUG, "%s() call to PdhCollectQueryData() failed: %s",
				__func__, strerror_from_module(pdh_status, L"PDH.DLL"));

		goto out;
	}

	/* get the raw values */
	for (cptr = ppsd.pPerfCounterList; NULL != cptr; cptr = cptr->next)
	{
		if (PERF_COUNTER_NOTSUPPORTED == cptr->status)
			continue;

		if (ERROR_SUCCESS != zbx_PdhGetRawCounterValue(__func__, cptr->counterpath,
				cptr->handle, &cptr->rawValues[cptr->olderRawValue]))
		{
			deactivate_perf_counter(cptr);
			continue;
		}

		cptr->olderRawValue = (cptr->olderRawValue + 1) & 1;

		pdh_status = PdhCalculateCounterFromRawValue(cptr->handle, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100,
				&cptr->rawValues[(cptr->olderRawValue + 1) & 1],
				(PERF_COUNTER_INITIALIZED < cptr->status ?
						&cptr->rawValues[cptr->olderRawValue] : NULL), &value);

		if (ERROR_SUCCESS == pdh_status && PDH_CSTATUS_VALID_DATA != value.CStatus &&
				PDH_CSTATUS_NEW_DATA != value.CStatus)
		{
			pdh_status = value.CStatus;
		}

		if (PDH_CSTATUS_INVALID_DATA == pdh_status)
		{
			/* some (e.g., rate) counters require two raw values, MSDN lacks documentation */
			/* about what happens but tests show that PDH_CSTATUS_INVALID_DATA is returned */

			cptr->status = PERF_COUNTER_GET_SECOND_VALUE;
			continue;
		}

		/* Negative values can occur when a counter rolls over. By default, this value entry does not appear  */
		/* in the registry and Performance Monitor does not log data errors or notify the user that it has    */
		/* received bad data; More info: https://support.microsoft.com/kb/177655/EN-US                        */

		if (PDH_CALC_NEGATIVE_DENOMINATOR == pdh_status)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "PDH_CALC_NEGATIVE_DENOMINATOR error occurred in counterpath '%s'."
					" Value ignored", cptr->counterpath);
			continue;
		}

		if (PDH_CALC_NEGATIVE_VALUE == pdh_status)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "PDH_CALC_NEGATIVE_VALUE error occurred in counterpath '%s'."
					" Value ignored", cptr->counterpath);
			continue;
		}

		if (ERROR_SUCCESS == pdh_status)
		{
			cptr->status = PERF_COUNTER_ACTIVE;
			cptr->value_current = (cptr->value_current + 1) % cptr->interval

			/* remove the oldest value, value_count will not increase */;
			if (cptr->value_count == cptr->interval)
				cptr->sum -= cptr->value_array[cptr->value_current];

			cptr->value_array[cptr->value_current] = value.doubleValue;
			cptr->sum += cptr->value_array[cptr->value_current];
			if (cptr->value_count < cptr->interval)
				cptr->value_count++;
		}
		else
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot calculate performance counter value \"%s\": %s",
					cptr->counterpath, strerror_from_module(pdh_status, L"PDH.DLL"));

			deactivate_perf_counter(cptr);
		}
	}
out:
	UNLOCK_PERFCOUNTERS;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets average named performance counter value                      *
 *                                                                            *
 * Parameters: name  - [IN] the performance counter name                      *
 *             value - [OUT] the calculated value                             *
 *             error - [OUT] the error message, it is not always produced     *
 *                     when FAIL is returned. It is a caller responsibility   *
 *                     to check if the error message is not NULL.             *
 *                                                                            *
 * Returns:  SUCCEED - the value was retrieved successfully                   *
 *           FAIL    - otherwise                                              *
 *                                                                            *
 * Comments: The value is retrieved from collector (if it has been requested  *
 *           before) or directly from Windows performance counters if         *
 *           possible.                                                        *
 *                                                                            *
 ******************************************************************************/
int	get_perf_counter_value_by_name(const char *name, double *value, char **error)
{
	int			ret = FAIL;
	zbx_perf_counter_data_t	*perfs = NULL;
	char			*counterpath = NULL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() name:%s", __func__, name);

	LOCK_PERFCOUNTERS;

	if (SUCCEED != perf_collector_started())
	{
		*error = zbx_strdup(*error, "Performance collector is not started.");
		goto out;
	}

	for (perfs = ppsd.pPerfCounterList; NULL != perfs; perfs = perfs->next)
	{
		if (NULL != perfs->name && 0 == strcmp(perfs->name, name))
		{
			if (PERF_COUNTER_ACTIVE != perfs->status)
				break;

			/* the counter data is already being collected, return it */
			*value = compute_average_value(perfs, perfs->interval);
			ret = SUCCEED;
			goto out;
		}
	}

	/* we can retrieve named counter data only if it has been registered before */
	if (NULL == perfs)
	{
		*error = zbx_dsprintf(*error, "Unknown performance counter name: %s.", name);
		goto out;
	}

	counterpath = zbx_strdup(counterpath, perfs->counterpath);
out:
	UNLOCK_PERFCOUNTERS;

	if (NULL != counterpath)
	{
		/* request counter value directly from Windows performance counters */
		PDH_STATUS pdh_status = zbx_calculate_counter_value(__func__, counterpath, perfs->lang, value);

		if (PDH_NOT_IMPLEMENTED == pdh_status)
			*error = zbx_strdup(*error, "Counter is not supported for this Microsoft Windows version");
		else if (ERROR_SUCCESS == pdh_status)
			ret = SUCCEED;

		zbx_free(counterpath);
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets average performance counter value                            *
 *                                                                            *
 * Parameters: counterpath - [IN] the performance counter path                *
 *             interval    - [IN] the data collection interval in seconds     *
 *             lang        - [IN] counterpath language (default or English)   *
 *             value       - [OUT] the calculated value                       *
 *             error       - [OUT] the error message                          *
 *                                                                            *
 * Returns:  SUCCEED - the value was retrieved successfully                   *
 *           FAIL    - otherwise                                              *
 *                                                                            *
 * Comments: The value is retrieved from collector (if it has been requested  *
 *           before) or directly from Windows performance counters if         *
 *           possible.                                                        *
 *                                                                            *
 ******************************************************************************/
int	get_perf_counter_value_by_path(const char *counterpath, int interval, zbx_perf_counter_lang_t lang,
		double *value, char **error)
{
	int			ret = FAIL;
	zbx_perf_counter_data_t	*perfs = NULL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() path:%s interval:%d lang:%d", __func__, counterpath,
			interval, lang);

	LOCK_PERFCOUNTERS;

	if (SUCCEED != perf_collector_started())
	{
		*error = zbx_strdup(*error, "Performance collector is not started.");
		goto out;
	}

	for (perfs = ppsd.pPerfCounterList; NULL != perfs; perfs = perfs->next)
	{
		if (0 == strcmp(perfs->counterpath, counterpath) && perfs->lang == lang)
		{
			if (perfs->interval < interval)
				extend_perf_counter_interval(perfs, interval);

			if (PERF_COUNTER_ACTIVE != perfs->status)
				break;

			/* the counter data is already being collected, return it */
			*value = compute_average_value(perfs, interval);
			ret = SUCCEED;
			goto out;
		}
	}

	/* if the requested counter is not already being monitored - start monitoring */
	if (NULL == perfs)
		perfs = add_perf_counter(NULL, counterpath, interval, lang, error);
out:
	UNLOCK_PERFCOUNTERS;

	if (SUCCEED != ret && NULL != perfs)
	{
		/* request counter value directly from Windows performance counters */
		if (ERROR_SUCCESS == zbx_calculate_counter_value(__func__, counterpath, lang, value))
			ret = SUCCEED;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets average value of the specified performance counter interval  *
 *                                                                            *
 * Parameters: counter  - [IN] the performance counter                        *
 *             interval - [IN] the data collection interval in seconds        *
 *             value    - [OUT] the calculated value                          *
 *             error    - [OUT] the error message                             *
 *                                                                            *
 * Returns:  SUCCEED - the value was retrieved successfully                   *
 *           FAIL    - otherwise                                              *
 *                                                                            *
 ******************************************************************************/
int	get_perf_counter_value(zbx_perf_counter_data_t *counter, int interval, double *value, char **error)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() path:%s interval:%d", __func__, counter->counterpath, interval);

	LOCK_PERFCOUNTERS;

	if (SUCCEED != perf_collector_started())
	{
		*error = zbx_strdup(*error, "Performance collector is not started.");
		goto out;
	}

	if (PERF_COUNTER_ACTIVE != counter->status)
	{
		*error = zbx_strdup(*error, "Performance counter is not ready.");
		goto out;
	}

	*value = compute_average_value(counter, interval);
	ret = SUCCEED;
out:
	UNLOCK_PERFCOUNTERS;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

int	refresh_object_cache(void)
{
	DWORD	sz = 0;
	int	ret = SUCCEED;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	LOCK_PERFCOUNTERS;

	if (ppsd.lastrefresh_objects + OBJECT_CACHE_REFRESH_INTERVAL < time(NULL))
	{
		if (PDH_MORE_DATA != PdhEnumObjects(NULL, NULL, NULL, &sz, PERF_DETAIL_WIZARD, TRUE))
		{
			ret = FAIL;
			goto out;
		}

		ppsd.lastrefresh_objects = time(NULL);
	}
out:
	UNLOCK_PERFCOUNTERS;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

static wchar_t	*get_object_name(char *eng_name)
{
	wchar_t	*loc_name = NULL;
	int	i;
	size_t	len;

	LOCK_PERFCOUNTERS;

	len = strlen(eng_name);

	for (i = 0; i < object_num; i++)
	{
		if (NULL != object_names[i].eng_name && len == strlen(object_names[i].eng_name) &&
				0 == zbx_strncasecmp(object_names[i].eng_name, eng_name, len))
		{
			size_t	sz;

			sz = (wcslen(object_names[i].loc_name) + 1) * sizeof(wchar_t);
			loc_name = zbx_malloc(NULL, sz);
			memcpy(loc_name, object_names[i].loc_name, sz);
			break;
		}
	}

	UNLOCK_PERFCOUNTERS;

	return loc_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get localized name of the object                                  *
 *                                                                            *
 * Parameters: eng_name - [IN] english name                                   *
 *                                                                            *
 * Returns:  localized name of the object                                     *
 *                                                                            *
 ******************************************************************************/
wchar_t	*get_object_name_local(char *eng_name)
{
	wchar_t	*name;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (NULL == (name = get_object_name(eng_name)) && SUCCEED == set_object_names())
		name = get_object_name(eng_name);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);

	return name;
}