/*
 * libsysactivity
 * http://sourceforge.net/projects/libsysactivity/
 * Copyright (c) 2010 Carlos Olmedo Escobar <carlos.olmedo.e@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <errno.h>
#include <kinfo.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>

#include "utils.h"

static void percentages(struct sa_cpu* dst, struct kinfo_cputime *new, struct kinfo_cputime *old) SA_NONNULL;

__thread uint16_t number_cpus;
__thread struct kinfo_cputime* cpu_times;
__thread size_t cpu_times_len;
__thread struct kinfo_cputime* cpu_old;

int sa_open_cpu() {
	cpu_old = NULL;
	cpu_times = NULL;
	cpu_times_len = 0;
	int num;
	if (kinfo_get_cpus(&num))
		return ENOSYS;

	number_cpus = num;

	cpu_old = calloc(num, sizeof(struct kinfo_cputime));
	if (cpu_old == NULL)
		return ENOMEM;

	return 0;
}

int sa_close_cpu() {
	if (cpu_old != NULL)
		free(cpu_old);
	if (cpu_times != NULL)
		free(cpu_times);
	return 0;
}

int sa_reset_cpus() {
	size_t len = sizeof(struct kinfo_cputime) * number_cpus;
	if (cpu_times_len < len) {
		safe_realloc(&cpu_times, &len);
		cpu_times_len = len;
		if (cpu_times == NULL)
			return ENOMEM;
	}
	if (sysctlbyname("kern.cputime", cpu_times, &cpu_times_len, NULL, 0) != 0)
		return ENOSYS;

	return 0;
}

int sa_get_cpu(uint16_t index, struct sa_cpu* dst) {
	if (index >= number_cpus || dst == NULL)
		return EINVAL;

	percentages(dst, &cpu_times[index], &cpu_old[index]);
	return 0;
}

int sa_count_cpus(uint16_t* number) {
	if (number == NULL)
		return EINVAL;

	*number = number_cpus;
	return 0;
}

int sa_get_cpus(struct sa_cpu* dst, uint16_t dst_size, uint16_t* written) {
	if (dst == NULL || dst_size == 0 || written == NULL)
		return EINVAL;

	*written = 0;
	uint16_t i;
	for (i = 0; i < number_cpus; ++i) {
		if (i >= dst_size)
			return ENOMEM;

		percentages(&dst[i], &cpu_times[i], &cpu_old[i]);
		++(*written);
	}
	return 0;
}

static void percentages(struct sa_cpu* dst, struct kinfo_cputime *new, struct kinfo_cputime *old) {
	struct kinfo_cputime delta;
	delta.cp_user = new->cp_user - old->cp_user;
	delta.cp_nice = new->cp_nice - old->cp_nice;
	delta.cp_sys = new->cp_sys - old->cp_sys;
	delta.cp_intr = new->cp_intr - old->cp_intr;
	delta.cp_idle = new->cp_idle - old->cp_idle;

	uint64_t total = delta.cp_user + delta.cp_nice + delta.cp_sys + delta.cp_intr + delta.cp_idle;
	if (total == 0)
		total = 1;

	dst->user = (double) delta.cp_user * 100.0 / (double) total;
	dst->nice = (double) delta.cp_nice * 100.0 / (double) total;
	dst->system = (double) delta.cp_sys * 100.0 / (double) total;
	dst->idle = (double) delta.cp_idle * 100.0 / (double) total;
	dst->intr = (double) delta.cp_intr * 100.0 / (double) total;

	*old = *new;
}
