/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
#ident "$Id$"
#ident "Copyright (c) 2007 Tokutek Inc.  All rights reserved."
#include "test.h"

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

#include <toku_pthread.h>
#include <unistd.h>
#include <memory.h>
#include <sys/stat.h>
#include <db.h>


//
// This test verifies that running evictions on a writer thread
// are ok. We create a dictionary bigger than the cachetable (around 4x greater).
// Then, we spawn a bunch of pthreads that do the following:
//  - scan dictionary forward with bulk fetch
//  - scan dictionary forward slowly
//  - scan dictionary backward with bulk fetch
//  - scan dictionary backward slowly
//  - update existing values in the dictionary with db->put(DB_YESOVERWRITE)
// With the small cachetable, this should produce quite a bit of churn in reading in and evicting nodes.
// If the test runs to completion without crashing, we consider it a success.
//

bool run_test;
int time_of_test;
int num_elements;

struct arg {
    int n;
    DB *db;
    DB_ENV* env;
    bool fast;
    bool fwd;
};

static int
go_fast(DBT const *a, DBT  const *b, void *c) {
    assert(a);
    assert(b);
    assert(c==NULL);
    return TOKUDB_CURSOR_CONTINUE;
}
static int
go_slow(DBT const *a, DBT  const *b, void *c) {
    assert(a);
    assert(b);
    assert(c==NULL);
    return 0;
}

static void *scan_db(void *arg) {
    struct arg *myarg = (struct arg *) arg;
    DB_ENV* env = myarg->env;
    DB* db = myarg->db;
    DB_TXN* txn = NULL;
    while(run_test) {
        int r = env->txn_begin(env, 0, &txn, DB_TXN_SNAPSHOT); CKERR(r);
        DBC* cursor = NULL;
        { int chk_r = db->cursor(db, txn, &cursor, 0); CKERR(chk_r); }
        while (r != DB_NOTFOUND) {
            if (myarg->fwd) {
                r = cursor->c_getf_next(cursor, 0, myarg->fast ? go_fast : go_slow, NULL);
            }
            else {
                r = cursor->c_getf_prev(cursor, 0, myarg->fast ? go_fast : go_slow, NULL);
            }
            assert(r==0 || r==DB_NOTFOUND);
        }
        
        { int chk_r = cursor->c_close(cursor); CKERR(chk_r); }
        { int chk_r = txn->commit(txn,0); CKERR(chk_r); }
    }
    return arg;
}

static void *ptquery_db(void *arg) {
    struct arg *myarg = (struct arg *) arg;
    DB_ENV* env = myarg->env;
    DB* db = myarg->db;
    DB_TXN* txn = NULL;
    int n = myarg->n;
    while(run_test) {
        int r = env->txn_begin(env, 0, &txn, DB_TXN_SNAPSHOT); CKERR(r);
        int rand_key = random() % n;        
        DBT key;
        DBT val;
        memset(&val, 0, sizeof(val));
        dbt_init(&key, &rand_key, sizeof(rand_key));
        r = db->get(db, txn, &key, &val, 0);
        assert(r != DB_NOTFOUND);
        { int chk_r = txn->commit(txn,0); CKERR(chk_r); }
    }
    return arg;
}

static void *update_db(void *arg) {
    struct arg *myarg = (struct arg *) arg;
    DB_ENV* env = myarg->env;
    DB* db = myarg->db;
    int n = myarg->n;

    DB_TXN* txn = NULL;
    while (run_test) {
        int r = env->txn_begin(env, 0, &txn, DB_TXN_SNAPSHOT); CKERR(r);
        for (uint32_t i = 0; i < 1000; i++) {
            int rand_key = random() % n;
            int rand_val = random();
            DBT key, val;
            r = db->put(
                db, 
                txn, 
                dbt_init(&key, &rand_key, sizeof(rand_key)), 
                dbt_init(&val, &rand_val, sizeof(rand_val)), 
                0
                );
            CKERR(r);
        }
        { int chk_r = txn->commit(txn,0); CKERR(chk_r); }
    }
    return arg;
}

static void *test_time(void *arg) {
    assert(arg == NULL);
    usleep(time_of_test*1000*1000);
    if (verbose) printf("should now end test\n");
    run_test = false;
    return arg;
}


static void
test_evictions (void) {
    int n = num_elements;
    if (verbose) printf("test_3645:%d \n", n);

    DB_TXN * const null_txn = 0;
    const char * const fname = "test.bulk_fetch.ft_handle";
    int r;

    toku_os_recursive_delete(TOKU_TEST_FILENAME);
    r=toku_os_mkdir(TOKU_TEST_FILENAME, S_IRWXU+S_IRWXG+S_IRWXO); assert(r==0);

    /* create the dup database file */
    DB_ENV *env;
    r = db_env_create(&env, 0); assert(r == 0);
    r=env->set_default_bt_compare(env, int_dbt_cmp); CKERR(r);
    // set the cache size to 10MB
    r = env->set_cachesize(env, 0, 100000, 1); CKERR(r);
    r=env->open(env, TOKU_TEST_FILENAME, DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_CREATE|DB_PRIVATE, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
    r = env->checkpointing_set_period(env, 10); 
    CKERR(r);



    DB *db;
    r = db_create(&db, env, 0);
    assert(r == 0);
    r = db->set_flags(db, 0);
    assert(r == 0);
    r = db->set_pagesize(db, 4096);
    assert(r == 0);
    r = db->set_readpagesize(db, 1024);
    assert(r == 0);
    r = db->open(db, null_txn, fname, "main", DB_BTREE, DB_CREATE, 0666);
    assert(r == 0);

    int keys[n];
    for (int i=0; i<n; i++) {
        keys[i] = i;
    }
    
    if (verbose) printf("starting insertion of elements to setup test\n");
    for (int i=0; i<n; i++) {
        DBT key, val;
        r = db->put(db, null_txn, dbt_init(&key, &keys[i], sizeof keys[i]), dbt_init(&val, &i, sizeof i), 0);
        assert(r == 0);
    } 

    //
    // the threads that we want:
    //   - one thread constantly updating random values
    //   - one thread doing table scan with bulk fetch
    //   - one thread doing table scan without bulk fetch
    //   - one thread doing random point queries
    //
    run_test = true;
    if (verbose) printf("starting creation of pthreads\n");
    toku_pthread_t mytids[7];
    struct arg myargs[7];
    for (uint32_t i = 0; i < sizeof(myargs)/sizeof(myargs[0]); i++) {
        myargs[i].n = n;
        myargs[i].db = db;
        myargs[i].env = env;
        myargs[i].fast = true;
        myargs[i].fwd = true;
    }

    // make the forward fast scanner
    myargs[0].fast = true;
    myargs[0].fwd = true;
    { int chk_r = toku_pthread_create(&mytids[0], NULL, scan_db, &myargs[0]); CKERR(chk_r); }

    // make the forward slow scanner
    myargs[1].fast = false;
    myargs[1].fwd = true;
    { int chk_r = toku_pthread_create(&mytids[1], NULL, scan_db, &myargs[1]); CKERR(chk_r); }

    // make the backward fast scanner
    myargs[2].fast = true;
    myargs[2].fwd = false;
    { int chk_r = toku_pthread_create(&mytids[2], NULL, scan_db, &myargs[2]); CKERR(chk_r); }

    // make the backward slow scanner
    myargs[3].fast = false;
    myargs[3].fwd = false;
    { int chk_r = toku_pthread_create(&mytids[3], NULL, scan_db, &myargs[3]); CKERR(chk_r); }

    // make the guy that updates the db
    { int chk_r = toku_pthread_create(&mytids[4], NULL, update_db, &myargs[4]); CKERR(chk_r); }

    // make the guy that does point queries
    { int chk_r = toku_pthread_create(&mytids[5], NULL, ptquery_db, &myargs[5]); CKERR(chk_r); }

    // make the guy that sleeps
    { int chk_r = toku_pthread_create(&mytids[6], NULL, test_time, NULL); CKERR(chk_r); }
    
    for (uint32_t i = 0; i < sizeof(myargs)/sizeof(myargs[0]); i++) {
        void *ret;
        r = toku_pthread_join(mytids[i], &ret); assert_zero(r);
    }
    if (verbose) printf("ending test, pthreads have joined\n");


    r = db->close(db, 0); CKERR(r);
    r = env->close(env, 0); CKERR(r);
}

static inline void parse_3645_args (int argc, char *const argv[]) {
    const char *argv0=argv[0];
    while (argc>1) {
        int resultcode=0;
        if (strcmp(argv[1], "-v")==0) {
            verbose++;
        } 
        else if (strcmp(argv[1], "-q")==0) {
            verbose=0;
        } 
        else if (strcmp(argv[1], "-h")==0) {
        do_usage:
            fprintf(stderr, "Usage:\n%s [-v|-h-q|--num_elements number | --num_seconds number]\n", argv0);
            exit(resultcode);
        } 
        else if (strcmp(argv[1], "--num_elements") == 0) {
            argc--;
            argv++;
            num_elements = atoi(argv[1]);
        }
        else if (strcmp(argv[1], "--num_seconds") == 0) {
            argc--;
            argv++;
            time_of_test = atoi(argv[1]);
        }
        else {
            resultcode=1;
            goto do_usage;
        }
        argc--;
        argv++;
    }
}


int
test_main(int argc, char *const argv[]) {
    // default values
    num_elements = 100000;
    time_of_test = 60;
    parse_3645_args(argc, argv);
    test_evictions();
    return 0;
}
