/*
  dbmperf.c
  by Yusuke Shinyama

  compile: cc -o dbmperf dbmperf.c tdb.o spinlock.o cdb.a unix.a alloc.a buffer.a byte.a -lgdbm -ldb
  
  required (from cdb-0.75): cdb.a unix.a alloc.a buffer.a byte.a cdb.h uint32.h cdb_make.h buffer.h
  required (from samba-3.0.0/source/tdb/): tdb.o spinlock.o tdb.h
  required (from gdbm): libgdbm.a gdbm.h
*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <limits.h>

#include <gdbm.h>
#include <db.h>
#include "tdb.h"
#include "cdb.h"
#include "cdb_make.h"


#define MAXKEYLEN 10
#define MAXDATALEN 100
#define HASHRATIO 2

typedef struct _rec {
    unsigned int klen, dlen;
    char *key, *data;
} rec;

static rec* records;

static struct timeval tp1;


void start_timer(void)
{
    gettimeofday(&tp1,NULL);
}

double end_timer(void)
{
    static struct timeval tp2;
    gettimeofday(&tp2,NULL);
    return((tp2.tv_sec - tp1.tv_sec) + 
	   (tp2.tv_usec - tp1.tv_usec)*1.0e-6);
}

void fatal(char *why)
{
    perror(why);
    exit(1);
}

void randbuf(unsigned int* lenp, char** bufp, unsigned int id, int len)
{
    char *buf;
    unsigned int i;
    /* make every key unique */
    len = (rand() % len)+1;
    *lenp = len + 8;
    buf = *bufp = (char *)malloc(*lenp+1);
    sprintf(buf+len, "%08x", id);
    for (i = 0; i < len; i++) {
	buf[i] = 'a' + (rand() % 26);
    }
    buf[*lenp] = 0;
}

void makerandrec(unsigned int n)
{
    unsigned int i;
    unsigned int seed = 0;
    rec* r;
    records = (rec*)malloc(sizeof(rec)*n);
    srand(seed);
    for (i = 0, r = records; i < n; i++, r++) {
	randbuf(&r->klen, &r->key, i, MAXKEYLEN);
	randbuf(&r->dlen, &r->data, i, MAXDATALEN);
    }
}

void swaprec(rec* r1, rec* r2) 
{
    unsigned int ti;
    char* tc;
    ti = r2->klen; r2->klen = r1->klen; r1->klen = ti;
    ti = r2->dlen; r2->dlen = r1->dlen; r1->dlen = ti;
    tc = r2->key; r2->key = r1->key; r1->key = tc;
    tc = r2->data; r2->data = r1->data; r1->data = tc;
}

void scramblerandrec(unsigned int n)
{
    unsigned int i;
    for (i = 0; i < n*2; i++) {
	swaprec(&records[rand() % n], &records[rand() % n]);
    }
}

void make_tdb(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    TDB_CONTEXT *tdb;
    TDB_DATA key, data;
    tdb = tdb_open(fname, n*HASHRATIO, TDB_CLEAR_IF_FIRST, 
		   O_RDWR | O_CREAT | O_TRUNC, 0666);

    printf("tdb: storing %s (%u records)\n", fname, n);
    for (i = 0, r = records; i < n; i++, r++) {
	key.dptr = r->key;
	key.dsize = r->klen+1;
	data.dptr = r->data;
	data.dsize = r->dlen+1;
	if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) {
	    fatal("tdb_store failed");
	}
    }
    tdb_close(tdb);
}

void make_gdbm(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    GDBM_FILE gdbm;
    datum key, data;
    gdbm = gdbm_open(fname, 512, GDBM_WRITER|GDBM_NEWDB|GDBM_FAST, 
		     0666, NULL);

    printf("gdbm: storing %s (%u records)\n", fname, n);
    for (i = 0, r = records; i < n; i++, r++) {
	key.dptr = r->key;
	key.dsize = r->klen+1;
	data.dptr = r->data;
	data.dsize = r->dlen+1;
	if (gdbm_store(gdbm, key, data, GDBM_REPLACE) != 0) {
	    fatal("gdbm_store failed");
	}
    }
    gdbm_close(gdbm);
}

void make_cdb(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    int fd;
    struct cdb_make cdbmake;
    fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);

    printf("cdb: storing %s (%u records)\n", fname, n);
    if (cdb_make_start(&cdbmake, fd) == -1)
	fatal("cdb_make_start failed");

    for (i = 0, r = records; i < n; i++, r++) {
	if (cdb_make_add(&cdbmake, r->key, r->klen+1, r->data, r->dlen+1) == -1) {
	    fatal("cdb_make_add failed");
	}
    }
    if (cdb_make_finish(&cdbmake) == -1)
	fatal("cdb_make_finish failed");
    close(fd);
}

void make_db(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    DB* db;
    DBT key, data;
    db = dbopen(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666, DB_HASH, NULL);

    printf("db: storing %s (%u records)\n", fname, n);
    for (i = 0, r = records; i < n; i++, r++) {
	key.data = r->key;
	key.size = r->klen+1;
	data.data = r->data;
	data.size = r->dlen+1;
	if (db->put(db, &key, &data, 0) != 0) {
	    fatal("db_store failed");
	}
    }
    db->close(db);
}

void test_tdb(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    TDB_CONTEXT *tdb;
    TDB_DATA key, data;

    printf("tdb: testing %s (%u records)\n", fname, n);
    start_timer();
    tdb = tdb_open(fname, n*HASHRATIO, TDB_DEFAULT, O_RDONLY, 0444);
    for (i = 0, r = records; i < n; i++, r++) {
	key.dptr = r->key;
	key.dsize = r->klen+1;
	data = tdb_fetch(tdb, key);
	if (!data.dptr || strcmp(data.dptr, r->data)) {
	    fatal("tdb_fetch failed");
	}
    }
    tdb_close(tdb);    
    printf("tdb: %.3f\n", end_timer());
}

void test_gdbm(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    GDBM_FILE gdbm;
    datum gkey, gdata;

    printf("gdbm: testing %s (%u records)\n", fname, n);
    start_timer();
    gdbm = gdbm_open(fname, 512, GDBM_READER|GDBM_FAST, 0444, NULL);
    for (i = 0, r = records; i < n; i++, r++) {
	gkey.dptr = r->key;
	gkey.dsize = r->klen+1;
	gdata = gdbm_fetch(gdbm, gkey);
	if (!gdata.dptr || strcmp(gdata.dptr, r->data)) {
	    fatal("gdbm_fetch failed");
	}
    }
    gdbm_close(gdbm);    
    printf("gdbm: %.3f\n", end_timer());
}

void test_cdb(unsigned int n, char* fname)
{
    rec* r;
    char* data;
    int i, fd;
    struct cdb db;

    printf("cdb: testing %s (%u records)\n", fname, n);
    start_timer();
    fd = open(fname, O_RDONLY);
    cdb_init(&db, fd);
    for (i = 0, r = records; i < n; i++, r++) {
	if (cdb_find(&db, r->key, r->klen+1) == 0) {
	    fatal("cdb_find failed");
	}
	data = malloc(cdb_datalen(&db));
	cdb_read(&db, data, cdb_datalen(&db), cdb_datapos(&db));
	if (strcmp(r->data, data))
	    fatal("cdb: mismatch");
	free(data);
    }
    cdb_free(&db);
    close(fd);
    printf("cdb: %.3f\n", end_timer());
}

void test_db(unsigned int n, char* fname)
{
    rec* r;
    unsigned int i;
    DB* db;
    DBT key, data;

    printf("db: testing %s (%u records)\n", fname, n);
    start_timer();
    db = dbopen(fname, O_RDONLY, 0444, DB_HASH, NULL);
    for (i = 0, r = records; i < n; i++, r++) {
	key.data = r->key;
	key.size = r->klen+1;
	if (db->get(db, &key, &data, 0) || strcmp(data.data, r->data)) {
	    fatal("db_fetch failed");
	}
    }
    db->close(db);
    printf("db: %.3f\n", end_timer());
}

int main(int argc, char *argv[])
{
    unsigned int n,i;
    
    if (argc < 2) exit(1);
    
    n = strtoul(argv[1], 0, 10);
    unlink("test.tdb");
    unlink("test.gdbm");
    unlink("test.cdb");
    unlink("test.db");
    
    printf("making samples...\n");
    makerandrec(n);
    
    make_tdb(n, "test.tdb");
    make_gdbm(n, "test.gdbm");
    make_cdb(n, "test.cdb");
    make_db(n, "test.db");

    for(i=0;i<3;i++) {
	printf("phase %d: randomizing samples...\n", i);
	scramblerandrec(n);
	
	test_tdb(n, "test.tdb");
	test_gdbm(n, "test.gdbm");
	test_cdb(n, "test.cdb");
	test_db(n, "test.db");
    }
    return 0;
}
