/*
** Copyright (C) 2001-2024 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "vmware_perfcntr.h"

#include "zbxcommon.h"

#if defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL)

#include "zbxvmware.h"
#include "vmware_shmem.h"
#include "vmware_internal.h"

#include "zbxnum.h"
#include "zbxstr.h"
#include "zbxxml.h"
#ifdef HAVE_LIBXML2
#	include <libxml/xpath.h>
#endif

ZBX_VECTOR_IMPL(uint16, uint16_t)
ZBX_PTR_VECTOR_IMPL(perf_available_ptr, zbx_vmware_perf_available_t *)
ZBX_PTR_VECTOR_IMPL(vmware_perf_value_ptr, zbx_vmware_perf_value_t *)
ZBX_PTR_VECTOR_IMPL(vmware_counter_ptr, zbx_vmware_counter_t *)

/******************************************************************************
 *                                                                            *
 * performance counter hashset support functions                              *
 *                                                                            *
 ******************************************************************************/
zbx_hash_t	vmware_counter_hash_func(const void *data)
{
	const zbx_vmware_counter_t	*counter = (const zbx_vmware_counter_t *)data;

	return ZBX_DEFAULT_STRING_HASH_ALGO(counter->path, strlen(counter->path), ZBX_DEFAULT_HASH_SEED);
}

int	vmware_counter_compare_func(const void *d1, const void *d2)
{
	const zbx_vmware_counter_t	*c1 = (const zbx_vmware_counter_t *)d1;
	const zbx_vmware_counter_t	*c2 = (const zbx_vmware_counter_t *)d2;

	return strcmp(c1->path, c2->path);
}

/******************************************************************************
 *                                                                            *
 * performance entities hashset support functions                             *
 *                                                                            *
 ******************************************************************************/
zbx_hash_t	vmware_perf_entity_hash_func(const void *data)
{
	zbx_hash_t	seed;

	const zbx_vmware_perf_entity_t	*entity = (const zbx_vmware_perf_entity_t *)data;

	seed = ZBX_DEFAULT_STRING_HASH_ALGO(entity->type, strlen(entity->type), ZBX_DEFAULT_HASH_SEED);

	return ZBX_DEFAULT_STRING_HASH_ALGO(entity->id, strlen(entity->id), seed);
}

int	vmware_perf_entity_compare_func(const void *d1, const void *d2)
{
	int	ret;

	const zbx_vmware_perf_entity_t	*e1 = (const zbx_vmware_perf_entity_t *)d1;
	const zbx_vmware_perf_entity_t	*e2 = (const zbx_vmware_perf_entity_t *)d2;

	if (0 == (ret = strcmp(e1->type, e2->type)))
		ret = strcmp(e1->id, e2->id);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * performance counter availability list support functions                    *
 *                                                                            *
 ******************************************************************************/
static int	vmware_perf_available_compare(const void *d1, const void *d2)
{
	int	ret;

	const zbx_vmware_perf_available_t	*e1 = *(const zbx_vmware_perf_available_t * const *)d1;
	const zbx_vmware_perf_available_t	*e2 = *(const zbx_vmware_perf_available_t * const *)d2;

	if (0 == (ret = strcmp(e1->type, e2->type)))
		ret = strcmp(e1->id, e2->id);

	return ret;
}

static void	vmware_perf_available_free(zbx_vmware_perf_available_t *value)
{
	zbx_free(value->type);
	zbx_free(value->id);
	zbx_vector_uint16_clear(&value->list);
	zbx_vector_uint16_destroy(&value->list);
	zbx_free(value);
}

static int	vmware_uint16_compare(const void *d1, const void *d2)
{
	const uint16_t	*i1 = (const uint16_t *)d1;
	const uint16_t	*i2 = (const uint16_t *)d2;

	ZBX_RETURN_IF_NOT_EQUAL(*i1, *i2);

	return 0;
}

static void	vmware_free_perfvalue(zbx_vmware_perf_value_t *value)
{
	zbx_free(value->instance);
	zbx_free(value);
}

static void	vmware_free_perfdata(zbx_vmware_perf_data_t *data)
{
	zbx_free(data->id);
	zbx_free(data->type);
	zbx_free(data->error);
	zbx_vector_vmware_perf_value_ptr_clear_ext(&data->values, vmware_free_perfvalue);
	zbx_vector_vmware_perf_value_ptr_destroy(&data->values);

	zbx_free(data);
}

/******************************************************************************
 *                                                                            *
 * Purpose: copies performance counter vector into shared memory hashset      *
 *                                                                            *
 * Parameters: dst - [IN] destination hashset                                 *
 *             src - [IN] source vector                                       *
 *                                                                            *
 ******************************************************************************/
void	vmware_counters_shared_copy(zbx_hashset_t *dst, const zbx_vector_vmware_counter_ptr_t *src)
{
	zbx_vmware_counter_t	*csrc, *cdst;

	if (SUCCEED != zbx_hashset_reserve(dst, src->values_num))
	{
		THIS_SHOULD_NEVER_HAPPEN;
		exit(EXIT_FAILURE);
	}

	for (int i = 0; i < src->values_num; i++)
	{
		csrc = src->values[i];

		cdst = (zbx_vmware_counter_t *)zbx_hashset_insert(dst, csrc, sizeof(zbx_vmware_counter_t));

		/* check if the counter was inserted - copy path only for inserted counters */
		if (cdst->path == csrc->path)
			cdst->path = vmware_shared_strdup(csrc->path);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees shared resources allocated to store instance performance    *
 *          counter values                                                    *
 *                                                                            *
 * Parameters: pairs - [IN] vector of performance counter pairs               *
 *                                                                            *
 ******************************************************************************/
void	vmware_vector_str_uint64_pair_shared_clean(zbx_vector_str_uint64_pair_t *pairs)
{
	for (int i = 0; i < pairs->values_num; i++)
	{
		zbx_str_uint64_pair_t	*pair = &pairs->values[i];

		if (NULL != pair->name)
			vmware_shared_strfree(pair->name);
	}

	pairs->values_num = 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: removes statistics data from vmware entities                      *
 *                                                                            *
 ******************************************************************************/
static void	vmware_entities_shared_clean_stats(zbx_hashset_t *entities)
{
	zbx_vmware_perf_entity_t	*entity;
	zbx_vmware_perf_counter_t	*counter;
	zbx_hashset_iter_t		iter;

	zbx_hashset_iter_reset(entities, &iter);
	while (NULL != (entity = (zbx_vmware_perf_entity_t *)zbx_hashset_iter_next(&iter)))
	{
		for (int i = 0; i < entity->counters.values_num; i++)
		{
			counter = (zbx_vmware_perf_counter_t *)entity->counters.values[i];
			vmware_vector_str_uint64_pair_shared_clean(&counter->values);

			if (0 != (counter->state & ZBX_VMWARE_COUNTER_UPDATING))
			{
				counter->state = ZBX_VMWARE_COUNTER_READY |
						(counter->state & ZBX_VMWARE_COUNTER_STATE_MASK);
			}
		}
		vmware_shared_strfree(entity->error);
		entity->error = NULL;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: cleans resources allocated by vmware performance entity in vmware *
 *          cache                                                             *
 *                                                                            *
 * Parameters: entity - [IN] entity to free                                   *
 *                                                                            *
 ******************************************************************************/
void	vmware_shared_perf_entity_clean(zbx_vmware_perf_entity_t *entity)
{
	zbx_vector_vmware_perf_counter_ptr_clear_ext(&entity->counters, vmware_shmem_perf_counter_free);
	zbx_vector_vmware_perf_counter_ptr_destroy(&entity->counters);

	vmware_shared_strfree(entity->query_instance);
	vmware_shared_strfree(entity->type);
	vmware_shared_strfree(entity->id);
	vmware_shared_strfree(entity->error);
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees resources allocated by vmware performance counter           *
 *                                                                            *
 * Parameters: counter - [IN] performance counter to free                     *
 *                                                                            *
 ******************************************************************************/
void	vmware_counter_shared_clean(zbx_vmware_counter_t *counter)
{
	vmware_shared_strfree(counter->path);
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees vmware performance counter and resources allocated by it    *
 *                                                                            *
 * Parameters: counter - [IN] performance counter to free                     *
 *                                                                            *
 ******************************************************************************/
void	vmware_counter_free(zbx_vmware_counter_t *counter)
{
	zbx_free(counter->path);
	zbx_free(counter);
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets performance counter refreshrate for specified entity         *
 *                                                                            *
 * Parameters: service      - [IN] vmware service                             *
 *             easyhandle   - [IN] CURL handle                                *
 *             type         - [IN] entity type (HostSystem, datastore or      *
 *                                 VirtualMachine)                            *
 *             id           - [IN] entity id                                  *
 *             refresh_rate - [OUT] pointer to variable to store refresh rate *
 *             error        - [OUT] error message in case of failure          *
 *                                                                            *
 * Return value: SUCCEED - authentication was completed successfully          *
 *               FAIL    - authentication process has failed                  *
 *                                                                            *
 ******************************************************************************/
static int	vmware_service_get_perf_counter_refreshrate(zbx_vmware_service_t *service, CURL *easyhandle,
		const char *type, const char *id, int *refresh_rate, char **error)
{
#	define ZBX_XPATH_REFRESHRATE()						\
		"/*/*/*/*/*[local-name()='refreshRate' and ../*[local-name()='currentSupported']='true']"

#	define ZBX_POST_VCENTER_PERF_COUNTERS_REFRESH_RATE			\
		ZBX_POST_VSPHERE_HEADER						\
		"<ns0:QueryPerfProviderSummary>"				\
			"<ns0:_this type=\"PerformanceManager\">%s</ns0:_this>"	\
			"<ns0:entity type=\"%s\">%s</ns0:entity>"		\
		"</ns0:QueryPerfProviderSummary>"				\
		ZBX_POST_VSPHERE_FOOTER

#	define ZBX_XPATH_ISAGGREGATE()										\
		"/*/*/*/*/*[local-name()='entity'][../*[local-name()='summarySupported']='true' and "		\
		"../*[local-name()='currentSupported']='false']"

	char	tmp[MAX_STRING_LEN], *value = NULL, *id_esc;
	int	ret = FAIL;
	xmlDoc	*doc = NULL;

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

	id_esc = zbx_xml_escape_dyn(id);
	zbx_snprintf(tmp, sizeof(tmp), ZBX_POST_VCENTER_PERF_COUNTERS_REFRESH_RATE,
			get_vmware_service_objects()[service->type].performance_manager, type, id_esc);
	zbx_free(id_esc);

	if (SUCCEED != zbx_soap_post(__func__, easyhandle, tmp, &doc, NULL, error))
		goto out;

	if (NULL != (value = zbx_xml_doc_read_value(doc, ZBX_XPATH_ISAGGREGATE())))
	{
		zbx_free(value);
		*refresh_rate = ZBX_VMWARE_PERF_INTERVAL_NONE;
		ret = SUCCEED;

		zabbix_log(LOG_LEVEL_DEBUG, "%s() refresh_rate: unused", __func__);
		goto out;
	}
	else if (NULL == (value = zbx_xml_doc_read_value(doc, ZBX_XPATH_REFRESHRATE())))
	{
		*error = zbx_strdup(*error, "Cannot find refreshRate.");
		goto out;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "%s() refresh_rate:%s", __func__, value);

	if (SUCCEED != (ret = zbx_is_uint31(value, refresh_rate)))
		*error = zbx_dsprintf(*error, "Cannot convert refreshRate from %s.",  value);

	zbx_free(value);
out:
	zbx_xml_free_doc(doc);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;

#	undef ZBX_XPATH_REFRESHRATE
#	undef ZBX_XPATH_ISAGGREGATE
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets performance counter ids                                      *
 *                                                                            *
 * Parameters: service      - [IN] vmware service                             *
 *             easyhandle   - [IN] CURL handle                                *
 *             counters     - [IN/OUT] vector, created performance counter    *
 *                                     object should be added to              *
 *             error        - [OUT] error message in case of failure          *
 *                                                                            *
 * Return value: SUCCEED - operation has completed successfully               *
 *               FAIL    - operation has failed                               *
 *                                                                            *
 ******************************************************************************/
int	vmware_service_get_perf_counters(zbx_vmware_service_t *service, CURL *easyhandle,
		zbx_vector_vmware_counter_ptr_t *counters, char **error)
{
#	define ZBX_XPATH_COUNTERINFO()								\
		"/*/*/*/*/*/*[local-name()='propSet']/*[local-name()='val']/*[local-name()='PerfCounterInfo']"

#	define ZBX_POST_VMWARE_GET_PERFCOUNTER							\
		ZBX_POST_VSPHERE_HEADER								\
		"<ns0:RetrievePropertiesEx>"							\
			"<ns0:_this type=\"PropertyCollector\">%s</ns0:_this>"			\
			"<ns0:specSet>"								\
				"<ns0:propSet>"							\
					"<ns0:type>PerformanceManager</ns0:type>"		\
					"<ns0:pathSet>perfCounter</ns0:pathSet>"		\
				"</ns0:propSet>"						\
				"<ns0:objectSet>"						\
					"<ns0:obj type=\"PerformanceManager\">%s</ns0:obj>"	\
				"</ns0:objectSet>"						\
			"</ns0:specSet>"							\
			"<ns0:options/>"							\
		"</ns0:RetrievePropertiesEx>"							\
		ZBX_POST_VSPHERE_FOOTER

#	define STR2UNIT(unit, val)								\
		if (0 == strcmp("joule",val))							\
			unit = ZBX_VMWARE_UNIT_JOULE;						\
		else if (0 == strcmp("kiloBytes",val))						\
			unit = ZBX_VMWARE_UNIT_KILOBYTES;					\
		else if (0 == strcmp("kiloBytesPerSecond",val))					\
			unit = ZBX_VMWARE_UNIT_KILOBYTESPERSECOND;				\
		else if (0 == strcmp("megaBytes",val))						\
			unit = ZBX_VMWARE_UNIT_MEGABYTES;					\
		else if (0 == strcmp("megaBytesPerSecond",val))					\
			unit = ZBX_VMWARE_UNIT_MEGABYTESPERSECOND;				\
		else if (0 == strcmp("megaHertz",val))						\
			unit = ZBX_VMWARE_UNIT_MEGAHERTZ;					\
		else if (0 == strcmp("nanosecond",val))						\
			unit = ZBX_VMWARE_UNIT_NANOSECOND;					\
		else if (0 == strcmp("microsecond",val))					\
			unit = ZBX_VMWARE_UNIT_MICROSECOND;					\
		else if (0 == strcmp("millisecond",val))					\
			unit = ZBX_VMWARE_UNIT_MILLISECOND;					\
		else if (0 == strcmp("number",val))						\
			unit = ZBX_VMWARE_UNIT_NUMBER;						\
		else if (0 == strcmp("percent",val))						\
			unit = ZBX_VMWARE_UNIT_PERCENT;						\
		else if (0 == strcmp("second",val))						\
			unit = ZBX_VMWARE_UNIT_SECOND;						\
		else if (0 == strcmp("teraBytes",val))						\
			unit = ZBX_VMWARE_UNIT_TERABYTES;					\
		else if (0 == strcmp("watt",val))						\
			unit = ZBX_VMWARE_UNIT_WATT;						\
		else if (0 == strcmp("celsius",val))						\
			unit = ZBX_VMWARE_UNIT_CELSIUS;						\
		else										\
			unit = ZBX_VMWARE_UNIT_UNDEFINED

	char		tmp[MAX_STRING_LEN], *group = NULL, *key = NULL, *rollup = NULL, *stats = NULL,
			*counterid = NULL, *unit = NULL;
	xmlDoc		*doc = NULL;
	xmlXPathContext	*xpathCtx;
	xmlXPathObject	*xpathObj;
	xmlNodeSetPtr	nodeset;
	int		ret = FAIL;

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

	zbx_snprintf(tmp, sizeof(tmp), ZBX_POST_VMWARE_GET_PERFCOUNTER,
			get_vmware_service_objects()[service->type].property_collector,
			get_vmware_service_objects()[service->type].performance_manager);

	if (SUCCEED != zbx_soap_post(__func__, easyhandle, tmp, &doc, NULL, error))
		goto out;

	xpathCtx = xmlXPathNewContext(doc);

	if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)ZBX_XPATH_COUNTERINFO(), xpathCtx)))
	{
		*error = zbx_strdup(*error, "Cannot make performance counter list parsing query.");
		goto clean;
	}

	if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
	{
		*error = zbx_strdup(*error, "Cannot find items in performance counter list.");
		goto clean;
	}

	nodeset = xpathObj->nodesetval;
	zbx_vector_vmware_counter_ptr_reserve(counters, (size_t)(2 * nodeset->nodeNr + counters->values_alloc));

	for (int i = 0; i < nodeset->nodeNr; i++)
	{
		zbx_vmware_counter_t	*counter;

		group = zbx_xml_node_read_value(doc, nodeset->nodeTab[i],
				"*[local-name()='groupInfo']/*[local-name()='key']");

		key = zbx_xml_node_read_value(doc, nodeset->nodeTab[i],
						"*[local-name()='nameInfo']/*[local-name()='key']");

		rollup = zbx_xml_node_read_value(doc, nodeset->nodeTab[i], "*[local-name()='rollupType']");
		stats = zbx_xml_node_read_value(doc, nodeset->nodeTab[i], "*[local-name()='statsType']");
		counterid = zbx_xml_node_read_value(doc, nodeset->nodeTab[i], "*[local-name()='key']");
		unit = zbx_xml_node_read_value(doc, nodeset->nodeTab[i],
				"*[local-name()='unitInfo']/*[local-name()='key']");

		if (NULL != group && NULL != key && NULL != rollup && NULL != counterid && NULL != unit)
		{
			counter = (zbx_vmware_counter_t *)zbx_malloc(NULL, sizeof(zbx_vmware_counter_t));
			counter->path = zbx_dsprintf(NULL, "%s/%s[%s]", group, key, rollup);
			ZBX_STR2UINT64(counter->id, counterid);
			STR2UNIT(counter->unit, unit);

			if (ZBX_VMWARE_UNIT_UNDEFINED == counter->unit)
			{
				zabbix_log(LOG_LEVEL_WARNING, "Unknown performance counter " ZBX_FS_UI64
						" type of unitInfo:%s", counter->id, unit);
			}

			zbx_vector_vmware_counter_ptr_append(counters, counter);

			zabbix_log(LOG_LEVEL_DEBUG, "adding performance counter %s:" ZBX_FS_UI64, counter->path,
					counter->id);
		}

		if (NULL != group && NULL != key && NULL != rollup && NULL != counterid && NULL != stats &&
				NULL != unit)
		{
			counter = (zbx_vmware_counter_t *)zbx_malloc(NULL, sizeof(zbx_vmware_counter_t));
			counter->path = zbx_dsprintf(NULL, "%s/%s[%s,%s]", group, key, rollup, stats);
			ZBX_STR2UINT64(counter->id, counterid);
			STR2UNIT(counter->unit, unit);

			if (ZBX_VMWARE_UNIT_UNDEFINED == counter->unit)
			{
				zabbix_log(LOG_LEVEL_WARNING, "Unknown performance counter " ZBX_FS_UI64
						" type of unitInfo:%s", counter->id, unit);
			}

			zbx_vector_vmware_counter_ptr_append(counters, counter);

			zabbix_log(LOG_LEVEL_DEBUG, "adding performance counter %s:" ZBX_FS_UI64, counter->path,
					counter->id);
		}

		zbx_free(counterid);
		zbx_free(stats);
		zbx_free(rollup);
		zbx_free(key);
		zbx_free(group);
		zbx_free(unit);
	}

	ret = SUCCEED;
clean:
	xmlXPathFreeObject(xpathObj);
	xmlXPathFreeContext(xpathCtx);
out:
	zbx_xml_free_doc(doc);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;

#	undef ZBX_XPATH_COUNTERINFO
#	undef STR2UNIT
#	undef ZBX_POST_VMWARE_GET_PERFCOUNTER
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds entity to vmware service performance entity list             *
 *                                                                            *
 * Parameters: service  - [IN] vmware service                                 *
 *             type     - [IN] performance entity type (HostSystem,           *
 *                             (Datastore, VirtualMachine...)                 *
 *             id       - [IN] performance entity id                          *
 *             counters - [IN] NULL terminated list of performance counters   *
 *                             to be monitored for this entity                *
 *             instance - [IN] performance counter instance name              *
 *             now      - [IN] current timestamp                              *
 *                                                                            *
 * Comments: performance counters are specified by their path:                *
 *             <group>/<key>[<rollup type>]                                   *
 *                                                                            *
 ******************************************************************************/
static void	vmware_service_add_perf_entity(zbx_vmware_service_t *service, const char *type, const char *id,
		const char **counters, const char *instance, time_t now)
{
	zbx_vmware_perf_entity_t	entity, *pentity;
	zbx_uint64_t			counterid;

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

	if (NULL == (pentity = zbx_vmware_service_get_perf_entity(service, type, id)))
	{
		entity.type = vmware_shared_strdup(type);
		entity.id = vmware_shared_strdup(id);

		pentity = (zbx_vmware_perf_entity_t *)zbx_hashset_insert(&service->entities, &entity,
				sizeof(zbx_vmware_perf_entity_t));

		vmware_perf_counters_vector_ptr_create_ext(pentity);

		for (int i = 0; NULL != counters[i]; i++)
		{
			if (SUCCEED == zbx_vmware_service_get_counterid(service, counters[i], &counterid, NULL))
				vmware_perf_counters_add_new(&pentity->counters, counterid, ZBX_VMWARE_COUNTER_NEW);
			else
				zabbix_log(LOG_LEVEL_DEBUG, "cannot find performance counter %s", counters[i]);
		}

		zbx_vector_vmware_perf_counter_ptr_sort(&pentity->counters, ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC);
		pentity->refresh = ZBX_VMWARE_PERF_INTERVAL_UNKNOWN;
		pentity->query_instance = vmware_shared_strdup(instance);
		pentity->error = NULL;
	}

	pentity->last_seen = now;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() perfcounters:%d", __func__, pentity->counters.values_num);
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds new or removes old entities (hypervisors, virtual machines)  *
 *          from service performance entity list                              *
 *                                                                            *
 * Parameters: service - [IN] vmware service                                  *
 *                                                                            *
 ******************************************************************************/
void	vmware_service_update_perf_entities(zbx_vmware_service_t *service)
{
	zbx_vmware_hv_t		*hv;
	zbx_vmware_vm_t		*vm;
	zbx_hashset_iter_t	iter;

	const char		*hv_perfcounters[] = {
					"net/packetsRx[summation]", "net/packetsTx[summation]",
					"net/received[average]", "net/transmitted[average]",
					"datastore/totalReadLatency[average]",
					"datastore/totalWriteLatency[average]",
					"datastore/numberReadAveraged[average]",
					"datastore/numberWriteAveraged[average]",
					"cpu/usage[average]", "cpu/utilization[average]",
					"power/power[average]", "power/powerCap[average]",
					"net/droppedRx[summation]", "net/droppedTx[summation]",
					"net/errorsRx[summation]", "net/errorsTx[summation]",
					"net/broadcastRx[summation]", "net/broadcastTx[summation]",
					NULL
				};

	const char		*vm_perfcounters[] = {
					"virtualDisk/read[average]", "virtualDisk/write[average]",
					"virtualDisk/numberReadAveraged[average]",
					"virtualDisk/numberWriteAveraged[average]",
					"net/packetsRx[summation]", "net/packetsTx[summation]",
					"net/received[average]", "net/transmitted[average]",
					"cpu/ready[summation]", "net/usage[average]", "cpu/usage[average]",
					"cpu/latency[average]", "cpu/readiness[average]",
					"cpu/swapwait[summation]", "sys/osUptime[latest]",
					"mem/consumed[average]", "mem/usage[average]", "mem/swapped[average]",
					"net/usage[average]", "virtualDisk/readOIO[latest]",
					"virtualDisk/writeOIO[latest]",
					"virtualDisk/totalWriteLatency[average]",
					"virtualDisk/totalReadLatency[average]",
					NULL
				};

	const char		*ds_perfcounters[] = {
					"disk/used[latest]", "disk/provisioned[latest]",
					"disk/capacity[latest]", NULL
				};

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

	/* update current performance entities */
	zbx_hashset_iter_reset(&service->data->hvs, &iter);
	while (NULL != (hv = (zbx_vmware_hv_t *)zbx_hashset_iter_next(&iter)))
	{
		vmware_service_add_perf_entity(service, ZBX_VMWARE_SOAP_HV, hv->id, hv_perfcounters,
				ZBX_VMWARE_PERF_QUERY_ALL, service->lastcheck);

		for (int i = 0; i < hv->vms.values_num; i++)
		{
			vm = (zbx_vmware_vm_t *)hv->vms.values[i];
			vmware_service_add_perf_entity(service, ZBX_VMWARE_SOAP_VM, vm->id, vm_perfcounters,
					ZBX_VMWARE_PERF_QUERY_ALL, service->lastcheck);
			zabbix_log(LOG_LEVEL_TRACE, "%s() for type: VirtualMachine hv id: %s hv uuid: %s linked vm id:"
					" %s vm uuid: %s", __func__, hv->id, hv->uuid, vm->id, vm->uuid);
		}
	}

	if (ZBX_VMWARE_TYPE_VCENTER == service->type)
	{
		for (int i = 0; i < service->data->datastores.values_num; i++)
		{
			zbx_vmware_datastore_t	*ds = service->data->datastores.values[i];
			vmware_service_add_perf_entity(service, ZBX_VMWARE_SOAP_DS, ds->id, ds_perfcounters,
					ZBX_VMWARE_PERF_QUERY_TOTAL, service->lastcheck);
			zabbix_log(LOG_LEVEL_TRACE, "%s() for type: Datastore id: %s name: %s uuid: %s", __func__,
					ds->id, ds->name, ds->uuid);
		}
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() entities:%d", __func__, service->entities.num_data);
}

/******************************************************************************
 *                                                                            *
 * Purpose: updates vmware performance statistics data                        *
 *                                                                            *
 * Parameters: perfdata  - [OUT] performance counter values                   *
 *             xdoc      - [IN] xml document containing performance           *
 *                              counter values for all entities               *
 *             node      - [IN] xml node containing performance counter       *
 *                              values for specified entity                   *
 *                                                                            *
 * Return value: SUCCEED - performance entity data was parsed                 *
 *               FAIL    - performance entity data did not contain valid      *
 *                         values                                             *
 *                                                                            *
 ******************************************************************************/
static int	vmware_service_process_perf_entity_data(zbx_vmware_perf_data_t *perfdata, xmlDoc *xdoc, xmlNode *node)
{
	xmlXPathContext				*xpathCtx;
	xmlXPathObject				*xpathObj;
	xmlNodeSetPtr				nodeset;
	char					*instance, *counter, *value;
	int					values = 0, ret = FAIL;
	zbx_vector_vmware_perf_value_ptr_t	*pervalues = &perfdata->values;
	zbx_vmware_perf_value_t			*perfvalue;

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

	xpathCtx = xmlXPathNewContext(xdoc);
	xpathCtx->node = node;

	if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)"*[local-name()='value']", xpathCtx)))
		goto out;

	if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
		goto out;

	nodeset = xpathObj->nodesetval;
	zbx_vector_vmware_perf_value_ptr_reserve(pervalues, (size_t)(nodeset->nodeNr + pervalues->values_alloc));

	for (int i = 0; i < nodeset->nodeNr; i++)
	{
		if (NULL == (value = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i],
				"*[local-name()='value'][text() != '-1'][last()]")))
		{
			value = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i], "*[local-name()='value'][last()]");
		}

		instance = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i], "*[local-name()='id']"
				"/*[local-name()='instance']");
		counter = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i], "*[local-name()='id']"
				"/*[local-name()='counterId']");

		if (NULL != value && NULL != counter)
		{
			perfvalue = (zbx_vmware_perf_value_t *)zbx_malloc(NULL, sizeof(zbx_vmware_perf_value_t));

			ZBX_STR2UINT64(perfvalue->counterid, counter);
			perfvalue->instance = (NULL != instance ? instance : zbx_strdup(NULL, ""));

			if (0 == strcmp(value, "-1") || SUCCEED != zbx_is_uint64(value, &perfvalue->value))
			{
				perfvalue->value = ZBX_MAX_UINT64;
				zabbix_log(LOG_LEVEL_DEBUG, "PerfCounter inaccessible. type:%s object id:%s "
						"counter id:" ZBX_FS_UI64 " instance:%s value:%s", perfdata->type,
						perfdata->id, perfvalue->counterid, perfvalue->instance, value);
			}
			else if (FAIL == ret)
				ret = SUCCEED;

			zbx_vector_vmware_perf_value_ptr_append(pervalues, perfvalue);

			instance = NULL;
			values++;
		}

		zbx_free(counter);
		zbx_free(instance);
		zbx_free(value);
	}

out:
	xmlXPathFreeObject(xpathObj);
	xmlXPathFreeContext(xpathCtx);

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

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: updates vmware performance statistics data                        *
 *                                                                            *
 * Parameters: perfdata - [OUT] performance entity data                       *
 *             xdoc     - [IN] performance data xml document                  *
 *                                                                            *
 ******************************************************************************/
static void	vmware_service_parse_perf_data(zbx_vector_vmware_perf_data_ptr_t *perfdata, xmlDoc *xdoc)
{
	xmlXPathContext	*xpathCtx;
	xmlXPathObject	*xpathObj;
	xmlNodeSetPtr	nodeset;

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

	xpathCtx = xmlXPathNewContext(xdoc);

	if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)"/*/*/*/*", xpathCtx)))
		goto clean;

	if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
		goto clean;

	nodeset = xpathObj->nodesetval;
	zbx_vector_vmware_perf_data_ptr_reserve(perfdata, (size_t)(nodeset->nodeNr + perfdata->values_alloc));

	for (int i = 0; i < nodeset->nodeNr; i++)
	{
		zbx_vmware_perf_data_t	*data;
		int			ret = FAIL;

		data = (zbx_vmware_perf_data_t *)zbx_malloc(NULL, sizeof(zbx_vmware_perf_data_t));

		data->id = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i], "*[local-name()='entity']");
		data->type = zbx_xml_node_read_value(xdoc, nodeset->nodeTab[i], "*[local-name()='entity']/@type");
		data->error = NULL;
		zbx_vector_vmware_perf_value_ptr_create(&data->values);

		if (NULL != data->type && NULL != data->id)
			ret = vmware_service_process_perf_entity_data(data, xdoc, nodeset->nodeTab[i]);

		if (SUCCEED == ret)
			zbx_vector_vmware_perf_data_ptr_append(perfdata, data);
		else
			vmware_free_perfdata(data);
	}
clean:
	xmlXPathFreeObject(xpathObj);
	xmlXPathFreeContext(xpathCtx);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds error for specified perf entity                              *
 *                                                                            *
 * Parameters: perfdata - [OUT] collected performance counter data            *
 *             type     - [IN] performance entity type (HostSystem,           *
 *                             (Datastore, VirtualMachine...)                 *
 *             id       - [IN] performance entity id                          *
 *             error    - [IN] error to add                                   *
 *                                                                            *
 * Comments: performance counters are specified by their path:                *
 *             <group>/<key>[<rollup type>]                                   *
 *                                                                            *
 ******************************************************************************/
static void	vmware_perf_data_add_error(zbx_vector_vmware_perf_data_ptr_t *perfdata, const char *type,
		const char *id, const char *error)
{
	zbx_vmware_perf_data_t	*data = zbx_malloc(NULL, sizeof(zbx_vmware_perf_data_t));

	data->type = zbx_strdup(NULL, type);
	data->id = zbx_strdup(NULL, id);
	data->error = zbx_strdup(NULL, error);
	zbx_vector_vmware_perf_value_ptr_create(&data->values);

	zbx_vector_vmware_perf_data_ptr_append(perfdata, data);
}

/******************************************************************************
 *                                                                            *
 * Purpose: copies vmware performance statistics of specified service         *
 *                                                                            *
 * Parameters: service  - [IN] vmware service                                 *
 *             perfdata - [IN/OUT]                                            *
 *                                                                            *
 ******************************************************************************/
static void	vmware_service_copy_perf_data(const zbx_vmware_service_t *service,
		zbx_vector_vmware_perf_data_ptr_t *perfdata)
{
	zbx_vmware_perf_data_t		*data;
	zbx_vmware_perf_value_t		*value;
	zbx_vmware_perf_entity_t	*entity;
	zbx_vmware_perf_counter_t	*perfcounter;
	zbx_str_uint64_pair_t		perfvalue;

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

	for (int i = 0; i < perfdata->values_num; i++)
	{
		data = (zbx_vmware_perf_data_t *)perfdata->values[i];

		if (NULL == (entity = zbx_vmware_service_get_perf_entity(service, data->type, data->id)))
			continue;

		if (NULL != data->error)
		{
			entity->error = vmware_shared_strdup(data->error);
			continue;
		}

		for (int j = 0; j < data->values.values_num; j++)
		{
			int	index;

			value = (zbx_vmware_perf_value_t *)data->values.values[j];

			zbx_vmware_perf_counter_t	loc = {.counterid = value->counterid};

			if (FAIL == (index = zbx_vector_vmware_perf_counter_ptr_bsearch(&entity->counters,
					&loc, ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
			{
				continue;
			}

			perfcounter = (zbx_vmware_perf_counter_t *)entity->counters.values[index];

			perfvalue.name = vmware_shared_strdup(value->instance);
			perfvalue.value = value->value;

			zbx_vector_str_uint64_pair_append_ptr(&perfcounter->values, &perfvalue);
		}
	}

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

#define ZBX_XML_DATETIME		26

/******************************************************************************
 *                                                                            *
 * Purpose: retrieves performance counter values from vmware service          *
 *                                                                            *
 * Parameters: service      - [IN] vmware service                             *
 *             easyhandle   - [IN] prepared cURL connection handle            *
 *             entities     - [IN] performance collector entities to retrieve *
 *                                 counters for                               *
 *             counters_max - [IN] maximum number of counters per query       *
 *             perfdata     - [OUT] performance counter values                *
 *                                                                            *
 ******************************************************************************/
static void	vmware_service_retrieve_perf_counters(zbx_vmware_service_t *service, CURL *easyhandle,
		zbx_vector_vmware_perf_entity_ptr_t *entities, int counters_max,
		zbx_vector_vmware_perf_data_ptr_t *perfdata)
{
	char				*tmp = NULL, *error = NULL;
	size_t				tmp_alloc = 0, tmp_offset;
	int				i, j, start_counter = 0;
	zbx_vmware_perf_entity_t	*entity;
	xmlDoc				*doc = NULL;

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

	while (0 != entities->values_num)
	{
		int	counters_num = 0;

		tmp_offset = 0;
		zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, ZBX_POST_VSPHERE_HEADER);
		zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "<ns0:QueryPerf>"
				"<ns0:_this type=\"PerformanceManager\">%s</ns0:_this>",
				get_vmware_service_objects()[service->type].performance_manager);

		zbx_vmware_lock();

		for (i = entities->values_num - 1; 0 <= i && counters_num < counters_max;)
		{
			char	*id_esc;

			entity = (zbx_vmware_perf_entity_t *)entities->values[i];

			id_esc = zbx_xml_escape_dyn(entity->id);

			/* add entity performance counter request */
			zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "<ns0:querySpec>"
					"<ns0:entity type=\"%s\">%s</ns0:entity>", entity->type, id_esc);

			zbx_free(id_esc);

			if (ZBX_VMWARE_PERF_INTERVAL_NONE == entity->refresh)
			{
				time_t	st_raw;
				struct	tm st;
				char	st_str[ZBX_XML_DATETIME];

				/* add startTime for entity performance counter request for decrease xml data load */
				st_raw = time(NULL) - SEC_PER_HOUR;
				gmtime_r(&st_raw, &st);
				strftime(st_str, sizeof(st_str), "%Y-%m-%dT%TZ", &st);
				zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "<ns0:startTime>%s</ns0:startTime>",
						st_str);
			}

			zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "<ns0:maxSample>2</ns0:maxSample>");

			for (j = start_counter; j < entity->counters.values_num && counters_num < counters_max; j++)
			{
				zbx_vmware_perf_counter_t	*counter;

				counter = (zbx_vmware_perf_counter_t *)entity->counters.values[j];

				if (0 != (counter->state & ZBX_VMWARE_COUNTER_CUSTOM) &&
						0 == (counter->state & ZBX_VMWARE_COUNTER_ACCEPTABLE))
				{
					continue;
				}

				zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset,
						"<ns0:metricId><ns0:counterId>" ZBX_FS_UI64
						"</ns0:counterId><ns0:instance>%s</ns0:instance></ns0:metricId>",
						counter->counterid, NULL == counter->query_instance ?
						entity->query_instance : counter->query_instance);

				counter->state |= ZBX_VMWARE_COUNTER_UPDATING;

				counters_num++;
			}

			if (j == entity->counters.values_num)
			{
				start_counter = 0;
				i--;
			}
			else
				start_counter = j;

			if (ZBX_VMWARE_PERF_INTERVAL_NONE != entity->refresh)
			{
				zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "<ns0:intervalId>%d</ns0:intervalId>",
					entity->refresh);
			}

			zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "</ns0:querySpec>");
		}

		zbx_vmware_unlock();
		zbx_xml_free_doc(doc);
		doc = NULL;

		zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, "</ns0:QueryPerf>");
		zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, ZBX_POST_VSPHERE_FOOTER);

		zabbix_log(LOG_LEVEL_TRACE, "%s() SOAP request: %s", __func__, tmp);

		if (SUCCEED != zbx_soap_post(__func__, easyhandle, tmp, &doc, NULL, &error))
		{
			for (j = i + 1; j < entities->values_num; j++)
			{
				entity = (zbx_vmware_perf_entity_t *)entities->values[j];
				vmware_perf_data_add_error(perfdata, entity->type, entity->id, error);
			}

			zbx_free(error);
			break;
		}

		/* parse performance data into local memory */
		vmware_service_parse_perf_data(perfdata, doc);

		while (entities->values_num > i + 1)
			zbx_vector_vmware_perf_entity_ptr_remove_noorder(entities, entities->values_num - 1);
	}

	zbx_free(tmp);
	zbx_xml_free_doc(doc);

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

/******************************************************************************
 *                                                                            *
 * Purpose: removes unused performance counters                               *
 *                                                                            *
 * Parameters: counters - [IN] list of perf counters                          *
 *                                                                            *
 * Return value: SUCCEED - performance entity is empty (can be deleted)       *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	vmware_perf_counters_expired_remove(zbx_vector_vmware_perf_counter_ptr_t *counters)
{
	time_t	now = time(NULL);

	for (int i = counters->values_num - 1; i >= 0 ; i--)
	{
		zbx_vmware_perf_counter_t	*counter = counters->values[i];

		if (0 == (counter->state & ZBX_VMWARE_COUNTER_CUSTOM))
			continue;

		if (0 == counter->last_used ||
				(0 != (counter->state & ZBX_VMWARE_COUNTER_NOTSUPPORTED) &&
				now - SEC_PER_HOUR * 2 < counter->last_used) ||
				(0 == (counter->state & ZBX_VMWARE_COUNTER_NOTSUPPORTED) &&
				now - SEC_PER_DAY < counter->last_used))
		{
			continue;
		}

		vmware_shmem_perf_counter_free(counter);
		zbx_vector_vmware_perf_counter_ptr_remove(counters, i);
	}

	return 0 == counters->values_num ? SUCCEED : FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: updates cache with lists of available perf counter for entity     *
 *                                                                            *
 * Parameters: service        - [IN] vmware service                           *
 *             easyhandle     - [IN] prepared cURL connection handle          *
 *             type           - [IN] vmware object type (vm, hv etc)          *
 *             id             - [IN] vmware object id (vm, hv etc)            *
 *             refresh        - [IN] vmware refresh interval for perf counter *
 *             begin_time     - [IN] vmware begin time for perf counters list *
 *             perf_available - [IN/OUT] list of available counter per object *
 *             perf           - [IN/OUT] list of perf entities                *
 *             error          - [OUT] error message in case of failure        *
 *                                                                            *
 * Return value: SUCCEED - operation has completed successfully               *
 *               FAIL    - operation has failed                               *
 *                                                                            *
 ******************************************************************************/
static int	vmware_perf_available_update(zbx_vmware_service_t *service, CURL *easyhandle, const char *type,
		const char *id, const int refresh, const char *begin_time,
		zbx_vector_perf_available_ptr_t *perf_available, zbx_vmware_perf_available_t **perf, char **error)
{
#	define ZBX_POST_VMWARE_GET_AVAIL_PERF							\
		ZBX_POST_VSPHERE_HEADER								\
		"<ns0:QueryAvailablePerfMetric>"						\
			"<ns0:_this type=\"PerformanceManager\">%s</ns0:_this>"			\
			"<ns0:entity type=\"%s\">%s</ns0:entity>"				\
			"<ns0:beginTime>%s</ns0:beginTime>"					\
			"%s"									\
		"</ns0:QueryAvailablePerfMetric>"						\
		ZBX_POST_VSPHERE_FOOTER

	int			ret;
	char			tmp[MAX_STRING_LEN], interval[MAX_STRING_LEN / 32];
	xmlDoc			*doc = NULL;
	zbx_vector_str_t	counters;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() type:%s id:%s begin_time:%s interval:%d", __func__, type, id,
			begin_time, refresh);

	zbx_vector_str_create(&counters);

	if (ZBX_VMWARE_PERF_INTERVAL_NONE == refresh)
		*interval = '\0';
	else
		zbx_snprintf(interval, sizeof(interval), "<ns0:intervalId>%d</ns0:intervalId>", refresh);

	zbx_snprintf(tmp, sizeof(tmp), ZBX_POST_VMWARE_GET_AVAIL_PERF,
			get_vmware_service_objects()[service->type].performance_manager, type, id, begin_time,
			interval);

	if (SUCCEED != (ret = zbx_soap_post(__func__, easyhandle, tmp, &doc, NULL, error)))
		goto out;

	if (FAIL == zbx_xml_read_values(doc, "/" ZBX_XPATH_LN2("returnval", "counterId"), &counters))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() empty list for type:%s id:%s interval:%d begin time:%s", __func__,
				type, id, refresh, begin_time);
	}

	*perf = (zbx_vmware_perf_available_t *)zbx_malloc(NULL, sizeof(zbx_vmware_perf_available_t));
	(*perf)->type = zbx_strdup(NULL, type);
	(*perf)->id = zbx_strdup(NULL, id);
	zbx_vector_uint16_create(&(*perf)->list);

	for (int i = 0; i < counters.values_num; i++)
		zbx_vector_uint16_append(&(*perf)->list, (uint16_t)atoi(counters.values[i]));

	zbx_vector_uint16_sort(&(*perf)->list, vmware_uint16_compare);
	zbx_vector_uint16_uniq(&(*perf)->list, vmware_uint16_compare);
	zbx_vector_perf_available_ptr_append(perf_available, *perf);
	zbx_vector_perf_available_ptr_sort(perf_available, vmware_perf_available_compare);
out:
	zbx_vector_str_clear_ext(&counters, zbx_str_free);
	zbx_vector_str_destroy(&counters);
	zbx_xml_free_doc(doc);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;

#	undef ZBX_POST_VMWARE_GET_AVAIL_PERF
}

/******************************************************************************
 *                                                                            *
 * Purpose: sets flag ZBX_VMWARE_COUNTER_ACCEPTABLE for new perf counters     *
 *                                                                            *
 * Parameters: service        - [IN] vmware service                           *
 *             easyhandle     - [IN] prepared cURL connection handle          *
 *             perf_available - [IN/OUT] list of available counter per object *
 *             entities       - [IN/OUT] list of perf entities                *
 *                                                                            *
 ******************************************************************************/
static void	vmware_perf_counters_availability_check(zbx_vmware_service_t *service, CURL *easyhandle,
		zbx_vector_perf_available_ptr_t *perf_available, zbx_vector_vmware_perf_entity_ptr_t *entities)
{
	char	begin_time[ZBX_XML_DATETIME];

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() entities:%d perf_available:%d", __func__,
			entities->values_num, perf_available->values_num);

	*begin_time = '\0';

	for (int i = 0; i < entities->values_num ; i++)
	{
		zbx_vmware_perf_entity_t	*entity;

		entity = (zbx_vmware_perf_entity_t *)entities->values[i];

		for (int j = 0; j < entity->counters.values_num; j++)
		{
			int				k;
			char				*err = NULL;
			zbx_vmware_perf_counter_t	*counter;
			zbx_vmware_perf_available_t	*perf, perf_cmp = {.type = entity->type, .id = entity->id};

			counter = (zbx_vmware_perf_counter_t *)entity->counters.values[j];

			if (0 == (counter->state & ZBX_VMWARE_COUNTER_CUSTOM) ||
					0 != (counter->state & ZBX_VMWARE_COUNTER_ACCEPTABLE))
			{
				continue;
			}

			if ('\0' == *begin_time)
			{
				time_t		st_raw;
				struct	tm	st;

				st_raw = time(NULL) - SEC_PER_HOUR;
				gmtime_r(&st_raw, &st);
				strftime(begin_time, sizeof(begin_time), "%Y-%m-%dT%TZ", &st);
			}

			if (FAIL != (k = zbx_vector_perf_available_ptr_bsearch(
					perf_available, &perf_cmp, vmware_perf_available_compare)))
			{
				perf = perf_available->values[k];
			}
			else if (FAIL == vmware_perf_available_update(service, easyhandle, entity->type,
					entity->id, entity->refresh, begin_time, perf_available, &perf, &err))
			{
				zabbix_log(LOG_LEVEL_WARNING, "%s() cache update error: %s", __func__, err);
				zbx_str_free(err);
				return;
			}

			if (FAIL == zbx_vector_uint16_bsearch(&perf->list, (uint16_t)counter->counterid,
					vmware_uint16_compare))
			{
				counter->state |= ZBX_VMWARE_COUNTER_NOTSUPPORTED;
			}
			else
			{
				counter->state |= ZBX_VMWARE_COUNTER_ACCEPTABLE;
			}

			zabbix_log(LOG_LEVEL_DEBUG, "%s() type:%s id:%s counterid:" ZBX_FS_UI64 " state:%X %s",
					__func__, entity->type, entity->id, counter->counterid, counter->state,
					0 == (counter->state & ZBX_VMWARE_COUNTER_ACCEPTABLE) ?
					"NOTSUPPORTED" : "ACCEPTABLE");
		}
	}

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

#undef ZBX_XML_DATETIME

/******************************************************************************
 *                                                                            *
 * Purpose: updates vmware statistics data                                    *
 *                                                                            *
 * Parameters: service               - [IN] vmware service                    *
 *             config_source_ip      - [IN]                                   *
 *             config_vmware_timeout - [IN]                                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_vmware_service_update_perf(zbx_vmware_service_t *service, const char *config_source_ip,
		int config_vmware_timeout)
{
#define INIT_PERF_XML_SIZE	200 * ZBX_KIBIBYTE
	CURL					*easyhandle = NULL;
	CURLoption				opt;
	CURLcode				err;
	struct curl_slist			*headers = NULL;
	int					ret = FAIL;
	char					*error = NULL;
	zbx_vector_vmware_perf_entity_ptr_t	entities, hist_entities;
	zbx_vmware_perf_entity_t		*entity;

	zbx_hashset_iter_t			iter;
	zbx_vector_vmware_perf_data_ptr_t	perfdata;
	zbx_vector_perf_available_ptr_t		perf_available;
	static ZBX_HTTPPAGE			page;	/* 173K */

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() '%s'@'%s'", __func__, service->username, service->url);

	zbx_vector_vmware_perf_entity_ptr_create(&entities);
	zbx_vector_vmware_perf_entity_ptr_create(&hist_entities);
	zbx_vector_vmware_perf_data_ptr_create(&perfdata);
	zbx_vector_perf_available_ptr_create(&perf_available);
	page.alloc = 0;

	if (NULL == (easyhandle = curl_easy_init()))
	{
		error = zbx_strdup(error, "cannot initialize cURL library");
		goto out;
	}

	page.alloc = INIT_PERF_XML_SIZE;
	page.data = (char *)zbx_malloc(NULL, page.alloc);
	headers = curl_slist_append(headers, ZBX_XML_HEADER1_V4);
	headers = curl_slist_append(headers, ZBX_XML_HEADER2);
	headers = curl_slist_append(headers, ZBX_XML_HEADER3);

	if (CURLE_OK != (err = curl_easy_setopt(easyhandle, opt = CURLOPT_HTTPHEADER, headers)))
	{
		error = zbx_dsprintf(error, "Cannot set cURL option %d: %s.", (int)opt, curl_easy_strerror(err));
		goto clean;
	}

	if (SUCCEED != vmware_service_authenticate(service, easyhandle, &page, config_source_ip, config_vmware_timeout,
			&error))
	{
		goto clean;
	}

	/* update performance counter refresh rate for entities */

	zbx_vmware_lock();

	zbx_hashset_iter_reset(&service->entities, &iter);
	while (NULL != (entity = (zbx_vmware_perf_entity_t *)zbx_hashset_iter_next(&iter)))
	{
		/* remove old entities */
		if ((0 != entity->last_seen && entity->last_seen < service->lastcheck) ||
				SUCCEED == vmware_perf_counters_expired_remove(&entity->counters))
		{
			vmware_shared_perf_entity_clean(entity);
			zbx_hashset_iter_remove(&iter);
			continue;
		}

		if (ZBX_VMWARE_PERF_INTERVAL_UNKNOWN != entity->refresh)
			continue;

		/* Entities are removed only during performance counter update and no two */
		/* performance counter updates for one service can happen simultaneously. */
		/* This means for refresh update we can safely use reference to entity    */
		/* outside vmware lock.                                                   */
		zbx_vector_vmware_perf_entity_ptr_append(&entities, entity);
	}

	zbx_vmware_unlock();

	/* get refresh rates */
	for (int i = 0; i < entities.values_num; i++)
	{
		entity = entities.values[i];

		if (SUCCEED != vmware_service_get_perf_counter_refreshrate(service, easyhandle, entity->type,
				entity->id, &entity->refresh, &error))
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot get refresh rate for %s \"%s\": %s", entity->type,
					entity->id, error);
			zbx_free(error);
		}
	}

	zbx_vector_vmware_perf_entity_ptr_clear(&entities);

	zbx_vmware_lock();

	/* checking the availability of custom performance counters */
	zbx_hashset_iter_reset(&service->entities, &iter);
	while (NULL != (entity = (zbx_vmware_perf_entity_t *)zbx_hashset_iter_next(&iter)))
	{
		for (int i = 0; i < entity->counters.values_num; i++)
		{
			zbx_vmware_perf_counter_t	*counter;

			counter = (zbx_vmware_perf_counter_t *)entity->counters.values[i];

			if (0 == (counter->state & ZBX_VMWARE_COUNTER_CUSTOM) || 0 != (counter->state &
					(ZBX_VMWARE_COUNTER_ACCEPTABLE | ZBX_VMWARE_COUNTER_NOTSUPPORTED)))
			{
				continue;
			}

			zbx_vector_vmware_perf_entity_ptr_append(&entities, entity);
			break;
		}
	}

	zbx_vmware_unlock();

	vmware_perf_counters_availability_check(service, easyhandle, &perf_available, &entities);
	zbx_vector_vmware_perf_entity_ptr_clear(&entities);

	zbx_vmware_lock();

	zbx_hashset_iter_reset(&service->entities, &iter);
	while (NULL != (entity = (zbx_vmware_perf_entity_t *)zbx_hashset_iter_next(&iter)))
	{
		if (ZBX_VMWARE_PERF_INTERVAL_UNKNOWN == entity->refresh)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "skipping performance entity with zero refresh rate "
					"type:%s id:%s", entity->type, entity->id);
			continue;
		}

		int	i;

		/* pre-check acceptable counters */
		for (i = 0; i < entity->counters.values_num; i++)
		{
			zbx_vmware_perf_counter_t	*counter;

			counter = (zbx_vmware_perf_counter_t *)entity->counters.values[i];

			if (0 != (counter->state & ZBX_VMWARE_COUNTER_CUSTOM) &&
					0 == (counter->state & ZBX_VMWARE_COUNTER_ACCEPTABLE))
			{
				continue;
			}

			break;
		}

		if (i == entity->counters.values_num)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "skipping performance entity with type:%s id:%s: "
					"unsupported counters", entity->type, entity->id);
			continue;
		}


		if (ZBX_VMWARE_PERF_INTERVAL_NONE == entity->refresh)
			zbx_vector_vmware_perf_entity_ptr_append(&hist_entities, entity);
		else
			zbx_vector_vmware_perf_entity_ptr_append(&entities, entity);
	}

	zbx_vmware_unlock();

	vmware_service_retrieve_perf_counters(service, easyhandle, &entities, ZBX_MAXQUERYMETRICS_UNLIMITED, &perfdata);
	vmware_service_retrieve_perf_counters(service, easyhandle, &hist_entities, service->data->max_query_metrics,
			&perfdata);

	if (SUCCEED != vmware_service_logout(service, easyhandle, &error))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "Cannot close vmware connection: %s.", error);
		zbx_free(error);
	}

	ret = SUCCEED;
clean:
	curl_slist_free_all(headers);
	curl_easy_cleanup(easyhandle);
	zbx_free(page.data);
out:
	zbx_vmware_lock();

	if (FAIL == ret)
	{
		zbx_hashset_iter_reset(&service->entities, &iter);
		while (NULL != (entity = zbx_hashset_iter_next(&iter)))
			entity->error = vmware_shared_strdup(error);

		zbx_free(error);
	}
	else
	{
		/* clean old performance data and copy the new data into shared memory */
		vmware_entities_shared_clean_stats(&service->entities);
		vmware_service_copy_perf_data(service, &perfdata);
	}

	zbx_vmware_unlock();

	zbx_vector_perf_available_ptr_clear_ext(&perf_available, vmware_perf_available_free);
	zbx_vector_perf_available_ptr_destroy(&perf_available);

	zbx_vector_vmware_perf_data_ptr_clear_ext(&perfdata, vmware_free_perfdata);
	zbx_vector_vmware_perf_data_ptr_destroy(&perfdata);

	zbx_vector_vmware_perf_entity_ptr_destroy(&hist_entities);
	zbx_vector_vmware_perf_entity_ptr_destroy(&entities);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s \tprocessed " ZBX_FS_SIZE_T " bytes of data", __func__,
			zbx_result_string(ret), (zbx_fs_size_t)page.alloc);

	return ret;
#undef INIT_PERF_XML_SIZE
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets vmware performance counter id and unit info by path          *
 *                                                                            *
 * Parameters: service   - [IN] vmware service                                *
 *             path      - [IN] path of counter to retrieve in format         *
 *                              <group>/<key>[<rollup type>]                  *
 *             counterid - [OUT]                                              *
 *             unit      - [OUT] counter unit info (kilo, mega, % etc)        *
 *                                                                            *
 * Return value: SUCCEED if counter was found, FAIL otherwise                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_vmware_service_get_counterid(const zbx_vmware_service_t *service, const char *path,
		zbx_uint64_t *counterid, int *unit)
{
#if defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL)
	zbx_vmware_counter_t	*counter;
	int			ret = FAIL;

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

	if (NULL == (counter = (zbx_vmware_counter_t *)zbx_hashset_search(&service->counters, &path)))
		goto out;

	*counterid = counter->id;

	if (NULL != unit)
		*unit = counter->unit;

	zabbix_log(LOG_LEVEL_DEBUG, "%s() counterid:" ZBX_FS_UI64, __func__, *counterid);

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

	return ret;
#else
	return FAIL;
#endif
}

/******************************************************************************
 *                                                                            *
 * Purpose: starts monitoring performance counter of specified entity         *
 *                                                                            *
 * Parameters: service   - [IN] vmware service                                *
 *             type      - [IN] entity type                                   *
 *             id        - [IN] entity id                                     *
 *             counterid - [IN] performance counter id                        *
 *             instance  - [IN] performance counter instance name             *
 *                                                                            *
 * Return value: SUCCEED - entity counter was added to monitoring list        *
 *               FAIL    - performance counter of specified entity is already *
 *                         being monitored.                                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_vmware_service_add_perf_counter(zbx_vmware_service_t *service, const char *type, const char *id,
		zbx_uint64_t counterid, const char *instance)
{
	zbx_vmware_perf_entity_t	*pentity, entity;
	zbx_vmware_perf_counter_t	*counter;
	int				i, ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() type:%s id:%s counterid:" ZBX_FS_UI64, __func__, type, id,
			counterid);

	if (NULL == (pentity = zbx_vmware_service_get_perf_entity(service, type, id)))
	{
		entity.refresh = ZBX_VMWARE_PERF_INTERVAL_UNKNOWN;
		entity.last_seen = 0;
		entity.query_instance = vmware_shared_strdup(instance);
		entity.type = vmware_shared_strdup(type);
		entity.id = vmware_shared_strdup(id);
		entity.error = NULL;
		vmware_shmem_vector_vmware_perf_counter_ptr_create_ext(&entity.counters);

		pentity = (zbx_vmware_perf_entity_t *)zbx_hashset_insert(&service->entities, &entity,
				sizeof(zbx_vmware_perf_entity_t));
	}

	zbx_vmware_perf_counter_t	loc = {.counterid = counterid};

	if (FAIL == (i = zbx_vector_vmware_perf_counter_ptr_search(&pentity->counters, &loc,
			ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
	{
		vmware_perf_counters_add_new(&pentity->counters, counterid,
				ZBX_VMWARE_COUNTER_NEW | ZBX_VMWARE_COUNTER_CUSTOM);
		counter = (zbx_vmware_perf_counter_t *)pentity->counters.values[pentity->counters.values_num - 1];
		zbx_vector_vmware_perf_counter_ptr_sort(&pentity->counters, ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC);

		ret = SUCCEED;
	}
	else
		counter = (zbx_vmware_perf_counter_t *)pentity->counters.values[i];

	if (*ZBX_VMWARE_PERF_QUERY_ALL != *pentity->query_instance)
		counter->query_instance = vmware_shared_strdup(instance);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s counter state:%X", __func__, zbx_result_string(ret),
			counter->state);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets performance entity by type and id                            *
 *                                                                            *
 * Parameters: service - [IN] vmware service                                  *
 *             type    - [IN] performance entity type                         *
 *             id      - [IN] performance entity id                           *
 *                                                                            *
 * Return value: performance entity or NULL if not found                      *
 *                                                                            *
 ******************************************************************************/
zbx_vmware_perf_entity_t	*zbx_vmware_service_get_perf_entity(const zbx_vmware_service_t *service,
		const char *type, const char *id)
{
	zbx_vmware_perf_entity_t	*pentity, entity = {.type = (char *)type, .id = (char *)id};

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

	pentity = (zbx_vmware_perf_entity_t *)zbx_hashset_search(&service->entities, &entity);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() entity:%p", __func__, (void *)pentity);

	return pentity;
}

#endif	/* defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL) */