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

#include "zbxkstat.h"

#if defined(HAVE_KSTAT_H) && defined(HAVE_VMINFO_T_UPDATES)

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

extern ZBX_COLLECTOR_DATA	*collector;

static kstat_ctl_t	*kc = NULL;
static kid_t		kc_id = 0;
static kstat_t		*kc_vminfo;

static zbx_mutex_t	kstat_lock = ZBX_MUTEX_NULL;

/******************************************************************************
 *                                                                            *
 * Purpose: refreshes kstat environment                                       *
 *                                                                            *
 * Parameters: error - [OUT] the error message                                *
 *                                                                            *
 * Return value: SUCCEED - the kstat environment was refreshed successfully   *
 *               FAIL - otherwise                                             *
 *                                                                            *
 ******************************************************************************/
static int	zbx_kstat_refresh(char **error)
{
	int	ret;
	kid_t	kid;

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

	if (-1 == (kid = kstat_chain_update(kc)))
	{
		*error = zbx_dsprintf(*error, "failed to update kstat chain: %s", zbx_strerror(errno));
		ret = FAIL;
		goto out;
	}

	if (0 != kid)
		kc_id = kid;

	if (NULL == (kc_vminfo = kstat_lookup(kc, "unix", -1, "vminfo")))
	{
		*error = zbx_dsprintf(*error, "failed to find vminfo data: %s", zbx_strerror(errno));
		ret = FAIL;
		goto out;
	}
	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize kstat environment                                      *
 *                                                                            *
 * Parameters: kstat - [IN] the kstat data storage                            *
 *             error - [OUT] the error message                                *
 *                                                                            *
 * Return value: SUCCEED - the kstat environment was initialized successfully *
 *               FAIL - otherwise                                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_kstat_init(zbx_kstat_t *kstat, char **error)
{
	char	*errmsg = NULL;
	int	ret = FAIL;

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

	if (NULL == (kc = kstat_open()))
	{
		*error = zbx_dsprintf(*error, "failed to open kstat: %s", zbx_strerror(errno));
		goto out;
	}

	kc_id = kc->kc_chain_id;
	if (SUCCEED != zbx_kstat_refresh(error))
		goto out;

	if (SUCCEED != (ret = zbx_mutex_create(&kstat_lock, ZBX_MUTEX_KSTAT, &errmsg)))
	{
		*error = zbx_dsprintf(*error, "failed to create kstat collector mutex : %s", errmsg);
		zbx_free(errmsg);
	}

	memset(kstat, 0, sizeof(zbx_kstat_t));
out:
	if (SUCCEED != ret && NULL != kc)
	{
		if (-1 == kstat_close(kc))
			zabbix_log(LOG_LEVEL_DEBUG, "Failed to close kstat");
		kc = NULL;
	}

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

	return ret;
}

void	zbx_kstat_destroy(void)
{
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (NULL == kc)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "kc is NULL");
		goto out;
	}

	if (-1 == kstat_close(kc))
		zabbix_log(LOG_LEVEL_DEBUG, "Failed to close kstat");

	zbx_mutex_destroy(&kstat_lock);
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: collect kstat stats                                               *
 *                                                                            *
 * Comments: This function is called every second to collect statistics.      *
 *                                                                            *
 ******************************************************************************/
void	zbx_kstat_collect(zbx_kstat_t *kstat)
{
	kid_t		kid;
	char		*error = NULL;
	vminfo_t	vminfo;

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

	while (-1 == (kid = kstat_read(kc, kc_vminfo, &vminfo)) || kc_id != kid)
	{
		if (-1 == kid)
			zabbix_log(LOG_LEVEL_DEBUG, "cannot collect kstat data, kstat_read: %s", ZBX_NULL2STR(error));

		if (SUCCEED != zbx_kstat_refresh(&error))
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot collect kstat data, kstat_refresh: %s",
					ZBX_NULL2STR(error));
			zbx_free(error);
			goto out;
		}
	}

	zbx_mutex_lock(kstat_lock);

	kstat->vminfo_index ^= 1;
	kstat->vminfo[kstat->vminfo_index].freemem = vminfo.freemem;
	kstat->vminfo[kstat->vminfo_index].updates = time(NULL);

	zbx_mutex_unlock(kstat_lock);

	zabbix_log(LOG_LEVEL_DEBUG, "vm_index: %d, freemem: " ZBX_FS_UI64 ", updates: " ZBX_FS_UI64,
			(int)kstat->vminfo_index, kstat->vminfo[kstat->vminfo_index].freemem,
			kstat->vminfo[kstat->vminfo_index].updates);
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get free memory size                                              *
 *                                                                            *
 * Parameters: value - [OUT] the free memory size in bytes                    *
 *             error - [OUT] the error message                                *
 *                                                                            *
 * Return value: SUCCEED - the free memory size was stored in value           *
 *               FAIL - either an error occurred (error parameter is set) or  *
 *                      data was not collected yet (error parameter is left   *
 *                      unchanged)                                            *
 *                                                                            *
 ******************************************************************************/
int	zbx_kstat_get_freemem(zbx_uint64_t *value, char **error)
{
	int			sysconf_pagesize, last, prev, ret = FAIL;
	zbx_kstat_vminfo_t	*vminfo;

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

	zbx_mutex_lock(kstat_lock);

	if (NULL == collector)
		goto out;

	last = collector->kstat.vminfo_index;
	prev = last ^ 1;
	vminfo = collector->kstat.vminfo;

	if (0 != vminfo[prev].updates && vminfo[prev].updates < vminfo[last].updates)
	{
		if (-1 == (sysconf_pagesize = sysconf(_SC_PAGESIZE)))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "sysconf(_SC_PAGESIZE) failed, errno is: %s", zbx_strerror(errno));
			goto out;
		}

		*value = (vminfo[last].freemem - vminfo[prev].freemem) /
				(vminfo[last].updates - vminfo[prev].updates) * sysconf_pagesize;
		ret = SUCCEED;
	}
	else
		zabbix_log(LOG_LEVEL_DEBUG, "no new vminfo update is available");
out:
	zbx_mutex_unlock(kstat_lock);

	if (NULL == collector)
	{
		*error = zbx_strdup(*error, "Collector is not started.");
		zabbix_log(LOG_LEVEL_DEBUG, "Collector is not started");
	}
	else
	{
		zabbix_log(LOG_LEVEL_DEBUG, "last: %d, prev: %d, vminfo[prev].updates: " ZBX_FS_UI64
				", vminfo[last].updates: " ZBX_FS_UI64, last, prev, vminfo[prev].updates,
				vminfo[last].updates);
	}

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

	return ret;
}

#endif /*#if defined(HAVE_KSTAT_H) && defined(HAVE_VMINFO_T_UPDATES)*/