From: Pekka Nousiainen Date: June 15 2011 10:38am Subject: bzr commit into mysql-5.1-telco-7.0-wl4124-new1 branch (pekka.nousiainen:4399) WL#4124 List-Archive: http://lists.mysql.com/commits/139224 Message-Id: <20110615103803.63B5A5586E@sama.localdomain> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2017570307==" --===============2017570307== 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 4399 Pekka Nousiainen 2011-06-15 wl#4124 b06_handler.diff handler (+ start on modularize), simple MTR fixes added: sql/ha_ndb_index_stat.cc sql/ha_ndb_index_stat.h 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/Makefile.am sql/ha_ndbcluster.cc sql/ha_ndbcluster.h sql/ha_ndbcluster_binlog.cc sql/ha_ndbcluster_binlog.h storage/ndb/CMakeLists.txt === 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-15 10:37:56 +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-15 10:37:56 +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-15 10:37:56 +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-15 10:37:56 +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/Makefile.am' --- a/sql/Makefile.am 2011-04-08 11:06:53 +0000 +++ b/sql/Makefile.am 2011-06-15 10:37:56 +0000 @@ -62,6 +62,7 @@ noinst_HEADERS = item.h item_func.h item ha_ndbcluster_connection.h ha_ndbcluster_connection.h \ ha_ndbcluster_lock_ext.h ha_ndbinfo.h \ ha_ndbcluster_glue.h \ + ha_ndb_index_stat.h \ ha_partition.h rpl_constants.h \ debug_sync.h \ opt_range.h protocol.h rpl_tblmap.h rpl_utility.h \ @@ -136,6 +137,7 @@ libndb_la_SOURCES= ha_ndbcluster.cc \ ha_ndbcluster_binlog.cc \ ha_ndbcluster_connection.cc \ ha_ndbcluster_cond.cc \ + ha_ndb_index_stat.cc \ ha_ndbinfo.cc gen_lex_hash_SOURCES = gen_lex_hash.cc === added file 'sql/ha_ndb_index_stat.cc' --- a/sql/ha_ndb_index_stat.cc 1970-01-01 00:00:00 +0000 +++ b/sql/ha_ndb_index_stat.cc 2011-06-15 10:37:56 +0000 @@ -0,0 +1,1975 @@ +/* + Copyright (c) 2011, Oracle and/or its affiliates. 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ha_ndbcluster_glue.h" + +#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE + +#include "ha_ndbcluster.h" +#include "ha_ndbcluster_binlog.h" +#include "ha_ndb_index_stat.h" +#include +#include + +// Typedefs for long names +typedef NdbDictionary::Table NDBTAB; +typedef NdbDictionary::Index NDBINDEX; + +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[]; + +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)) + { + DBUG_PRINT("index_stat", ("time moved backwards %d seconds", + int(ndb_index_stat_time_now - now))); + now= ndb_index_stat_time_now; + } + + ndb_index_stat_time_now= now; + return now; +} + +bool ndb_index_stat_allow_flag= false; + +bool +ndb_index_stat_allow(int flag= -1) +{ + 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; +} + +/* Options */ + +/* 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*); + +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_opt(char* buf); + uint get(Idx i) const { + assert(i < Imax); + return val[i].val; + } +}; + +Ndb_index_stat_opt::Ndb_index_stat_opt(char* buf) : + option(buf) +{ +#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; + +char ndb_index_stat_option_buf[ndb_index_stat_option_sz]; +Ndb_index_stat_opt ndb_index_stat_opt(ndb_index_stat_option_buf); + +/* Copy option struct to string buffer */ +void +ndb_index_stat_opt2str(const Ndb_index_stat_opt& opt, char* str) +{ + DBUG_ENTER("ndb_index_stat_opt2str"); + + char buf[ndb_index_stat_option_sz]; + char *const end= &buf[sizeof(buf)]; + char* ptr= buf; + *ptr= 0; + + const uint imax= Ndb_index_stat_opt::Imax; + for (uint i= 0; i < imax; i++) + { + 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; + + 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)); + char buf2[ndb_index_stat_option_sz]; + Ndb_index_stat_opt opt(buf2); + 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); + } + + if (is->getNdbError().code == 721) + { + // race between mysqlds, maybe + DBUG_PRINT("index_stat", ("create index stats tables failed: error %d line %d", + is->getNdbError().code, is->getNdbError().line)); + DBUG_RETURN(-1); + } + + sql_print_warning("create index stats tables failed: error %d line %d", + is->getNdbError().code, is->getNdbError().line); + 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); */ + const bool enable_ok_new= ndb_index_stat_get_enable(NULL); + 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 + 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); +} + +#endif === added file 'sql/ha_ndb_index_stat.h' --- a/sql/ha_ndb_index_stat.h 1970-01-01 00:00:00 +0000 +++ b/sql/ha_ndb_index_stat.h 2011-06-15 10:37:56 +0000 @@ -0,0 +1,38 @@ +/* + Copyright (c) 2011, Oracle and/or its affiliates. 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* provides declarations only to index_stat.cc */ + +extern struct st_ndb_status g_ndb_status; + +extern pthread_t ndb_index_stat_thread; +extern pthread_cond_t COND_ndb_index_stat_thread; +extern pthread_mutex_t LOCK_ndb_index_stat_thread; +extern pthread_mutex_t ndb_index_stat_glob_mutex; +extern pthread_mutex_t ndb_index_stat_list_mutex; +extern pthread_mutex_t ndb_index_stat_stat_mutex; +extern pthread_cond_t ndb_index_stat_stat_cond; + +extern bool ndb_index_stat_get_enable(THD *thd); + +extern long g_ndb_status_index_stat_cache_query; +extern long g_ndb_status_index_stat_cache_clean; + +void +compute_index_bounds(NdbIndexScanOperation::IndexBound & bound, + const KEY *key_info, + const key_range *start_key, const key_range *end_key); === 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-15 10:37:56 +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 */ @@ -259,6 +260,15 @@ static MYSQL_THDVAR_UINT( ); /* + Required in index_stat.cc but available only from here + thanks to use of top level anonymous structs. +*/ +bool ndb_index_stat_get_enable(THD *thd) +{ + return THDVAR(thd, index_stat_enable); +} + +/* Default value for max number of transactions createable against NDB from the handler. Should really be 2 but there is a transaction to much allocated when lock table is used, and one extra to used for global schema lock. @@ -404,29 +414,30 @@ pthread_cond_t COND_ndb_util_thread; pthread_cond_t COND_ndb_util_ready; pthread_handler_t ndb_util_thread_func(void *arg); -/* Status variables shown with 'show status like 'Ndb%' */ +// 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); -struct st_ndb_status { - st_ndb_status() { bzero(this, sizeof(struct st_ndb_status)); } - long cluster_node_id; - const char * connected_host; - long connected_port; - long number_of_replicas; - long number_of_data_nodes; - long number_of_ready_data_nodes; - long connect_count; - long execute_count; - long scan_count; - long pruned_scan_count; - long transaction_no_hint_count[MAX_NDB_NODES]; - long transaction_hint_count[MAX_NDB_NODES]; - long long api_client_stats[Ndb::NumClientStatistics]; -}; +extern void ndb_index_stat_free(NDB_SHARE *share); +extern void ndb_index_stat_end(); + +/* Status variables shown with 'show status like 'Ndb%' */ -static struct st_ndb_status g_ndb_status; +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; +long g_ndb_status_index_stat_cache_query = 0; +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 +615,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 +1140,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 +2180,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 +2191,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 +2262,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) { @@ -3479,7 +3480,7 @@ count_key_columns(const KEY *key_info, c } /* Helper method to compute NDB index bounds. Note: does not set range_no. */ -static void +void compute_index_bounds(NdbIndexScanOperation::IndexBound & bound, const KEY *key_info, const key_range *start_key, const key_range *end_key) @@ -3530,6 +3531,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 +6074,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 +9872,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 +10920,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 +11024,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 +11089,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 +11117,7 @@ static int ndbcluster_end(handlerton *ht } my_hash_free(&ndbcluster_open_tables); + ndb_index_stat_end(); ndbcluster_disconnect(); // cleanup ndb interface @@ -11020,6 +11128,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 +11240,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 +11271,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) + 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; + + do { - 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 (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); + 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 +12217,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); @@ -15353,6 +15453,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} }; @@ -15431,6 +15532,31 @@ static MYSQL_SYSVAR_UINT( 0 /* block */ ); +/* should be in index_stat.h */ + +extern int +ndb_index_stat_option_check(MYSQL_THD, + struct st_mysql_sys_var *var, + void *save, + struct st_mysql_value *value); +extern void +ndb_index_stat_option_update(MYSQL_THD, + struct st_mysql_sys_var *var, + void *var_ptr, + const void *save); + +extern char ndb_index_stat_option_buf[]; + +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_option_buf +); + ulong opt_ndb_report_thresh_binlog_epoch_slip; static MYSQL_SYSVAR_ULONG( @@ -15624,6 +15750,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-15 10:37:56 +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). @@ -183,6 +177,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; @@ -366,6 +361,23 @@ class Thd_ndb bool recycle_ndb(THD* thd); }; +struct st_ndb_status { + st_ndb_status() { bzero(this, sizeof(struct st_ndb_status)); } + long cluster_node_id; + const char * connected_host; + long connected_port; + long number_of_replicas; + long number_of_data_nodes; + long number_of_ready_data_nodes; + long connect_count; + long execute_count; + long scan_count; + long pruned_scan_count; + long transaction_no_hint_count[MAX_NDB_NODES]; + long transaction_hint_count[MAX_NDB_NODES]; + long long api_client_stats[Ndb::NumClientStatistics]; +}; + int ndbcluster_commit(handlerton *hton, THD *thd, bool all); class ha_ndbcluster: public handler { @@ -382,6 +394,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 +762,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 +924,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-15 10:37:56 +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-15 10:37:56 +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; === modified file 'storage/ndb/CMakeLists.txt' --- a/storage/ndb/CMakeLists.txt 2011-05-19 09:05:45 +0000 +++ b/storage/ndb/CMakeLists.txt 2011-06-15 10:37:56 +0000 @@ -146,6 +146,7 @@ SET(NDBCLUSTER_SOURCES ../../sql/ha_ndbcluster_cond.cc ../../sql/ha_ndbcluster_connection.cc ../../sql/ha_ndbcluster_binlog.cc + ../../sql/ha_ndb_index_stat.cc ../../sql/ha_ndbinfo.cc) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/storage/ndb/include) --===============2017570307== 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\ # c4qjavyvanmbqn0q # target_branch: file:///export/space/pekka/ms/ms-wl4124-70/ # testament_sha1: 2fb3432a7f4695fe9c3289f0d37b5b7776847ba8 # timestamp: 2011-06-15 13:38:03 +0300 # base_revision_id: pekka.nousiainen@stripped\ # c21k0bfszweck57p # # Begin bundle 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 --===============2017570307==--