/*
 * (C) Copyright Fuji Xerox Co., Ltd. All rights are reserved.
 * 
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * 2019/05/29	nobuo.iwata@fujixerox.co.jp
 */

#include "fxlsm_prof.h"
#include "fxlsm_log.h"

#ifdef FXLSM_USE_PROF_TIME

struct __fxlsm_prof_time {
	rwlock_t lock;
	unsigned long long time;
	unsigned long count;
};

static struct __fxlsm_prof_time times[FXLSM_PROF_TIME_NUM];

static inline struct __fxlsm_prof_time *id2time(int id)
{
	return (id >= 0 && id < FXLSM_PROF_TIME_NUM) ? times + id : NULL;
}

void fxlsm_prof_time_add(int id, ktime_t time)
{
	struct __fxlsm_prof_time *__p = id2time(id);

	if (!__p)
		return;
	write_lock(&(__p->lock));
	__p->time += time.tv64;
	++(__p->count);
	write_unlock(&(__p->lock));
}

static inline void set_time_average(struct fxlsm_prof_time *p)
{
	if (p->count)
		p->average = (p->time + (p->count - 1)) / p->count;
	else
		p->average = 0;
}

void fxlsm_prof_time_average(struct fxlsm_prof_time *p)
{
	set_time_average(p);
}

void fxlsm_prof_time_read(int id, struct fxlsm_prof_time *p)
{
	struct __fxlsm_prof_time *__p = id2time(id);

	if (!__p)
		return;
	read_lock(&(__p->lock));
	p->time = __p->time;
	p->count = __p->count;
	read_unlock(&(__p->lock));
	set_time_average(p);
}

void fxlsm_prof_time_marge(
		struct fxlsm_prof_time *all,
		struct fxlsm_prof_time *hit, struct fxlsm_prof_time *mis,
		int *rate)
{
	all->time = hit->time + mis->time;
	all->count = hit->count + mis->count;
	set_time_average(all);
	if (rate) {
		if (all->count)
			*rate = (hit->count * 100) / all->count;
		else
			*rate = 0;
	}
}

static ktime_t prof_time_started;

static void prof_time_start(void)
{
	prof_time_started = ktime_get();
}

unsigned long long fxlsm_prof_time_running(void)
{
	ktime_t time = ktime_sub(ktime_get(), prof_time_started);

	return time.tv64;
}

static void prof_time_reset(int id)
{
	struct __fxlsm_prof_time *__p = id2time(id);

	if (!__p)
		return;
	write_lock(&(__p->lock));
	__p->time = 0;
	__p->count = 0;
	write_unlock(&(__p->lock));
}

void fxlsm_prof_time_reset(void)
{
	int i;

	prof_time_start();
	for (i = 0; i < FXLSM_PROF_TIME_NUM; i++)
		prof_time_reset(i);
}

static void prof_time_init(void)
{
	int i;

	prof_time_start();
	for (i = 0; i < FXLSM_PROF_TIME_NUM; i++)
		rwlock_init(&((times + i)->lock));
}

#endif /* FXLSM_USE_PROF_TIME */

#ifdef FXLSM_USE_PROF_MEM

struct __fxlsm_prof_mem {
	rwlock_t lock;
	const char *name;
	unsigned long bytes;
	unsigned long blocks;
};

static struct __fxlsm_prof_mem mems[FXLSM_PROF_MEM_NUM] = {
	{ .name = "seq" },
	{ .name = "seq2" },
	{ .name = "nseq" },
	{ .name = "nseq2" },
	{ .name = "capf" },
	{ .name = "comm" },
	{ .name = "comm2" },
	{ .name = "capc" },
	{ .name = "ld" },
	{ .name = "ld2" },
	{ .name = "events" }
};

static inline struct __fxlsm_prof_mem *id2mem(int id)
{
	return mems + id;
}

/*
 * assumed less than PATH_MAX(4096)*2
 */
static int round_tab[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192, 0 };

unsigned long mem_round(unsigned long bytes)
{
	int *p;

	for (p = round_tab; *p != 0; p++) {
		if (*p >= bytes)
			return *p;
	}
	WRN("mem_round exceeded %lu", bytes);
	return 16384;
}

void fxlsm_prof_mem_add(int id, unsigned long bytes)
{
	struct __fxlsm_prof_mem *__p = id2mem(id);

	if (!__p)
		return;
	write_lock(&(__p->lock));
	__p->bytes += mem_round(bytes);
	++(__p->blocks);
	write_unlock(&(__p->lock));
}

void fxlsm_prof_mem_sub(int id, unsigned long bytes)
{
	struct __fxlsm_prof_mem *__p = id2mem(id);

	if (!__p)
		return;
	write_lock(&(__p->lock));
	__p->bytes -= mem_round(bytes);
	--(__p->blocks);
	write_unlock(&(__p->lock));
}

const char *fxlsm_prof_mem_name(int id)
{
	struct __fxlsm_prof_mem *__p = id2mem(id);

	if (!__p)
		return "?";
	return __p->name;
}

void fxlsm_prof_mem_read(int id, struct fxlsm_prof_mem *p)
{
	struct __fxlsm_prof_mem *__p = id2mem(id);

	if (!__p)
		return;
	read_lock(&(__p->lock));
	p->bytes = __p->bytes;
	p->blocks = __p->blocks;
	read_unlock(&(__p->lock));
}

void fxlsm_prof_mem_total(struct fxlsm_prof_mem *p)
{
	int i;
	struct fxlsm_prof_mem one;

	for (i = 0; i < FXLSM_PROF_MEM_NUM; i++) {
		fxlsm_prof_mem_read(i, &one);
		p->bytes += one.bytes;
		p->blocks += one.blocks;
	}
}

static void prof_mem_init(void)
{
	int i;

	for (i = 0; i < FXLSM_PROF_MEM_NUM; i++)
		rwlock_init(&((mems + i)->lock));
}

#endif /* FXLSM_USE_PROF_MEM */

void fxlsm_prof_init()
{
#ifdef FXLSM_USE_PROF_TIME
	prof_time_init();
#endif /* FXLSM_USE_PROF_TIME */
#ifdef FXLSM_USE_PROF_MEM
	prof_mem_init();
#endif /* FXLSM_USE_PROF_MEM */
}
