diff options
Diffstat (limited to 'storage/ndb/test/ndbapi/testIndexStat.cpp')
-rw-r--r-- | storage/ndb/test/ndbapi/testIndexStat.cpp | 1406 |
1 files changed, 1406 insertions, 0 deletions
diff --git a/storage/ndb/test/ndbapi/testIndexStat.cpp b/storage/ndb/test/ndbapi/testIndexStat.cpp new file mode 100644 index 00000000000..18524d0ac10 --- /dev/null +++ b/storage/ndb/test/ndbapi/testIndexStat.cpp @@ -0,0 +1,1406 @@ +/* Copyright (C) 2005 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <ndb_global.h> +#include <ndb_opts.h> +#include <NdbApi.hpp> +#include <NdbIndexStat.hpp> +#include <NdbTest.hpp> +#include <my_sys.h> +#include <ndb_version.h> +#include <math.h> + +/* + * Sample results: + * + * 0. err pct: count: 1000 min: -99.99 max: 99.92 avg: 6.88 stddev: 27.61 + * + * 0. baseline with same options as handler + */ + +#undef min +#undef max +#define min(a, b) ((a) <= (b) ? (a) : (b)) +#define max(a, b) ((a) >= (b) ? (a) : (b)) + +inline NdbOut& +NdbOut::operator<<(double x) +{ + char buf[100]; + sprintf(buf, "%.2f", x); + *this << buf; + return *this; +} + +struct Opts { + int loglevel; + uint seed; + uint loop; + uint rows; + uint ops; + uint nullkeys; + uint dupkeys; + uint scanpct; + uint eqscans; + uint dupscans; + my_bool keeptable; + my_bool loaddata; + my_bool nochecks; + my_bool abort; + // internal + uint tryhard; + Opts() : + loglevel(0), + seed(-1), + loop(1), + rows(100000), + ops(1000), + nullkeys(10), + dupkeys(1000), + scanpct(5), + eqscans(50), + dupscans(10), + keeptable(false), + loaddata(true), + nochecks(false), + abort(false), + // internal + tryhard(20) + {} +}; + +static Opts g_opts; +const char* g_progname = "testIndexStat"; +static uint g_loop = 0; + +static const char* g_tabname = "ts0"; +static const char* g_indname = "ts0x1"; +static const char g_numattrs = 3; +static const uint g_charlen = 10; +static const char* g_csname = "latin1_swedish_ci"; +static CHARSET_INFO* g_cs; + +// value and bound ranges +static uint g_val_b_max = 10; +static uint g_bnd_b_max = 20; +static const char* g_val_c_char = "bcd"; +static const char* g_bnd_c_char = "abcde"; +static uint g_val_d_max = 100; +static uint g_bnd_d_max = 200; + +static Ndb_cluster_connection* g_ncc = 0; +static Ndb* g_ndb = 0; +static NdbDictionary::Dictionary* g_dic = 0; +static const NdbDictionary::Table* g_tab = 0; +static const NdbDictionary::Index* g_ind = 0; + +static NdbIndexStat* g_stat = 0; + +static NdbTransaction* g_con = 0; +static NdbOperation* g_op = 0; +static NdbScanOperation* g_scan_op = 0; +static NdbIndexScanOperation* g_rangescan_op = 0; + +static uint +urandom() +{ + uint r = (uint)random(); + return r; +} + +static uint +urandom(uint m) +{ + if (m == 0) + return 0; + uint r = urandom(); + r = r % m; + return r; +} + +static int& g_loglevel = g_opts.loglevel; // default log level + +#define chkdb(x) \ + do { if (likely(x)) break; ndbout << "line " << __LINE__ << " FAIL " << #x << endl; errdb(); if (g_opts.abort) abort(); return -1; } while (0) + +#define chkrc(x) \ + do { if (likely(x)) break; ndbout << "line " << __LINE__ << " FAIL " << #x << endl; if (g_opts.abort) abort(); return -1; } while (0) + +#define reqrc(x) \ + do { if (likely(x)) break; ndbout << "line " << __LINE__ << " ASSERT " << #x << endl; abort(); } while (0) + +#define llx(n, x) \ + do { if (likely(g_loglevel < n)) break; ndbout << x << endl; } while (0) + +#define ll0(x) llx(0, x) +#define ll1(x) llx(1, x) +#define ll2(x) llx(2, x) +#define ll3(x) llx(3, x) + +static void +errdb() +{ + uint any = 0; + // g_ncc return no error... + if (g_ndb != 0) { + const NdbError& e = g_ndb->getNdbError(); + if (e.code != 0) + ll0(++any << " ndb: error " << e); + } + if (g_dic != 0) { + const NdbError& e = g_dic->getNdbError(); + if (e.code != 0) + ll0(++any << " dic: error " << e); + } + if (g_con != 0) { + const NdbError& e = g_con->getNdbError(); + if (e.code != 0) + ll0(++any << " con: error " << e); + } + if (g_op != 0) { + const NdbError& e = g_op->getNdbError(); + if (e.code != 0) + ll0(++any << " op: error " << e); + } + if (g_scan_op != 0) { + const NdbError& e = g_scan_op->getNdbError(); + if (e.code != 0) + ll0(++any << " scan_op: error " << e); + } + if (g_rangescan_op != 0) { + const NdbError& e = g_rangescan_op->getNdbError(); + if (e.code != 0) + ll0(++any << " rangescan_op: error " << e); + } + if (g_stat != 0) { + const NdbError& e = g_stat->getNdbError(); + if (e.code != 0) + ll0(++any << " stat: error " << e); + } + if (! any) + ll0("unknown db error"); +} + +// create table ts0 ( +// a int unsigned, b smallint not null, c varchar(10), d int unsigned, +// primary key using hash (a), index (b, c, d) ) + +static int +createtable() +{ + NdbDictionary::Table tab(g_tabname); + tab.setLogging(false); + { + NdbDictionary::Column col("a"); + col.setType(NdbDictionary::Column::Unsigned); + col.setPrimaryKey(true); + tab.addColumn(col); + } + { + NdbDictionary::Column col("b"); + col.setType(NdbDictionary::Column::Smallint); + col.setNullable(false); + tab.addColumn(col); + } + { + NdbDictionary::Column col("c"); + col.setType(NdbDictionary::Column::Varchar); + col.setLength(g_charlen); + col.setCharset(g_cs); + col.setNullable(true); + tab.addColumn(col); + } + { + NdbDictionary::Column col("d"); + col.setType(NdbDictionary::Column::Unsigned); + col.setNullable(true); + tab.addColumn(col); + } + NdbDictionary::Index ind(g_indname); + ind.setTable(g_tabname); + ind.setType(NdbDictionary::Index::OrderedIndex); + ind.setLogging(false); + ind.addColumnName("b"); + ind.addColumnName("c"); + ind.addColumnName("d"); + g_dic = g_ndb->getDictionary(); + if (! g_opts.keeptable) { + if (g_dic->getTable(g_tabname) != 0) + chkdb(g_dic->dropTable(g_tabname) == 0); + chkdb(g_dic->createTable(tab) == 0); + chkdb(g_dic->createIndex(ind) == 0); + } else { + if (g_dic->getTable(g_tabname) == 0) { + chkdb(g_dic->createTable(tab) == 0); + chkdb(g_dic->createIndex(ind) == 0); + } else + g_opts.loaddata = false; + } + chkdb((g_tab = g_dic->getTable(g_tabname)) != 0); + chkdb((g_ind = g_dic->getIndex(g_indname, g_tabname)) != 0); + g_dic = 0; + return 0; +} + +static int +droptable() +{ + g_dic = g_ndb->getDictionary(); + if (! g_opts.keeptable) + chkdb(g_dic->dropTable(g_tabname) == 0); + g_dic = 0; + return 0; +} + +struct Val { + Int16 b; + bool c_null; + uchar c[1 + g_charlen]; + bool d_null; + Uint32 d; + // partial values for use in Bnd + uint numattrs; + void make(uint n = g_numattrs, bool is_val = true); + int cmp(const Val& val, uint n = g_numattrs) const; +}; + +static NdbOut& +operator<<(NdbOut& out, const Val& val) +{ + out << "["; + if (val.numattrs >= 1) { + out << val.b; + } + if (val.numattrs >= 2) { + out << " "; + if (val.c_null) + out << "NULL"; + else { + char buf[1 + g_charlen]; + sprintf(buf, "%.*s", val.c[0], &val.c[1]); + out << "'" << buf << "'"; + } + } + if (val.numattrs >= 3) { + out << " "; + if (val.d_null) + out <<" NULL"; + else + out << val.d; + } + out << "]"; + return out; +} + +void +Val::make(uint n, bool is_val) +{ + if (n >= 1) { + uint b_max = is_val ? g_val_b_max : g_bnd_b_max; + b = (int)urandom(2 * b_max) - (int)b_max; + } + if (n >= 2) { + if (urandom(100) < g_opts.nullkeys) + c_null = 1; + else { + const char* c_char = is_val ? g_val_c_char : g_bnd_c_char; + // prefer shorter + uint len = urandom(urandom(g_charlen + 2)); + c[0] = len; + uint j; + for (j = 0; j < len; j++) { + uint k = urandom(strlen(c_char)); + c[1 + j] = c_char[k]; + } + c_null = 0; + } + } + if (n >= 3) { + if (urandom(100) < g_opts.nullkeys) + d_null = 1; + else { + uint d_max = is_val ? g_val_d_max : g_bnd_d_max; + d = urandom(d_max); + d_null = 0; + } + } + numattrs = n; +} + +int +Val::cmp(const Val& val, uint n) const +{ + int k = 0; + if (k == 0 && n >= 1) { + if (b < val.b) + k = -1; + else if (b > val.b) + k = +1; + } + if (k == 0 && n >= 2) { + if (! c_null && ! val.c_null) { + const uchar* s1 = &c[1]; + const uchar* s2 = &val.c[1]; + const uint l1 = (uint)c[0]; + const uint l2 = (uint)val.c[0]; + assert(l1 <= g_charlen && l2 <= g_charlen); + k = g_cs->coll->strnncollsp(g_cs, s1, l1, s2, l2, 0); + } else if (! c_null) { + k = +1; + } else if (! val.c_null) { + k = -1; + } + } + if (k == 0 && n >= 3) { + if (! d_null && ! val.d_null) { + if (d < val.d) + k = -1; + else if (d > val.d) + k = +1; + } else if (! d_null) { + k = +1; + } else if (! val.d_null) { + k = -1; + } + } + return k; +} + +struct Key { + Val val; + union { + bool flag; + uint count; + uint rpk; + }; +}; + +static NdbOut& +operator<<(NdbOut& out, const Key& key) +{ + out << key.val << " info:" << key.count; + return out; +} + +static Key* g_keys = 0; +static Key* g_sortkeys = 0; +static uint g_sortcount = 0; +static Key* g_minkey = 0; +static Key* g_maxkey = 0; + +static void +freekeys() +{ + if (g_keys != 0) + my_free((char*)g_keys, MYF(0)); + if (g_sortkeys != 0) + my_free((char*)g_sortkeys, MYF(0)); + g_keys = 0; + g_sortkeys = 0; +} + +static int +allockeys() +{ + freekeys(); + size_t sz = sizeof(Key) * g_opts.rows; + g_keys = (Key*)my_malloc(sz, MYF(0)); + g_sortkeys = (Key*)my_malloc(sz, MYF(0)); + chkrc(g_keys != 0 && g_sortkeys != 0); + memset(g_keys, 0x1f, sz); + memset(g_sortkeys, 0x1f, sz); + return 0; +} + +static void +makekeys() +{ + uint i; + for (i = 0; i < g_opts.rows; i++) { + Key& key = g_keys[i]; + key.val.make(); + key.flag = false; // mark for dup generation done + } + for (i = 0; i < g_opts.rows; i++) { + Key& key = g_keys[i]; + if (key.flag) + continue; + key.flag = true; + uint fudge = 9; + uint n = (urandom(fudge * (g_opts.dupkeys - 100)) + 99) / 100; + uint k; + for (k = 1; k < n; k++) { + uint j = urandom(g_opts.rows); + do { + Key& dst = g_keys[j]; + if (! dst.flag) { + dst.val = key.val; + dst.flag = true; + break; + } + } while (urandom(g_opts.tryhard) != 0); + } + } +} + +static int +insertdata() +{ + const uint batch = 512; + chkdb((g_con = g_ndb->startTransaction()) != 0); + uint i = 0; + while (i < g_opts.rows) { + chkdb((g_op = g_con->getNdbOperation(g_tab)) != 0); + chkdb(g_op->insertTuple() == 0); + Uint32 a = i; + const Val& val = g_keys[i].val; + const char* a_addr = (const char*)&a; + const char* b_addr = (const char*)&val.b; + const char* c_addr = ! val.c_null ? (const char*)val.c : 0; + const char* d_addr = ! val.d_null ? (const char*)&val.d : 0; + Uint32 no = 0; + chkdb(g_op->equal(no++, a_addr) == 0); + chkdb(g_op->setValue(no++, b_addr) == 0); + chkdb(g_op->setValue(no++, c_addr) == 0); + chkdb(g_op->setValue(no++, d_addr) == 0); + if (i++ % batch == 0) { + chkdb(g_con->execute(NdbTransaction::Commit) == 0); + g_ndb->closeTransaction(g_con); + g_con = 0; + g_op = 0; + chkdb((g_con = g_ndb->startTransaction()) != 0); + } + } + chkdb(g_con->execute(NdbTransaction::Commit) == 0); + g_ndb->closeTransaction(g_con); + g_con = 0; + g_op = 0; + ll0(g_tabname << ": inserted " << g_opts.rows << " rows"); + return 0; +} + +static int +countrows() +{ + Uint64 rows = 0; + Uint64 r; + char* r_addr = (char*)&r; + chkdb((g_con = g_ndb->startTransaction()) != 0); + chkdb((g_scan_op = g_con->getNdbScanOperation(g_tab)) != 0); + chkdb(g_scan_op->readTuples() == 0); + chkdb(g_scan_op->interpret_exit_last_row() == 0); + chkdb(g_scan_op->getValue(NdbDictionary::Column::ROW_COUNT, r_addr) != 0); + chkdb(g_con->execute(NdbTransaction::NoCommit) == 0); + while (1) { + int ret; + r = ~(Uint64)0; + chkdb((ret = g_scan_op->nextResult()) == 0 || ret == 1); + if (ret == 1) + break; + rows += r; + } + g_ndb->closeTransaction(g_con); + g_con = 0; + g_scan_op = 0; + g_opts.rows = rows; + return 0; +} + +static int +scandata() +{ + chkdb((g_con = g_ndb->startTransaction()) != 0); + chkdb((g_scan_op = g_con->getNdbScanOperation(g_tab)) != 0); + chkdb(g_scan_op->readTuples() == 0); + Uint32 a; + Val val; + char* a_addr = (char*)&a; + char* b_addr = (char*)&val.b; + char* c_addr = (char*)val.c; + char* d_addr = (char*)&val.d; + Uint32 no = 0; + NdbRecAttr* b_ra; + NdbRecAttr* c_ra; + NdbRecAttr* d_ra; + chkdb(g_scan_op->getValue(no++, a_addr) != 0); + chkdb((b_ra = g_scan_op->getValue(no++, b_addr)) != 0); + chkdb((c_ra = g_scan_op->getValue(no++, c_addr)) != 0); + chkdb((d_ra = g_scan_op->getValue(no++, d_addr)) != 0); + chkdb(g_con->execute(NdbTransaction::NoCommit) == 0); + uint count = 0; + uint i; + for (i = 0; i < g_opts.rows; i++) + g_keys[i].count = 0; + while (1) { + int ret; + a = ~(Uint32)0; + chkdb((ret = g_scan_op->nextResult()) == 0 || ret == 1); + if (ret == 1) + break; + assert(b_ra->isNULL() == 0 && c_ra->isNULL() != -1 && d_ra->isNULL() != -1); + val.c_null = c_ra->isNULL(); + val.d_null = d_ra->isNULL(); + i = (uint)a; + chkrc(i < g_opts.rows); + Key& key = g_keys[i]; + if (g_opts.loaddata) + chkrc(key.val.cmp(val) == 0); + else + key.val = val; + key.count++; + count++; + } + g_ndb->closeTransaction(g_con); + g_con = 0; + g_scan_op = 0; + for (i = 0; i < g_opts.rows; i++) + chkrc(g_keys[i].count == 1); + assert(count == g_opts.rows); + int level = g_opts.loaddata ? 1 : 0; + llx(level, g_tabname << ": scanned " << g_opts.rows << " rows"); + return 0; +} + +static int +loaddata() +{ + if (g_opts.loaddata) { + chkrc(allockeys() == 0); + makekeys(); + chkrc(insertdata() == 0); + } else { + chkrc(countrows() == 0); + chkrc(g_opts.rows != 0); + ll0(g_tabname << ": using old table of " << g_opts.rows << " rows"); + chkrc(allockeys() == 0); + } + chkrc(scandata() == 0); + uint i; + for (i = 0; i < g_opts.rows; i++) + ll3(i << ": " << g_keys[i]); + return 0; +} + +// true = match, index = match or next higher +static bool +sortval(const Val& val, int& index) +{ + if (unlikely(g_sortcount == 0)) { + index = 0; + return false; + } + int lo = -1; + int hi = (int)g_sortcount; + int ret; + int j; + do { + j = (hi + lo) / 2; + ret = val.cmp(g_sortkeys[j].val); + if (ret < 0) + hi = j; + else if (ret > 0) + lo = j; + else + break; + } while (hi - lo > 1); + if (ret == 0) { + index = j; + return true; + } + index = hi; + return false; +} + +static void +sortkeys() +{ + // insert sort with binary search + g_sortcount = 0; + uint i; + for (i = 0; i < g_opts.rows; i++) { + const Val& val = g_keys[i].val; + int index; + bool match = sortval(val, index); + Key& dst = g_sortkeys[index]; + if (match) { + dst.rpk++; + } else { + uint bytes = ((int)g_sortcount - index) * sizeof(Key); + memmove(&dst + 1, &dst, bytes); + dst.val = val; + dst.rpk = 1; + g_sortcount++; + } + } + g_minkey = &g_sortkeys[0]; + g_maxkey = &g_sortkeys[g_sortcount - 1]; + ll1("counted " << g_sortcount << " distinct keys"); +} + +struct Bnd { + Val val; + /* + * A bound is a partial key value (0 to g_numattrs attributes). + * It is not equal to any key value. Instead, it has a "side". + * + * side = 0 if the bound is empty + * side = -1 if the bound is "just before" its value + * side = +1 if the bound is "just after" its value + * + * This is another way of looking at strictness of non-empty + * start and end keys in a range. + * + * start key is strict if side = +1 + * end key is strict if side = -1 + * + * NDB API specifies strictness in the bound type of the last + * index attribute which is part of the start/end key. + * + * LE (0) - strict: n - side: -1 + * LT (1) - strict: y - side: +1 + * GE (2) - strict: n - side: +1 + * GT (3) - strict: y - side: -1 + * + * A non-empty bound divides keys into 2 disjoint subsets: + * keys before (cmp() == -1) and keys after (cmp() == +1). + */ + int side; + Bnd& make(uint minattrs); + Bnd& make(uint minattrs, const Val& theval); + int cmp(const Val& val) const; + int type(uint lohi, uint colno) const; // for setBound +}; + +static NdbOut& +operator<<(NdbOut& out, const Bnd& bnd) +{ + out << bnd.val; + out << " side: " << bnd.side; + return out; +} + +Bnd& +Bnd::make(uint minattrs) +{ + uint numattrs = minattrs + urandom(g_numattrs - minattrs); + val.make(numattrs, false); + side = val.numattrs == 0 ? 0 : urandom(2) == 0 ? -1 : +1; + return *this; +} + +Bnd& +Bnd::make(uint minattrs, const Val& theval) +{ + uint numattrs = minattrs + urandom(g_numattrs - minattrs); + val = theval; + val.numattrs = numattrs; + side = val.numattrs == 0 ? 0 : urandom(2) == 0 ? -1 : +1; + return *this; +} + +int +Bnd::cmp(const Val& theval) const +{ + int place; // debug + int ret; + do { + assert(theval.numattrs == g_numattrs); + int k = theval.cmp(val, val.numattrs); + if (k != 0) { + place = 1; + ret = k; + break; + } + if (side != 0) { + place = 2; + ret = -side; + break; + } + place = 3; + ret = 0; + assert(val.numattrs == 0); + } while (0); + ll3("cmp: val: " << theval << " bnd: " << *this << + " return: " << ret << " at " << place); + return ret; +} + +int +Bnd::type(uint lohi, uint colno) const +{ + int t; + assert(lohi <= 1 && colno < val.numattrs && (side == -1 || side == +1)); + if (lohi == 0) { + if (colno + 1 < val.numattrs) + t = 0; // LE + else if (side == -1) + t = 0; // LE + else + t = 1; // LT + } else { + if (colno + 1 < val.numattrs) + t = 2; // GE + else if (side == +1) + t = 2; // GE + else + t = 3; // GT + } + return t; +} + +struct Range { + Bnd bnd[2]; + uint minattrs() const; + uint maxattrs() const; + int cmp(const Val& val) const; // -1,0,+1 = key is before,in,after range + uint rowcount() const; + bool iseq() const; + // stats + bool flag; + uint statrows; + uint scanrows; + double errpct; +}; + +static NdbOut& +operator<<(NdbOut& out, const Range& range) +{ + out << "bnd0: " << range.bnd[0] << " bnd1: " << range.bnd[1]; + return out; +} + +uint +Range::minattrs() const +{ + return min(bnd[0].val.numattrs, bnd[1].val.numattrs); +} + +uint +Range::maxattrs() const +{ + return max(bnd[0].val.numattrs, bnd[1].val.numattrs); +} + +int +Range::cmp(const Val& theval) const +{ + int place; // debug + int ret; + do { + int k; + k = bnd[0].cmp(theval); + if (k < 0) { + place = 1; + ret = -1; + break; + } + k = bnd[1].cmp(theval); + if (k > 0) { + place = 2; + ret = +1; + break; + } + place = 3; + ret = 0; + } while (0); + ll3("cmp: val: " << theval << " range: " << *this << + " return: " << ret << " at " << place); + return ret; +} + +uint +Range::rowcount() const +{ + ll2("rowcount: " << *this); + int i; + // binary search for first and last in range + int lim[2]; + for (i = 0; i <= 1; i++) { + ll3("search i=" << i); + int lo = -1; + int hi = (int)g_sortcount; + int ret; + int j; + do { + j = (hi + lo) / 2; + ret = cmp(g_sortkeys[j].val); + if (i == 0) { + if (ret < 0) + lo = j; + else + hi = j; + } else { + if (ret > 0) + hi = j; + else + lo = j; + } + } while (hi - lo > 1); + if (ret == 0) + lim[i] = j; + else if (i == 0) + lim[i] = hi; + else + lim[i] = lo; + } + // the range + const int lo = max(lim[0], 0); + const int hi = min(lim[1], (int)g_sortcount - 1); + if (! g_opts.nochecks) { + int curr = -1; + for (i = 0; i < (int)g_sortcount; i++) { + int k = cmp(g_sortkeys[i].val); + if (k < 0) + assert(i < lo); + else if (k == 0) + assert(lo <= i && i <= hi); + else + assert(i > hi); + assert(curr <= k); + if (curr < k) + curr = k; + } + } + // sum them up + uint count = 0; + for (i = lo; i <= hi; i++) + count += g_sortkeys[i].count; + ll2("count: " << count << " index lim: " << lim[0] << " " << lim[1]); + return count; +} + +bool +Range::iseq() const +{ + return + minattrs() == maxattrs() && + bnd[0].val.cmp(bnd[1].val, minattrs()) == 0 && + bnd[0].side < bnd[1].side; +} + +static Range* g_ranges = 0; + +static void +freeranges() +{ + if (g_ranges != 0) + my_free((char*)g_ranges, MYF(0)); + g_ranges = 0; +} + +static int +allocranges() +{ + freeranges(); + size_t sz = sizeof(Range) * g_opts.ops; + g_ranges = (Range*)my_malloc(sz, MYF(0)); + chkrc(g_ranges != 0); + memset(g_ranges, 0x1f, sz); + return 0; +} + +static void +makeranges() +{ + uint i; + for (i = 0; i < g_opts.ops; i++) { + Range& range = g_ranges[i]; + range.flag = false; // mark for dup generation done + bool fulleq = (urandom(100) < g_opts.eqscans); + bool eq = fulleq || (urandom(100) < g_opts.eqscans); + bool matcheq = eq && (urandom(10) != 0); + if (! eq) { + // random but prefer non-empty and no more than scanpct + do { + range.bnd[0].make(0); + range.bnd[1].make(0); + uint count = range.rowcount(); + if (count != 0 && 100 * count <= g_opts.scanpct * g_opts.rows) + break; + } while (urandom(g_opts.tryhard) != 0); + } else { + uint minattrs = fulleq ? g_numattrs : 1; + if (! matcheq) { + range.bnd[0].make(minattrs); + } else { + uint m = urandom(g_sortcount); + const Val& val = g_sortkeys[m].val; + range.bnd[0].make(minattrs, val); + } + range.bnd[1] = range.bnd[0]; + range.bnd[0].side = -1; + range.bnd[1].side = +1; + // fix types + range.bnd[0]; + range.bnd[1]; + assert(range.iseq()); + } + } + for (i = 0; i < g_opts.ops; i++) { + Range& range = g_ranges[i]; + if (range.flag) + continue; + range.flag = true; + if (urandom(100) < g_opts.dupscans) { + uint j = urandom(g_opts.ops); + do { + Range& dst = g_ranges[j]; + if (! dst.flag) { + dst.bnd[0] = range.bnd[0]; + dst.bnd[1] = range.bnd[1]; + dst.flag = true; + break; + } + } while (urandom(g_opts.tryhard) != 0); + } + } +} + +static int +setbounds(const Range& range) +{ + // currently must do each attr in order + ll2("setbounds: " << range); + uint i; + const Bnd (&bnd)[2] = range.bnd; + for (i = 0; i < g_numattrs; i++) { + const Uint32 no = i; // index attribute number + uint j; + int type[2] = { -1, -1 }; + for (j = 0; j <= 1; j++) { + if (no < bnd[j].val.numattrs) + type[j] = bnd[j].type(j, no); + } + for (j = 0; j <= 1; j++) { + int t = type[j]; + if (t == -1) + continue; + if (no + 1 < bnd[j].val.numattrs) + t &= ~(uint)1; // strict bit is set on last bound only + const Val& val = bnd[j].val; + const void* addr = 0; + if (no == 0) + addr = (const void*)&val.b; + else if (no == 1) + addr = ! val.c_null ? (const void*)val.c : 0; + else if (no == 2) + addr = ! val.d_null ? (const void*)&val.d : 0; + else + assert(false); + ll2("setBound attr:" << no << " type:" << t << " val: " << val); + chkdb(g_rangescan_op->setBound(no, t, addr) == 0); + } + } + return 0; +} + +static int +allocstat() +{ + g_stat = new NdbIndexStat(g_ind); + chkdb(g_stat->alloc_cache(32) == 0); + return 0; +} + +static int +runstat(Range& range, int flags) +{ + ll2("runstat: " << range << " flags=" << flags); + chkdb((g_con = g_ndb->startTransaction()) != 0); + chkdb((g_rangescan_op = g_con->getNdbIndexScanOperation(g_ind, g_tab)) != 0); + chkdb(g_rangescan_op->readTuples(NdbOperation::LM_CommittedRead) == 0); + chkrc(setbounds(range) == 0); + Uint64 count = ~(Uint64)0; + chkdb(g_stat->records_in_range(g_ind, g_rangescan_op, g_opts.rows, &count, flags) == 0); + g_ndb->closeTransaction(g_con); + g_con = 0; + g_rangescan_op = 0; + range.statrows = (uint)count; + chkrc((Uint64)range.statrows == count); + ll2("stat: " << range.statrows); + return 0; +} + +static int +runscan(Range& range) +{ + ll2("runscan: " << range); + chkdb((g_con = g_ndb->startTransaction()) != 0); + chkdb((g_rangescan_op = g_con->getNdbIndexScanOperation(g_ind, g_tab)) != 0); + chkdb(g_rangescan_op->readTuples() == 0); + chkrc(setbounds(range) == 0); + Uint32 a; + char* a_addr = (char*)&a; + Uint32 no = 0; + chkdb(g_rangescan_op->getValue(no++, a_addr) != 0); + chkdb(g_con->execute(NdbTransaction::NoCommit) == 0); + uint count = 0; + uint i; + for (i = 0; i < g_opts.rows; i++) + g_keys[i].count = 0; + while (1) { + int ret; + a = ~(Uint32)0; + chkdb((ret = g_rangescan_op->nextResult()) == 0 || ret == 1); + if (ret == 1) + break; + i = (uint)a; + chkrc(i < g_opts.rows); + Key& key = g_keys[i]; + ll3("scan: " << key); + int k = range.cmp(key.val); + chkrc(k == 0); + chkrc(key.count == 0); + key.count++; + count++; + } + g_ndb->closeTransaction(g_con); + g_con = 0; + g_rangescan_op = 0; + if (! g_opts.nochecks) { + for (i = 0; i < g_opts.rows; i++) { + const Key& key = g_keys[i]; + int k = range.cmp(key.val); + assert((k != 0 && key.count == 0) || (k == 0 && key.count == 1)); + } + assert(range.rowcount() == count); + } + range.scanrows = count; + ll2("scan: " << range.scanrows); + return 0; +} + +static int +runscans() +{ + uint i; + for (i = 0; i < g_opts.ops; i++) { + Range& range = g_ranges[i]; + ll1("range " << i << ": " << range); + // simulate old handler code + int flags = 0; + if (i < 32 || i % 20 == 0) + flags |= NdbIndexStat::RR_UseDb; + chkrc(runstat(range, flags) == 0); + chkrc(runscan(range) == 0); + // if stat is 0 then it is exact scan count + chkrc(range.statrows != 0 || range.scanrows == 0); + // measure error as fraction of total rows + double x = (double)range.statrows; + double y = (double)range.scanrows; + double z = (double)g_opts.rows; + double err = (x - y) / z; + // report in pct + range.errpct = 100.0 * err; + ll1("range " << i << ":" << + " stat: " << range.statrows << " scan: " << range.scanrows << + " errpct: " << range.errpct); + } + return 0; +} + +struct Stat { + const char* name; + uint count; + double sum; + double minval; + double maxval; + double avg; + double varsum; + double var; + double stddev; + void init(); + void add(const Stat& stat); +}; + +void +Stat::init() +{ + name = "stat"; + count = 0; + sum = minval = maxval = avg = varsum = var = stddev = 0.0; +} + +void +Stat::add(const Stat& stat) +{ + if (count == 0) { + *this = stat; + return; + } + Stat tmp = *this; + tmp.count = count + stat.count; + tmp.sum = sum + stat.sum; + tmp.minval = minval <= stat.minval ? minval : stat.minval; + tmp.maxval = maxval >= stat.maxval ? maxval : stat.maxval; + tmp.avg = tmp.sum / double(tmp.count); + tmp.varsum = varsum + stat.varsum; + tmp.var = tmp.varsum / double(tmp.count); + tmp.stddev = sqrt(tmp.var); + *this = tmp; +} + +static NdbOut& +operator<<(NdbOut& out, const Stat& stat) +{ + out << stat.name << ": " << "count: " << stat.count + << " min: " << stat.minval << " max: " << stat.maxval + << " avg: " << stat.avg << " stddev: " << stat.stddev; + return out; +} + +template <class T, class V> +static void +computestat(Stat& stat) +{ + stat.init(); + stat.name = V::name(); + const T* array = V::array(); + stat.count = V::count(); + assert(stat.count != 0); + uint i; + for (i = 0; i < stat.count; i++) { + const T& item = array[i]; + double data = V::data(item); + stat.sum += data; + if (i == 0) + stat.minval = stat.maxval = data; + else { + if (stat.minval > data) + stat.minval = data; + if (stat.maxval < data) + stat.maxval = data; + } + } + stat.avg = stat.sum / double(stat.count); + stat.varsum = 0.0; + for (i = 0; i < stat.count; i++) { + const T& item = array[i]; + double data = V::data(item); + double x = data - stat.avg; + stat.varsum += x * x; + } + stat.var = stat.varsum / double(stat.count); + stat.stddev = sqrt(stat.var); +} + +struct V_rpk { + static const char* name() { return "rec per key"; } + static const Key* array() { return g_sortkeys; } + static uint count() { return g_sortcount; } + static double data(const Key& key) { return (double)key.rpk; } +}; + +struct V_rir { + static const char* name() { return "rir err pct"; } + static const Range* array() { return g_ranges; } + static uint count() { return g_opts.ops; } + static double data(const Range& range) { return (double)range.errpct; } +}; + +template static void computestat<Key, V_rpk>(Stat& stat); +template static void computestat<Range, V_rir>(Stat& stat); + +static Stat g_stat_rpk; // summaries over loops +static Stat g_stat_rir; + +static void +loopstats() +{ + Stat stat_rpk; // records per key + Stat stat_rir; // record in range + if (g_loop == 0) { + g_stat_rpk.init(); + g_stat_rir.init(); + } + computestat<Key, V_rpk>(stat_rpk); + computestat<Range, V_rir>(stat_rir); + if (g_opts.loop != 1) { + ll0("=== loop " << g_loop << " summary ==="); + ll0(stat_rpk); + ll0(stat_rir); + } + // accumulate + g_stat_rpk.add(stat_rpk); + g_stat_rir.add(stat_rir); +} + +static void +finalstats() +{ + ll0("=== summary ==="); + ll0(g_stat_rpk); + ll0(g_stat_rir); +} + +static void +setseed(int n) +{ + uint seed; + if (n == -1) { + if (g_opts.seed == 0) + return; + if (g_opts.seed != -1) + seed = (uint)g_opts.seed; + else + seed = 1 + (ushort)getpid(); + } else { + if (g_opts.seed != 0) + return; + seed = n; + } + ll0("seed=" << seed); + srandom(seed); +} + +static int +runtest() +{ + setseed(-1); + g_cs = get_charset_by_name(g_csname, MYF(0)); + if (g_cs == 0) + g_cs = get_charset_by_csname(g_csname, MY_CS_PRIMARY, MYF(0)); + chkrc(g_cs != 0); + for (g_loop = 0; g_opts.loop == 0 || g_loop < g_opts.loop; g_loop++) { + ll0("=== loop " << g_loop << " ==="); + setseed(g_loop); + chkrc(createtable() == 0); + chkrc(loaddata() == 0); + sortkeys(); + chkrc(allocranges() == 0); + makeranges(); + chkrc(allocstat() == 0); + chkrc(runscans() == 0); + chkrc(droptable() == 0); + loopstats(); + } + finalstats(); + return 0; +} + +NDB_STD_OPTS_VARS; + +static struct my_option +my_long_options[] = +{ + NDB_STD_OPTS("testIndexStat"), + { "loglevel", 1001, "Logging level in this program 0-3 (default 0)", + (gptr*)&g_opts.loglevel, (gptr*)&g_opts.loglevel, 0, + GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "seed", 1002, "Random seed (0=loop number, default -1=random)", + (gptr*)&g_opts.seed, (gptr*)&g_opts.seed, 0, + GET_INT, REQUIRED_ARG, -1, 0, 0, 0, 0, 0 }, + { "loop", 1003, "Number of test loops (default 1, 0=forever)", + (gptr*)&g_opts.loop, (gptr*)&g_opts.loop, 0, + GET_INT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0 }, + { "rows", 1004, "Number of rows (default 100000)", + (gptr*)&g_opts.rows, (gptr*)&g_opts.rows, 0, + GET_UINT, REQUIRED_ARG, 100000, 0, 0, 0, 0, 0 }, + { "ops", 1005, "Number of index scans per loop (default 1000)", + (gptr*)&g_opts.ops, (gptr*)&g_opts.ops, 0, + GET_UINT, REQUIRED_ARG, 1000, 0, 0, 0, 0, 0 }, + { "dupkeys", 1006, "Pct records per key (min 100, default 1000)", + (gptr*)&g_opts.dupkeys, (gptr*)&g_opts.dupkeys, 0, + GET_UINT, REQUIRED_ARG, 1000, 0, 0, 0, 0, 0 }, + { "scanpct", 1007, "Preferred max pct of total rows per scan (default 5)", + (gptr*)&g_opts.scanpct, (gptr*)&g_opts.scanpct, 0, + GET_UINT, REQUIRED_ARG, 5, 0, 0, 0, 0, 0 }, + { "nullkeys", 1008, "Pct nulls in each key attribute (default 10)", + (gptr*)&g_opts.nullkeys, (gptr*)&g_opts.nullkeys, 0, + GET_UINT, REQUIRED_ARG, 10, 0, 0, 0, 0, 0 }, + { "eqscans", 1009, "Pct scans for partial/full equality (default 50)", + (gptr*)&g_opts.eqscans, (gptr*)&g_opts.eqscans, 0, + GET_UINT, REQUIRED_ARG, 50, 0, 0, 0, 0, 0 }, + { "dupscans", 1010, "Pct scans using same bounds (default 10)", + (gptr*)&g_opts.dupscans, (gptr*)&g_opts.dupscans, 0, + GET_UINT, REQUIRED_ARG, 10, 0, 0, 0, 0, 0 }, + { "keeptable", 1011, "Use existing table and data if any and do not drop", + (gptr*)&g_opts.keeptable, (gptr*)&g_opts.keeptable, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "no-extra-checks", 1012, "Omit expensive consistency checks", + (gptr*)&g_opts.nochecks, (gptr*)&g_opts.nochecks, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "abort-on-error", 1013, "Dump core on any error", + (gptr*)&g_opts.abort, (gptr*)&g_opts.abort, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, + 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 } +}; + +static void +usage() +{ + ndbout + << g_progname + << ": measure records_in_range error as percentage of total rows" << endl; + my_print_help(my_long_options); +} + +static int +checkoptions() +{ + chkrc(g_opts.rows != 0); + chkrc(g_opts.nullkeys <= 100); + chkrc(g_opts.dupkeys >= 100); + chkrc(g_opts.scanpct <= 100); + chkrc(g_opts.eqscans <= 100); + chkrc(g_opts.dupscans <= 100); + return 0; +} + +static int +doconnect() +{ + g_ncc = new Ndb_cluster_connection(); + chkdb(g_ncc->connect(30) == 0); + g_ndb = new Ndb(g_ncc, "TEST_DB"); + chkdb(g_ndb->init() == 0 && g_ndb->waitUntilReady(30) == 0); + return 0; +} + +static void +freeall() +{ + delete g_stat; + freekeys(); + freeranges(); + delete g_ndb; + delete g_ncc; +} + +int +main(int argc, char** argv) +{ + ndb_init(); + const char* g_progname = + strchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; + uint i; + ndbout << g_progname; + for (i = 1; i < argc; i++) + ndbout << " " << argv[i]; + ndbout << endl; + int ret; + ret = handle_options(&argc, &argv, my_long_options, ndb_std_get_one_option); + if (ret != 0 || argc != 0) + return NDBT_ProgramExit(NDBT_WRONGARGS); + if (checkoptions() == 0 && doconnect() == 0 && runtest() == 0) { + freeall(); + return NDBT_ProgramExit(NDBT_OK); + } + freeall(); + return NDBT_ProgramExit(NDBT_FAILED); +} |