/**
 * the truly parallel memory allocator
 *
 * Copyright (C) 2014 OpenSIPS Solutions
 *
 * This file is part of opensips, a free SIP server.
 *
 * opensips 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
 *
 * opensips 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
 *
 * History:
 * --------
 *  2014-01-19 initial version (liviu)
 */

#if !defined(q_malloc) && !(defined VQ_MALLOC)  && !(defined F_MALLOC) && \
	(defined HP_MALLOC)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "sys/time.h"

#include "../lock_ops.h"
#include "hp_malloc.h"
#include "hp_malloc_stats.h"

gen_lock_t *hp_stats_lock;

#ifdef STATISTICS

int stats_are_expired(struct hp_block *hpb)
{
	struct timeval now;

	gettimeofday(&now, NULL);

	return (now.tv_sec * 1000000L + now.tv_usec) -
	       (hpb->last_updated.tv_sec * 1000000L + hpb->last_updated.tv_usec)

	       > SHM_STATS_SAMPLING_PERIOD;
}

void update_shm_stats(struct hp_block *hpb)
{
	struct hp_frag_lnk *bucket, *it;
	int i, j, used_mem;
	unsigned long in_use_frags;
	long size = 0;

	lock_get(hp_stats_lock);

	hpb->used = hpb->real_used = hpb->total_fragments = 0;

	for (i = 0, bucket = hpb->free_hash;
	     bucket < &hpb->free_hash[HP_HASH_SIZE];
		 i++, bucket++) {

		size = UN_HASH(i);

		if (!bucket->is_optimized) {
			in_use_frags = bucket->total_no - bucket->no;
			used_mem = in_use_frags * size;

			hpb->used            += used_mem;
			hpb->real_used       += used_mem + bucket->total_no * FRAG_OVERHEAD;
			hpb->total_fragments += bucket->total_no;
		} else {
			for (j = 0, it = &hpb->free_hash[HP_HASH_SIZE + i * shm_secondary_hash_size];
			     j < shm_secondary_hash_size;
				 j++, it++) {

				in_use_frags = it->total_no - it->no;
				used_mem = in_use_frags * size;

				hpb->used            += used_mem;
				hpb->real_used       += used_mem + it->total_no * FRAG_OVERHEAD;
				hpb->total_fragments += it->total_no;
			}
		}
	}

	if (hpb->real_used > hpb->max_real_used)
		hpb->max_real_used = hpb->real_used;

	LM_DBG("updated shm statistics: [ us: %ld | rus: %ld | frags: %ld ]\n",
	        hpb->used, hpb->real_used, hpb->total_fragments);

	gettimeofday(&hpb->last_updated, NULL);

	lock_release(hp_stats_lock);
}

unsigned long hp_shm_get_size(struct hp_block *hpb)
{
	return hpb->size;
}

#ifdef HP_MALLOC_FAST_STATS
unsigned long hp_shm_get_used(struct hp_block *hpb)
{
	if (stats_are_expired(hpb))
		update_shm_stats(hpb);

	return hpb->used;
}

unsigned long hp_shm_get_real_used(struct hp_block *hpb)
{
	if (stats_are_expired(hpb))
		update_shm_stats(hpb);

	return hpb->real_used;
}

unsigned long hp_shm_get_max_real_used(struct hp_block *hpb)
{
	if (stats_are_expired(hpb))
		update_shm_stats(hpb);

	return hpb->max_real_used;
}

unsigned long hp_shm_get_free(struct hp_block *hpb)
{
	if (stats_are_expired(hpb))
		update_shm_stats(hpb);

	return hpb->size - hpb->real_used;
}

unsigned long hp_shm_get_frags(struct hp_block *hpb)
{
	if (stats_are_expired(hpb))
		update_shm_stats(hpb);

	return hpb->total_fragments;
}

void hp_init_shm_statistics(struct hp_block *hpb)
{
	update_shm_stats(hpb);
}

#else /* HP_MALLOC_FAST_STATS */

void hp_init_shm_statistics(struct hp_block *hpb)
{
#ifdef DBG_MALLOC
	/* reset stats updated by mallocs before this init */
	shm_used->flags &= ~STAT_NO_RESET;
	shm_rused->flags &= ~STAT_NO_RESET;
	shm_frags->flags &= ~STAT_NO_RESET;
	reset_stat(shm_used);
	reset_stat(shm_rused);
	reset_stat(shm_frags);
	shm_used->flags |= STAT_NO_RESET;
	shm_rused->flags |= STAT_NO_RESET;
	shm_frags->flags |= STAT_NO_RESET;
#endif
	update_stat(shm_used, hpb->used);
	update_stat(shm_rused, hpb->real_used);
	update_stat(shm_frags, hpb->total_fragments);

	LM_DBG("initializing atomic shm statistics: "
	       "[ us: %ld | rus: %ld | frags: %ld ]\n", hpb->used, hpb->real_used, hpb->total_fragments);
}

unsigned long hp_shm_get_used(struct hp_block *hpb)
{
	return get_stat_val(shm_used);
}

unsigned long hp_shm_get_real_used(struct hp_block *hpb)
{
	return get_stat_val(shm_rused);
}

unsigned long hp_shm_get_max_real_used(struct hp_block *hpb)
{
	return hpb->max_real_used;
}

unsigned long hp_shm_get_free(struct hp_block *hpb)
{
	return hpb->size - get_stat_val(shm_rused);
}

unsigned long hp_shm_get_frags(struct hp_block *hpb)
{
	return get_stat_val(shm_frags);
}
#endif /* HP_MALLOC_FAST_STATS */


unsigned long hp_pkg_get_size(struct hp_block *hpb)
{
	return hpb->size;
}

unsigned long hp_pkg_get_used(struct hp_block *hpb)
{
	return hpb->used;
}

unsigned long hp_pkg_get_real_used(struct hp_block *hpb)
{
	return hpb->real_used;
}

unsigned long hp_pkg_get_max_real_used(struct hp_block *hpb)
{
	return hpb->max_real_used;
}

unsigned long hp_pkg_get_free(struct hp_block *hpb)
{
	return hpb->size - hpb->real_used;
}

unsigned long hp_pkg_get_frags(struct hp_block *hpb)
{
	return hpb->total_fragments;
}

#endif /* STATISTICS */

#endif
