/*
 *  FS aggregate statistics module.
 *  Copyright (c) 2005 SUNY at Stony Brook
 *  Copyright (c) 2005 Nikolai Joukov and Erez Zadok
 */

#include <linux/config.h> 
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#define NEED_DESCRIPTIVE_MESSAGES
#include "aggregate.h"

/* Must be a power of two. */
/* 32L << 32 cycles should be enough even for VERY slow FSs :-) */
#define MAX_DISTR_DIGIT 32
/* XXX: Should be a dynamic parameter */
/* set to 0 to have no timeline and just the totals */
#define MAX_DISTR_TIME 0

/* This whole structure is smaller than 4K if MAX_DISTR_TIME = 0 */
struct aggregate_pd {
	unsigned long opcounts[AGSOP_MAX];
	unsigned long long init_cycle;
	unsigned long long tot_cycles[AGSOP_MAX];
	struct proc_dir_entry *aggregate_proc_root;
	unsigned long distribution[AGSOP_MAX][MAX_DISTR_TIME + 1][MAX_DISTR_DIGIT + 1];
};

/* For now just use a static variable. */
static struct aggregate_pd* this = NULL;

#define INIT_CYCLE (context->init_cycle)

#ifdef __ia64__
#define RDTSC(qp) \
	do { \
		unsigned long result; \
		__asm__ __volatile__("mov %0=ar.itc" : "=r" (result) :: "memory"); \
		qp = result; \
	} while (0);

#else

#define RDTSC(qp) \
	do { \
		unsigned long lowPart, highPart; \
		__asm__ __volatile__("rdtsc" : "=a" (lowPart), "=d" (highPart)); \
		qp = (((unsigned long long) highPart) << 32) | lowPart; \
	} while (0);

#endif

static void aggregate_stat_reset(void)
{
	memset(this, 0, sizeof(struct aggregate_pd));
	RDTSC(this->init_cycle);
}

#define PROC_NAME "fs_aggregate_stats_nfsdas"

#define ADD_TO_BUFFER(str) \
	do { \
		char* tmp; \
		len = strlen((str)); \
		if (file->f_pos < total + len) { \
			if (count < done + len) { \
				len = count - done; \
			} \
			if (total < file->f_pos) { \
				len -= file->f_pos - total;\
				tmp = (str) + file->f_pos - total; \
			} else {\
				tmp = (str); \
			} \
			if (copy_to_user(buf, tmp, len)) { \
				done = -EFAULT; \
				goto out; \
			} \
			buf+= len; \
			done+= len; \
			if (count == done) \
				goto out; \
		} \
		total+= len; \
	} while(0);


static inline char *op_to_str(int type)
{
        int i;
        for (i = 0; msg_desc[i].str_type != NULL; i++) {
                if (type == msg_desc[i].msg_type)
                        return &msg_desc[i].str_type[3]; /* AGSOP_ -> OP_ */
        }
        return "MSG_UNKNOWN";
}

static ssize_t aggregate_proc_read(struct file *file, char *buf, size_t count, loff_t *ppos) {
	char localbuf[1024];
	int len, done = 0, total = 0;

	unsigned int i, ii, iii, last_tick;
	unsigned long long now;

	RDTSC(now);
	now -= this->init_cycle;
	last_tick = (unsigned int)(now >> 32);
	if (last_tick > MAX_DISTR_TIME)
		last_tick = MAX_DISTR_TIME;
	
	for (i = 0; i < AGSOP_MAX; i++) {
		if (this->opcounts[i] > 0) {
			ADD_TO_BUFFER(op_to_str(i));
			sprintf(localbuf, " %lu %llu\n", this->opcounts[i], this->tot_cycles[i]);
			ADD_TO_BUFFER(localbuf);
			for (ii = 0; ii < last_tick + 1; ii++) {
				for (iii = 0; iii < MAX_DISTR_DIGIT + 1; iii++) {
					sprintf(localbuf, " %lu", this->distribution[i][ii][iii]);
					ADD_TO_BUFFER(localbuf);
				}
				ADD_TO_BUFFER("\n");
			}
		}
	}
out:
	if (done > 0) {
		file->f_pos += done;
		ppos[0] += done;
	}
	return done;
}

static ssize_t aggregate_proc_write(struct file *file, const char *buf, size_t count, loff_t *ppos) {
	aggregate_stat_reset();
	return count;
}

struct file_operations proc_aggregate_file_operations = {
	read: aggregate_proc_read,
	write: aggregate_proc_write
};

static int aggregate_proc_create(void)
{
        struct proc_dir_entry *proc_de;
	int err = 0;

	/*x_proc_root = create_proc_entry(PROC_NAME, S_IFDIR, NULL);
	if (!x_proc_root) {
    	    printk(KERN_ERR "Adding x to proc failed\n");
    	    result = -EPERM;
    	    goto out;
	}
	x_proc_root->owner = THIS_MODULE;*/
        
        proc_de = create_proc_entry(PROC_NAME, 0, NULL);
        if (!proc_de) {
                printk(KERN_ERR "Adding proc entry failed\n");
                goto out;
        }
        proc_de->owner = THIS_MODULE;
        proc_de->data = (void *)this;
        proc_de->proc_fops = &proc_aggregate_file_operations;
out:
	return err;
}

static void aggregate_proc_destroy(void)
{
	//remove_proc_entry(stopd(sb)->proc_name, x_proc_root);
	remove_proc_entry(PROC_NAME, NULL);
}



int aggregate_init(void)
{
	int err = -ENOMEM;

#if (MAX_DISTR_TIME > 0)
	this = vmalloc(sizeof(struct aggregate_pd));
#else
	this = kmalloc(sizeof(struct aggregate_pd), GFP_KERNEL);
#endif
	if (!this)
		goto out;

	aggregate_stat_reset();
    
	err = aggregate_proc_create();
    
out: 
	return err; 
}

void aggregate_release(void)
{
	aggregate_proc_destroy();
    
	if (this) {
#if (MAX_DISTR_TIME > 0)
		vfree(this);
#else
		kfree(this);
#endif
		this = NULL;
	}
}

void aggregate_pre(int op, struct aggregate_context* context)
{
	this->opcounts[(op)]++;
	RDTSC(INIT_CYCLE);
}

void aggregate_post_slow(int op, struct aggregate_context* context)
{
	unsigned long long l;
	RDTSC(l);
	unsigned long long delay = (l - INIT_CYCLE);
	unsigned long long ll;
	int i, ii;
	
	this->tot_cycles[(op)] += delay;
	
	ll = 32L;
	for (i = 0; i < MAX_DISTR_DIGIT; i++) {
		if (delay < ll)
			break;
		ll = ll << 1;
	}
	
	ii = (int)((l - this->init_cycle) >> 32);
	if (ii > MAX_DISTR_TIME)
		ii = MAX_DISTR_TIME;
	this->distribution[(op)][ii][i]++;
}

void aggregate_post(int op, struct aggregate_context* context)
{
	unsigned long long l;
	RDTSC(l);
	unsigned long long ll_delay = (l - INIT_CYCLE);
	unsigned int i, ii, iii, i_delay;

	this->tot_cycles[(op)] += ll_delay;

	iii = 1;
	i_delay = (unsigned int)(ll_delay >> 5);
	for (i = 0; i < MAX_DISTR_DIGIT; i++) {
		if (i_delay < iii)
			break;
		iii <<= 1;
	}

	ii = (int)((l - this->init_cycle) >> 32);
	if (ii > MAX_DISTR_TIME)
		ii = MAX_DISTR_TIME;
	this->distribution[(op)][ii][i]++;
}
