#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <stdlib.h>

#define PAGESIZE 8192
const char* filename = "./direct_io.data";
const int file_size = 1024 * 1024 * 1024;

static void prepare_test_file(const char* filename, int file_size);
static void read_file_seq(int fd, int blocks);
static double get_fs_cache_latency_us(const char* filename);
static double get_seq_page_latency(const char* filename);
static double get_random_page_latency(const char* filename);
static void cal_random_page_cost(double seq_read_lat, double random_page_lat, double fs_cache_lat,
								 double cache_hit_ratio);
static void auto_gather();

int main(int argc, char *argv[])
{
	auto_gather();
	return 0;
}

static void
auto_gather()
{
	double fs_cache_lat, seq_read_lat, random_page_lat;
	int i;

	double cache_hit_ratio[] = {
		1, 0.9, 0.5, 0.1, 0, -1
	};

	prepare_test_file(filename, file_size);

	/* Test file system cache buffer latency */
	fs_cache_lat = get_fs_cache_latency_us(filename);

	seq_read_lat = get_seq_page_latency(filename);
	random_page_lat = get_random_page_latency(filename);

	printf("fs_cache_lat = %fus, seq_read_lat = %fus, random_page_lat = %fus\n\n",
		   fs_cache_lat,
		   seq_read_lat,
		   random_page_lat);

	for(i = 0; cache_hit_ratio[i] != -1; i++)
	{
		cal_random_page_cost(seq_read_lat, random_page_lat, fs_cache_lat, cache_hit_ratio[i]);
	}
 }

static double
cal_real_lat(double cache_hit_ratio,  double per_io_lat, double per_cache_lat)
{
	double cache_lat, disk_lat;
	cache_lat = cache_hit_ratio * per_cache_lat;
	disk_lat = (1 - cache_hit_ratio) * per_io_lat;
	return cache_lat + disk_lat;
}

static void
cal_random_page_cost(double seq_read_lat, double random_page_lat, double fs_cache_lat,
					 double cache_hit_ratio)
{
	double real_seqscan_lat, real_random_lat;
	/* File System Cache impacts on seq read as well */
	real_seqscan_lat = cal_real_lat(cache_hit_ratio, seq_read_lat, fs_cache_lat);
	real_random_lat = cal_real_lat(cache_hit_ratio, random_page_lat, fs_cache_lat);

	printf("cache hit ratio: %f random_page_cost %f\n",
		   cache_hit_ratio,
		   real_random_lat / real_seqscan_lat);

	return;
}

static void
prepare_test_file(const char* filename, int file_size)
{
	int fd;
	int ret;
	int buf[8 * 1024];
	int current_size = 0;

	fd = open(filename, O_WRONLY | O_CREAT, 0644);
	if (fd < 0)
	{
		perror("open ./direct_io.data failed");
		exit(1);
	}

	do {
		ret = write(fd, buf, PAGESIZE);
		if (ret < 0)
		{
			perror("write ./direct_io.data failed");
		}
		current_size += ret;
	} while (current_size < file_size);
	close(fd);
}

static double
get_time_interval(struct timeval *start_tm, struct timeval *end_tm)
{
	return (end_tm->tv_sec - start_tm->tv_sec) * 1000000 + (end_tm->tv_usec - start_tm->tv_usec);
}


static void
read_file_seq(int fd, int blocks)
{
	char buf[PAGESIZE];
	long total_size = blocks * PAGESIZE;
	long current_size = 0;
	int ret;
	lseek(fd, 0,SEEK_SET);
	do
	{
		ret = read(fd, buf, PAGESIZE);
		if (ret < 0)
		{
			perror("read file seq failed.");
			exit(1);
		}
		else if (ret == 0)
		{
			perror("eof");
		}
		current_size += ret;
	} while(current_size < total_size);
}


static double
get_fs_cache_latency_us(const char* filename)
{
	int i;
	int fd;
	int warmup_blocks = 16;
	struct timeval start_tm, end_tm;
	int loops = 10000;
	fd = open(filename, O_RDONLY, 0755);
	if (fd < 0)
	{
		perror("open ./direct_io.data failed");
		exit(1);
	}

	/* read twice so that the blocks will be in the file system cache */
	read_file_seq(fd, warmup_blocks);
	read_file_seq(fd, warmup_blocks);

	gettimeofday(&start_tm, NULL);
	for(i = 0; i < loops; i++) {
		read_file_seq(fd, warmup_blocks);
	}
	gettimeofday(&end_tm, NULL);
	close(fd);
	return get_time_interval(&start_tm, &end_tm) / loops / warmup_blocks;
}


static void
drop_fs_cache()
{
	int fd;
	char* data = "3";

	sync();
	fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
	write(fd, data, sizeof(char));
	close(fd);
}

/*
 * XXX The scare of xxx_cost is based on seq_page_cost, but what is the impact of file system
 * cache for this number.
 */
static double
get_seq_page_latency(const char* filename)
{
	int fd;
	struct timeval start_tm, end_tm;
	int ret;
	unsigned char* buf;
	long current_size = 0, total_size = file_size;
	ret = posix_memalign((void **)&buf, 512, PAGESIZE);
	if (ret) {
		perror("posix_memalign failed");
		exit(1);
	}

	fd = open(filename, O_RDONLY, 0644);
	if (fd < 0)
	{
		perror("open ./direct_io.data failed");
		exit(1);
	}

	drop_fs_cache();
	gettimeofday(&start_tm, NULL);
	
	/*
	 * We have to read the file just once, or else the IO will hint file
	 * system buffer 
	 */
	do
	{
		ret = read(fd, buf, PAGESIZE);
		if (ret < 0)
		{
			perror("read file seq failed.");
			exit(1);
		}
		if (ret == 0)
		{
			perror("eof");
			exit(1);
		}
		current_size += ret;
	} while(current_size < total_size);

	gettimeofday(&end_tm, NULL);

	close(fd);
	return get_time_interval(&start_tm, &end_tm) / (total_size / PAGESIZE);
}


static double
get_random_page_latency(const char* filename)
{
	int fd;
	struct timeval start_tm, end_tm;
	int ret;
	unsigned char* buf;
	long current_size = 0, total_size = file_size;
	off_t offset;
	int blocks = file_size / PAGESIZE;
	ret = posix_memalign((void **)&buf, 512, PAGESIZE);
	if (ret) {
		perror("posix_memalign failed");
		exit(1);
	}

	fd = open(filename, O_RDONLY | O_DIRECT, 0644);
	if (fd < 0)
	{
		perror("open ./direct_io.data failed");
		exit(1);
	}

	gettimeofday(&start_tm, NULL);

	do
	{
		offset = rand() % blocks * PAGESIZE;
		ret = pread(fd, buf, PAGESIZE, offset);
		if (ret < 0)
		{
			perror("read file seq failed.");
			exit(1);
		}
		current_size += ret;
	} while(current_size < total_size);

	gettimeofday(&end_tm, NULL);
	close(fd);
	return get_time_interval(&start_tm, &end_tm) / blocks;
}
