text: /*
  ** Zabbix
  ** Copyright (C) 2001-2021 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 "common.h"
  #include "stats.h"
  #include "cpustat.h"
  #ifdef _WINDOWS
  #	include "perfstat.h"
  /* defined in sysinfo lib */
  extern int get_cpu_group_num_win32(void);
  extern int get_numa_node_num_win32(void);
  #endif
  #include "mutexs.h"
  #include "log.h"
  
  /* <sys/dkstat.h> removed in OpenBSD 5.7, only <sys/sched.h> with the same CP_* definitions remained */
  #if defined(OpenBSD) && defined(HAVE_SYS_SCHED_H) && !defined(HAVE_SYS_DKSTAT_H)
  #	include <sys/sched.h>
  #endif
  
  #if !defined(_WINDOWS)
  #	define LOCK_CPUSTATS	zbx_mutex_lock(cpustats_lock)
  #	define UNLOCK_CPUSTATS	zbx_mutex_unlock(cpustats_lock)
  static zbx_mutex_t	cpustats_lock = ZBX_MUTEX_NULL;
  #else
  #	define LOCK_CPUSTATS
  #	define UNLOCK_CPUSTATS
  #endif
  
  #ifdef HAVE_KSTAT_H
  static kstat_ctl_t	*kc = NULL;
  static kid_t		kc_id = 0;
  static kstat_t		*(*ksp)[] = NULL;	/* array of pointers to "cpu_stat" elements in kstat chain */
  
  static int	refresh_kstat(ZBX_CPUS_STAT_DATA *pcpus)
  {
  	static int	cpu_over_count_prev = 0;
  	int		cpu_over_count = 0, i, inserted;
  	kid_t		id;
  	kstat_t		*k;
  
  	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
  
  	for (i = 0; i < pcpus->count; i++)
  		(*ksp)[i] = NULL;
  
  	/* kstat_chain_update() can return:							*/
  	/*   - -1 (error),									*/
  	/*   -  a new kstat chain ID (chain successfully updated),				*/
  	/*   -  0 (kstat chain was up-to-date). We ignore this case to make refresh_kstat()	*/
  	/*        usable for first-time initialization as the kstat chain is up-to-date after	*/
  	/*        kstat_open().									*/
  	if (-1 == (id = kstat_chain_update(kc)))
  	{
  		zabbix_log(LOG_LEVEL_ERR, "%s: kstat_chain_update() failed", __func__);
  		return FAIL;
  	}
  
  	if (0 != id)
  		kc_id = id;
  
  	for (k = kc->kc_chain; NULL != k; k = k->ks_next)	/* traverse all kstat chain */
  	{
  		if (0 == strcmp("cpu_stat", k->ks_module))
  		{
  			inserted = 0;
  			for (i = 1; i <= pcpus->count; i++)	/* search in our array of ZBX_SINGLE_CPU_STAT_DATAs */
  			{
  				if (pcpus->cpu[i].cpu_num == k->ks_instance)	/* CPU instance found */
  				{
  					(*ksp)[i - 1] = k;
  					inserted = 1;
  
  					break;
  				}
  
  				if (ZBX_CPUNUM_UNDEF == pcpus->cpu[i].cpu_num)
  				{
  					/* free slot found, most likely first-time initialization */
  					pcpus->cpu[i].cpu_num = k->ks_instance;
  					(*ksp)[i - 1] = k;
  					inserted = 1;
  
  					break;
  				}
  			}
  			if (0 == inserted)	/* new CPU added, no place to keep its data */
  				cpu_over_count++;
  		}
  	}
  
  	if (0 < cpu_over_count)
  	{
  		if (cpu_over_count_prev < cpu_over_count)
  		{
  			zabbix_log(LOG_LEVEL_WARNING, "%d new processor(s) added. Restart Zabbix agentd to enable"
  					" collecting new data.", cpu_over_count - cpu_over_count_prev);
  			cpu_over_count_prev = cpu_over_count;
  		}
  	}
  
  	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
  
  	return SUCCEED;
  }
  #endif
  
  int	init_cpu_collector(ZBX_CPUS_STAT_DATA *pcpus)
  {
  	char				*error = NULL;
  	int				idx, ret = FAIL;
  #ifdef _WINDOWS
  	wchar_t				cpu[16]; /* 16 is enough to store instance name string (group and index) */
  	char				counterPath[PDH_MAX_COUNTER_PATH];
  	PDH_COUNTER_PATH_ELEMENTS	cpe;
  #endif
  	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
  
  #ifdef _WINDOWS
  	cpe.szMachineName = NULL;
  	cpe.szObjectName = get_builtin_object_name(PCI_PROCESSOR_TIME);
  	cpe.szInstanceName = cpu;
  	cpe.szParentInstance = NULL;
  	cpe.dwInstanceIndex = (DWORD)-1;
  	cpe.szCounterName = get_builtin_counter_name(PCI_PROCESSOR_TIME);
  
  	/* 64 logical CPUs (threads) is a hard limit for 32-bit Windows systems and some old 64-bit versions,  */
  	/* such as Windows Vista. Systems with <= 64 threads will always have one processor group, which means */
  	/* it's ok to use old performance counter "\Processor(n)\% Processor Time". However, for systems with  */
  	/* more than 64 threads Windows distributes them evenly across multiple processor groups with maximum  */
  	/* 64 threads per single group. Given that "\Processor(n)" doesn't report values for n >= 64 we need   */
  	/* to use "\Processor Information(g, n)" where g is a group number and n is a thread number within     */
  	/* the group. So, for 72-thread system there will be two groups with 36 threads each and Windows will  */
  	/* report counters "\Processor Information(0, n)" with 0 <= n <= 31 and "\Processor Information(1,n)". */
  
  	if (pcpus->count <= 64)
  	{
  		for (idx = 0; idx <= pcpus->count; idx++)
  		{
  			if (0 == idx)
  				StringCchPrintf(cpu, ARRSIZE(cpu), L"_Total");
  			else
  				_itow_s(idx - 1, cpu, ARRSIZE(cpu), 10);
  
  			if (ERROR_SUCCESS != zbx_PdhMakeCounterPath(__func__, &cpe, counterPath))
  				goto clean;
  
  			if (NULL == (pcpus->cpu_counter[idx] = add_perf_counter(NULL, counterPath, MAX_COLLECTOR_PERIOD,
  					PERF_COUNTER_LANG_DEFAULT, &error)))
  			{
  				goto clean;
  			}
  		}
  	}
  	else
  	{
  		int	gidx, cpu_groups, cpus_per_group, numa_nodes;
  
  		zabbix_log(LOG_LEVEL_DEBUG, "more than 64 CPUs, using \"Processor Information\" counter");
  
  		cpe.szObjectName = get_builtin_object_name(PCI_INFORMATION_PROCESSOR_TIME);
  		cpe.szCounterName = get_builtin_counter_name(PCI_INFORMATION_PROCESSOR_TIME);
  
  		/* This doesn't seem to be well documented but it looks like Windows treats Processor Information */
  		/* object differently on NUMA-enabled systems. First index for the object may either mean logical */
  		/* processor group on non-NUMA systems or NUMA node number when NUMA is available. There may be more */
  		/* NUMA nodes than processor groups. */
  		numa_nodes = get_numa_node_num_win32();
  		cpu_groups = numa_nodes == 1 ? get_cpu_group_num_win32() : numa_nodes;
  		cpus_per_group = pcpus->count / cpu_groups;
  
  		zabbix_log(LOG_LEVEL_DEBUG, "cpu_groups = %d, cpus_per_group = %d, cpus = %d", cpu_groups,
  				cpus_per_group, pcpus->count);
  
  		for (gidx = 0; gidx < cpu_groups; gidx++)
  		{
  			for (idx = 0; idx <= cpus_per_group; idx++)
  			{
  				if (0 == idx)
  				{
  					if (0 != gidx)
  						continue;
  					StringCchPrintf(cpu, ARRSIZE(cpu), L"_Total");
  				}
  				else
  				{
  					StringCchPrintf(cpu, ARRSIZE(cpu), L"%d,%d", gidx, idx - 1);
  				}
  
  				if (ERROR_SUCCESS != zbx_PdhMakeCounterPath(__func__, &cpe, counterPath))
  					goto clean;
  
  				if (NULL == (pcpus->cpu_counter[gidx * cpus_per_group + idx] =
  						add_perf_counter(NULL, counterPath, MAX_COLLECTOR_PERIOD,
  								PERF_COUNTER_LANG_DEFAULT, &error)))
  				{
  					goto clean;
  				}
  			}
  		}
  	}
  
  	cpe.szObjectName = get_builtin_object_name(PCI_PROCESSOR_QUEUE_LENGTH);
  	cpe.szInstanceName = NULL;
  	cpe.szCounterName = get_builtin_counter_name(PCI_PROCESSOR_QUEUE_LENGTH);
  
  	if (ERROR_SUCCESS != zbx_PdhMakeCounterPath(__func__, &cpe, counterPath))
  		goto clean;
  
  	if (NULL == (pcpus->queue_counter = add_perf_counter(NULL, counterPath, MAX_COLLECTOR_PERIOD,
  			PERF_COUNTER_LANG_DEFAULT, &error)))
  	{
  		goto clean;
  	}
  
  	ret = SUCCEED;
  clean:
  	if (NULL != error)
  	{
  		zabbix_log(LOG_LEVEL_WARNING, "cannot add performance counter \"%s\": %s", counterPath, error);
  		zbx_free(error);
  	}
  
  #else	/* not _WINDOWS */
  	if (SUCCEED != zbx_mutex_create(&cpustats_lock, ZBX_MUTEX_CPUSTATS, &error))
  	{
  		zbx_error("unable to create mutex for cpu collector: %s", error);
  		zbx_free(error);
  		exit(EXIT_FAILURE);
  	}
  
  	pcpus->cpu[0].cpu_num = ZBX_CPUNUM_ALL;
  
  #ifndef HAVE_KSTAT_H
  
  	for (idx = 1; idx <= pcpus->count; idx++)
  		pcpus->cpu[idx].cpu_num = idx - 1;
  #else
  	/* Solaris */
  
  	/* CPU instance numbers on Solaris can be non-contiguous, we don't know them yet */
  	for (idx = 1; idx <= pcpus->count; idx++)
  		pcpus->cpu[idx].cpu_num = ZBX_CPUNUM_UNDEF;
  
  	if (NULL == (kc = kstat_open()))
  	{
  		zbx_error("kstat_open() failed");
  		exit(EXIT_FAILURE);
  	}
  
  	kc_id = kc->kc_chain_id;
  
  	if (NULL == ksp)
  		ksp = zbx_malloc(ksp, sizeof(kstat_t *) * pcpus->count);
  
  	if (SUCCEED != refresh_kstat(pcpus))
  	{
  		zbx_error("kstat_chain_update() failed");
  		exit(EXIT_FAILURE);
  	}
  #endif	/* HAVE_KSTAT_H */
  
  	ret = SUCCEED;
  #endif	/* _WINDOWS */
  
  	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
  
  	return ret;
  }
  
  void	free_cpu_collector(ZBX_CPUS_STAT_DATA *pcpus)
  {
  #ifdef _WINDOWS
  	int	idx;
  #endif
  	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
  
  #ifdef _WINDOWS
  	remove_perf_counter(pcpus->queue_counter);
  	pcpus->queue_counter = NULL;
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		remove_perf_counter(pcpus->cpu_counter[idx]);
  		pcpus->cpu_counter[idx] = NULL;
  	}
  #else
  	ZBX_UNUSED(pcpus);
  	zbx_mutex_destroy(&cpustats_lock);
  #endif
  
  #ifdef HAVE_KSTAT_H
  	kstat_close(kc);
  	zbx_free(ksp);
  #endif
  	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
  }
  
  #ifdef _WINDOWS
  int	get_cpu_perf_counter_value(int cpu_num, int interval, double *value, char **error)
  {
  	int	idx;
  
  	/* For Windows we identify CPU by it's index in cpus array, which is CPU ID + 1. */
  	/* At index 0 we keep information about all CPUs. */
  
  	if (ZBX_CPUNUM_ALL == cpu_num)
  		idx = 0;
  	else
  		idx = cpu_num + 1;
  
  	return get_perf_counter_value(collector->cpus.cpu_counter[idx], interval, value, error);
  }
  
  static int	get_cpu_perf_counter_status(zbx_perf_counter_status_t pc_status)
  {
  	switch (pc_status)
  	{
  		case PERF_COUNTER_ACTIVE:
  			return ZBX_CPU_STATUS_ONLINE;
  		case PERF_COUNTER_INITIALIZED:
  			return ZBX_CPU_STATUS_UNKNOWN;
  	}
  
  	return ZBX_CPU_STATUS_OFFLINE;
  }
  #else	/* not _WINDOWS */
  static void	update_cpu_counters(ZBX_SINGLE_CPU_STAT_DATA *cpu, zbx_uint64_t *counter)
  {
  	int	i, index;
  
  	LOCK_CPUSTATS;
  
  	if (MAX_COLLECTOR_HISTORY <= (index = cpu->h_first + cpu->h_count))
  		index -= MAX_COLLECTOR_HISTORY;
  
  	if (MAX_COLLECTOR_HISTORY > cpu->h_count)
  		cpu->h_count++;
  	else if (MAX_COLLECTOR_HISTORY == ++cpu->h_first)
  		cpu->h_first = 0;
  
  	if (NULL != counter)
  	{
  		for (i = 0; i < ZBX_CPU_STATE_COUNT; i++)
  			cpu->h_counter[i][index] = counter[i];
  
  		cpu->h_status[index] = SYSINFO_RET_OK;
  	}
  	else
  		cpu->h_status[index] = SYSINFO_RET_FAIL;
  
  	UNLOCK_CPUSTATS;
  }
  
  static void	update_cpustats(ZBX_CPUS_STAT_DATA *pcpus)
  {
  	int		idx;
  	zbx_uint64_t	counter[ZBX_CPU_STATE_COUNT];
  
  #if defined(HAVE_PROC_STAT)
  
  	FILE		*file;
  	char		line[1024];
  	unsigned char	*cpu_status = NULL;
  	const char	*filename = "/proc/stat";
  
  #elif defined(HAVE_SYS_PSTAT_H)
  
  	struct pst_dynamic	psd;
  	struct pst_processor	psp;
  
  #elif defined(HAVE_FUNCTION_SYSCTLBYNAME) && defined(CPUSTATES)
  
  	long	cp_time[CPUSTATES], *cp_times = NULL;
  	size_t	nlen, nlen_alloc;
  
  #elif defined(HAVE_KSTAT_H)
  
  	cpu_stat_t	*cpu;
  	zbx_uint64_t	total[ZBX_CPU_STATE_COUNT];
  	kid_t		id;
  
  #elif defined(HAVE_FUNCTION_SYSCTL_KERN_CPTIME)
  
  	int		mib[3];
  	long		all_states[CPUSTATES];
  	u_int64_t	one_states[CPUSTATES];
  	size_t		sz;
  
  #elif defined(HAVE_LIBPERFSTAT)
  
  	perfstat_cpu_total_t	ps_cpu_total;
  	perfstat_cpu_t		ps_cpu;
  	perfstat_id_t		ps_id;
  
  #endif
  
  	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
  
  #define ZBX_SET_CPUS_NOTSUPPORTED()				\
  	for (idx = 0; idx <= pcpus->count; idx++)		\
  		update_cpu_counters(&pcpus->cpu[idx], NULL)
  
  #if defined(HAVE_PROC_STAT)
  
  	if (NULL == (file = fopen(filename, "r")))
  	{
  		zbx_error("cannot open [%s]: %s", filename, zbx_strerror(errno));
  		ZBX_SET_CPUS_NOTSUPPORTED();
  		goto exit;
  	}
  
  	cpu_status = (unsigned char *)zbx_malloc(cpu_status, sizeof(unsigned char) * (pcpus->count + 1));
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  		cpu_status[idx] = SYSINFO_RET_FAIL;
  
  	while (NULL != fgets(line, sizeof(line), file))
  	{
  		if (0 != strncmp(line, "cpu", 3))
  			continue;
  
  		if ('0' <= line[3] && line[3] <= '9')
  		{
  			idx = atoi(line + 3) + 1;
  			if (1 > idx || idx > pcpus->count)
  				continue;
  		}
  		else if (' ' == line[3])
  			idx = 0;
  		else
  			continue;
  
  		memset(counter, 0, sizeof(counter));
  
  		sscanf(line, "%*s " ZBX_FS_UI64 " " ZBX_FS_UI64 " " ZBX_FS_UI64 " " ZBX_FS_UI64
  				" " ZBX_FS_UI64 " " ZBX_FS_UI64 " " ZBX_FS_UI64 " " ZBX_FS_UI64
  				" " ZBX_FS_UI64 " " ZBX_FS_UI64,
  				&counter[ZBX_CPU_STATE_USER], &counter[ZBX_CPU_STATE_NICE],
  				&counter[ZBX_CPU_STATE_SYSTEM], &counter[ZBX_CPU_STATE_IDLE],
  				&counter[ZBX_CPU_STATE_IOWAIT], &counter[ZBX_CPU_STATE_INTERRUPT],
  				&counter[ZBX_CPU_STATE_SOFTIRQ], &counter[ZBX_CPU_STATE_STEAL],
  				&counter[ZBX_CPU_STATE_GCPU], &counter[ZBX_CPU_STATE_GNICE]);
  
  		/* Linux includes guest times in user and nice times */
  		counter[ZBX_CPU_STATE_USER] -= counter[ZBX_CPU_STATE_GCPU];
  		counter[ZBX_CPU_STATE_NICE] -= counter[ZBX_CPU_STATE_GNICE];
  
  		update_cpu_counters(&pcpus->cpu[idx], counter);
  		cpu_status[idx] = SYSINFO_RET_OK;
  	}
  	zbx_fclose(file);
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		if (SYSINFO_RET_FAIL == cpu_status[idx])
  			update_cpu_counters(&pcpus->cpu[idx], NULL);
  	}
  
  	zbx_free(cpu_status);
  
  #elif defined(HAVE_SYS_PSTAT_H)
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		memset(counter, 0, sizeof(counter));
  
  		if (0 == idx)
  		{
  			if (-1 == pstat_getdynamic(&psd, sizeof(psd), 1, 0))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)psd.psd_cpu_time[CP_USER];
  			counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)psd.psd_cpu_time[CP_NICE];
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)psd.psd_cpu_time[CP_SYS];
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)psd.psd_cpu_time[CP_IDLE];
  		}
  		else
  		{
  			if (-1 == pstat_getprocessor(&psp, sizeof(psp), 1, pcpus->cpu[idx].cpu_num))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)psp.psp_cpu_time[CP_USER];
  			counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)psp.psp_cpu_time[CP_NICE];
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)psp.psp_cpu_time[CP_SYS];
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)psp.psp_cpu_time[CP_IDLE];
  		}
  
  		update_cpu_counters(&pcpus->cpu[idx], counter);
  	}
  
  #elif defined(HAVE_FUNCTION_SYSCTLBYNAME) && defined(CPUSTATES)
  	/* FreeBSD 7.0 */
  
  	nlen = sizeof(cp_time);
  	if (-1 == sysctlbyname("kern.cp_time", &cp_time, &nlen, NULL, 0) || nlen != sizeof(cp_time))
  	{
  		ZBX_SET_CPUS_NOTSUPPORTED();
  		goto exit;
  	}
  
  	memset(counter, 0, sizeof(counter));
  
  	counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)cp_time[CP_USER];
  	counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)cp_time[CP_NICE];
  	counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)cp_time[CP_SYS];
  	counter[ZBX_CPU_STATE_INTERRUPT] = (zbx_uint64_t)cp_time[CP_INTR];
  	counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)cp_time[CP_IDLE];
  
  	update_cpu_counters(&pcpus->cpu[0], counter);
  
  	/* get size of result set for CPU statistics */
  	if (-1 == sysctlbyname("kern.cp_times", NULL, &nlen_alloc, NULL, 0))
  	{
  		for (idx = 1; idx <= pcpus->count; idx++)
  			update_cpu_counters(&pcpus->cpu[idx], NULL);
  		goto exit;
  	}
  
  	cp_times = zbx_malloc(cp_times, nlen_alloc);
  
  	nlen = nlen_alloc;
  	if (0 == sysctlbyname("kern.cp_times", cp_times, &nlen, NULL, 0) && nlen == nlen_alloc)
  	{
  		for (idx = 1; idx <= pcpus->count; idx++)
  		{
  			int	cpu_num = pcpus->cpu[idx].cpu_num;
  
  			memset(counter, 0, sizeof(counter));
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)*(cp_times + cpu_num * CPUSTATES + CP_USER);
  			counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)*(cp_times + cpu_num * CPUSTATES + CP_NICE);
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)*(cp_times + cpu_num * CPUSTATES + CP_SYS);
  			counter[ZBX_CPU_STATE_INTERRUPT] = (zbx_uint64_t)*(cp_times + cpu_num * CPUSTATES + CP_INTR);
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)*(cp_times + cpu_num * CPUSTATES + CP_IDLE);
  
  			update_cpu_counters(&pcpus->cpu[idx], counter);
  		}
  	}
  	else
  	{
  		for (idx = 1; idx <= pcpus->count; idx++)
  			update_cpu_counters(&pcpus->cpu[idx], NULL);
  	}
  
  	zbx_free(cp_times);
  
  #elif defined(HAVE_KSTAT_H)
  	/* Solaris */
  
  	if (NULL == kc)
  	{
  		ZBX_SET_CPUS_NOTSUPPORTED();
  		goto exit;
  	}
  
  	memset(total, 0, sizeof(total));
  
  	for (idx = 1; idx <= pcpus->count; idx++)
  	{
  read_again:
  		if (NULL != (*ksp)[idx - 1])
  		{
  			zbx_uint64_t	last_idle, last_user, last_system, last_iowait;
  
  			id = kstat_read(kc, (*ksp)[idx - 1], NULL);
  			if (-1 == id || kc_id != id)	/* error or our kstat chain copy is out-of-date */
  			{
  				if (SUCCEED != refresh_kstat(pcpus))
  				{
  					update_cpu_counters(&pcpus->cpu[idx], NULL);
  					continue;
  				}
  				else
  					goto read_again;
  			}
  
  			cpu = (cpu_stat_t *)(*ksp)[idx - 1]->ks_data;
  
  			memset(counter, 0, sizeof(counter));
  
  			/* The cpu counters are stored in 32 bit unsigned integer that can wrap around. */
  			/* To account for possible wraparounds instead of storing the counter directly  */
  			/* in cache, increment the last stored value by the unsigned 32 bit difference  */
  			/* between new value and last value.                                            */
  			if (0 != pcpus->cpu[idx].h_count)
  			{
  				int	index;
  
  				/* only collector can write into cpu history, so for reading */
  				/* collector itself can access it without locking            */
  
  				if (MAX_COLLECTOR_HISTORY <= (index = pcpus->cpu[idx].h_first + pcpus->cpu[idx].h_count - 1))
  					index -= MAX_COLLECTOR_HISTORY;
  
  				last_idle = pcpus->cpu[idx].h_counter[ZBX_CPU_STATE_IDLE][index];
  				last_user = pcpus->cpu[idx].h_counter[ZBX_CPU_STATE_USER][index];
  				last_system = pcpus->cpu[idx].h_counter[ZBX_CPU_STATE_SYSTEM][index];
  				last_iowait = pcpus->cpu[idx].h_counter[ZBX_CPU_STATE_IOWAIT][index];
  			}
  			else
  			{
  				last_idle = 0;
  				last_user = 0;
  				last_system = 0;
  				last_iowait = 0;
  			}
  
  			counter[ZBX_CPU_STATE_IDLE] = cpu->cpu_sysinfo.cpu[CPU_IDLE] - (zbx_uint32_t)last_idle +
  					last_idle;
  			counter[ZBX_CPU_STATE_USER] = cpu->cpu_sysinfo.cpu[CPU_USER] - (zbx_uint32_t)last_user +
  					last_user;
  			counter[ZBX_CPU_STATE_SYSTEM] = cpu->cpu_sysinfo.cpu[CPU_KERNEL] - (zbx_uint32_t)last_system +
  					last_system;
  			counter[ZBX_CPU_STATE_IOWAIT] = cpu->cpu_sysinfo.cpu[CPU_WAIT] - (zbx_uint32_t)last_iowait +
  					last_iowait;
  
  			total[ZBX_CPU_STATE_IDLE] += counter[ZBX_CPU_STATE_IDLE];
  			total[ZBX_CPU_STATE_USER] += counter[ZBX_CPU_STATE_USER];
  			total[ZBX_CPU_STATE_SYSTEM] += counter[ZBX_CPU_STATE_SYSTEM];
  			total[ZBX_CPU_STATE_IOWAIT] += counter[ZBX_CPU_STATE_IOWAIT];
  
  			update_cpu_counters(&pcpus->cpu[idx], counter);
  		}
  		else
  			update_cpu_counters(&pcpus->cpu[idx], NULL);
  	}
  
  	update_cpu_counters(&pcpus->cpu[0], total);
  
  #elif defined(HAVE_FUNCTION_SYSCTL_KERN_CPTIME)
  	/* OpenBSD 4.3 */
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		memset(counter, 0, sizeof(counter));
  
  		if (0 == idx)
  		{
  			mib[0] = CTL_KERN;
  			mib[1] = KERN_CPTIME;
  
  			sz = sizeof(all_states);
  
  			if (-1 == sysctl(mib, 2, &all_states, &sz, NULL, 0) || sz != sizeof(all_states))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)all_states[CP_USER];
  			counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)all_states[CP_NICE];
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)all_states[CP_SYS];
  			counter[ZBX_CPU_STATE_INTERRUPT] = (zbx_uint64_t)all_states[CP_INTR];
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)all_states[CP_IDLE];
  		}
  		else
  		{
  			mib[0] = CTL_KERN;
  			mib[1] = KERN_CPTIME2;
  			mib[2] = pcpus->cpu[idx].cpu_num;
  
  			sz = sizeof(one_states);
  
  			if (-1 == sysctl(mib, 3, &one_states, &sz, NULL, 0) || sz != sizeof(one_states))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)one_states[CP_USER];
  			counter[ZBX_CPU_STATE_NICE] = (zbx_uint64_t)one_states[CP_NICE];
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)one_states[CP_SYS];
  			counter[ZBX_CPU_STATE_INTERRUPT] = (zbx_uint64_t)one_states[CP_INTR];
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)one_states[CP_IDLE];
  		}
  
  		update_cpu_counters(&pcpus->cpu[idx], counter);
  	}
  
  #elif defined(HAVE_LIBPERFSTAT)
  	/* AIX 6.1 */
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		memset(counter, 0, sizeof(counter));
  
  		if (0 == idx)
  		{
  			if (-1 == perfstat_cpu_total(NULL, &ps_cpu_total, sizeof(ps_cpu_total), 1))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)ps_cpu_total.user;
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)ps_cpu_total.sys;
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)ps_cpu_total.idle;
  			counter[ZBX_CPU_STATE_IOWAIT] = (zbx_uint64_t)ps_cpu_total.wait;
  		}
  		else
  		{
  			zbx_snprintf(ps_id.name, sizeof(ps_id.name), "cpu%d", pcpus->cpu[idx].cpu_num);
  
  			/* perfstat_cpu can return -1 for error or 0 when no data is copied */
  			if (1 != perfstat_cpu(&ps_id, &ps_cpu, sizeof(ps_cpu), 1))
  			{
  				update_cpu_counters(&pcpus->cpu[idx], NULL);
  				continue;
  			}
  
  			counter[ZBX_CPU_STATE_USER] = (zbx_uint64_t)ps_cpu.user;
  			counter[ZBX_CPU_STATE_SYSTEM] = (zbx_uint64_t)ps_cpu.sys;
  			counter[ZBX_CPU_STATE_IDLE] = (zbx_uint64_t)ps_cpu.idle;
  			counter[ZBX_CPU_STATE_IOWAIT] = (zbx_uint64_t)ps_cpu.wait;
  		}
  
  		update_cpu_counters(&pcpus->cpu[idx], counter);
  	}
  
  #endif	/* HAVE_LIBPERFSTAT */
  
  #undef ZBX_SET_CPUS_NOTSUPPORTED
  #if defined(HAVE_PROC_STAT) || (defined(HAVE_FUNCTION_SYSCTLBYNAME) && defined(CPUSTATES)) || defined(HAVE_KSTAT_H)
  exit:
  #endif
  	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
  }
  
  void	collect_cpustat(ZBX_CPUS_STAT_DATA *pcpus)
  {
  	update_cpustats(pcpus);
  }
  
  #if defined(HAVE_LIBPERFSTAT)
  static ZBX_CPU_UTIL_PCT_AIX	*increment_address_in_collector(ZBX_CPUS_UTIL_DATA_AIX *p)
  {
  	if (0 != p->h_count && p->row_num == ++p->h_latest)
  		p->h_latest = 0;
  
  	if (p->row_num > p->h_count)
  		p->h_count++;
  
  	return p->counters + p->h_latest * p->column_num;
  }
  
  /* ZBX_PCT_MULTIPLIER value has been chosen to not lose precision (see FLT_EPSILON) and on the other hand */
  /* ensure enough time before counter wrap around ( > 500 years of updating with 100% every second) */
  #define ZBX_PCT_MULTIPLIER	10000000
  
  static zbx_uint64_t	convert_pct_to_uint64(float pct)
  {
  	return (zbx_uint64_t)(pct * (float)ZBX_PCT_MULTIPLIER);
  }
  
  static double	convert_uint64_to_pct(zbx_uint64_t num)
  {
  	return (double)num / (double)ZBX_PCT_MULTIPLIER;
  }
  
  #undef ZBX_PCT_MULTIPLIER
  
  static void	insert_phys_util_into_collector(ZBX_CPUS_UTIL_DATA_AIX *cpus_phys_util,
  		const ZBX_CPU_UTIL_PCT_AIX *util_data, int util_data_count)
  {
  	ZBX_CPU_UTIL_PCT_AIX	*p;
  	int			i;
  
  	LOCK_CPUSTATS;
  
  	p = increment_address_in_collector(cpus_phys_util);
  
  	if (1 == cpus_phys_util->h_count)	/* initial data element */
  	{
  		for (i = 0; i < util_data_count; i++)
  		{
  			p->status = util_data[i].status;
  			p->user_pct = util_data[i].user_pct;
  			p->kern_pct = util_data[i].kern_pct;
  			p->idle_pct = util_data[i].idle_pct;
  			p->wait_pct = util_data[i].wait_pct;
  			p++;
  		}
  
  		for (i = util_data_count; i < cpus_phys_util->column_num; i++)
  		{
  			p->status = SYSINFO_RET_FAIL;
  			p++;
  		}
  	}
  	else
  	{
  		/* index of previous data element */
  		int	prev_idx = (cpus_phys_util->h_latest > 0) ?
  				cpus_phys_util->h_latest - 1 : cpus_phys_util->row_num - 1;
  
  		/* pointer to previous data element */
  		ZBX_CPU_UTIL_PCT_AIX	*prev = cpus_phys_util->counters + prev_idx * cpus_phys_util->column_num;
  
  		for (i = 0; i < util_data_count; i++)
  		{
  			p->status = util_data[i].status;
  			p->user_pct = prev->user_pct + util_data[i].user_pct;
  			p->kern_pct = prev->kern_pct + util_data[i].kern_pct;
  			p->idle_pct = prev->idle_pct + util_data[i].idle_pct;
  			p->wait_pct = prev->wait_pct + util_data[i].wait_pct;
  			p++;
  			prev++;
  		}
  
  		for (i = util_data_count; i < cpus_phys_util->column_num; i++)
  		{
  			p->status = SYSINFO_RET_FAIL;
  			p++;
  		}
  	}
  
  	UNLOCK_CPUSTATS;
  }
  
  static void	insert_error_status_into_collector(ZBX_CPUS_UTIL_DATA_AIX *cpus_phys_util, int cpu_start_nr,
  		int cpu_end_nr)
  {
  	ZBX_CPU_UTIL_PCT_AIX	*p;
  	int			i;
  
  	LOCK_CPUSTATS;
  
  	p = increment_address_in_collector(cpus_phys_util);
  
  	for (i = cpu_start_nr; i <= cpu_end_nr; i++)
  		(p + i)->status = SYSINFO_RET_FAIL;
  
  	UNLOCK_CPUSTATS;
  }
  
  static void	update_cpustats_physical(ZBX_CPUS_UTIL_DATA_AIX *cpus_phys_util)
  {
  	static int			initialized = 0, old_cpu_count, old_stats_count;
  	static perfstat_cpu_total_t	old_cpu_total;
  	static perfstat_cpu_t		*old_cpu_stats = NULL, *new_cpu_stats = NULL, *tmp_cpu_stats;
  	static perfstat_id_t		cpu_id;
  	static perfstat_cpu_util_t	*cpu_util = NULL;
  	static ZBX_CPU_UTIL_PCT_AIX	*util_data = NULL;	/* array for passing utilization data into collector */
  	/* maximum number of CPUs the collector has been configured to handle */
  	int				max_cpu_count = cpus_phys_util->column_num - 1;
  
  	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
  
  	if (0 != initialized)
  	{
  		perfstat_cpu_total_t	new_cpu_total;
  		perfstat_rawdata_t	rawdata;
  		int			new_cpu_count, new_stats_count, i, count_changed = 0;
  
  		/* get total utilization for all CPUs */
  
  		if (-1 == perfstat_cpu_total(NULL, &new_cpu_total, sizeof(perfstat_cpu_total_t), 1))
  		{
  			zabbix_log(LOG_LEVEL_DEBUG, "%s(): perfstat_cpu_total() failed: %s", __func__,
  					zbx_strerror(errno));
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			goto exit;
  		}
  
  		rawdata.type = UTIL_CPU_TOTAL;
  		rawdata.prevstat = &old_cpu_total;
  		rawdata.curstat = &new_cpu_total;
  		rawdata.sizeof_data = sizeof(perfstat_cpu_total_t);
  		rawdata.prev_elems = 1;
  		rawdata.cur_elems = 1;
  
  		if (-1 == perfstat_cpu_util(&rawdata, cpu_util, sizeof(perfstat_cpu_util_t), 1))
  		{
  			zabbix_log(LOG_LEVEL_DEBUG, "%s(): perfstat_cpu_util() failed: %s", __func__,
  					zbx_strerror(errno));
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			goto exit;
  		}
  
  		util_data[0].status = SYSINFO_RET_OK;
  		util_data[0].user_pct = convert_pct_to_uint64(cpu_util[0].user_pct);
  		util_data[0].kern_pct = convert_pct_to_uint64(cpu_util[0].kern_pct);
  		util_data[0].idle_pct = convert_pct_to_uint64(cpu_util[0].idle_pct);
  		util_data[0].wait_pct = convert_pct_to_uint64(cpu_util[0].wait_pct);
  
  		/* get utilization for individual CPUs in one batch */
  
  		if (-1 == (new_cpu_count = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0)))
  		{
  			zabbix_log(LOG_LEVEL_DEBUG, "%s(): perfstat_cpu() failed: %s", __func__,
  					zbx_strerror(errno));
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			goto exit;
  		}
  
  		if (max_cpu_count < new_cpu_count)
  		{
  			zbx_error("number of CPUs has increased. Restart agent to adjust configuration.");
  			exit(EXIT_FAILURE);
  		}
  
  		if (old_cpu_count != new_cpu_count)
  		{
  			old_cpu_count = new_cpu_count;
  			zabbix_log(LOG_LEVEL_WARNING, "number of CPUs has changed from %d to %d,"
  					" skipping this measurement.", old_cpu_count, new_cpu_count);
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			count_changed = 1;
  		}
  
  		zbx_strlcpy(cpu_id.name, FIRST_CPU, sizeof(cpu_id.name));
  
  		if (-1 == (new_stats_count = perfstat_cpu(&cpu_id, new_cpu_stats, sizeof(perfstat_cpu_t),
  				max_cpu_count)))
  		{
  			zabbix_log(LOG_LEVEL_DEBUG, "%s(): perfstat_cpu() failed: %s", __func__,
  					zbx_strerror(errno));
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			goto exit;
  		}
  
  		if (old_stats_count != new_stats_count)
  		{
  			old_stats_count = new_stats_count;
  			zabbix_log(LOG_LEVEL_WARNING, "number of CPU statistics has changed from %d to %d,"
  					" skipping this measurement.", old_stats_count, new_stats_count);
  			insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  			count_changed = 1;
  		}
  
  		if (0 == count_changed)
  		{
  			rawdata.type = UTIL_CPU;
  			rawdata.prevstat = old_cpu_stats;
  			rawdata.curstat = new_cpu_stats;
  			rawdata.sizeof_data = sizeof(perfstat_cpu_t);
  			rawdata.prev_elems = old_stats_count;
  			rawdata.cur_elems = new_stats_count;
  
  			if (-1 == perfstat_cpu_util(&rawdata, cpu_util, sizeof(perfstat_cpu_util_t), new_stats_count))
  			{
  				zabbix_log(LOG_LEVEL_DEBUG, "%s(): perfstat_cpu_util() failed: %s", __func__,
  						zbx_strerror(errno));
  				insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  				goto copy_to_old;
  			}
  
  			for (i = 0; i < new_stats_count; i++)
  			{
  				util_data[i + 1].status = SYSINFO_RET_OK;
  
  				/* It was observed that perfstat_cpu_util() can return 'NaNQ' as percents */
  				/* of utilization and physical counters do not change in this case. */
  
  				if (0 == isnan(cpu_util[i].user_pct) && 0 == isnan(cpu_util[i].kern_pct) &&
  						0 == isnan(cpu_util[i].idle_pct) && 0 == isnan(cpu_util[i].wait_pct))
  				{
  					util_data[i + 1].user_pct = convert_pct_to_uint64(cpu_util[i].user_pct);
  					util_data[i + 1].kern_pct = convert_pct_to_uint64(cpu_util[i].kern_pct);
  					util_data[i + 1].idle_pct = convert_pct_to_uint64(cpu_util[i].idle_pct);
  					util_data[i + 1].wait_pct = convert_pct_to_uint64(cpu_util[i].wait_pct);
  				}
  				else if (old_cpu_stats[i].puser == new_cpu_stats[i].puser &&
  						old_cpu_stats[i].psys == new_cpu_stats[i].psys &&
  						old_cpu_stats[i].pidle == new_cpu_stats[i].pidle &&
  						old_cpu_stats[i].pwait == new_cpu_stats[i].pwait)
  				{
  					util_data[i + 1].user_pct = convert_pct_to_uint64(0);
  					util_data[i + 1].kern_pct = convert_pct_to_uint64(0);
  					util_data[i + 1].idle_pct = convert_pct_to_uint64(100);
  					util_data[i + 1].wait_pct = convert_pct_to_uint64(0);
  				}
  				else
  				{
  					zabbix_log(LOG_LEVEL_DEBUG, "%s(): unexpected case:"
  							" i=%d name=%s puser=%llu psys=%llu pidle=%llu pwait=%llu"
  							" user_pct=%f kern_pct=%f idle_pct=%f wait_pct=%f",
  							__func__, i, new_cpu_stats[i].name,
  							new_cpu_stats[i].puser, new_cpu_stats[i].psys,
  							new_cpu_stats[i].pidle, new_cpu_stats[i].pwait,
  							cpu_util[i].user_pct, cpu_util[i].kern_pct,
  							cpu_util[i].idle_pct, cpu_util[i].wait_pct);
  					insert_error_status_into_collector(cpus_phys_util, 0, max_cpu_count);
  					goto copy_to_old;
  				}
  			}
  
  			insert_phys_util_into_collector(cpus_phys_util, util_data, new_stats_count + 1);
  		}
  copy_to_old:
  		old_cpu_total = new_cpu_total;
  
  		/* swap pointers to old and new data to avoid copying from new to old */
  		tmp_cpu_stats = old_cpu_stats;
  		old_cpu_stats = new_cpu_stats;
  		new_cpu_stats = tmp_cpu_stats;
  	}
  	else	/* the first call */
  	{
  		if (-1 == perfstat_cpu_total(NULL, &old_cpu_total, sizeof(perfstat_cpu_total_t), 1))
  		{
  			zbx_error("the first call of perfstat_cpu_total() failed: %s", zbx_strerror(errno));
  			exit(EXIT_FAILURE);
  		}
  
  		if (-1 == (old_cpu_count = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0)))
  		{
  			zbx_error("the first call of perfstat_cpu() failed: %s", zbx_strerror(errno));
  			exit(EXIT_FAILURE);
  		}
  
  		if (max_cpu_count < old_cpu_count)
  		{
  			zbx_error("number of CPUs has increased. Restart agent to adjust configuration.");
  			exit(EXIT_FAILURE);
  		}
  
  		old_cpu_stats = (perfstat_cpu_t *)zbx_calloc(old_cpu_stats, max_cpu_count, sizeof(perfstat_cpu_t));
  		new_cpu_stats = (perfstat_cpu_t *)zbx_calloc(new_cpu_stats, max_cpu_count, sizeof(perfstat_cpu_t));
  		cpu_util = (perfstat_cpu_util_t *)zbx_calloc(cpu_util, max_cpu_count, sizeof(perfstat_cpu_util_t));
  		util_data = (ZBX_CPU_UTIL_PCT_AIX *)zbx_malloc(util_data,
  				sizeof(ZBX_CPU_UTIL_PCT_AIX) * (max_cpu_count + 1));
  		zbx_strlcpy(cpu_id.name, FIRST_CPU, sizeof(cpu_id.name));
  
  		if (-1 == (old_stats_count = perfstat_cpu(&cpu_id, old_cpu_stats, sizeof(perfstat_cpu_t),
  				max_cpu_count)))
  		{
  			zbx_error("perfstat_cpu() for getting all CPU statistics failed: %s", zbx_strerror(errno));
  			exit(EXIT_FAILURE);
  		}
  
  		initialized = 1;
  	}
  exit:
  	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
  }
  
  void	collect_cpustat_physical(ZBX_CPUS_UTIL_DATA_AIX *cpus_phys_util)
  {
  	update_cpustats_physical(cpus_phys_util);
  }
  #endif
  
  static ZBX_SINGLE_CPU_STAT_DATA	*get_cpustat_by_num(ZBX_CPUS_STAT_DATA *pcpus, int cpu_num)
  {
  	int	idx;
  
  	for (idx = 0; idx <= pcpus->count; idx++)
  	{
  		if (pcpus->cpu[idx].cpu_num == cpu_num)
  			return &pcpus->cpu[idx];
  	}
  
  	return NULL;
  }
  
  int	get_cpustat(AGENT_RESULT *result, int cpu_num, int state, int mode)
  {
  	int				i, time, idx_curr, idx_base;
  	zbx_uint64_t			counter, total = 0;
  	ZBX_SINGLE_CPU_STAT_DATA	*cpu;
  
  	if (0 > state || state >= ZBX_CPU_STATE_COUNT)
  		return SYSINFO_RET_FAIL;
  
  	switch (mode)
  	{
  		case ZBX_AVG1:
  			time = SEC_PER_MIN;
  			break;
  		case ZBX_AVG5:
  			time = 5 * SEC_PER_MIN;
  			break;
  		case ZBX_AVG15:
  			time = 15 * SEC_PER_MIN;
  			break;
  		default:
  			return SYSINFO_RET_FAIL;
  	}
  
  	if (0 == CPU_COLLECTOR_STARTED(collector))
  	{
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	if (NULL == (cpu = get_cpustat_by_num(&collector->cpus, cpu_num)))
  	{
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	if (0 == cpu->h_count)
  	{
  		SET_DBL_RESULT(result, 0);
  		return SYSINFO_RET_OK;
  	}
  
  	LOCK_CPUSTATS;
  
  	if (MAX_COLLECTOR_HISTORY <= (idx_curr = (cpu->h_first + cpu->h_count - 1)))
  		idx_curr -= MAX_COLLECTOR_HISTORY;
  
  	if (SYSINFO_RET_FAIL == cpu->h_status[idx_curr])
  	{
  		UNLOCK_CPUSTATS;
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	if (1 == cpu->h_count)
  	{
  		for (i = 0; i < ZBX_CPU_STATE_COUNT; i++)
  			total += cpu->h_counter[i][idx_curr];
  		counter = cpu->h_counter[state][idx_curr];
  	}
  	else
  	{
  		if (0 > (idx_base = idx_curr - MIN(cpu->h_count - 1, time)))
  			idx_base += MAX_COLLECTOR_HISTORY;
  
  		while (SYSINFO_RET_OK != cpu->h_status[idx_base])
  			if (MAX_COLLECTOR_HISTORY == ++idx_base)
  				idx_base -= MAX_COLLECTOR_HISTORY;
  
  		for (i = 0; i < ZBX_CPU_STATE_COUNT; i++)
  		{
  			if (cpu->h_counter[i][idx_curr] > cpu->h_counter[i][idx_base])
  				total += cpu->h_counter[i][idx_curr] - cpu->h_counter[i][idx_base];
  		}
  
  		/* current counter might be less than previous due to guest time sometimes not being fully included */
  		/* in user time by "/proc/stat" */
  		if (cpu->h_counter[state][idx_curr] > cpu->h_counter[state][idx_base])
  			counter = cpu->h_counter[state][idx_curr] - cpu->h_counter[state][idx_base];
  		else
  			counter = 0;
  	}
  
  	UNLOCK_CPUSTATS;
  
  	SET_DBL_RESULT(result, 0 == total ? 0 : 100. * (double)counter / (double)total);
  
  	return SYSINFO_RET_OK;
  }
  
  #ifdef _AIX
  int	get_cpustat_physical(AGENT_RESULT *result, int cpu_num, int state, int mode)
  {
  	ZBX_CPUS_UTIL_DATA_AIX	*p = &collector->cpus_phys_util;
  	int			time_interval, offset;
  
  	if (ZBX_CPUNUM_ALL != cpu_num && p->column_num - 2 < cpu_num)
  	{
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	switch (mode)
  	{
  		case ZBX_AVG1:
  			time_interval = SEC_PER_MIN;
  			break;
  		case ZBX_AVG5:
  			time_interval = 5 * SEC_PER_MIN;
  			break;
  		case ZBX_AVG15:
  			time_interval = 15 * SEC_PER_MIN;
  			break;
  		default:
  			return SYSINFO_RET_FAIL;
  	}
  
  	if (0 == CPU_COLLECTOR_STARTED(collector))
  	{
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	if (0 == p->h_count)
  	{
  		SET_DBL_RESULT(result, 0);
  		return SYSINFO_RET_OK;
  	}
  
  	LOCK_CPUSTATS;
  
  	if (ZBX_CPUNUM_ALL == cpu_num)
  		offset = p->h_latest * p->column_num;	/* total for all CPUs is in column 0 */
  	else
  		offset = p->h_latest * p->column_num + cpu_num + 1;
  
  	if (SYSINFO_RET_FAIL == p->counters[offset].status)
  	{
  		UNLOCK_CPUSTATS;
  		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information."));
  		return SYSINFO_RET_FAIL;
  	}
  
  	if (1 == p->h_count)
  	{
  		switch (state)
  		{
  			case ZBX_CPU_STATE_USER:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].user_pct));
  				break;
  			case ZBX_CPU_STATE_SYSTEM:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].kern_pct));
  				break;
  			case ZBX_CPU_STATE_IDLE:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].idle_pct));
  				break;
  			case ZBX_CPU_STATE_IOWAIT:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].wait_pct));
  				break;
  			default:
  				UNLOCK_CPUSTATS;
  				SET_MSG_RESULT(result, zbx_strdup(NULL, "Statistics for invalid CPU state requested."));
  				return SYSINFO_RET_FAIL;
  		}
  	}
  	else
  	{
  		int	prev_idx, prev_offset;
  
  		if (p->h_count - 1 < time_interval)	/* less data than averaging interval */
  			time_interval = p->h_count - 1;
  
  		/* index of data element a time interval back */
  		prev_idx = (p->h_latest >= time_interval) ? p->h_latest - time_interval :
  				p->h_latest - time_interval + p->row_num;
  
  		/* offset to data element a time interval back */
  		if (ZBX_CPUNUM_ALL == cpu_num)
  			prev_offset = prev_idx * p->column_num;
  		else
  			prev_offset = prev_idx * p->column_num + cpu_num + 1;
  
  		if (SYSINFO_RET_FAIL == p->counters[prev_offset].status)
  		{
  			UNLOCK_CPUSTATS;
  			SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information."));
  			return SYSINFO_RET_FAIL;
  		}
  
  		switch (state)
  		{
  			case ZBX_CPU_STATE_USER:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].user_pct -
  						p->counters[prev_offset].user_pct) / time_interval);
  				break;
  			case ZBX_CPU_STATE_SYSTEM:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].kern_pct -
  						p->counters[prev_offset].kern_pct) / time_interval);
  				break;
  			case ZBX_CPU_STATE_IDLE:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].idle_pct -
  						p->counters[prev_offset].idle_pct) / time_interval);
  				break;
  			case ZBX_CPU_STATE_IOWAIT:
  				SET_DBL_RESULT(result, convert_uint64_to_pct(p->counters[offset].wait_pct -
  						p->counters[prev_offset].wait_pct) / time_interval);
  				break;
  			default:
  				UNLOCK_CPUSTATS;
  				SET_MSG_RESULT(result, zbx_strdup(NULL, "Statistics for invalid CPU state requested."));
  				return SYSINFO_RET_FAIL;
  		}
  	}
  
  	UNLOCK_CPUSTATS;
  
  	return SYSINFO_RET_OK;
  }
  #endif
  
  static int	get_cpu_status(int pc_status)
  {
  	if (SYSINFO_RET_OK == pc_status)
  		return ZBX_CPU_STATUS_ONLINE;
  
  	return ZBX_CPU_STATUS_OFFLINE;
  }
  #endif	/* _WINDOWS */
  
  /******************************************************************************
   *                                                                            *
   * Function: get_cpus                                                         *
   *                                                                            *
   * Purpose: Retrieve list of available CPUs in the collector                  *
   *                                                                            *
   * Parameters: vector [OUT] - vector for CPUNUM/STATUS pairs                  *
   *                                                                            *
   * Return value: SUCCEED if collector started and has at least one CPU        *
   *               FAIL otherwise                                               *
   *                                                                            *
   * Comments: The data returned is designed for item system.cpu.discovery      *
   *                                                                            *
   ******************************************************************************/
  int	get_cpus(zbx_vector_uint64_pair_t *vector)
  {
  	ZBX_CPUS_STAT_DATA	*pcpus;
  	int			idx, ret = FAIL;
  
  	if (!CPU_COLLECTOR_STARTED(collector) || NULL == (pcpus = &collector->cpus))
  		goto out;
  
  	LOCK_CPUSTATS;
  
  	/* Per-CPU information is stored in the ZBX_SINGLE_CPU_STAT_DATA array */
  	/* starting with index 1. Index 0 contains information about all CPUs. */
  
  	for (idx = 1; idx <= pcpus->count; idx++)
  	{
  		zbx_uint64_pair_t		pair;
  #ifndef _WINDOWS
  		ZBX_SINGLE_CPU_STAT_DATA	*cpu;
  		int				index;
  
  		cpu = &pcpus->cpu[idx];
  
  		if (MAX_COLLECTOR_HISTORY <= (index = cpu->h_first + cpu->h_count - 1))
  			index -= MAX_COLLECTOR_HISTORY;
  
  		pair.first = cpu->cpu_num;
  		pair.second = get_cpu_status(cpu->h_status[index]);
  #else
  		pair.first = idx - 1;
  		pair.second = get_cpu_perf_counter_status(pcpus->cpu_counter[idx]->status);
  #endif
  		zbx_vector_uint64_pair_append(vector, pair);
  	}
  
  	UNLOCK_CPUSTATS;
  
  	ret = SUCCEED;
  out:
  	return ret;
  }
  