/**
 * @file odb.h
 * This file contains various definitions and interface for management
 * of in-memory, through mmaped file, growable hash table, that stores
 * sample files.
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 */

#ifndef ODB_HASH_H
#define ODB_HASH_H

#include <stddef.h>
#include <stdint.h>

#include "op_list.h"

/** the type of key. 64-bit because CG needs 32-bit pair {from,to} */
typedef uint64_t odb_key_t;
/** the type of an information in the database */
typedef unsigned int odb_value_t;
/** the type of index (node number), list are implemented through index */
typedef unsigned int odb_index_t;
/** the type store node number */
typedef odb_index_t odb_node_nr_t;
/** store the hash mask, hash table size are always power of two */
typedef odb_index_t odb_hash_mask_t;

/* there is (bucket factor * nr node) entry in hash table, this can seem
 * excessive but hash coding eip don't give a good distributions and our
 * goal is to get a O(1) amortized insert time. bucket factor must be a
 * power of two. FIXME: see big comment in odb_hash_add_node, you must
 * re-enable zeroing hash table if BUCKET_FACTOR > 2 (roughly exact, you
 * want to read the comment in odb_hash_add_node() if you tune this define)
 */
#define BUCKET_FACTOR 1

/** a db hash node */
typedef struct {
	odb_key_t key;			/**< eip */
	odb_value_t value;		/**< samples count */
	odb_index_t next;		/**< next entry for this bucket */
} odb_node_t;

/** the minimal information which must be stored in the file to reload
 * properly the data base, following this header is the node array then
 * the hash table (when growing we avoid to copy node array)
 */
typedef struct {
	odb_node_nr_t size;		/**< in node nr (power of two) */
	odb_node_nr_t current_size;	/**< nr used node + 1, node 0 unused */
	int padding[6];			/**< for padding and future use */
} odb_descr_t;

/** a "database". this is an in memory only description.
 *
 * We allow to manage a database inside a mapped file with an "header" of
 * unknown size so odb_open get a parameter to specify the size of this header.
 * A typical use is:
 *
 * struct header { int etc; ... };
 * odb_open(&hash, filename, ODB_RW, sizeof(header));
 * so on this library have no dependency on the header type.
 *
 * the internal memory layout from base_memory is:
 *  the unknown header (sizeof_header)
 *  odb_descr_t
 *  the node array: (descr->size * sizeof(odb_node_t) entries
 *  the hash table: array of odb_index_t indexing the node array 
 *    (descr->size * BUCKET_FACTOR) entries
 */
typedef struct odb_data {
	odb_node_t * node_base;		/**< base memory area of the page */
	odb_index_t * hash_base;	/**< base memory of hash table */
	odb_descr_t * descr;		/**< the current state of database */
	odb_hash_mask_t hash_mask;	/**< == descr->size - 1 */
	unsigned int sizeof_header;	/**< from base_memory to odb header */
	unsigned int offset_node;	/**< from base_memory to node array */
	void * base_memory;		/**< base memory of the maped memory */
	int fd;				/**< mmaped memory file descriptor */
	char * filename;                /**< full path name of sample file */
	int ref_count;                  /**< reference count */
	struct list_head list;          /**< hash bucket list */
} odb_data_t;

typedef struct {
	odb_data_t * data;
} odb_t;

#ifdef __cplusplus
extern "C" {
#endif

/* db_manage.c */

/** how to open the DB file */
enum odb_rw {
	ODB_RDONLY = 0,	/**< open for read only */
	ODB_RDWR = 1	/**< open for read and/or write */
};

/**
 * odb_init - initialize a DB file
 * @param odb the DB file to init
 */
void odb_init(odb_t * odb);

/**
 * odb_open - open a DB file
 * @param odb the data base object to setup
 * @param filename the filename where go the maped memory
 * @param rw \enum ODB_RW if opening for writing, else \enum ODB_RDONLY
 * @param sizeof_header size of the file header if any
 *
 * The sizeof_header parameter allows the data file to have a header
 * at the start of the file which is skipped.
 * odb_open() always preallocate a few number of pages.
 * returns 0 on success, errno on failure
 */
int odb_open(odb_t * odb, char const * filename,
             enum odb_rw rw, size_t sizeof_header);

/** Close the given ODB file */
void odb_close(odb_t * odb);

/** return the number of times this sample file is open */
int odb_open_count(odb_t const * odb);

/** return the start of the mapped data */
void * odb_get_data(odb_t * odb);

/** issue a msync on the used size of the mmaped file */
void odb_sync(odb_t const * odb);

/**
 * grow the hashtable in such way current_size is the index of the first free
 * node. Take care all node pointer can be invalidated by this call.
 *
 * Node allocation is done in a two step way 1st) ensure a free node exist
 * eventually, caller can setup it, 2nd) commit the node allocation with
 * odb_commit_reservation().
 * This is done in this way to ensure node setup is visible from another
 * process like pp tools in an atomic way.
 *
 * returns 0 on success, non zero on failure in this case this function do
 * nothing and errno is set by the first libc call failure allowing to retry
 * after cleanup some program resource.
 */
int odb_grow_hashtable(odb_data_t * data);
/**
 * commit a previously successfull node reservation. This can't fail.
 */
static __inline void odb_commit_reservation(odb_data_t * data)
{
	++data->descr->current_size;
}

/** "immpossible" node number to indicate an error from odb_hash_add_node() */
#define ODB_NODE_NR_INVALID ((odb_node_nr_t)-1)

/* db_debug.c */
/** check that the hash is well built */
int odb_check_hash(odb_t const * odb);

/* db_stat.c */
typedef struct odb_hash_stat_t odb_hash_stat_t;
odb_hash_stat_t * odb_hash_stat(odb_t const * odb);
void odb_hash_display_stat(odb_hash_stat_t const * stats);
void odb_hash_free_stat(odb_hash_stat_t * stats);

/* db_insert.c */
/** update info at key by incrementing its associated value by one, 
 * if the key does not exist a new node is created and the value associated
 * is set to one.
 *
 * returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
int odb_update_node(odb_t * odb, odb_key_t key);

/**
 * odb_update_node_with_offset
 * @param odb the data base object to setup
 * @param key the hash key
 * @param offset the offset to be added
 *
 * update info at key by adding the specified offset to its associated value,
 * if the key does not exist a new node is created and the value associated
 * is set to offset.
 *
 * returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
int odb_update_node_with_offset(odb_t * odb, 
				odb_key_t key, 
				unsigned long int offset);

/** Add a new node w/o regarding if a node with the same key already exists
 *
 * returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
int odb_add_node(odb_t * odb, odb_key_t key, odb_value_t value);

/* db_travel.c */
/**
 * return a base pointer to the node array and number of node in this array
 * caller then will iterate through:
 *
 * odb_node_nr_t node_nr, pos;
 * odb_node_t * node = odb_get_iterator(odb, &node_nr);
 *	for ( pos = 0 ; pos < node_nr ; ++pos)
 *		// do something
 *
 *  note than caller does not need to filter nil key as it's a valid key,
 * The returned range is all valid (i.e. should never contain zero value).
 */
odb_node_t * odb_get_iterator(odb_t const * odb, odb_node_nr_t * nr);

static __inline unsigned int
odb_do_hash(odb_data_t const * data, odb_key_t value)
{
	/* FIXME: better hash for eip value, needs to instrument code
	 * and do a lot of tests ... */
	/* trying to combine high order bits his a no-op: inside a binary image
	 * high order bits don't vary a lot, hash table start with 7 bits mask
	 * so this hash coding use bits 0-7, 8-15. Hash table is stored in
	 * files avoiding to rebuilding them at profiling re-start so
	 * on changing do_hash() change the file format!
	 */
	uint32_t temp = value & 0xffffffff;
	return ((temp << 0) ^ (temp >> 8)) & data->hash_mask;
}

#ifdef __cplusplus
}
#endif

#endif /* !ODB_H */