/*********************************************************************** Copyright (c) 2009 Innobase Oy. All rights reserved. Copyright (c) 2009 Oracle. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ************************************************************************/ /********************************************************************** NOTE: This test is currently Unix centric and will not compile on Windows. This module is a driver framework for running arbitrary DML and DDL statements on arbitrary number of tables using configurable number of threads. The DML statements that can be triggered are: SELECT INSERT UPDATE DELETE The DDL statements that can be triggered are: CREATE TABLE ALTER TABLE DROP TABLE TRUNCATE TABLE How it works: ============= This is just a driver. You have to implement the DML and DDL functionality for each table (see mt_base.c and mt_t1.c for examples) Once a derived table class is implemented it can be plugged into this driver by calling the register function in register_test_tables(). This driver goes through following sequence: 1) Create database test 2) Register all tables 3) Create each registered table 4) Seed each table with n_rows 5) Create n_ddl_threads and n_dml_threads 6) Each DDL thread does following: a) Choose one of the table randomly b) Choose a DDL operation randomly c) Execute the DDL operation and update the stats struct d) Wait for ddl_interval before going back to (a) 7) Each DML thread does following: a) Choose one of the table randomly b) Choose a DML operation randomly c) Execute the DDL operation and update the stats struct d) Return to (a) 8) The test continues for test_time 9) Waits for all worker threads to finish 10) Check all tables (for now checking mean only doing a select *) 11) Drop all tables and the database 12) Print the statistics 13) Clean up **********************************************************************/ #include #include #include #include #include #include #include #include "test0aux.h" #include "ib_mt_drv.h" #ifdef UNIV_DEBUG_VALGRIND #include #endif #define DATABASE "test" /* Number of worker threads*/ static int n_ddl_thr = 4; static int n_dml_thr = 16; static pthread_t* ddl_tid; static pthread_t* dml_tid; /* Initial number of rows. */ static const int n_rows = 1000; /* batch size for DML. Commit after that many rows are worked upon */ static const int batch_size = 1; /* %age transactions to be rolled back */ static const int rollback_percent = 10; /* isolation level for transactions */ static const int isolation_level = IB_TRX_REPEATABLE_READ; /* Test duration in seconds */ static int test_time = 100; /* Interval between DDL statements in seconds */ static int ddl_interval = 1; /* To be incremented every time we do a DROP or TRUNCATE. This is for debug purposes so that we know that the old data actually vanished from the table after a DROP or TRUNCATE */ static int run_number = 1; #define get_cur_run_number() (run_number) #define incr_cur_run_number() (++run_number) /* Flag used by worker threads to finish the run */ static ib_bool_t test_running = IB_FALSE; /* These functions are defined in the corresponding modules where table classes are defined. Base class is defined in mt_base.c */ extern void register_base_table(tbl_class_t*); /* Register new tables by providing initialaztion function def here and calling it in register_test_tables() */ extern void register_t1_table(tbl_class_t*); extern void register_t2_table(tbl_class_t*); /* Maximum number of tables that this test suite can work with */ #define NUM_TBLS 64 static tbl_class_t tbl_array[NUM_TBLS]; /* How many tables we have registered currently */ static int num_tables = 0; /* These are going to be configurable parameters for table creation */ static ib_tbl_fmt_t tbl_format = IB_TBL_COMPACT; static ib_ulint_t page_size = 0; /* Datastructure to hold error statistics for DML and DDL operations */ static op_err_t dml_op_errs[DML_OP_TYPE_MAX]; static op_err_t ddl_op_errs[DDL_OP_TYPE_MAX]; /********************************************************************** Update the error stats */ void update_err_stats( /*=============*/ op_err_t* e, /* in: error stat struct */ ib_err_t err) /* in: error code */ { pthread_mutex_lock(&e->mutex); e->n_ops++; if (err != DB_SUCCESS ) { e->n_errs++; e->errs[err]++; } pthread_mutex_unlock(&e->mutex); } /********************************************************************* Create an InnoDB database (sub-directory). */ static ib_err_t create_database( /*============*/ const char* name) { ib_bool_t err; err = ib_database_create(name); assert(err == IB_TRUE); return(DB_SUCCESS); } /********************************************************************* Drop the database. */ static ib_err_t drop_database( /*==========*/ const char* dbname) /* in: db to drop */ { ib_err_t err; err = ib_database_drop(dbname); return(err); } /********************************************************************** Create test tables at startup */ static void create_test_table( /*==============*/ tbl_class_t* tbl) /* in: table to create */ { ib_err_t err; cb_args_t args = {0}; args.tbl = tbl; args.isolation_level = isolation_level; args.run_number = get_cur_run_number(); args.err_st = &ddl_op_errs[DDL_OP_TYPE_CREATE]; err = tbl->ddl_fn[DDL_OP_TYPE_CREATE](&args); assert(err == DB_SUCCESS); } /********************************************************************** Seed test table with initial data */ static void seed_test_table( /*===========*/ tbl_class_t* tbl) /* in: table to populate */ { ib_err_t err; ib_trx_t trx; cb_args_t args = {0}; int i; args.isolation_level = isolation_level; args.batch_size = 1; args.tbl = tbl; for (i = 0; i < n_rows; ++i) { trx = ib_trx_begin(isolation_level); assert(trx != NULL); args.trx = trx; args.run_number = get_cur_run_number(); args.err_st = &dml_op_errs[DML_OP_TYPE_INSERT]; err = tbl->dml_fn[DML_OP_TYPE_INSERT](&args); err = ib_trx_commit(trx); assert(err == DB_SUCCESS); } } /********************************************************************** Create test tables and populate them with n_rows of data */ static void init_test_tables(void) /*==================*/ { int i; for (i = 0; i < num_tables; ++i) { create_test_table(&tbl_array[i]); } for (i = 0; i < num_tables; ++i) { seed_test_table(&tbl_array[i]); } } /********************************************************************** Drop test tables */ static void drop_test_tables(void) /*==================*/ { ib_err_t err; int i; for (i = 0; i < num_tables; ++i) { cb_args_t args = {0}; tbl_class_t* tbl = &tbl_array[i]; args.tbl = tbl; args.isolation_level = isolation_level; args.err_st = &ddl_op_errs[DDL_OP_TYPE_DROP]; err = tbl->ddl_fn[DDL_OP_TYPE_DROP](&args); } } /********************************************************************** Depending on rollback_percent decides whether to commit or rollback a given transaction */ static void commit_or_rollback( /*===============*/ ib_trx_t trx, /* in: trx to commit or rollback */ int cnt, /* in: trx counter */ ib_err_t err) /* in: err from last operation */ { /* We have set rollback_on_timeout therefore we need not to commit a trx that returns with this error. */ if (err == DB_LOCK_WAIT_TIMEOUT) { ; } else if ((!rollback_percent || cnt % (100 / rollback_percent)) && err == DB_SUCCESS) { //printf("Commit transaction\n"); err = ib_trx_commit(trx); assert(err == DB_SUCCESS); } else { //printf("Rollback transaction\n"); err = ib_trx_rollback(trx); assert(err == DB_SUCCESS); } } /********************************************************************** A DML worker thread that performs a randomly chosen DML operation on a randomly chosen table */ static void* dml_worker( /*=======*/ void* dummy) /* in: unused */ { ib_err_t err; ib_trx_t trx; tbl_class_t* tbl; cb_args_t args = {0}; int j; int cnt = 0; fprintf(stderr, "dml_worker up!\n"); args.isolation_level = isolation_level; args.batch_size = batch_size; do { trx = ib_trx_begin(isolation_level); assert(trx != NULL); /* choose a table randomly */ tbl = &tbl_array[random() % num_tables]; args.tbl = tbl; args.trx = trx; args.run_number = get_cur_run_number(); /* choose a DML operation randomly */ j = random() % DML_OP_TYPE_MAX; ++cnt; args.err_st = &dml_op_errs[j]; err = tbl->dml_fn[j](&args); /* If it was an insert or update and the run_number has changed we unconditionally rollback in order to maintain consistency in the values that are inserted in the columns */ if (args.run_number != get_cur_run_number() && (j == DML_OP_TYPE_UPDATE || j == DML_OP_TYPE_INSERT)) { ib_trx_rollback(trx); } else { commit_or_rollback(trx, cnt, err); } } while (test_running); fprintf(stderr, "dml_worker done!\n"); return(NULL); } /********************************************************************** A DDL worker thread that performs a randomly chosen DDL operation on a randomly chosen table */ static void* ddl_worker( /*=======*/ void* dummy) /* in: unused */ { ib_err_t err; tbl_class_t* tbl; cb_args_t args = {0}; int j; fprintf(stderr, "ddl_worker up!\n"); args.isolation_level = isolation_level; do { /* wait for DDL interval */ sleep(ddl_interval); /* choose a table randomly */ tbl = &tbl_array[random() % num_tables]; args.tbl = tbl; /* choose a DDL operation randomly */ j = random() % DDL_OP_TYPE_MAX; /* When doing DROP or TRUNCATE we increment the run_number */ if (j == DDL_OP_TYPE_DROP || j == DDL_OP_TYPE_TRUNCATE) { incr_cur_run_number(); } args.err_st = &ddl_op_errs[j]; err = tbl->ddl_fn[j](&args); /* If it was a DROP TABLE or if it returned DB_TABLE_NOT_FOUND then we should make an attempt to create the table right away */ if ((err == DB_SUCCESS && j == DDL_OP_TYPE_DROP) || (err == DB_TABLE_NOT_FOUND)) { args.err_st = &ddl_op_errs[DDL_OP_TYPE_CREATE]; err = tbl->ddl_fn[DDL_OP_TYPE_CREATE](&args); } } while (test_running); fprintf(stderr, "ddl_worker done!\n"); return(NULL); } /********************************************************************** Create worker threads */ static void create_worker_threads(void) /*=======================*/ { int rc; int i; dml_tid = (pthread_t*)malloc(sizeof(pthread_t) * n_dml_thr); assert(dml_tid != NULL); for (i = 0; i < n_dml_thr; ++i) { rc = pthread_create(&dml_tid[i], NULL, dml_worker, NULL); assert(!rc); } ddl_tid = (pthread_t*)malloc(sizeof(pthread_t) * n_ddl_thr); assert(ddl_tid != NULL); for (i = 0; i < n_ddl_thr; ++i) { rc = pthread_create(&ddl_tid[i], NULL, ddl_worker, NULL); assert(!rc); } } /********************************************************************** Initialize the structure to hold error statistics */ static void init_err_op_struct( /*===============*/ op_err_t* st) /* in/out: struct to initialize */ { int rc; memset(st, 0x00, sizeof(*st)); rc = pthread_mutex_init(&st->mutex, NULL); assert(!rc); } /********************************************************************** Initialize statistic structures. */ static void init_stat_structs(void) /*===================*/ { int i; for (i = 0; i < DML_OP_TYPE_MAX; ++i) { init_err_op_struct(&dml_op_errs[i]); } for (i = 0; i < DDL_OP_TYPE_MAX; ++i) { init_err_op_struct(&ddl_op_errs[i]); } } /********************************************************************** Free up the resources used in the test */ static void clean_up(void) /*==========*/ { int i; for (i = 0; i < DML_OP_TYPE_MAX; ++i) { pthread_mutex_destroy(&dml_op_errs[i].mutex); } for (i = 0; i < DDL_OP_TYPE_MAX; ++i) { pthread_mutex_destroy(&ddl_op_errs[i].mutex); } free(dml_tid); free(ddl_tid); } /********************************************************************** Print statistics at the end of the test */ static void print_one_struct( /*=============*/ op_err_t* st) { int i; fprintf(stderr, "n_ops = %d n_err = %d\n", st->n_ops, st->n_errs); fprintf(stderr, "err freq\n"); fprintf(stderr, "=========\n"); for (i = 0; i < DB_DATA_MISMATCH; ++i) { if (st->errs[i] != 0) { fprintf(stderr, "%d %d\n", i, st->errs[i]); } } fprintf(stderr, "=========\n"); } /********************************************************************** Print statistics at the end of the test */ static void print_results(void) /*===============*/ { fprintf(stderr, "SELECT: "); print_one_struct(&dml_op_errs[DML_OP_TYPE_SELECT]); fprintf(stderr, "DELETE: "); print_one_struct(&dml_op_errs[DML_OP_TYPE_DELETE]); fprintf(stderr, "UPDATE: "); print_one_struct(&dml_op_errs[DML_OP_TYPE_UPDATE]); fprintf(stderr, "INSERT: "); print_one_struct(&dml_op_errs[DML_OP_TYPE_INSERT]); fprintf(stderr, "CREATE: "); print_one_struct(&ddl_op_errs[DDL_OP_TYPE_CREATE]); fprintf(stderr, "DROP: "); print_one_struct(&ddl_op_errs[DDL_OP_TYPE_DROP]); fprintf(stderr, "ALTER: "); print_one_struct(&ddl_op_errs[DDL_OP_TYPE_ALTER]); fprintf(stderr, "TRUNCATE: "); print_one_struct(&ddl_op_errs[DDL_OP_TYPE_TRUNCATE]); } /********************************************************************** Register a table to be part of this driver. This is a bit of polymorphism using function pointers. Each table is first initialized to the base table which provides the generic functionality for some operations. The derived classes can decide to override this if they want to. See mt_base.c and mt_t1.c on how to add new tables to this driver. */ static void register_test_tables(void) /*======================*/ { int i; /* Initialize all table classes as base table */ for (i = 0; i < NUM_TBLS; ++i) { register_base_table(&tbl_array[i]); strcpy((char *)&tbl_array[i].db_name, DATABASE); tbl_array[i].format = tbl_format; tbl_array[i].page_size = page_size; } /* Individually register different table types here */ register_t1_table(&tbl_array[num_tables++]); register_t2_table(&tbl_array[num_tables++]); assert(num_tables < NUM_TBLS); } /********************************************************************** Whatever checks we want to run after the test after all the worker threads have finished. For now I just call the select function to print the contents of the table */ static void check_test_tables(void) /*======================*/ { int i; ib_trx_t trx; tbl_class_t* tbl; cb_args_t args = {0}; /* Initialize all table classes as dummy table */ for (i = 0; i < num_tables; ++i) { trx = ib_trx_begin(isolation_level); assert(trx != NULL); tbl = &tbl_array[i]; args.tbl = tbl; args.trx = trx; args.isolation_level = isolation_level; args.print_res = IB_TRUE; args.run_number = get_cur_run_number(); args.err_st = &dml_op_errs[DML_OP_TYPE_SELECT]; tbl->dml_fn[DML_OP_TYPE_SELECT](&args); ib_trx_commit(trx); } } int main(int argc, char* argv[]) { int i; void* res; ib_err_t err; srandom(time(NULL)); ib_init(); test_configure(); init_stat_structs(); err = ib_startup("barracuda"); assert(err == DB_SUCCESS); err = create_database(DATABASE); assert(err == DB_SUCCESS); register_test_tables(); init_test_tables(); /* start the test. */ test_running = IB_TRUE; create_worker_threads(); /* sleep for test duration */ sleep(test_time); /* stop test and let workers exit */ test_running = IB_FALSE; for (i = 0; i < n_ddl_thr; ++i) { pthread_join(ddl_tid[i], &res); } for (i = 0; i < n_dml_thr; ++i) { pthread_join(dml_tid[i], &res); } /* Let innodb finish off any background drop table requests */ sleep(1); check_test_tables(); drop_test_tables(); err = drop_database(DATABASE); assert(err == DB_SUCCESS); err = ib_shutdown(); assert(err == DB_SUCCESS); print_results(); clean_up(); #ifdef UNIV_DEBUG_VALGRIND VALGRIND_DO_LEAK_CHECK; #endif return(EXIT_SUCCESS); }