From: Pekka Nousiainen Date: June 12 2011 4:55pm Subject: bzr commit into mysql-5.1-telco-7.0-wl4124-new0 branch (pekka.nousiainen:4397) WL#4124 List-Archive: http://lists.mysql.com/commits/139115 Message-Id: <20110612165503.064095586E@sama.localdomain> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1413270436==" --===============1413270436== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///export/space/pekka/ms/ms-wl4124-70/ based on revid:pekka.nousiainen@stripped 4397 Pekka Nousiainen 2011-06-12 wl#4124 b06_handler.diff handler, simple MTR fixes modified: mysql-test/suite/ndb/r/ndb_basic.result mysql-test/suite/ndb/r/ndb_index_ordered.result mysql-test/suite/ndb/t/ndb_basic.test mysql-test/suite/ndb/t/ndb_index_ordered.test sql/ha_ndbcluster.cc sql/ha_ndbcluster.h sql/ha_ndbcluster_binlog.cc sql/ha_ndbcluster_binlog.h === modified file 'mysql-test/suite/ndb/r/ndb_basic.result' --- a/mysql-test/suite/ndb/r/ndb_basic.result 2011-04-28 07:47:53 +0000 +++ b/mysql-test/suite/ndb/r/ndb_basic.result 2011-06-12 16:54:59 +0000 @@ -69,6 +69,8 @@ Ndb_conflict_fn_max # Ndb_conflict_fn_old # Ndb_connect_count # Ndb_execute_count # +Ndb_index_stat_cache_clean # +Ndb_index_stat_cache_query # Ndb_number_of_data_nodes # Ndb_number_of_ready_data_nodes # Ndb_pruned_scan_count # @@ -88,6 +90,7 @@ ndb_extra_logging # ndb_force_send # ndb_index_stat_cache_entries # ndb_index_stat_enable # +ndb_index_stat_option # ndb_index_stat_update_freq # ndb_log_apply_status # ndb_log_bin # @@ -898,8 +901,8 @@ drop table t10,t20; # # bug #39872 - explain causes segv # (ndb_index_stat_enable=1 must be set to trigger bug) +# index stats v4: do not set, the v2 setting was local # -set ndb_index_stat_enable=1; CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) @@ -913,8 +916,8 @@ KEY `obj_id` (`obj_id`) # here we used to segv explain SELECT t1.id FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t2.obj_id=1; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 ref id,obj_id obj_id 5 const 1 Using where with pushed condition -1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.id 1 +1 SIMPLE t2 ref id,obj_id obj_id 5 const # Using where with pushed condition +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.id # drop table t1, t2; # # Bug #47054 Cluster only deletes first matched row in delete with left join === modified file 'mysql-test/suite/ndb/r/ndb_index_ordered.result' --- a/mysql-test/suite/ndb/r/ndb_index_ordered.result 2010-10-20 17:39:53 +0000 +++ b/mysql-test/suite/ndb/r/ndb_index_ordered.result 2011-06-12 16:54:59 +0000 @@ -659,143 +659,6 @@ select count(*) from t1 where c<'bbb'; count(*) 1 drop table t1; -set autocommit=1; -show session variables like 'ndb_index_stat_%'; -Variable_name Value -ndb_index_stat_cache_entries 32 -ndb_index_stat_enable OFF -ndb_index_stat_update_freq 20 -set ndb_index_stat_enable = off; -show session variables like 'ndb_index_stat_%'; -Variable_name Value -ndb_index_stat_cache_entries 32 -ndb_index_stat_enable OFF -ndb_index_stat_update_freq 20 -create table t1 (a int, b int, c varchar(10) not null, -primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values -(1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), -(4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), -(7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -count(*) -0 -select count(*) from t1 where b >= 10 and c >= 'bbb'; -count(*) -6 -select count(*) from t1 where b > 10; -count(*) -6 -select count(*) from t1 where b <= 20 and c < 'ccc'; -count(*) -4 -select count(*) from t1 where b = 20 and c = 'ccc'; -count(*) -1 -select count(*) from t1 where b > 20; -count(*) -3 -select count(*) from t1 where b = 30 and c > 'aaa'; -count(*) -2 -select count(*) from t1 where b <= 20; -count(*) -6 -select count(*) from t1 where b >= 20 and c > 'aaa'; -count(*) -4 -drop table t1; -set ndb_index_stat_enable = on; -set ndb_index_stat_cache_entries = 0; -show session variables like 'ndb_index_stat_%'; -Variable_name Value -ndb_index_stat_cache_entries 0 -ndb_index_stat_enable ON -ndb_index_stat_update_freq 20 -create table t1 (a int, b int, c varchar(10) not null, -primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values -(1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), -(4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), -(7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -count(*) -0 -select count(*) from t1 where b >= 10 and c >= 'bbb'; -count(*) -6 -select count(*) from t1 where b > 10; -count(*) -6 -select count(*) from t1 where b <= 20 and c < 'ccc'; -count(*) -4 -select count(*) from t1 where b = 20 and c = 'ccc'; -count(*) -1 -select count(*) from t1 where b > 20; -count(*) -3 -select count(*) from t1 where b = 30 and c > 'aaa'; -count(*) -2 -select count(*) from t1 where b <= 20; -count(*) -6 -select count(*) from t1 where b >= 20 and c > 'aaa'; -count(*) -4 -drop table t1; -set ndb_index_stat_enable = on; -set ndb_index_stat_cache_entries = 4; -set ndb_index_stat_update_freq = 2; -show session variables like 'ndb_index_stat_%'; -Variable_name Value -ndb_index_stat_cache_entries 4 -ndb_index_stat_enable ON -ndb_index_stat_update_freq 2 -create table t1 (a int, b int, c varchar(10) not null, -primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values -(1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), -(4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), -(7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -count(*) -0 -select count(*) from t1 where b >= 10 and c >= 'bbb'; -count(*) -6 -select count(*) from t1 where b > 10; -count(*) -6 -select count(*) from t1 where b <= 20 and c < 'ccc'; -count(*) -4 -select count(*) from t1 where b = 20 and c = 'ccc'; -count(*) -1 -select count(*) from t1 where b > 20; -count(*) -3 -select count(*) from t1 where b = 30 and c > 'aaa'; -count(*) -2 -select count(*) from t1 where b <= 20; -count(*) -6 -select count(*) from t1 where b >= 20 and c > 'aaa'; -count(*) -4 -drop table t1; -set ndb_index_stat_enable = @@global.ndb_index_stat_enable; -set ndb_index_stat_cache_entries = @@global.ndb_index_stat_cache_entries; -set ndb_index_stat_update_freq = @@global.ndb_index_stat_update_freq; -show session variables like 'ndb_index_stat_%'; -Variable_name Value -ndb_index_stat_cache_entries 32 -ndb_index_stat_enable OFF -ndb_index_stat_update_freq 20 create table t1 (a int primary key) engine = ndb; insert into t1 values (1), (2), (3); begin; === modified file 'mysql-test/suite/ndb/t/ndb_basic.test' --- a/mysql-test/suite/ndb/t/ndb_basic.test 2011-04-11 13:36:12 +0000 +++ b/mysql-test/suite/ndb/t/ndb_basic.test 2011-06-12 16:54:59 +0000 @@ -758,8 +758,9 @@ drop table t10,t20; --echo # --echo # bug #39872 - explain causes segv --echo # (ndb_index_stat_enable=1 must be set to trigger bug) +--echo # index stats v4: do not set, the v2 setting was local --echo # -set ndb_index_stat_enable=1; +# set ndb_index_stat_enable=1; CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) @@ -771,6 +772,7 @@ CREATE TABLE `t2` ( KEY `obj_id` (`obj_id`) ) ENGINE=ndbcluster DEFAULT CHARSET=utf8; --echo # here we used to segv +--replace_column 9 # explain SELECT t1.id FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t2.obj_id=1; drop table t1, t2; === modified file 'mysql-test/suite/ndb/t/ndb_index_ordered.test' --- a/mysql-test/suite/ndb/t/ndb_index_ordered.test 2010-10-20 17:39:53 +0000 +++ b/mysql-test/suite/ndb/t/ndb_index_ordered.test 2011-06-12 16:54:59 +0000 @@ -355,78 +355,7 @@ insert into t1 (a, c) values (1,'aaa'),( select count(*) from t1 where c<'bbb'; drop table t1; -# -- index statistics -- - -set autocommit=1; -show session variables like 'ndb_index_stat_%'; - -set ndb_index_stat_enable = off; -show session variables like 'ndb_index_stat_%'; - -create table t1 (a int, b int, c varchar(10) not null, - primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values - (1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), - (4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), - (7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -select count(*) from t1 where b >= 10 and c >= 'bbb'; -select count(*) from t1 where b > 10; -select count(*) from t1 where b <= 20 and c < 'ccc'; -select count(*) from t1 where b = 20 and c = 'ccc'; -select count(*) from t1 where b > 20; -select count(*) from t1 where b = 30 and c > 'aaa'; -select count(*) from t1 where b <= 20; -select count(*) from t1 where b >= 20 and c > 'aaa'; -drop table t1; - -set ndb_index_stat_enable = on; -set ndb_index_stat_cache_entries = 0; -show session variables like 'ndb_index_stat_%'; - -create table t1 (a int, b int, c varchar(10) not null, - primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values - (1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), - (4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), - (7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -select count(*) from t1 where b >= 10 and c >= 'bbb'; -select count(*) from t1 where b > 10; -select count(*) from t1 where b <= 20 and c < 'ccc'; -select count(*) from t1 where b = 20 and c = 'ccc'; -select count(*) from t1 where b > 20; -select count(*) from t1 where b = 30 and c > 'aaa'; -select count(*) from t1 where b <= 20; -select count(*) from t1 where b >= 20 and c > 'aaa'; -drop table t1; - -set ndb_index_stat_enable = on; -set ndb_index_stat_cache_entries = 4; -set ndb_index_stat_update_freq = 2; -show session variables like 'ndb_index_stat_%'; - -create table t1 (a int, b int, c varchar(10) not null, - primary key using hash (a), index(b,c)) engine=ndb; -insert into t1 values - (1,10,'aaa'),(2,10,'bbb'),(3,10,'ccc'), - (4,20,'aaa'),(5,20,'bbb'),(6,20,'ccc'), - (7,30,'aaa'),(8,30,'bbb'),(9,30,'ccc'); -select count(*) from t1 where b < 10; -select count(*) from t1 where b >= 10 and c >= 'bbb'; -select count(*) from t1 where b > 10; -select count(*) from t1 where b <= 20 and c < 'ccc'; -select count(*) from t1 where b = 20 and c = 'ccc'; -select count(*) from t1 where b > 20; -select count(*) from t1 where b = 30 and c > 'aaa'; -select count(*) from t1 where b <= 20; -select count(*) from t1 where b >= 20 and c > 'aaa'; -drop table t1; - -set ndb_index_stat_enable = @@global.ndb_index_stat_enable; -set ndb_index_stat_cache_entries = @@global.ndb_index_stat_cache_entries; -set ndb_index_stat_update_freq = @@global.ndb_index_stat_update_freq; -show session variables like 'ndb_index_stat_%'; +# index stats v4: old v2 tests are not meaningful and are removed # End of 4.1 tests === modified file 'sql/ha_ndbcluster.cc' --- a/sql/ha_ndbcluster.cc 2011-06-06 12:18:27 +0000 +++ b/sql/ha_ndbcluster.cc 2011-06-12 16:54:59 +0000 @@ -65,6 +65,7 @@ static ulong opt_ndb_wait_connected; ulong opt_ndb_wait_setup; static ulong opt_ndb_cache_check_time; static uint opt_ndb_cluster_connection_pool; +static char* opt_ndb_index_stat_option; static char* opt_ndb_connectstring; static uint opt_ndb_nodeid; @@ -178,7 +179,7 @@ static MYSQL_THDVAR_BOOL( static MYSQL_THDVAR_ULONG( index_stat_cache_entries, /* name */ PLUGIN_VAR_NOCMDARG, - "", + "Obsolete (ignored and will be removed later).", NULL, /* check func. */ NULL, /* update func. */ 32, /* default */ @@ -191,7 +192,7 @@ static MYSQL_THDVAR_ULONG( static MYSQL_THDVAR_ULONG( index_stat_update_freq, /* name */ PLUGIN_VAR_NOCMDARG, - "", + "Obsolete (ignored and will be removed later).", NULL, /* check func. */ NULL, /* update func. */ 20, /* default */ @@ -404,6 +405,21 @@ pthread_cond_t COND_ndb_util_thread; pthread_cond_t COND_ndb_util_ready; pthread_handler_t ndb_util_thread_func(void *arg); +// Index stats thread variables +pthread_t ndb_index_stat_thread; +int ndb_index_stat_thread_running= 0; +pthread_mutex_t LOCK_ndb_index_stat_thread; +pthread_cond_t COND_ndb_index_stat_thread; +pthread_cond_t COND_ndb_index_stat_ready; +pthread_mutex_t ndb_index_stat_glob_mutex; +pthread_mutex_t ndb_index_stat_list_mutex; +pthread_mutex_t ndb_index_stat_stat_mutex; +pthread_cond_t ndb_index_stat_stat_cond; +pthread_handler_t ndb_index_stat_thread_func(void *arg); + +extern void ndb_index_stat_free(NDB_SHARE *share); +extern void ndb_index_stat_end(); + /* Status variables shown with 'show status like 'Ndb%' */ struct st_ndb_status { @@ -427,6 +443,9 @@ static struct st_ndb_status g_ndb_status static long g_ndb_status_conflict_fn_max= 0; static long g_ndb_status_conflict_fn_old= 0; +static long g_ndb_status_index_stat_cache_query = 0; +static long g_ndb_status_index_stat_cache_clean = 0; + long long g_event_data_count = 0; long long g_event_nondata_count = 0; long long g_event_bytes_count = 0; @@ -604,6 +623,11 @@ static int show_ndb_server_api_stats(THD return 0; } +SHOW_VAR ndb_status_index_stat_variables[]= { + {"cache_query", (char*) &g_ndb_status_index_stat_cache_query, SHOW_LONG}, + {"cache_clean", (char*) &g_ndb_status_index_stat_cache_clean, SHOW_LONG}, + {NullS, NullS, SHOW_LONG} +}; /* Error handling functions @@ -1124,7 +1148,19 @@ void ha_ndbcluster::set_rec_per_key() case ORDERED_INDEX: // 'Records pr. key' are unknown for non-unique indexes. // (May change when we get better index statistics.) + { + THD *thd= current_thd; + const bool index_stat_enable= THDVAR(NULL, index_stat_enable) && + THDVAR(thd, index_stat_enable); + if (index_stat_enable) + { + int err= ndb_index_stat_set_rpk(i); + if (err == 0) + break; + } + // no fallback method... break; + } default: DBUG_ASSERT(false); } @@ -2152,10 +2188,6 @@ static void ndb_init_index(NDB_INDEX_DAT data.unique_index= NULL; data.index= NULL; data.unique_index_attrid_map= NULL; - data.index_stat=NULL; - data.index_stat_cache_entries=0; - data.index_stat_update_freq=0; - data.index_stat_query_count=0; data.ndb_record_key= NULL; data.ndb_unique_record_key= NULL; data.ndb_unique_record_row= NULL; @@ -2167,10 +2199,6 @@ static void ndb_clear_index(NDBDICT *dic { my_free((char*)data.unique_index_attrid_map, MYF(0)); } - if (data.index_stat) - { - delete data.index_stat; - } if (data.ndb_unique_record_key) dict->releaseRecord(data.ndb_unique_record_key); if (data.ndb_unique_record_row) @@ -2242,25 +2270,6 @@ int ha_ndbcluster::add_index_handle(THD break; } while (1); m_index[index_no].index= index; - // ordered index - add stats - NDB_INDEX_DATA& d=m_index[index_no]; - delete d.index_stat; - d.index_stat=NULL; - if (THDVAR(thd, index_stat_enable)) - { - d.index_stat=new NdbIndexStat(); - d.index_stat_cache_entries= THDVAR(thd, index_stat_cache_entries); - d.index_stat_update_freq= THDVAR(thd, index_stat_update_freq); - d.index_stat_query_count=0; - d.index_stat->alloc_cache(d.index_stat_cache_entries); - DBUG_PRINT("info", ("index %s stat=on cache_entries=%u update_freq=%u", - index->getName(), - d.index_stat_cache_entries, - d.index_stat_update_freq)); - } else - { - DBUG_PRINT("info", ("index %s stat=off", index->getName())); - } } if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX) { @@ -3530,6 +3539,11 @@ compute_index_bounds(NdbIndexScanOperati bound.high_key= NULL; bound.high_key_count= 0; } + DBUG_PRINT("info", ("start_flag=0x%x end_flag=0x%x" + " lo_keys=%d lo_incl=%d hi_keys=%d hi_incl=%d", + start_key?start_key->flag:0, end_key?end_key->flag:0, + bound.low_key_count, bound.low_inclusive, + bound.high_key_count, bound.high_inclusive)); } /** @@ -6068,9 +6082,11 @@ int ha_ndbcluster::info(uint flag) result= update_stats(thd, 1); break; } - if (flag & HA_STATUS_CONST) + /* RPK moved to variable part */ + if (flag & HA_STATUS_VARIABLE) { - DBUG_PRINT("info", ("HA_STATUS_CONST")); + /* No meaningful way to return error */ + DBUG_PRINT("info", ("rec_per_key")); set_rec_per_key(); } if (flag & HA_STATUS_ERRKEY) @@ -9864,7 +9880,51 @@ int ha_ndbcluster::ndb_optimize_table(TH int ha_ndbcluster::analyze(THD* thd, HA_CHECK_OPT* check_opt) { - return update_stats(thd, 1); + int err; + if ((err= update_stats(thd, 1)) != 0) + return err; + const bool index_stat_enable= THDVAR(NULL, index_stat_enable) && + THDVAR(thd, index_stat_enable); + if (index_stat_enable) + { + if ((err= analyze_index(thd)) != 0) + return err; + } + return 0; +} + +int +ha_ndbcluster::analyze_index(THD *thd) +{ + DBUG_ENTER("ha_ndbcluster::analyze_index"); + + Thd_ndb *thd_ndb= get_thd_ndb(thd); + Ndb *ndb= thd_ndb->ndb; + + uint inx_list[MAX_INDEXES]; + uint inx_count= 0; + + uint inx; + for (inx= 0; inx < table_share->keys; inx++) + { + NDB_INDEX_TYPE idx_type= get_index_type(inx); + + if ((idx_type == PRIMARY_KEY_ORDERED_INDEX || + idx_type == UNIQUE_ORDERED_INDEX || + idx_type == ORDERED_INDEX)) + { + if (inx_count < MAX_INDEXES) + inx_list[inx_count++]= inx; + } + } + + if (inx_count != 0) + { + int err= ndb_index_stat_analyze(ndb, inx_list, inx_count); + if (err != 0) + DBUG_RETURN(err); + } + DBUG_RETURN(0); } /* @@ -10868,6 +10928,14 @@ static int ndbcluster_init(void *p) pthread_cond_init(&COND_ndb_util_ready, NULL); pthread_cond_init(&COND_ndb_setup_complete, NULL); ndb_util_thread_running= -1; + pthread_mutex_init(&LOCK_ndb_index_stat_thread, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_ndb_index_stat_thread, NULL); + pthread_cond_init(&COND_ndb_index_stat_ready, NULL); + pthread_mutex_init(&ndb_index_stat_glob_mutex, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&ndb_index_stat_list_mutex, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&ndb_index_stat_stat_mutex, MY_MUTEX_INIT_FAST); + pthread_cond_init(&ndb_index_stat_stat_cond, NULL); + ndb_index_stat_thread_running= -1; ndbcluster_terminating= 0; ndb_dictionary_is_mysqld= 1; ndb_setup_complete= 0; @@ -10964,6 +11032,44 @@ static int ndbcluster_init(void *p) goto ndbcluster_init_error; } + // Create index statistics thread + pthread_t tmp2; + if (pthread_create(&tmp2, &connection_attrib, ndb_index_stat_thread_func, 0)) + { + DBUG_PRINT("error", ("Could not create ndb index statistics thread")); + hash_free(&ndbcluster_open_tables); + pthread_mutex_destroy(&ndbcluster_mutex); + pthread_mutex_destroy(&LOCK_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_ready); + pthread_mutex_destroy(&ndb_index_stat_glob_mutex); + pthread_mutex_destroy(&ndb_index_stat_list_mutex); + pthread_mutex_destroy(&ndb_index_stat_stat_mutex); + pthread_cond_destroy(&ndb_index_stat_stat_cond); + goto ndbcluster_init_error; + } + + /* Wait for the index statistics thread to start */ + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + while (ndb_index_stat_thread_running < 0) + pthread_cond_wait(&COND_ndb_index_stat_ready, &LOCK_ndb_index_stat_thread); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + + if (!ndb_index_stat_thread_running) + { + DBUG_PRINT("error", ("ndb index statistics thread exited prematurely")); + hash_free(&ndbcluster_open_tables); + pthread_mutex_destroy(&ndbcluster_mutex); + pthread_mutex_destroy(&LOCK_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_ready); + pthread_mutex_destroy(&ndb_index_stat_glob_mutex); + pthread_mutex_destroy(&ndb_index_stat_list_mutex); + pthread_mutex_destroy(&ndb_index_stat_stat_mutex); + pthread_cond_destroy(&ndb_index_stat_stat_cond); + goto ndbcluster_init_error; + } + #ifndef NDB_NO_WAIT_SETUP ndb_wait_setup_func= ndb_wait_setup_func_impl; #endif @@ -10991,6 +11097,15 @@ static int ndbcluster_end(handlerton *ht DBUG_RETURN(0); ndbcluster_inited= 0; + /* wait for index stat thread to finish */ + sql_print_information("Stopping Cluster Index Statistics thread"); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + ndbcluster_terminating= 1; + pthread_cond_signal(&COND_ndb_index_stat_thread); + while (ndb_index_stat_thread_running > 0) + pthread_cond_wait(&COND_ndb_index_stat_ready, &LOCK_ndb_index_stat_thread); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + /* wait for util and binlog thread to finish */ ndbcluster_binlog_end(NULL); @@ -11010,6 +11125,7 @@ static int ndbcluster_end(handlerton *ht } my_hash_free(&ndbcluster_open_tables); + ndb_index_stat_end(); ndbcluster_disconnect(); // cleanup ndb interface @@ -11020,6 +11136,9 @@ static int ndbcluster_end(handlerton *ht pthread_cond_destroy(&COND_ndb_util_thread); pthread_cond_destroy(&COND_ndb_util_ready); pthread_cond_destroy(&COND_ndb_setup_complete); + pthread_mutex_destroy(&LOCK_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_thread); + pthread_cond_destroy(&COND_ndb_index_stat_ready); ndbcluster_global_schema_lock_deinit(); DBUG_RETURN(0); } @@ -11129,6 +11248,12 @@ void ha_ndbcluster::set_tabname(const ch } +/* + If there are no stored stats, should we do a tree-dive on all db + nodes. The result is fairly good but does mean a round-trip. + */ +static const bool g_ndb_records_in_range_tree_dive= false; + /* Determine roughly how many records are in the range specified */ ha_rows ha_ndbcluster::records_in_range(uint inx, key_range *min_key, @@ -11154,92 +11279,73 @@ ha_ndbcluster::records_in_range(uint inx memcmp(min_key->key, max_key->key, key_length)==0))) DBUG_RETURN(1); + // XXX why this if if ((idx_type == PRIMARY_KEY_ORDERED_INDEX || idx_type == UNIQUE_ORDERED_INDEX || - idx_type == ORDERED_INDEX) && - m_index[inx].index_stat != NULL) // --ndb-index-stat-enable=1 + idx_type == ORDERED_INDEX)) { THD *thd= current_thd; - NDB_INDEX_DATA& d=m_index[inx]; - const NDBINDEX* index= d.index; - Ndb *ndb= get_ndb(thd); - NdbTransaction* active_trans= m_thd_ndb ? m_thd_ndb->trans : 0; - NdbTransaction* trans=NULL; - int res=0; - Uint64 rows; + const bool index_stat_enable= THDVAR(NULL, index_stat_enable) && + THDVAR(thd, index_stat_enable); - do + if (index_stat_enable) { - // We must provide approx table rows - Uint64 table_rows=0; - if (stats.records != ~(ha_rows)0 && stats.records != 0) - { - table_rows = stats.records; - DBUG_PRINT("info", ("use info->records: %lu", (ulong) table_rows)); - } - else - { - if (update_stats(thd, 1)) - break; - table_rows= stats.records; - DBUG_PRINT("info", ("use db row_count: %lu", (ulong) table_rows)); - if (table_rows == 0) { - // Problem if autocommit=0 -#ifdef ndb_get_table_statistics_uses_active_trans - rows=0; - break; -#endif - } - } + ha_rows rows= HA_POS_ERROR; + int err= ndb_index_stat_get_rir(inx, min_key, max_key, &rows); + if (err == 0) + DBUG_RETURN(rows); + /*fall through*/ + } - /* - Query the index statistics for our range. - */ - if ((trans=active_trans) == NULL || - trans->commitStatus() != NdbTransaction::Started) - { - DBUG_PRINT("info", ("no active trans")); - if (! (trans=ndb->startTransaction())) - ERR_BREAK(ndb->getNdbError(), res); - } - - /* Create an IndexBound struct for the keys */ - NdbIndexScanOperation::IndexBound ib; - compute_index_bounds(ib, - key_info, - min_key, - max_key); - - ib.range_no= 0; - - // Decide if db should be contacted - int flags=0; - if (d.index_stat_query_count < d.index_stat_cache_entries || - (d.index_stat_update_freq != 0 && - d.index_stat_query_count % d.index_stat_update_freq == 0)) - { - DBUG_PRINT("info", ("force stat from db")); - flags|=NdbIndexStat::RR_UseDb; - } - if (d.index_stat->records_in_range(index, - trans, - d.ndb_record_key, - m_ndb_record, - &ib, - table_rows, - &rows, - flags) == -1) - ERR_BREAK(d.index_stat->getNdbError(), res); - d.index_stat_query_count++; - } while (0); + if (g_ndb_records_in_range_tree_dive) + { + NDB_INDEX_DATA& d=m_index[inx]; + const NDBINDEX* index= d.index; + Ndb *ndb= get_ndb(thd); + NdbTransaction* active_trans= m_thd_ndb ? m_thd_ndb->trans : 0; + NdbTransaction* trans=NULL; + int res=0; + Uint64 rows; - if (trans != active_trans && rows == 0) - rows = 1; - if (trans != active_trans && trans != NULL) - ndb->closeTransaction(trans); - if (res != 0) - DBUG_RETURN(HA_POS_ERROR); - DBUG_RETURN(rows); + do + { + if ((trans=active_trans) == NULL || + trans->commitStatus() != NdbTransaction::Started) + { + DBUG_PRINT("info", ("no active trans")); + if (! (trans=ndb->startTransaction())) + ERR_BREAK(ndb->getNdbError(), res); + } + + /* Create an IndexBound struct for the keys */ + NdbIndexScanOperation::IndexBound ib; + compute_index_bounds(ib, + key_info, + min_key, + max_key); + + ib.range_no= 0; + + NdbIndexStat is; + if (is.records_in_range(index, + trans, + d.ndb_record_key, + m_ndb_record, + &ib, + 0, + &rows, + 0) == -1) + ERR_BREAK(is.getNdbError(), res); + } while (0); + + if (trans != active_trans && rows == 0) + rows = 1; + if (trans != active_trans && trans != NULL) + ndb->closeTransaction(trans); + if (res == 0) + DBUG_RETURN(rows); + /*fall through*/ + } } /* Use simple heuristics to estimate fraction @@ -12119,6 +12225,8 @@ void ndbcluster_real_free_share(NDB_SHAR if (opt_ndb_extra_logging > 9) sql_print_information ("ndbcluster_real_free_share: %s use_count: %u", (*share)->key, (*share)->use_count); + ndb_index_stat_free(*share); + my_hash_delete(&ndbcluster_open_tables, (uchar*) *share); thr_lock_delete(&(*share)->lock); pthread_mutex_destroy(&(*share)->mutex); @@ -13469,188 +13577,2076 @@ ndb_util_thread_fail: } /* - Condition pushdown -*/ -/** - Push a condition to ndbcluster storage engine for evaluation - during table and index scans. The conditions will be stored on a stack - for possibly storing several conditions. The stack can be popped - by calling cond_pop, handler::extra(HA_EXTRA_RESET) (handler::reset()) - will clear the stack. - The current implementation supports arbitrary AND/OR nested conditions - with comparisons between columns and constants (including constant - expressions and function calls) and the following comparison operators: - =, !=, >, >=, <, <=, "is null", and "is not null". - - @retval - NULL The condition was supported and will be evaluated for each - row found during the scan - @retval - cond The condition was not supported and all rows will be returned from - the scan for evaluation (and thus not saved on stack) + Ordered index stats */ -const -Item* -ha_ndbcluster::cond_push(const Item *cond) -{ - DBUG_ENTER("cond_push"); - if (cond->used_tables() & ~table->map) - { - /** - * 'cond' refers fields from other tables, or other instances - * of this table, -> reject it. - * (Optimizer need to have a better understanding of what is - * pushable by each handler.) - */ - DBUG_EXECUTE("where",print_where((Item *)cond, "Rejected cond_push", QT_ORDINARY);); - DBUG_RETURN(cond); - } - if (!m_cond) - m_cond= new ha_ndbcluster_cond; - if (!m_cond) +time_t ndb_index_stat_time_now= 0; + +time_t +ndb_index_stat_time() +{ + time_t now= time(0); + + if (unlikely(ndb_index_stat_time_now == 0)) + ndb_index_stat_time_now= now; + + if (unlikely(now < ndb_index_stat_time_now)) { - my_errno= HA_ERR_OUT_OF_MEM; - DBUG_RETURN(cond); + DBUG_PRINT("index_stat", ("time moved backwards %d seconds", + int(ndb_index_stat_time_now - now))); + now= ndb_index_stat_time_now; } - DBUG_EXECUTE("where",print_where((Item *)cond, m_tabname, QT_ORDINARY);); - DBUG_RETURN(m_cond->cond_push(cond, table, (NDBTAB *)m_table)); -} -/** - Pop the top condition from the condition stack of the handler instance. -*/ -void -ha_ndbcluster::cond_pop() -{ - if (m_cond) - m_cond->cond_pop(); + ndb_index_stat_time_now= now; + return now; } +bool ndb_index_stat_allow_flag= false; -/* - Implements the SHOW NDB STATUS command. -*/ bool -ndbcluster_show_status(handlerton *hton, THD* thd, stat_print_fn *stat_print, - enum ha_stat_type stat_type) +ndb_index_stat_allow(int flag= -1) { - char name[16]; - char buf[IO_SIZE]; - uint buflen; - DBUG_ENTER("ndbcluster_show_status"); - - if (stat_type != HA_ENGINE_STATUS) - { - DBUG_RETURN(FALSE); + if (flag != -1) { + pthread_mutex_lock(&ndb_index_stat_list_mutex); + ndb_index_stat_allow_flag= (bool)flag; + pthread_mutex_unlock(&ndb_index_stat_list_mutex); } + return ndb_index_stat_allow_flag; +} - Ndb* ndb= check_ndb_in_thd(thd); - Thd_ndb *thd_ndb= get_thd_ndb(thd); - struct st_ndb_status ns; - if (ndb) - update_status_variables(thd_ndb, &ns, thd_ndb->connection); - else - update_status_variables(NULL, &ns, g_ndb_cluster_connection); - - buflen= - my_snprintf(buf, sizeof(buf), - "cluster_node_id=%ld, " - "connected_host=%s, " - "connected_port=%ld, " - "number_of_data_nodes=%ld, " - "number_of_ready_data_nodes=%ld, " - "connect_count=%ld", - ns.cluster_node_id, - ns.connected_host, - ns.connected_port, - ns.number_of_data_nodes, - ns.number_of_ready_data_nodes, - ns.connect_count); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - STRING_WITH_LEN("connection"), buf, buflen)) - DBUG_RETURN(TRUE); +/* Options */ - for (int i= 0; i < MAX_NDB_NODES; i++) - { - if (ns.transaction_hint_count[i] > 0 || - ns.transaction_no_hint_count[i] > 0) - { - uint namelen= my_snprintf(name, sizeof(name), "node[%d]", i); - buflen= my_snprintf(buf, sizeof(buf), - "transaction_hint=%ld, transaction_no_hint=%ld", - ns.transaction_hint_count[i], - ns.transaction_no_hint_count[i]); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - name, namelen, buf, buflen)) - DBUG_RETURN(TRUE); - } - } +/* Options in string format buffer size */ +static const uint ndb_index_stat_option_sz= 512; +void ndb_index_stat_opt2str(const class Ndb_index_stat_opt&, char*); - if (ndb) - { - Ndb::Free_list_usage tmp; - tmp.m_name= 0; - while (ndb->get_free_list_usage(&tmp)) - { - buflen= - my_snprintf(buf, sizeof(buf), - "created=%u, free=%u, sizeof=%u", - tmp.m_created, tmp.m_free, tmp.m_sizeof); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - tmp.m_name, strlen(tmp.m_name), buf, buflen)) - DBUG_RETURN(TRUE); - } +struct Ndb_index_stat_opt { + enum Unit { + Ubool = 1, + Usize = 2, + Utime = 3, + Umsec = 4 + }; + enum Flag { + Freadonly = (1 << 0) + }; + struct Val { + const char* name; + uint val; + uint minval; + uint maxval; + Unit unit; + uint flag; + }; + enum Idx { + Iloop_checkon = 0, + Iloop_idle = 1, + Iloop_busy = 2, + Iupdate_batch = 3, + Iread_batch = 4, + Iidle_batch = 5, + Icheck_batch = 6, + Icheck_delay = 7, + Idelete_batch = 8, + Iclean_delay = 9, + Ierror_batch = 10, + Ierror_delay = 11, + Ievict_batch = 12, + Ievict_delay = 13, + Icache_limit = 14, + Icache_lowpct = 15, + Imax = 16 + }; + Val val[Imax]; + /* Options in string format (SYSVAR ndb_index_stat_option) */ + char option[ndb_index_stat_option_sz]; + Ndb_index_stat_opt(); + uint get(Idx i) const { + assert(i < Imax); + return val[i].val; } - ndbcluster_show_status_binlog(thd, stat_print, stat_type); +}; - DBUG_RETURN(FALSE); +Ndb_index_stat_opt::Ndb_index_stat_opt() +{ +#define ival(aname, aval, aminval, amaxval, aunit, aflag) \ + val[I##aname].name = #aname; \ + val[I##aname].val = aval; \ + val[I##aname].minval = aminval; \ + val[I##aname].maxval = amaxval; \ + val[I##aname].unit = aunit; \ + val[I##aname].flag = aflag + ival(loop_checkon, 1000, 0, ~0, Umsec, 0); + ival(loop_idle, 1000, 0, ~0, Umsec, 0); + ival(loop_busy, 100, 0, ~0, Umsec, 0); + ival(update_batch, 1, 1, ~0, Usize, 0); + ival(read_batch, 4, 1, ~0, Usize, 0); + ival(idle_batch, 32, 1, ~0, Usize, 0); + ival(check_batch, 32, 1, ~0, Usize, 0); + ival(check_delay, 60, 0, ~0, Utime, 0); + ival(clean_delay, 0, 0, ~0, Utime, 0); + ival(delete_batch, 8, 1, ~0, Usize, 0); + ival(error_batch, 4, 1, ~0, Usize, 0); + ival(error_delay, 60, 0, ~0, Utime, 0); + ival(evict_batch, 8, 1, ~0, Usize, 0); + ival(evict_delay, 60, 0, ~0, Utime, 0); + ival(cache_limit, 32*1024*1024, 1024*1024, ~0, Usize, 0); + ival(cache_lowpct, 90, 0, 100, Usize, 0); +#undef ival + + ndb_index_stat_opt2str(*this, option); } +/* Hard limits */ +static const uint ndb_index_stat_max_evict_batch = 32; -int ha_ndbcluster::get_default_no_partitions(HA_CREATE_INFO *create_info) +Ndb_index_stat_opt ndb_index_stat_opt; + +/* Copy option struct to string buffer */ +void +ndb_index_stat_opt2str(const Ndb_index_stat_opt& opt, char* str) { - if (unlikely(g_ndb_cluster_connection->get_no_ready() <= 0)) - { -err: - my_error(HA_ERR_NO_CONNECTION, MYF(0)); - return -1; - } + DBUG_ENTER("ndb_index_stat_opt2str"); - THD* thd = current_thd; - if (thd == 0) - goto err; - Thd_ndb * thd_ndb = get_thd_ndb(thd); - if (thd_ndb == 0) - goto err; + char buf[ndb_index_stat_option_sz]; + char *const end= &buf[sizeof(buf)]; + char* ptr= buf; + *ptr= 0; - ha_rows max_rows, min_rows; - if (create_info) - { - max_rows= create_info->max_rows; - min_rows= create_info->min_rows; - } - else + const uint imax= Ndb_index_stat_opt::Imax; + for (uint i= 0; i < imax; i++) { - max_rows= table_share->max_rows; - min_rows= table_share->min_rows; - } - uint no_fragments= get_no_fragments(max_rows >= min_rows ? - max_rows : min_rows); - uint reported_frags; - adjusted_frag_count(thd_ndb->ndb, - no_fragments, - reported_frags); - return reported_frags; -} + const Ndb_index_stat_opt::Val& v= opt.val[i]; + ptr+= strlen(ptr); + const char* sep= (ptr == buf ? "" : ","); + const uint sz= ptr < end ? end - ptr : 0; -uint32 ha_ndbcluster::calculate_key_hash_value(Field **field_array) -{ - Uint32 hash_value; - struct Ndb::Key_part_ptr key_data[MAX_REF_PARTS]; - struct Ndb::Key_part_ptr *key_data_ptr= &key_data[0]; - Uint32 i= 0; - int ret_val; + switch (v.unit) { + case Ndb_index_stat_opt::Ubool: + { + DBUG_ASSERT(v.val == 0 || v.val == 1); + if (v.val == 0) + my_snprintf(ptr, sz, "%s%s=OFF", sep, v.name); + else + my_snprintf(ptr, sz, "%s%s=ON", sep, v.name); + } + break; + + case Ndb_index_stat_opt::Usize: + { + uint m; + if (v.val == 0) + my_snprintf(ptr, sz, "%s%s=0", sep, v.name); + else if (v.val % (m= 1024*1024*1024) == 0) + my_snprintf(ptr, sz, "%s%s=%uG", sep, v.name, v.val / m); + else if (v.val % (m= 1024*1024) == 0) + my_snprintf(ptr, sz, "%s%s=%uM", sep, v.name, v.val / m); + else if (v.val % (m= 1024) == 0) + my_snprintf(ptr, sz, "%s%s=%uK", sep, v.name, v.val / m); + else + my_snprintf(ptr, sz, "%s%s=%u", sep, v.name, v.val); + } + break; + + case Ndb_index_stat_opt::Utime: + { + uint m; + if (v.val == 0) + my_snprintf(ptr, sz, "%s%s=0", sep, v.name); + else if (v.val % (m= 60*60*24) == 0) + snprintf(ptr, sz, "%s%s=%ud", sep, v.name, v.val / m); + else if (v.val % (m= 60*60) == 0) + snprintf(ptr, sz, "%s%s=%uh", sep, v.name, v.val / m); + else if (v.val % (m= 60) == 0) + snprintf(ptr, sz, "%s%s=%um", sep, v.name, v.val / m); + else + snprintf(ptr, sz, "%s%s=%us", sep, v.name, v.val); + } + break; + + case Ndb_index_stat_opt::Umsec: + { + if (v.val == 0) + my_snprintf(ptr, sz, "%s%s=0", sep, v.name); + else + snprintf(ptr, sz, "%s%s=%ums", sep, v.name, v.val); + } + break; + + default: + DBUG_ASSERT(false); + break; + } + } + + memset(str, 0, ndb_index_stat_option_sz); + strcpy(str, buf); + DBUG_PRINT("index_stat", ("str: \"%s\"", str)); + DBUG_VOID_RETURN; +} + +int +ndb_index_stat_option_parse(char* p, Ndb_index_stat_opt& opt) +{ + DBUG_ENTER("ndb_index_stat_option_parse"); + + char *r= strchr(p, '='); + if (r == 0) + DBUG_RETURN(-1); + *r++= 0; + + while (isspace(*r)) + *r++= 0; + if (*r == 0) + DBUG_RETURN(-1); + + const uint imax= Ndb_index_stat_opt::Imax; + for (uint i= 0; i < imax; i++) + { + Ndb_index_stat_opt::Val& v= opt.val[i]; + if (strcmp(p, v.name) != 0) + continue; + + char *s; + for (s= r; *s != 0; s++) + *s= tolower(*s); + ulonglong val= strtoull(r, &s, 10); + + switch (v.unit) { + case Ndb_index_stat_opt::Ubool: + { + if ((s > r && *s == 0 && val == 0) || + strcmp(r, "off") == 0 || + strcmp(r, "false") == 0) + val= 0; + else if ((s > r && *s == 0 && val == 1) || + strcmp(r, "on") == 0 || + strcmp(r, "true") == 0) + val= 1; + else + DBUG_RETURN(-1); + v.val= val; + } + break; + + case Ndb_index_stat_opt::Usize: + { + if (s == r) + DBUG_RETURN(-1); + if (strcmp(s, "") == 0) + ; + else if (strcmp(s, "k") == 0) + val*= 1024; + else if (strcmp(s, "m") == 0) + val*= 1024*1024; + else if (strcmp(s, "g") == 0) + val*= 1024*1024*1024; + else + DBUG_RETURN(-1); + if (val < v.minval || val > v.maxval) + DBUG_RETURN(-1); + v.val= val; + } + break; + + case Ndb_index_stat_opt::Utime: + { + if (s == r) + DBUG_RETURN(-1); + if (strcmp(s, "") == 0) + ; + else if (strcmp(s, "s") == 0) + ; + else if (strcmp(s, "m") == 0) + val*= 60; + else if (strcmp(s, "h") == 0) + val*= 60*60; + else if (strcmp(s, "d") == 0) + val*= 24*60*60; + else + DBUG_RETURN(-1); + if (val < v.minval || val > v.maxval) + DBUG_RETURN(-1); + v.val= val; + } + break; + + case Ndb_index_stat_opt::Umsec: + { + if (s == r) + DBUG_RETURN(-1); + if (strcmp(s, "") == 0) + ; + else if (strcmp(s, "ms") == 0) + ; + else + DBUG_RETURN(-1); + if (val < v.minval || val > v.maxval) + DBUG_RETURN(-1); + v.val= val; + } + break; + + default: + DBUG_ASSERT(false); + break; + } + } + DBUG_RETURN(0); +} + +/* Copy option string to option struct */ +int +ndb_index_stat_str2opt(const char *str, Ndb_index_stat_opt& opt) +{ + DBUG_ENTER("ndb_index_stat_str2opt"); + DBUG_PRINT("index_stat", ("str: \"%s\"", str)); + + char buf[ndb_index_stat_option_sz]; + + assert(str != 0); + if (strlen(str) >= sizeof(buf)) + DBUG_RETURN(-1); + strcpy(buf, str); + + char *p= buf; + while (1) + { + while (isspace(*p)) + p++; + if (*p == 0) + break; + + char *q= strchr(p, ','); + if (q == p) + DBUG_RETURN(-1); + if (q != 0) + *q= 0; + + DBUG_PRINT("index_stat", ("parse: %s", p)); + if (ndb_index_stat_option_parse(p, opt) == -1) + DBUG_RETURN(-1); + + if (q == 0) + break; + p= q + 1; + } + + ndb_index_stat_opt2str(opt, opt.option); + DBUG_RETURN(0); +} + +/* Thanks to ha_innodb.cc */ + +/* Need storage between check and update (assume locked) */ +char ndb_index_stat_option_tmp[ndb_index_stat_option_sz]; + +int +ndb_index_stat_option_check(MYSQL_THD, + struct st_mysql_sys_var *var, + void *save, + struct st_mysql_value *value) +{ + DBUG_ENTER("ndb_index_stat_option_check"); + char buf[ndb_index_stat_option_sz]; + int len= sizeof(buf); + const char *str= value->val_str(value, buf, &len); + if (str != 0) + { + /* Seems to be nothing in buf */ + DBUG_PRINT("index_stat", ("str: %s len: %d", str, len)); + Ndb_index_stat_opt opt; + if (ndb_index_stat_str2opt(str, opt) == 0) + { + /* Passed to update */ + strcpy(ndb_index_stat_option_tmp, str); + *(const char**)save= ndb_index_stat_option_tmp; + DBUG_RETURN(0); + } + } + DBUG_RETURN(1); +} + +void +ndb_index_stat_option_update(MYSQL_THD, + struct st_mysql_sys_var *var, + void *var_ptr, + const void *save) +{ + DBUG_ENTER("ndb_index_stat_option_update"); + const char *str= *(const char**)save; + DBUG_PRINT("index_stat", ("str: %s", str)); + Ndb_index_stat_opt& opt= ndb_index_stat_opt; + int ret= ndb_index_stat_str2opt(str, opt); + assert(ret == 0); + *(const char**)var_ptr= ndb_index_stat_opt.option; + DBUG_VOID_RETURN; +} + +/* Global stuff */ + +struct Ndb_index_stat_glob { + uint list_count[Ndb_index_stat::LT_Count]; /* Temporary use */ + uint total_count; + uint force_update; + uint wait_update; + uint cache_query_bytes; /* In use */ + uint cache_clean_bytes; /* Obsolete versions not yet removed */ + bool is_locked; + Ndb_index_stat_glob() : + total_count(0), + force_update(0), + wait_update(0), + cache_query_bytes(0), + cache_clean_bytes(0), + is_locked(false) + { + } + void set_list_count() + { + int lt; + for (lt= 0; lt < Ndb_index_stat::LT_Count; lt++) + { + const Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + list_count[lt]= list.count; + } + } + void lock() + { + pthread_mutex_lock(&ndb_index_stat_glob_mutex); + assert(!is_locked); + is_locked= true; + } + void unlock() + { + assert(is_locked); + g_ndb_status_index_stat_cache_query= cache_query_bytes; + g_ndb_status_index_stat_cache_clean= cache_clean_bytes; + is_locked= false; + pthread_mutex_unlock(&ndb_index_stat_glob_mutex); + } +}; + +Ndb_index_stat_glob ndb_index_stat_glob; + +/* Shared index entries */ + +Ndb_index_stat::Ndb_index_stat() +{ + is= 0; + index_id= 0; + index_version= 0; +#ifndef DBUG_OFF + memset(id, 0, sizeof(id)); +#endif + access_time= 0; + load_time= 0; + read_time= 0; + sample_version= 0; + check_time= 0; + cache_clean= false; + force_update= 0; + error_time= 0; + error_count= 0; + share_next= 0; + lt= 0; + lt_old= 0; + list_next= 0; + list_prev= 0; + share= 0; +} + +void +ndb_index_stat_error(Ndb_index_stat *st, const char* place, int line) +{ + pthread_mutex_lock(&ndb_index_stat_stat_mutex); + time_t now= ndb_index_stat_time(); + NdbIndexStat::Error error= st->is->getNdbError(); + if (error.code == 0) + { + // XXX why this if + NdbIndexStat::Error error2; + error= error2; + error.code= NdbIndexStat::InternalError; + error.status= NdbError::TemporaryError; + } + st->error= error; + st->error_time= now; + st->error_count++; + pthread_cond_broadcast(&ndb_index_stat_stat_cond); + pthread_mutex_unlock(&ndb_index_stat_stat_mutex); + + DBUG_PRINT("index_stat", ("%s line %d: error %d line %d extra %d", + place, line, error.code, error.line, error.extra)); +} + +/* Lists across shares */ + +Ndb_index_stat_list::Ndb_index_stat_list(int the_lt, const char* the_name) +{ + lt= the_lt; + name= the_name; + head= 0; + tail= 0; + count= 0; +} + +Ndb_index_stat_list ndb_index_stat_list[Ndb_index_stat::LT_Count] = { + Ndb_index_stat_list(0, 0), + Ndb_index_stat_list(Ndb_index_stat::LT_New, "New"), + Ndb_index_stat_list(Ndb_index_stat::LT_Update, "Update"), + Ndb_index_stat_list(Ndb_index_stat::LT_Read, "Read"), + Ndb_index_stat_list(Ndb_index_stat::LT_Idle, "Idle"), + Ndb_index_stat_list(Ndb_index_stat::LT_Check, "Check"), + Ndb_index_stat_list(Ndb_index_stat::LT_Delete, "Delete"), + Ndb_index_stat_list(Ndb_index_stat::LT_Error, "Error") +}; + +void +ndb_index_stat_list_add(Ndb_index_stat* st, int lt, int place= +1) +{ + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + assert(st != 0 && st->lt == 0); + assert(st->list_next == 0 && st->list_prev == 0); + assert(1 <= lt && lt < Ndb_index_stat::LT_Count); + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + + DBUG_PRINT("index_stat", ("st %s -> %s", st->id, list.name)); + + if (list.count == 0) + { + assert(list.head == 0 && list.tail == 0); + list.head= st; + list.tail= st; + } + else if (place < 0) + { + assert(list.head != 0 && list.head->list_prev == 0); + st->list_next= list.head; + list.head->list_prev= st; + list.head= st; + } + else + { + assert(list.tail != 0 && list.tail->list_next == 0); + st->list_prev= list.tail; + list.tail->list_next= st; + list.tail= st; + } + list.count++; + glob.lock(); + glob.total_count++; + glob.unlock(); + + st->lt= lt; +} + +void +ndb_index_stat_list_remove(Ndb_index_stat* st) +{ + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + assert(st != 0); + int lt= st->lt; + assert(1 <= lt && lt < Ndb_index_stat::LT_Count); + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + + DBUG_PRINT("index_stat", ("st %s <- %s", st->id, list.name)); + + Ndb_index_stat* next= st->list_next; + Ndb_index_stat* prev= st->list_prev; + + if (list.head == st) + list.head= next; + if (list.tail == st) + list.tail= prev; + assert(list.count != 0); + list.count--; + glob.lock(); + assert(glob.total_count != 0); + glob.total_count--; + glob.unlock(); + + if (next != 0) + next->list_prev= prev; + if (prev != 0) + prev->list_next= next; + + st->lt= 0; + st->lt_old= 0; + st->list_next= 0; + st->list_prev= 0; +} + +void +ndb_index_stat_list_move(Ndb_index_stat *st, int lt, int place= +1) +{ + assert(st != 0); + ndb_index_stat_list_remove(st); + ndb_index_stat_list_add(st, lt, place); +} + +/* Move entry in / out error list */ + +void +ndb_index_stat_list_to_error(Ndb_index_stat *st) +{ + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + + assert(st != 0); + const int lt= st->lt; + assert(1 <= lt && lt < Ndb_index_stat::LT_Count); + assert(lt != Ndb_index_stat::LT_Error); + + if (st->force_update != 0) + { + glob.lock(); + assert(glob.force_update >= st->force_update); + glob.force_update-= st->force_update; + glob.unlock(); + st->force_update= 0; + } + + time_t now= ndb_index_stat_time(); + st->error_time= now; + ndb_index_stat_list_move(st, Ndb_index_stat::LT_Error); +} + +void +ndb_index_stat_list_from_error(Ndb_index_stat *st) +{ + assert(st != 0); + assert(st->lt == Ndb_index_stat::LT_Error); + if (st->force_update) + ndb_index_stat_list_move(st, Ndb_index_stat::LT_Update); + else + ndb_index_stat_list_move(st, Ndb_index_stat::LT_Read); + st->error.code= 0; + st->error.status= NdbError::Success; +} + +/* Find or add entry under the share */ + +Ndb_index_stat* +ndb_index_stat_alloc() +{ + Ndb_index_stat *st= new Ndb_index_stat; + NdbIndexStat *is= new NdbIndexStat; + if (st != 0 && is != 0) + { + st->is= is; + return st; + } + delete is; + delete st; + return 0; +} + +/* Subroutine, have lock */ +Ndb_index_stat* +ndb_index_stat_find_share(NDB_SHARE *share, + const NDBINDEX *index, + Ndb_index_stat *&st_last) +{ + struct Ndb_index_stat *st= share->index_stat_list; + st_last= 0; + while (st != 0) + { + assert(st->share == share); + assert(st->is != 0); + NdbIndexStat::Head head; + st->is->get_head(head); + if (head.m_indexId == (uint)index->getObjectId() && + head.m_indexVersion == (uint)index->getObjectVersion()) + break; + st_last= st; + st= st->share_next; + } + return st; +} + +/* Subroutine, have lock */ +Ndb_index_stat* +ndb_index_stat_add_share(NDB_SHARE *share, + const NDBINDEX *index, + const NDBTAB *table, + Ndb_index_stat *st_last) +{ + struct Ndb_index_stat *st= ndb_index_stat_alloc(); + if (st != 0) + { + st->share= share; + if (st_last == 0) + share->index_stat_list= st; + else + st_last->share_next= st; + st->index_id= index->getObjectId(); + st->index_version= index->getObjectVersion(); +#ifndef DBUG_OFF + snprintf(st->id, sizeof(st->id), "%d.%d", st->index_id, st->index_version); +#endif + if (st->is->set_index(*index, *table) == -1) + { + ndb_index_stat_error(st, "set_index", __LINE__); + /* Caller assigns list */ + } + } + return st; +} + +Ndb_index_stat* +ndb_index_stat_get_share(NDB_SHARE *share, + const NDBINDEX *index, + const NDBTAB *table, + bool allow_add, + bool force_update) +{ + pthread_mutex_lock(&share->mutex); + pthread_mutex_lock(&ndb_index_stat_list_mutex); + time_t now= ndb_index_stat_time(); + + struct Ndb_index_stat *st= 0; + struct Ndb_index_stat *st_last= 0; + if (ndb_index_stat_allow()) + { + st= ndb_index_stat_find_share(share, index, st_last); + if (st == 0 && allow_add) + { + st= ndb_index_stat_add_share(share, index, table, st_last); + if (st != 0) + ndb_index_stat_list_add(st, Ndb_index_stat::LT_New); + } + if (st != 0) + { + if (force_update != 0) + { + st->force_update++; + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + glob.lock(); + glob.force_update++; + glob.unlock(); + } + st->access_time= now; + } + } + + pthread_mutex_unlock(&ndb_index_stat_list_mutex); + pthread_mutex_unlock(&share->mutex); + return st; +} +void +ndb_index_stat_free(Ndb_index_stat *st) +{ + pthread_mutex_lock(&ndb_index_stat_list_mutex); + NDB_SHARE *share= st->share; + assert(share != 0); + + Ndb_index_stat *st_head= 0; + Ndb_index_stat *st_tail= 0; + Ndb_index_stat *st_loop= share->index_stat_list; + bool found= false; + while (st_loop != 0) { + if (st == st_loop) { + st->share= 0; + assert(st->lt != 0); + assert(st->lt != Ndb_index_stat::LT_Delete); + ndb_index_stat_list_move(st, Ndb_index_stat::LT_Delete); + st_loop= st_loop->share_next; + assert(!found); + found++; + } else { + if (st_head == 0) + st_head= st_loop; + else + st_tail->share_next= st_loop; + st_tail= st_loop; + st_loop= st_loop->share_next; + st_tail->share_next= 0; + } + } + assert(found); + share->index_stat_list= st_head; + pthread_mutex_unlock(&ndb_index_stat_list_mutex); +} + +void +ndb_index_stat_free(NDB_SHARE *share) +{ + pthread_mutex_lock(&ndb_index_stat_list_mutex); + Ndb_index_stat *st; + while ((st= share->index_stat_list) != 0) + { + share->index_stat_list= st->share_next; + st->share= 0; + assert(st->lt != 0); + assert(st->lt != Ndb_index_stat::LT_Delete); + ndb_index_stat_list_move(st, Ndb_index_stat::LT_Delete); + } + pthread_mutex_unlock(&ndb_index_stat_list_mutex); +} + +/* Statistics thread sub-routines */ + +void +ndb_index_stat_cache_move(Ndb_index_stat *st) +{ + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + NdbIndexStat::CacheInfo infoBuild; + NdbIndexStat::CacheInfo infoQuery; + + st->is->get_cache_info(infoBuild, NdbIndexStat::CacheBuild); + st->is->get_cache_info(infoQuery, NdbIndexStat::CacheQuery); + const uint new_query_bytes= infoBuild.m_totalBytes; + const uint old_query_bytes= infoQuery.m_totalBytes; + DBUG_PRINT("index_stat", ("st %s cache move: query:%u clean:%u", + st->id, new_query_bytes, old_query_bytes)); + st->is->move_cache(); + glob.lock(); + assert(glob.cache_query_bytes >= old_query_bytes); + glob.cache_query_bytes-= old_query_bytes; + glob.cache_query_bytes+= new_query_bytes; + glob.cache_clean_bytes+= old_query_bytes; + glob.unlock(); +} + +void +ndb_index_stat_cache_clean(Ndb_index_stat *st) +{ + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + NdbIndexStat::CacheInfo infoClean; + + st->is->get_cache_info(infoClean, NdbIndexStat::CacheClean); + const uint old_clean_bytes= infoClean.m_totalBytes; + DBUG_PRINT("index_stat", ("st %s cache clean: clean:%u", + st->id, old_clean_bytes)); + st->is->clean_cache(); + glob.lock(); + assert(glob.cache_clean_bytes >= old_clean_bytes); + glob.cache_clean_bytes-= old_clean_bytes; + glob.unlock(); +} + +/* Misc in/out parameters for process steps */ +struct Ndb_index_stat_proc { + Ndb *ndb; + time_t now; + int lt; + bool busy; + bool end; + Ndb_index_stat_proc() : + ndb(0), + now(0), + lt(0), + busy(false), + end(false) + {} +}; + +void +ndb_index_stat_proc_new(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + if (st->error.code != 0) + pr.lt= Ndb_index_stat::LT_Error; + else if (st->force_update) + pr.lt= Ndb_index_stat::LT_Update; + else + pr.lt= Ndb_index_stat::LT_Read; +} + +void +ndb_index_stat_proc_new(Ndb_index_stat_proc &pr) +{ + pthread_mutex_lock(&ndb_index_stat_list_mutex); + const int lt= Ndb_index_stat::LT_New; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + + Ndb_index_stat *st_loop= list.head; + while (st_loop != 0) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_new(pr, st); + ndb_index_stat_list_move(st, pr.lt); + } + pthread_mutex_unlock(&ndb_index_stat_list_mutex); +} + +void +ndb_index_stat_proc_update(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + if (st->is->update_stat(pr.ndb) == -1) + { + ndb_index_stat_error(st, "update_stat", __LINE__); + pr.lt= Ndb_index_stat::LT_Error; + return; + } + pr.lt= Ndb_index_stat::LT_Read; +} + +void +ndb_index_stat_proc_update(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Update; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Iupdate_batch); + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_update(pr, st); + ndb_index_stat_list_move(st, pr.lt); + cnt++; + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_read(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + NdbIndexStat::Head head; + if (st->is->read_stat(pr.ndb) == -1) + { + ndb_index_stat_error(st, "read_stat", __LINE__); + pr.lt= Ndb_index_stat::LT_Error; + return; + } + + pthread_mutex_lock(&ndb_index_stat_stat_mutex); + pr.now= ndb_index_stat_time(); + st->is->get_head(head); + st->load_time= head.m_loadTime; + st->read_time= pr.now; + st->sample_version= head.m_sampleVersion; + + if (st->force_update != 0) + { + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + glob.lock(); + assert(glob.force_update >= st->force_update); + glob.force_update-= st->force_update; + glob.unlock(); + st->force_update= 0; + } + + ndb_index_stat_cache_move(st); + st->cache_clean= false; + pr.lt= Ndb_index_stat::LT_Idle; + pthread_cond_broadcast(&ndb_index_stat_stat_cond); + pthread_mutex_unlock(&ndb_index_stat_stat_mutex); +} + +void +ndb_index_stat_proc_read(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Read; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Iread_batch); + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_read(pr, st); + ndb_index_stat_list_move(st, pr.lt); + cnt++; + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_idle(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const int clean_delay= opt.get(Ndb_index_stat_opt::Iclean_delay); + const int check_delay= opt.get(Ndb_index_stat_opt::Icheck_delay); + const time_t clean_wait= + st->cache_clean ? 0 : st->read_time + clean_delay - pr.now; + const time_t check_wait= + st->check_time == 0 ? 0 : st->check_time + check_delay - pr.now; + + DBUG_PRINT("index_stat", ("st %s check wait:%ds force update:%u" + " clean wait:%ds cache clean:%d", + st->id, check_wait, st->force_update, + clean_wait, st->cache_clean)); + + if (!st->cache_clean && clean_wait <= 0) + { + ndb_index_stat_cache_clean(st); + st->cache_clean= true; + } + if (st->force_update) + { + pr.lt= Ndb_index_stat::LT_Update; + return; + } + if (check_wait <= 0) + { + pr.lt= Ndb_index_stat::LT_Check; + return; + } + pr.lt= Ndb_index_stat::LT_Idle; +} + +void +ndb_index_stat_proc_idle(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Idle; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Iidle_batch); + pr.now= ndb_index_stat_time(); + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_idle(pr, st); + if (pr.lt != lt) + { + ndb_index_stat_list_move(st, pr.lt); + cnt++; + } + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_check(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + pr.now= ndb_index_stat_time(); + st->check_time= pr.now; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + NdbIndexStat::Head head; + if (st->is->read_head(pr.ndb) == -1) + { + ndb_index_stat_error(st, "read_head", __LINE__); + pr.lt= Ndb_index_stat::LT_Error; + return; + } + st->is->get_head(head); + const uint version_old= st->sample_version; + const uint version_new= head.m_sampleVersion; + if (version_old != version_new) + { + DBUG_PRINT("index_stat", ("st %s sample version old:%u new:%u", + st->id, version_old, version_new)); + pr.lt= Ndb_index_stat::LT_Read; + return; + } + pr.lt= Ndb_index_stat::LT_Idle; +} + +void +ndb_index_stat_proc_check(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Check; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Icheck_batch); + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_check(pr, st); + ndb_index_stat_list_move(st, pr.lt); + cnt++; + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_evict(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + + NdbIndexStat::Head head; + NdbIndexStat::CacheInfo infoBuild; + NdbIndexStat::CacheInfo infoQuery; + NdbIndexStat::CacheInfo infoClean; + st->is->get_head(head); + st->is->get_cache_info(infoBuild, NdbIndexStat::CacheBuild); + st->is->get_cache_info(infoQuery, NdbIndexStat::CacheQuery); + st->is->get_cache_info(infoClean, NdbIndexStat::CacheClean); + + DBUG_PRINT("index_stat", + ("evict table: %u index: %u version: %u" + " sample version: %u" + " cache bytes build:%u query:%u clean:%u", + head.m_tableId, head.m_indexId, head.m_indexVersion, + head.m_sampleVersion, + infoBuild.m_totalBytes, infoQuery.m_totalBytes, infoClean.m_totalBytes)); + + /* Twice to move all caches to clean */ + ndb_index_stat_cache_move(st); + ndb_index_stat_cache_move(st); + ndb_index_stat_cache_clean(st); +} + +bool +ndb_index_stat_proc_evict() +{ + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + Ndb_index_stat_glob &glob= ndb_index_stat_glob; + glob.lock(); + uint curr_size= glob.cache_query_bytes + glob.cache_clean_bytes; + glob.unlock(); + const uint cache_lowpct= opt.get(Ndb_index_stat_opt::Icache_lowpct); + const uint cache_limit= opt.get(Ndb_index_stat_opt::Icache_limit); + if (100 * curr_size <= cache_lowpct * cache_limit) + return false; + return true; +} + +void +ndb_index_stat_proc_evict(Ndb_index_stat_proc &pr, int lt) +{ + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Ievict_batch); + const int evict_delay= opt.get(Ndb_index_stat_opt::Ievict_delay); + pr.now= ndb_index_stat_time(); + + if (!ndb_index_stat_proc_evict()) + return; + + /* Create a LRU batch */ + Ndb_index_stat* st_lru_arr[ndb_index_stat_max_evict_batch + 1]; + uint st_lru_cnt= 0; + Ndb_index_stat *st_loop= list.head; + while (st_loop != 0 && st_lru_cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + if (st->read_time + evict_delay <= pr.now) + { + /* Insertion sort into the batch from the end */ + if (st_lru_cnt == 0) + st_lru_arr[st_lru_cnt++]= st; + else + { + uint i= st_lru_cnt; + while (i != 0) + { + if (st_lru_arr[i-1]->access_time < st->access_time) + break; + i--; + } + if (i < st_lru_cnt) + { + uint j= st_lru_cnt; /* There is place for one more at end */ + while (j > i) + { + st_lru_arr[j]= st_lru_arr[j-1]; + j--; + } + st_lru_arr[i]= st; + if (st_lru_cnt < batch) + st_lru_cnt++; + } + } + } + } + + /* Process the LRU batch */ + uint cnt= 0; + while (cnt < st_lru_cnt) + { + if (!ndb_index_stat_proc_evict()) + break; + + Ndb_index_stat *st= st_lru_arr[cnt]; + DBUG_PRINT("index_stat", ("st %s proc evict %s", st->id, list.name)); + ndb_index_stat_proc_evict(pr, st); + ndb_index_stat_free(st); + cnt++; + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_evict(Ndb_index_stat_proc &pr) +{ + ndb_index_stat_proc_evict(pr, Ndb_index_stat::LT_Error); + ndb_index_stat_proc_evict(pr, Ndb_index_stat::LT_Idle); +} + +void +ndb_index_stat_proc_delete(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Delete; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint delete_batch= opt.get(Ndb_index_stat_opt::Idelete_batch); + const uint batch= !pr.end ? delete_batch : 0xFFFFFFFF; + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_evict(pr, st); + ndb_index_stat_list_remove(st); + delete st->is; + delete st; + cnt++; + } + if (cnt == batch) + pr.busy= true; +} + +void +ndb_index_stat_proc_error(Ndb_index_stat_proc &pr, Ndb_index_stat *st) +{ + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const int error_delay= opt.get(Ndb_index_stat_opt::Ierror_delay); + const time_t error_wait= st->error_time + error_delay - pr.now; + + if (error_wait <= 0) + { + ndb_index_stat_list_from_error(st); + DBUG_PRINT("index_stat", ("st %s error wait:%ds error count:%u", + st->id, (int)error_wait, st->error_count)); + if (st->force_update) + pr.lt= Ndb_index_stat::LT_Update; + else + pr.lt= Ndb_index_stat::LT_Read; + return; + } + pr.lt= Ndb_index_stat::LT_Error; +} + +void +ndb_index_stat_proc_error(Ndb_index_stat_proc &pr) +{ + const int lt= Ndb_index_stat::LT_Error; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + const uint batch= opt.get(Ndb_index_stat_opt::Ierror_batch); + pr.now= ndb_index_stat_time(); + + Ndb_index_stat *st_loop= list.head; + uint cnt= 0; + while (st_loop != 0 && cnt < batch) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s proc %s", st->id, list.name)); + ndb_index_stat_proc_error(pr, st); + if (pr.lt != lt) + { + ndb_index_stat_list_move(st, pr.lt); + cnt++; + } + } + if (cnt == batch) + pr.busy= true; +} + +#ifndef DBUG_OFF +void +ndb_index_stat_report(const Ndb_index_stat_glob& old_glob) +{ + Ndb_index_stat_glob new_glob= ndb_index_stat_glob; + new_glob.set_list_count(); + + /* List counts */ + { + const uint (&old_count)[Ndb_index_stat::LT_Count]= old_glob.list_count; + const uint (&new_count)[Ndb_index_stat::LT_Count]= new_glob.list_count; + bool any= false; + int lt; + for (lt=1; lt < Ndb_index_stat::LT_Count; lt++) + { + const Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const char* name= list.name; + if (old_count[lt] != new_count[lt]) + { + DBUG_PRINT("index_stat", ("%s: %u -> %u", + name, old_count[lt], new_count[lt])); + any= true; + } + } + if (any) + { + const uint bufsz= 20 * Ndb_index_stat::LT_Count; + char buf[bufsz]; + char *ptr= buf; + for (lt= 1; lt < Ndb_index_stat::LT_Count; lt++) + { + const Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + const char* name= list.name; + sprintf(ptr, " %s:%u", name, new_count[lt]); + ptr+= strlen(ptr); + } + DBUG_PRINT("index_stat", ("list:%s", buf)); + } + } + + /* Cache summary */ + { + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + uint query_size= new_glob.cache_query_bytes; + uint clean_size= new_glob.cache_clean_bytes; + uint total_size= query_size + clean_size; + const uint limit= opt.get(Ndb_index_stat_opt::Icache_limit); + double pct= 100.0; + if (limit != 0) + pct= 100.0 * (double)total_size / (double)limit; + DBUG_PRINT("index_stat", ("cache query:%u clean:%u (%.2f pct)", + query_size, clean_size, pct)); + } + + /* Updates waited for and forced updates */ + { + pthread_mutex_lock(&ndb_index_stat_list_mutex); + uint wait_update= new_glob.wait_update; + uint force_update= new_glob.force_update; + pthread_mutex_unlock(&ndb_index_stat_list_mutex); + if (wait_update != 0 || force_update != 0) + { + DBUG_PRINT("index_stat", ("wait update:%u force update:%u", + wait_update, force_update)); + } + } +} +#endif + +void +ndb_index_stat_proc(Ndb_index_stat_proc &pr) +{ +#ifndef DBUG_OFF + Ndb_index_stat_glob old_glob= ndb_index_stat_glob; + old_glob.set_list_count(); +#endif + + DBUG_ENTER("ndb_index_stat_proc"); + + ndb_index_stat_proc_new(pr); + ndb_index_stat_proc_update(pr); + ndb_index_stat_proc_read(pr); + ndb_index_stat_proc_idle(pr); + ndb_index_stat_proc_check(pr); + ndb_index_stat_proc_evict(pr); + ndb_index_stat_proc_delete(pr); + ndb_index_stat_proc_error(pr); + +#ifndef DBUG_OFF + ndb_index_stat_report(old_glob); +#endif + DBUG_VOID_RETURN; +} + +void +ndb_index_stat_end() +{ + DBUG_ENTER("ndb_index_stat_end"); + Ndb_index_stat_proc pr; + pr.end= true; + + /* + * Shares have been freed so any index stat entries left should be + * in LT_Delete. The first two steps here should be unnecessary. + */ + + ndb_index_stat_allow(0); + + int lt; + for (lt= 1; lt < Ndb_index_stat::LT_Count; lt++) + { + if (lt == (int)Ndb_index_stat::LT_Delete) + continue; + Ndb_index_stat_list &list= ndb_index_stat_list[lt]; + Ndb_index_stat *st_loop= list.head; + while (st_loop != 0) + { + Ndb_index_stat *st= st_loop; + st_loop= st_loop->list_next; + DBUG_PRINT("index_stat", ("st %s end %s", st->id, list.name)); + pr.lt= Ndb_index_stat::LT_Delete; + ndb_index_stat_list_move(st, pr.lt); + } + } + + /* Real free */ + ndb_index_stat_proc_delete(pr); + DBUG_VOID_RETURN; +} + +/* Index stats thread */ + +static int +ndb_index_stat_check_or_create_systables(NdbIndexStat* is, Ndb* ndb) +{ + DBUG_ENTER("ndb_index_stat_check_or_create_systables"); + + if (is->check_systables(ndb) == 0) + { + DBUG_PRINT("index_stat", ("using existing index stats tables")); + DBUG_RETURN(0); + } + if (is->create_systables(ndb) == 0) + { + DBUG_PRINT("index_stat", ("created index stats tables")); + DBUG_RETURN(0); + } + + DBUG_RETURN(-1); +} + +pthread_handler_t +ndb_index_stat_thread_func(void *arg __attribute__((unused))) +{ + THD *thd; /* needs to be first for thread_stack */ + struct timespec abstime; + Thd_ndb *thd_ndb; + + my_thread_init(); + DBUG_ENTER("ndb_index_stat_thread_func"); + + // wl4124_todo remove useless stuff copied from utility thread + + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + + thd= new THD; /* note that contructor of THD uses DBUG_ */ + if (thd == NULL) + { + my_errno= HA_ERR_OUT_OF_MEM; + DBUG_RETURN(NULL); + } + THD_CHECK_SENTRY(thd); + pthread_detach_this_thread(); + ndb_index_stat_thread= pthread_self(); + + thd->thread_stack= (char*)&thd; /* remember where our stack is */ + if (thd->store_globals()) + goto ndb_index_stat_thread_fail; + lex_start(thd); + thd->init_for_queries(); +#ifndef NDB_THD_HAS_NO_VERSION + thd->version=refresh_version; +#endif + thd->client_capabilities = 0; + thd->security_ctx->skip_grants(); + my_net_init(&thd->net, 0); + + CHARSET_INFO *charset_connection; + charset_connection= get_charset_by_csname("utf8", + MY_CS_PRIMARY, MYF(MY_WME)); + thd->variables.character_set_client= charset_connection; + thd->variables.character_set_results= charset_connection; + thd->variables.collation_connection= charset_connection; + thd->update_charset(); + + /* Signal successful initialization */ + ndb_index_stat_thread_running= 1; + pthread_cond_signal(&COND_ndb_index_stat_ready); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + + /* + wait for mysql server to start + */ + pthread_mutex_lock(&LOCK_server_started); + while (!mysqld_server_started) + { + set_timespec(abstime, 1); + pthread_cond_timedwait(&COND_server_started, &LOCK_server_started, + &abstime); + if (ndbcluster_terminating) + { + pthread_mutex_unlock(&LOCK_server_started); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + goto ndb_index_stat_thread_end; + } + } + pthread_mutex_unlock(&LOCK_server_started); + + /* + Wait for cluster to start + */ + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + while (!g_ndb_status.cluster_node_id && (ndbcluster_hton->slot != ~(uint)0)) + { + /* ndb not connected yet */ + pthread_cond_wait(&COND_ndb_index_stat_thread, &LOCK_ndb_index_stat_thread); + if (ndbcluster_terminating) + goto ndb_index_stat_thread_end; + } + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + + /* Get thd_ndb for this thread */ + if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) + { + sql_print_error("Could not allocate Thd_ndb object"); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + goto ndb_index_stat_thread_end; + } + set_thd_ndb(thd, thd_ndb); + thd_ndb->options|= TNO_NO_LOG_SCHEMA_OP; + if (thd_ndb->ndb->setDatabaseName(NDB_INDEX_STAT_DB) == -1) + { + sql_print_error("Could not change index stats thd_ndb database to %s", + NDB_INDEX_STAT_DB); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + goto ndb_index_stat_thread_end; + } + + ndb_index_stat_allow(1); + bool enable_ok; + enable_ok= false; + + set_timespec(abstime, 0); + for (;;) + { + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + if (!ndbcluster_terminating) { + int ret= pthread_cond_timedwait(&COND_ndb_index_stat_thread, + &LOCK_ndb_index_stat_thread, + &abstime); + const char* reason= ret == ETIMEDOUT ? "timed out" : "wake up"; + DBUG_PRINT("index_stat", ("loop: %s", reason)); + } + if (ndbcluster_terminating) /* Shutting down server */ + goto ndb_index_stat_thread_end; + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + + pthread_mutex_lock(&LOCK_global_system_variables); + const bool enable_ok_new= THDVAR(NULL, index_stat_enable); + pthread_mutex_unlock(&LOCK_global_system_variables); + + Ndb_index_stat_proc pr; + pr.ndb= thd_ndb->ndb; + + do + { + if (enable_ok != enable_ok_new) + { + DBUG_PRINT("index_stat", ("global enable: %d -> %d", + enable_ok, enable_ok_new)); + + if (enable_ok_new) + { + // at enable check or create stats tables + NdbIndexStat is; + if (ndb_index_stat_check_or_create_systables(&is, thd_ndb->ndb) == -1) + { + // try again in next loop + sql_print_warning("create stats tables failed: error code %d", + is.getNdbError().code); + break; + } + } + enable_ok= enable_ok_new; + } + + if (!enable_ok) + break; + + pr.busy= false; + ndb_index_stat_proc(pr); + } while (0); + + /* Calculate new time to wake up */ + + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + int msecs= 0; + if (!enable_ok) + msecs= opt.get(Ndb_index_stat_opt::Iloop_checkon); + else if (!pr.busy) + msecs= opt.get(Ndb_index_stat_opt::Iloop_idle); + else + msecs= opt.get(Ndb_index_stat_opt::Iloop_busy); + DBUG_PRINT("index_stat", ("sleep %dms", msecs)); + + struct timeval tick_time; + gettimeofday(&tick_time, 0); + abstime.tv_sec= tick_time.tv_sec; + abstime.tv_nsec= tick_time.tv_usec * 1000; + + int secs= 0; + if (msecs >= 1000) + { + secs= msecs / 1000; + msecs= msecs % 1000; + } + + abstime.tv_sec+= secs; + abstime.tv_nsec+= msecs * 1000000; + if (abstime.tv_nsec >= 1000000000) + { + abstime.tv_sec+= 1; + abstime.tv_nsec-= 1000000000; + } + } + +ndb_index_stat_thread_end: + net_end(&thd->net); + +ndb_index_stat_thread_fail: + if (thd_ndb) + { + ha_ndbcluster::release_thd_ndb(thd_ndb); + set_thd_ndb(thd, NULL); + } + thd->cleanup(); + delete thd; + + /* signal termination */ + ndb_index_stat_thread_running= 0; + pthread_cond_signal(&COND_ndb_index_stat_ready); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + DBUG_PRINT("exit", ("ndb_index_stat_thread")); + + DBUG_LEAVE; + my_thread_end(); + pthread_exit(0); + return NULL; +} + +/* Optimizer queries */ + +static ulonglong +ndb_index_stat_round(double x) +{ + char buf[100]; + if (x < 0.0) + x= 0.0; + snprintf(buf, sizeof(buf), "%.0f", x); + /* mysql provides strtoull */ + ulonglong n= strtoull(buf, 0, 10); + return n; +} + +int +ha_ndbcluster::ndb_index_stat_wait(Ndb_index_stat *st, + uint sample_version) +{ + DBUG_ENTER("ha_ndbcluster::ndb_index_stat_wait"); + + pthread_mutex_lock(&ndb_index_stat_stat_mutex); + int err= 0; + NdbIndexStat::Head head; + uint count= 0; + struct timespec abstime; + while (true) { + int ret= 0; + if (st->error.code != 0 && + (st->error.code != NdbIndexStat::NoIndexStats || + st->force_update == 0)) + { + err= st->error.code; + break; + } + if (st->sample_version > sample_version) + break; + DBUG_PRINT("index_stat", ("st %s wait count:%u", + st->id, ++count)); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + pthread_cond_signal(&COND_ndb_index_stat_thread); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + set_timespec(abstime, 1); + ret= pthread_cond_timedwait(&ndb_index_stat_stat_cond, + &ndb_index_stat_stat_mutex, + &abstime); + if (ret != 0 && ret != ETIMEDOUT) + { + err= ret; + break; + } + } + pthread_mutex_unlock(&ndb_index_stat_stat_mutex); + if (err != 0) { + DBUG_PRINT("index_stat", ("st %s wait error: %d", + st->id, err)); + DBUG_RETURN(err); + } + DBUG_PRINT("index_stat", ("st %s wait ok: sample_version %u -> %u", + st->id, sample_version, st->sample_version)); + DBUG_RETURN(0); +} + +int +ha_ndbcluster::ndb_index_stat_query(uint inx, + const key_range *min_key, + const key_range *max_key, + NdbIndexStat::Stat& stat) +{ + DBUG_ENTER("ha_ndbcluster::ndb_index_stat_query"); + + const KEY *key_info= table->key_info + inx; + const NDB_INDEX_DATA &data= m_index[inx]; + const NDBINDEX *index= data.index; + DBUG_PRINT("index_stat", ("index: %s", index->getName())); + + THD *thd= current_thd; + Ndb *ndb= get_ndb(thd); + int err= 0; + + /* Create an IndexBound struct for the keys */ + NdbIndexScanOperation::IndexBound ib; + compute_index_bounds(ib, key_info, min_key, max_key); + ib.range_no= 0; + + Ndb_index_stat *st= + ndb_index_stat_get_share(m_share, index, m_table, true, false); + if (st == 0) + { + DBUG_PRINT("index_stat", ("failed to add index stat share")); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + } + + err= ndb_index_stat_wait(st, 0); + if (err != 0) + DBUG_RETURN(err); + + if (st->read_time == 0) + { + DBUG_PRINT("index_stat", ("no index stats")); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + pthread_cond_signal(&COND_ndb_index_stat_thread); + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + DBUG_RETURN(NdbIndexStat::NoIndexStats); + } + + uint8 bound_lo_buffer[NdbIndexStat::BoundBufferBytes]; + uint8 bound_hi_buffer[NdbIndexStat::BoundBufferBytes]; + NdbIndexStat::Bound bound_lo(st->is, bound_lo_buffer); + NdbIndexStat::Bound bound_hi(st->is, bound_hi_buffer); + NdbIndexStat::Range range(bound_lo, bound_hi); + + const NdbRecord* key_record= data.ndb_record_key; + if (st->is->convert_range(range, key_record, &ib) == -1) + { + ndb_index_stat_error(st, "convert_range", __LINE__); + DBUG_RETURN(st->error.code); + } + if (st->is->query_stat(range, stat) == -1) + { + /* Invalid cache - should remove the entry */ + ndb_index_stat_error(st, "query_stat", __LINE__); + DBUG_RETURN(st->error.code); + } + + DBUG_RETURN(0); +} + +int +ha_ndbcluster::ndb_index_stat_get_rir(uint inx, + key_range *min_key, + key_range *max_key, + ha_rows *rows_out) +{ + DBUG_ENTER("ha_ndbcluster::ndb_index_stat_get_rir"); + uint8 stat_buffer[NdbIndexStat::StatBufferBytes]; + NdbIndexStat::Stat stat(stat_buffer); + int err= ndb_index_stat_query(inx, min_key, max_key, stat); + if (err == 0) + { + double rir= -1.0; + NdbIndexStat::get_rir(stat, &rir); + DBUG_PRINT("index_stat", ("stat rir: %.2f", rir)); + ha_rows rows= ndb_index_stat_round(rir); + /* Estimate only so cannot return exact zero */ + if (rows == 0) + rows= 1; + *rows_out= rows; + DBUG_PRINT("index_stat", ("rir: %u", (uint)rows)); + DBUG_RETURN(0); + } + DBUG_RETURN(err); +} + +int +ha_ndbcluster::ndb_index_stat_set_rpk(uint inx) +{ + DBUG_ENTER("ha_ndbcluster::ndb_index_stat_set_rpk"); + + KEY *key_info= table->key_info + inx; + int err= 0; + + uint8 stat_buffer[NdbIndexStat::StatBufferBytes]; + NdbIndexStat::Stat stat(stat_buffer); + const key_range *min_key= 0; + const key_range *max_key= 0; + err= ndb_index_stat_query(inx, min_key, max_key, stat); + if (err == 0) + { + uint k; + for (k= 0; k < key_info->key_parts; k++) + { + double rpk= -1.0; + NdbIndexStat::get_rpk(stat, k, &rpk); + ulonglong recs= ndb_index_stat_round(rpk); + key_info->rec_per_key[k]= recs; + DBUG_PRINT("index_stat", ("rpk[%u]: %u", k, (uint)recs)); + } + DBUG_RETURN(0); + } + DBUG_RETURN(err); +} + +int +ha_ndbcluster::ndb_index_stat_analyze(Ndb *ndb, + uint *inx_list, + uint inx_count) +{ + DBUG_ENTER("ha_ndbcluster::ndb_index_stat_analyze"); + const Ndb_index_stat_opt &opt= ndb_index_stat_opt; + + struct { + uint sample_version; + uint error_count; + } old[MAX_INDEXES]; + + int err= 0; + uint i; + + /* Force stats update on each index */ + for (i= 0; i < inx_count; i++) + { + uint inx= inx_list[i]; + const NDB_INDEX_DATA &data= m_index[inx]; + const NDBINDEX *index= data.index; + DBUG_PRINT("index_stat", ("force update: %s", index->getName())); + + Ndb_index_stat *st= + ndb_index_stat_get_share(m_share, index, m_table, true, true); + + if (st == 0) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + + old[i].sample_version= st->sample_version; + old[i].error_count= st->error_count; + } + + /* Wait for each update (or error) */ + for (i = 0; i < inx_count; i++) + { + uint inx= inx_list[i]; + const NDB_INDEX_DATA &data= m_index[inx]; + const NDBINDEX *index= data.index; + DBUG_PRINT("index_stat", ("wait for update: %s", index->getName())); + + Ndb_index_stat *st= + ndb_index_stat_get_share(m_share, index, m_table, false, false); + if (st == 0) + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); + + err= ndb_index_stat_wait(st, old[i].sample_version); + if (err != 0) + DBUG_RETURN(err); + } + + DBUG_RETURN(0); +} + +/* END: Index statistcs */ + +/* + Condition pushdown +*/ +/** + Push a condition to ndbcluster storage engine for evaluation + during table and index scans. The conditions will be stored on a stack + for possibly storing several conditions. The stack can be popped + by calling cond_pop, handler::extra(HA_EXTRA_RESET) (handler::reset()) + will clear the stack. + The current implementation supports arbitrary AND/OR nested conditions + with comparisons between columns and constants (including constant + expressions and function calls) and the following comparison operators: + =, !=, >, >=, <, <=, "is null", and "is not null". + + @retval + NULL The condition was supported and will be evaluated for each + row found during the scan + @retval + cond The condition was not supported and all rows will be returned from + the scan for evaluation (and thus not saved on stack) +*/ +const +Item* +ha_ndbcluster::cond_push(const Item *cond) +{ + DBUG_ENTER("cond_push"); + + if (cond->used_tables() & ~table->map) + { + /** + * 'cond' refers fields from other tables, or other instances + * of this table, -> reject it. + * (Optimizer need to have a better understanding of what is + * pushable by each handler.) + */ + DBUG_EXECUTE("where",print_where((Item *)cond, "Rejected cond_push", QT_ORDINARY);); + DBUG_RETURN(cond); + } + if (!m_cond) + m_cond= new ha_ndbcluster_cond; + if (!m_cond) + { + my_errno= HA_ERR_OUT_OF_MEM; + DBUG_RETURN(cond); + } + DBUG_EXECUTE("where",print_where((Item *)cond, m_tabname, QT_ORDINARY);); + DBUG_RETURN(m_cond->cond_push(cond, table, (NDBTAB *)m_table)); +} + +/** + Pop the top condition from the condition stack of the handler instance. +*/ +void +ha_ndbcluster::cond_pop() +{ + if (m_cond) + m_cond->cond_pop(); +} + + +/* + Implements the SHOW NDB STATUS command. +*/ +bool +ndbcluster_show_status(handlerton *hton, THD* thd, stat_print_fn *stat_print, + enum ha_stat_type stat_type) +{ + char name[16]; + char buf[IO_SIZE]; + uint buflen; + DBUG_ENTER("ndbcluster_show_status"); + + if (stat_type != HA_ENGINE_STATUS) + { + DBUG_RETURN(FALSE); + } + + Ndb* ndb= check_ndb_in_thd(thd); + Thd_ndb *thd_ndb= get_thd_ndb(thd); + struct st_ndb_status ns; + if (ndb) + update_status_variables(thd_ndb, &ns, thd_ndb->connection); + else + update_status_variables(NULL, &ns, g_ndb_cluster_connection); + + buflen= + my_snprintf(buf, sizeof(buf), + "cluster_node_id=%ld, " + "connected_host=%s, " + "connected_port=%ld, " + "number_of_data_nodes=%ld, " + "number_of_ready_data_nodes=%ld, " + "connect_count=%ld", + ns.cluster_node_id, + ns.connected_host, + ns.connected_port, + ns.number_of_data_nodes, + ns.number_of_ready_data_nodes, + ns.connect_count); + if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, + STRING_WITH_LEN("connection"), buf, buflen)) + DBUG_RETURN(TRUE); + + for (int i= 0; i < MAX_NDB_NODES; i++) + { + if (ns.transaction_hint_count[i] > 0 || + ns.transaction_no_hint_count[i] > 0) + { + uint namelen= my_snprintf(name, sizeof(name), "node[%d]", i); + buflen= my_snprintf(buf, sizeof(buf), + "transaction_hint=%ld, transaction_no_hint=%ld", + ns.transaction_hint_count[i], + ns.transaction_no_hint_count[i]); + if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, + name, namelen, buf, buflen)) + DBUG_RETURN(TRUE); + } + } + + if (ndb) + { + Ndb::Free_list_usage tmp; + tmp.m_name= 0; + while (ndb->get_free_list_usage(&tmp)) + { + buflen= + my_snprintf(buf, sizeof(buf), + "created=%u, free=%u, sizeof=%u", + tmp.m_created, tmp.m_free, tmp.m_sizeof); + if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, + tmp.m_name, strlen(tmp.m_name), buf, buflen)) + DBUG_RETURN(TRUE); + } + } + ndbcluster_show_status_binlog(thd, stat_print, stat_type); + + DBUG_RETURN(FALSE); +} + + +int ha_ndbcluster::get_default_no_partitions(HA_CREATE_INFO *create_info) +{ + if (unlikely(g_ndb_cluster_connection->get_no_ready() <= 0)) + { +err: + my_error(HA_ERR_NO_CONNECTION, MYF(0)); + return -1; + } + + THD* thd = current_thd; + if (thd == 0) + goto err; + Thd_ndb * thd_ndb = get_thd_ndb(thd); + if (thd_ndb == 0) + goto err; + + ha_rows max_rows, min_rows; + if (create_info) + { + max_rows= create_info->max_rows; + min_rows= create_info->min_rows; + } + else + { + max_rows= table_share->max_rows; + min_rows= table_share->min_rows; + } + uint no_fragments= get_no_fragments(max_rows >= min_rows ? + max_rows : min_rows); + uint reported_frags; + adjusted_frag_count(thd_ndb->ndb, + no_fragments, + reported_frags); + return reported_frags; +} + +uint32 ha_ndbcluster::calculate_key_hash_value(Field **field_array) +{ + Uint32 hash_value; + struct Ndb::Key_part_ptr key_data[MAX_REF_PARTS]; + struct Ndb::Key_part_ptr *key_data_ptr= &key_data[0]; + Uint32 i= 0; + int ret_val; Uint64 tmp[(MAX_KEY_SIZE_IN_WORDS*MAX_XFRM_MULTIPLY) >> 1]; void *buf= (void*)&tmp[0]; Ndb *ndb= m_thd_ndb->ndb; @@ -15353,6 +17349,7 @@ SHOW_VAR ndb_status_variables_export[]= {"Ndb", (char*) &ndb_status_injector_variables, SHOW_ARRAY}, {"Ndb", (char*) &ndb_status_slave_variables, SHOW_ARRAY}, {"Ndb", (char*) &show_ndb_server_api_stats, SHOW_FUNC}, + {"Ndb_index_stat", (char*) &ndb_status_index_stat_variables, SHOW_ARRAY}, {NullS, NullS, SHOW_LONG} }; @@ -15432,6 +17429,17 @@ static MYSQL_SYSVAR_UINT( ); +static MYSQL_SYSVAR_STR( + index_stat_option, /* name */ + opt_ndb_index_stat_option, /* var */ + PLUGIN_VAR_RQCMDARG, + "Comma-separated tunable options for ndb index statistics", + ndb_index_stat_option_check, /* check func. */ + ndb_index_stat_option_update, /* update func. */ + ndb_index_stat_opt.option +); + + ulong opt_ndb_report_thresh_binlog_epoch_slip; static MYSQL_SYSVAR_ULONG( report_thresh_binlog_epoch_slip, /* name */ @@ -15624,6 +17632,7 @@ static struct st_mysql_sys_var* system_v MYSQL_SYSVAR(batch_size), MYSQL_SYSVAR(optimization_delay), MYSQL_SYSVAR(index_stat_enable), + MYSQL_SYSVAR(index_stat_option), MYSQL_SYSVAR(index_stat_cache_entries), MYSQL_SYSVAR(index_stat_update_freq), MYSQL_SYSVAR(table_no_logging), === modified file 'sql/ha_ndbcluster.h' --- a/sql/ha_ndbcluster.h 2011-04-29 09:06:21 +0000 +++ b/sql/ha_ndbcluster.h 2011-06-12 16:54:59 +0000 @@ -71,12 +71,6 @@ typedef struct ndb_index_data { const NdbDictionary::Index *unique_index; unsigned char *unique_index_attrid_map; bool null_in_unique_index; - // In this version stats are not shared between threads - NdbIndexStat* index_stat; - uint index_stat_cache_entries; - // Simple counter mechanism to decide when to connect to db - uint index_stat_update_freq; - uint index_stat_query_count; /* In mysqld, keys and rows are stored differently (using KEY_PART_INFO for keys and Field for rows). @@ -168,6 +162,54 @@ struct Ndb_statistics { Uint64 fragment_extent_free_space; }; +struct Ndb_index_stat { + enum { + LT_Undef= 0, + LT_New= 1, /* new entry added by a table handler */ + LT_Update = 2, /* force kernel update from analyze table */ + LT_Read= 3, /* read or reread stats into new query cache */ + LT_Idle= 4, /* stats exist */ + LT_Check= 5, /* check for new stats */ + LT_Delete= 6, /* delete the entry */ + LT_Error= 7, /* error, on hold for a while */ + LT_Count= 8 + }; + NdbIndexStat* is; + int index_id; + int index_version; +#ifndef DBUG_OFF + char id[32]; +#endif + time_t access_time; /* by any table handler */ + time_t load_time; /* when stats were created by kernel */ + time_t read_time; /* when stats were read by us (>= load_time) */ + uint sample_version; /* goes with read_time */ + time_t check_time; /* when checked for updated stats (>= read_time) */ + bool cache_clean; /* old caches have been deleted */ + uint force_update; /* one-time force update from analyze table */ + NdbIndexStat::Error error; + time_t error_time; + int error_count; + struct Ndb_index_stat *share_next; /* per-share list */ + int lt; + int lt_old; /* for info only */ + struct Ndb_index_stat *list_next; + struct Ndb_index_stat *list_prev; + struct st_ndbcluster_share *share; + Ndb_index_stat(); +}; + +struct Ndb_index_stat_list { + const char *name; + int lt; + struct Ndb_index_stat *head; + struct Ndb_index_stat *tail; + uint count; + Ndb_index_stat_list(int the_lt, const char* the_name); +}; + +extern Ndb_index_stat_list ndb_index_stat_list[]; + typedef struct st_ndbcluster_share { NDB_SHARE_STATE state; MEM_ROOT mem_root; @@ -183,6 +225,7 @@ typedef struct st_ndbcluster_share { char *table_name; Ndb::TupleIdRange tuple_id_range; struct Ndb_statistics stat; + struct Ndb_index_stat *index_stat_list; bool util_thread; // if opened by util thread uint32 connect_count; uint32 flags; @@ -382,6 +425,7 @@ class ha_ndbcluster: public handler int optimize(THD* thd, HA_CHECK_OPT* check_opt); int analyze(THD* thd, HA_CHECK_OPT* check_opt); + int analyze_index(THD* thd); int write_row(uchar *buf); int update_row(const uchar *old_data, uchar *new_data); @@ -749,6 +793,21 @@ private: void no_uncommitted_rows_update(int); void no_uncommitted_rows_reset(THD *); + /* Ordered index statistics v4 */ + int ndb_index_stat_get_rir(uint inx, + key_range *min_key, + key_range *max_key, + ha_rows *rows_out); + int ndb_index_stat_set_rpk(uint inx); + int ndb_index_stat_wait(Ndb_index_stat *st, + uint sample_version); + int ndb_index_stat_query(uint inx, + const key_range *min_key, + const key_range *max_key, + NdbIndexStat::Stat& stat); + int ndb_index_stat_analyze(Ndb *ndb, + uint *inx_list, + uint inx_count); NdbTransaction *start_transaction_part_id(uint32 part_id, int &error); inline NdbTransaction *get_transaction_part_id(uint32 part_id, int &error) @@ -896,3 +955,5 @@ static const int ndbcluster_hton_name_le extern int ndbcluster_terminating; extern int ndb_util_thread_running; extern pthread_cond_t COND_ndb_util_ready; +extern int ndb_index_stat_thread_running; +extern pthread_cond_t COND_ndb_index_stat_ready; === modified file 'sql/ha_ndbcluster_binlog.cc' --- a/sql/ha_ndbcluster_binlog.cc 2011-03-22 08:32:32 +0000 +++ b/sql/ha_ndbcluster_binlog.cc 2011-06-12 16:54:59 +0000 @@ -831,6 +831,24 @@ int ndbcluster_binlog_end(THD *thd) pthread_mutex_unlock(&LOCK_ndb_util_thread); } + if (ndb_index_stat_thread_running > 0) + { + /* + Index stats thread blindly imitates util thread. Following actually + fixes some "[Warning] Plugin 'ndbcluster' will be forced to shutdown". + */ + sql_print_information("Stopping Cluster Index Stats thread"); + pthread_mutex_lock(&LOCK_ndb_index_stat_thread); + /* Ensure mutex are not freed if ndb_cluster_end is running at same time */ + ndb_index_stat_thread_running++; + ndbcluster_terminating= 1; + pthread_cond_signal(&COND_ndb_index_stat_thread); + while (ndb_index_stat_thread_running > 1) + pthread_cond_wait(&COND_ndb_index_stat_ready, &LOCK_ndb_index_stat_thread); + ndb_index_stat_thread_running--; + pthread_mutex_unlock(&LOCK_ndb_index_stat_thread); + } + if (ndbcluster_binlog_inited) { ndbcluster_binlog_inited= 0; === modified file 'sql/ha_ndbcluster_binlog.h' --- a/sql/ha_ndbcluster_binlog.h 2011-02-18 13:55:27 +0000 +++ b/sql/ha_ndbcluster_binlog.h 2011-06-12 16:54:59 +0000 @@ -195,6 +195,8 @@ void ndbcluster_global_schema_lock_deini extern unsigned char g_node_id_map[max_ndb_nodes]; extern pthread_mutex_t LOCK_ndb_util_thread; extern pthread_cond_t COND_ndb_util_thread; +extern pthread_mutex_t LOCK_ndb_index_stat_thread; +extern pthread_cond_t COND_ndb_index_stat_thread; extern pthread_mutex_t ndbcluster_mutex; extern HASH ndbcluster_open_tables; --===============1413270436== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/pekka.nousiainen@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: pekka.nousiainen@stripped\ # 1e4b24mixhcfa17g # target_branch: file:///export/space/pekka/ms/ms-wl4124-70/ # testament_sha1: 9e2add11c652356a1ee46b2566f29b91a6fe2cef # timestamp: 2011-06-12 19:55:02 +0300 # source_branch: bzr+ssh://pnousiainen@stripped/bzrroot\ # /server/mysql-5.1-telco-7.0/ # base_revision_id: pekka.nousiainen@stripped\ # zmsj0oqru7pskjmm # # Begin bundle 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 /XK9/56NktIR/8XckU4UJATpBZtA --===============1413270436==--