/* Copyright (c) 2006, 2018, 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, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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 #include #include "m_ctype.h" #include #include #include #include #include #include #include #include struct Opts { int loglevel; uint seed; uint attrs; uint loops; uint rows; uint ops; uint nullkeys; uint rpk; uint rpkvar; uint scanpct; uint eqscans; bool keeptable; bool abort; const char* dump; Opts() : loglevel(0), seed(0), attrs(3), loops(1), rows(10000), ops(100), nullkeys(10), rpk(10), rpkvar(10), scanpct(10), eqscans(30), keeptable(false), abort(false), dump(0) {} }; static Opts g_opts; static uint g_loop = 0; static const char* g_tabname = "ts1"; static const char* g_indname = "ts1x1"; static const uint g_numattrs = 3; static const uint g_charlen = 10; static const char* g_csname = "latin1_swedish_ci"; static CHARSET_INFO* g_cs; // keys nullability static const bool g_b_nullable = true; static const bool g_c_nullable = true; static const bool g_d_nullable = true; // value limits struct Lim { bool all_nullable; uint b_min; uint b_max; const char* c_char; uint d_min; uint d_max; }; static Lim g_lim_val; static Lim g_lim_bnd; static Ndb_cluster_connection* g_ncc = 0; static Ndb* g_ndb = 0; static Ndb* g_ndb_sys = 0; static NdbDictionary::Dictionary* g_dic = 0; static const NdbDictionary::Table* g_tab = 0; static const NdbDictionary::Index* g_ind = 0; static const NdbRecord* g_tab_rec = 0; static const NdbRecord* g_ind_rec = 0; struct my_record { Uint8 m_null_bm; Uint8 fill[3]; Uint32 m_a; Uint32 m_b; char m_c[1+g_charlen]; Uint16 m_d; }; static const Uint32 g_ndbrec_a_offset=offsetof(my_record, m_a); static const Uint32 g_ndbrec_b_offset=offsetof(my_record, m_b); static const Uint32 g_ndbrec_b_nb_offset=1; static const Uint32 g_ndbrec_c_offset=offsetof(my_record, m_c); static const Uint32 g_ndbrec_c_nb_offset=2; static const Uint32 g_ndbrec_d_offset=offsetof(my_record, m_d); static const Uint32 g_ndbrec_d_nb_offset=3; static NdbTransaction* g_con = 0; static NdbOperation* g_op = 0; static NdbScanOperation* g_scan_op = 0; static NdbIndexScanOperation* g_rangescan_op = 0; static NdbIndexStat* g_is = 0; static bool g_has_created_stat_tables = false; static bool g_has_created_stat_events = false; static uint urandom(uint m) { if (m == 0) return 0; uint r = (uint)rand(); 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 chker(x) \ do { if (likely(x)) break; ndbout << "line " << __LINE__ << " FAIL " << #x << endl; ndbout << "errno: " << errno; 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 chkrc_break(x) \ if (unlikely(!(x))) { ndbout << "line " << __LINE__ << " FAIL " << #x << endl; break; } #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; if (g_ncc != 0) { NdbError e; e.code = g_ncc->get_latest_error(); e.message = g_ncc->get_latest_error_msg(); if (e.code != 0) ll0(++any << " ncc: error" << e); } 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_is != 0) { const NdbIndexStat::Error& e = g_is->getNdbError(); if (e.code != 0) ll0(++any << " stat: error " << e); } if (! any) ll0("unknown db error"); } /* Methods to create NdbRecord structs for the table and index */ static int createNdbRecords() { ll1("createNdbRecords"); const Uint32 numCols=4; const Uint32 numIndexCols=3; NdbDictionary::RecordSpecification recSpec[numCols]; recSpec[0].column= g_tab->getColumn("a"); // 4 bytes recSpec[0].offset= g_ndbrec_a_offset; recSpec[0].nullbit_byte_offset= ~(Uint32)0; recSpec[0].nullbit_bit_in_byte= ~(Uint32)0; recSpec[1].column= g_tab->getColumn("b"); // 4 bytes recSpec[1].offset= g_ndbrec_b_offset; if (g_b_nullable) { recSpec[1].nullbit_byte_offset= 0; recSpec[1].nullbit_bit_in_byte= g_ndbrec_b_nb_offset; } else { recSpec[1].nullbit_byte_offset= ~(Uint32)0; recSpec[1].nullbit_bit_in_byte= ~(Uint32)0; } recSpec[2].column= g_tab->getColumn("c"); // Varchar(10) -> ~12 bytes recSpec[2].offset= g_ndbrec_c_offset; if (g_c_nullable) { recSpec[2].nullbit_byte_offset= 0; recSpec[2].nullbit_bit_in_byte= g_ndbrec_c_nb_offset; } else { recSpec[2].nullbit_byte_offset= ~(Uint32)0; recSpec[2].nullbit_bit_in_byte= ~(Uint32)0; } recSpec[3].column= g_tab->getColumn("d"); // 2 bytes recSpec[3].offset= g_ndbrec_d_offset; if (g_d_nullable) { recSpec[3].nullbit_byte_offset= 0; recSpec[3].nullbit_bit_in_byte= g_ndbrec_d_nb_offset; } else { recSpec[3].nullbit_byte_offset= ~(Uint32)0; recSpec[3].nullbit_bit_in_byte= ~(Uint32)0; } g_dic = g_ndb->getDictionary(); g_tab_rec= g_dic->createRecord(g_tab, &recSpec[0], numCols, sizeof(NdbDictionary::RecordSpecification), 0); chkdb(g_tab_rec != NULL); g_ind_rec= g_dic->createRecord(g_ind, &recSpec[1], numIndexCols, sizeof(NdbDictionary::RecordSpecification), 0); chkdb(g_ind_rec != NULL); g_dic = 0; return 0; } // create table ts0 ( // a int unsigned, // b int unsigned, c varchar(10), d smallint unsigned, // primary key using hash (a), index (b, c, d) ) static int createtable() { ll1("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::Unsigned); col.setNullable(g_b_nullable); tab.addColumn(col); } { NdbDictionary::Column col("c"); col.setType(NdbDictionary::Column::Varchar); col.setLength(g_charlen); col.setCharset(g_cs); col.setNullable(g_c_nullable); tab.addColumn(col); } { NdbDictionary::Column col("d"); col.setType(NdbDictionary::Column::Smallunsigned); col.setNullable(g_d_nullable); tab.addColumn(col); } g_dic = g_ndb->getDictionary(); if (g_dic->getTable(g_tabname) != 0) chkdb(g_dic->dropTable(g_tabname) == 0); chkdb(g_dic->createTable(tab) == 0); chkdb((g_tab = g_dic->getTable(g_tabname)) != 0); g_dic = 0; return 0; } static int createindex() { ll1("createindex"); 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(); chkdb(g_dic->createIndex(ind) == 0); chkdb((g_ind = g_dic->getIndex(g_indname, g_tabname)) != 0); g_dic = 0; return 0; } static int droptable() { ll1("droptable"); g_dic = g_ndb->getDictionary(); chkdb(g_dic->dropTable(g_tabname) == 0); g_dic = 0; return 0; } // values for keys and bounds struct Val { uint8 m_numattrs; int8 b_null; int8 c_null; int8 d_null; Uint32 b; uchar c[1 + g_charlen]; Uint16 d; Val(); void init(); void copy(const Val& val2); void make(uint numattrs, const Lim& lim); int cmp(const Val& val2, uint numattrs = g_numattrs, uint* num_eq = 0) const; void fromib(const NdbIndexScanOperation::IndexBound& ib, uint j); private: Val& operator=(const Val&); Val(const Val&); }; static NdbOut& operator<<(NdbOut& out, const Val& val) { out << "["; if (val.m_numattrs >= 1) { if (val.b_null) out << "NULL"; else out << val.b; } if (val.m_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.m_numattrs >= 3) { out << " "; if (val.d_null) out <<" NULL"; else out << val.d; } out << "]"; return out; } Val::Val() { init(); } void Val::init() { m_numattrs = 0; // junk rest b_null = -1; c_null = -1; d_null = -1; b = ~(Uint32)0; memset(c, 0xff, sizeof(c)); d = ~(Uint16)0; } void Val::copy(const Val& val2) { require(this != &val2); init(); m_numattrs = val2.m_numattrs; if (m_numattrs >= 1) { require(val2.b_null == 0 || val2.b_null == 1); b_null = val2.b_null; if (!b_null) b = val2.b; } if (m_numattrs >= 2) { require(val2.c_null == 0 || val2.c_null == 1); c_null = val2.c_null; if (!c_null) memcpy(c, val2.c, sizeof(c)); } if (m_numattrs >= 3) { require(val2.d_null == 0 || val2.d_null == 1); d_null = val2.d_null; if (!d_null) d = val2.d; } } void Val::make(uint numattrs, const Lim& lim) { require(numattrs <= g_numattrs); if (numattrs >= 1) { const bool nullable = g_b_nullable || lim.all_nullable; if (nullable && urandom(100) < g_opts.nullkeys) b_null = 1; else { require(lim.b_min <= lim.b_max); b = lim.b_min + urandom(lim.b_max - lim.b_min + 1); b_null = 0; } } if (numattrs >= 2) { const bool nullable = g_c_nullable || lim.all_nullable; if (nullable && urandom(100) < g_opts.nullkeys) c_null = 1; else { // prefer shorter const uint len = urandom(urandom(g_charlen + 1) + 1); c[0] = len; for (uint j = 0; j < len; j++) { uint k = urandom((uint)strlen(lim.c_char)); c[1 + j] = lim.c_char[k]; } c_null = 0; } } if (numattrs >= 3) { const bool nullable = g_d_nullable || lim.all_nullable; if (nullable && urandom(100) < g_opts.nullkeys) d_null = 1; else { require(lim.d_min <= lim.d_max); d = lim.d_min + urandom(lim.d_max - lim.d_min + 1); d_null = 0; } } m_numattrs = numattrs; } int Val::cmp(const Val& val2, uint numattrs, uint* num_eq) const { require(numattrs <= m_numattrs); require(numattrs <= val2.m_numattrs); uint n = 0; // attr index where differs uint k = 0; if (k == 0 && numattrs >= 1) { if (! b_null && ! val2.b_null) { if (b < val2.b) k = -1; else if (b > val2.b) k = +1; } else if (! b_null) { k = +1; } else if (! val2.b_null) { k = -1; } if (k == 0) n++; } if (k == 0 && numattrs >= 2) { if (! c_null && ! val2.c_null) { const uchar* s1 = &c[1]; const uchar* s2 = &val2.c[1]; const uint l1 = (uint)c[0]; const uint l2 = (uint)val2.c[0]; require(l1 <= g_charlen && l2 <= g_charlen); k = g_cs->coll->strnncollsp(g_cs, s1, l1, s2, l2); } else if (! c_null) { k = +1; } else if (! val2.c_null) { k = -1; } if (k == 0) n++; } if (k == 0 && numattrs >= 3) { if (! d_null && ! val2.d_null) { if (d < val2.d) k = -1; else if (d > val2.d) k = +1; } else if (! d_null) { k = +1; } else if (! val2.d_null) { k = -1; } if (k == 0) n++; } require(n <= numattrs); if (num_eq != 0) *num_eq = n; return k; } void Val::fromib(const NdbIndexScanOperation::IndexBound& ib, uint j) { const char* key = (j == 0 ? ib.low_key : ib.high_key); const uint numattrs = (j == 0 ? ib.low_key_count : ib.high_key_count); const Uint8 nullbits = *(const Uint8*)key; require(numattrs <= g_numattrs); if (numattrs >= 1) { if (nullbits & (1 << g_ndbrec_b_nb_offset)) b_null = 1; else { memcpy(&b, &key[g_ndbrec_b_offset], sizeof(b)); b_null = 0; } } if (numattrs >= 2) { if (nullbits & (1 << g_ndbrec_c_nb_offset)) c_null = 1; else { memcpy(c, &key[g_ndbrec_c_offset], sizeof(c)); c_null = 0; } } if (numattrs >= 3) { if (nullbits & (1 << g_ndbrec_d_nb_offset)) d_null = 1; else { memcpy(&d, &key[g_ndbrec_d_offset], sizeof(d)); d_null = 0; } } m_numattrs = numattrs; } // index keys struct Key { Val m_val; int8 m_flag; // temp use Key(); private: Key& operator=(const Key&); Key(const Key&); }; static NdbOut& operator<<(NdbOut& out, const Key& key) { out << key.m_val; if (key.m_flag != -1) out << " flag: " << key.m_flag; return out; } Key::Key() { m_flag = -1; } static Key* g_keys = 0; static uint* g_sortkeys = 0; static void freekeys() { delete [] g_keys; delete [] g_sortkeys; g_keys = 0; g_sortkeys = 0; } static void allockeys() { freekeys(); g_keys = new Key [g_opts.rows]; g_sortkeys = new uint [g_opts.rows]; require(g_keys != 0 && g_sortkeys != 0); memset(g_sortkeys, 0xff, sizeof(uint) * g_opts.rows); } static int cmpkeys(const void* p1, const void* p2) { const uint i1 = *(const uint*)p1; const uint i2 = *(const uint*)p2; require(i1 < g_opts.rows && i2 < g_opts.rows); const Key& key1 = g_keys[i1]; const Key& key2 = g_keys[i2]; const int k = key1.m_val.cmp(key2.m_val, g_opts.attrs); return k; } static void sortkeys() { ll2("sortkeys"); uint i; // sort for (i = 0; i < g_opts.rows; i++) g_sortkeys[i] = i; qsort(g_sortkeys, g_opts.rows, sizeof(uint), cmpkeys); // verify uint unique = 1; for (i = 1; i < g_opts.rows; i++) { const uint i1 = g_sortkeys[i - 1]; const uint i2 = g_sortkeys[i]; require(i1 < g_opts.rows && i2 < g_opts.rows); const Key& key1 = g_keys[i1]; const Key& key2 = g_keys[i2]; const int k = key1.m_val.cmp(key2.m_val, g_opts.attrs); require(k <= 0); if (k < 0) unique++; } // show min max key ll1("minkey:" << g_keys[g_sortkeys[0]]); ll1("maxkey:" << g_keys[g_sortkeys[g_opts.rows - 1]]); ll1("unique:" << unique); } static void makekeys() { ll1("makekeys"); uint initrows = g_opts.rows / g_opts.rpk; require(initrows != 0); // distinct keys uint i = 0; while (i < initrows) { Key& key = g_keys[i]; key.m_val.make(g_numattrs, g_lim_val); i++; } // remaining keys while (i < g_opts.rows) { // if rpkvar is 10, multiply rpk by number between 0.1 and 10.0 double a = (double)(1 + urandom(g_opts.rpkvar * g_opts.rpkvar)); double b = a / (double)g_opts.rpkvar; double c = b * (double)g_opts.rpk; const uint n = (uint)(c + 0.5); // select random key to duplicate from initrows const uint k = urandom(initrows); uint j = 0; while (i < g_opts.rows && j < n) { g_keys[i].m_val.copy(g_keys[k].m_val); j++; i++; } } // shuffle i = 0; while (i < g_opts.rows) { uint j = urandom(g_opts.rows); if (i != j) { Key tmp; tmp.m_val.copy(g_keys[i].m_val); g_keys[i].m_val.copy(g_keys[j].m_val); g_keys[j].m_val.copy(tmp.m_val); } i++; } // sort sortkeys(); } // data loading static int verifydata() { ll3("verifydata"); chkdb((g_con = g_ndb->startTransaction()) != 0); chkdb((g_scan_op = g_con->getNdbScanOperation(g_tab)) != 0); chkdb(g_scan_op->readTuples(NdbScanOperation::LM_CommittedRead) == 0); Uint32 a; Val val; val.m_numattrs = g_numattrs; 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++) { Key& key = g_keys[i]; key.m_flag = false; // not scanned } while (1) { int ret; a = ~(Uint32)0; chkdb((ret = g_scan_op->nextResult()) == 0 || ret == 1); if (ret == 1) break; val.b_null = b_ra->isNULL(); val.c_null = c_ra->isNULL(); val.d_null = d_ra->isNULL(); require(val.b_null == 0 || (g_b_nullable && val.b_null == 1)); require(val.c_null == 0 || (g_c_nullable && val.c_null == 1)); require(val.d_null == 0 || (g_d_nullable && val.d_null == 1)); i = (uint)a; chkrc(i < g_opts.rows); Key& key = g_keys[i]; chkrc(key.m_val.cmp(val) == 0); chkrc(key.m_flag == false); key.m_flag = true; count++; } g_ndb->closeTransaction(g_con); g_con = 0; g_scan_op = 0; for (i = 0; i < g_opts.rows; i++) { Key& key = g_keys[i]; chkrc(key.m_flag == true); key.m_flag = -1; // forget } require(count == g_opts.rows); ll3("verifydata: " << g_opts.rows << " rows"); return 0; } static int loaddata(bool update) { ll1("loaddata: update: " << update); 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); if (!update) chkdb(g_op->insertTuple() == 0); else chkdb(g_op->updateTuple() == 0); Uint32 a = i; const Val& val = g_keys[i].m_val; const char* a_addr = (const char*)&a; const char* b_addr = ! val.b_null ? (const char*)&val.b : 0; 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; // check data and cmp routines chkrc(verifydata() == 0); for (uint i = 0; i < g_opts.rows; i++) ll3("load " << i << ": " << g_keys[i]); ll0("loaddata: " << g_opts.rows << " rows"); return 0; } // bounds struct Bnd { Val m_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). */ int8 m_side; int8 m_lohi; // 0-lo 1-hi as part of Rng Bnd(); bool isempty() const; void copy(const Bnd& bnd2); // does not copy m_lohi Bnd& make(uint minattrs); Bnd& make(uint minattrs, const Val& theval); int cmp(const Key& key) const; int cmp(const Bnd& bnd2); int type(uint colno) const; // for setBound void fromib(const NdbIndexScanOperation::IndexBound& ib, uint j); private: Bnd& operator=(const Bnd&); Bnd(const Bnd&); }; static NdbOut& operator<<(NdbOut& out, const Bnd& bnd) { if (bnd.m_lohi == 0) out << "L"; else if (bnd.m_lohi == 1) out << "H"; else out << bnd.m_lohi << "?"; out << bnd.m_val; if (bnd.m_side == 0) ; else if (bnd.m_side == -1) out << "-"; else if (bnd.m_side == +1) out << "+"; return out; } Bnd::Bnd() { m_side = 0; m_lohi = -1; } bool Bnd::isempty() const { return m_val.m_numattrs == 0; } void Bnd::copy(const Bnd& bnd2) { m_val.copy(bnd2.m_val); m_side = bnd2.m_side; } Bnd& Bnd::make(uint minattrs) { require(minattrs <= g_opts.attrs); require(m_lohi == 0 || m_lohi == 1); uint numattrs = minattrs + urandom(g_numattrs - minattrs + 1); m_val.make(numattrs, g_lim_bnd); m_side = m_val.m_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); m_val.copy(theval); m_val.m_numattrs = numattrs; m_side = m_val.m_numattrs == 0 ? 0 : urandom(2) == 0 ? -1 : +1; return *this; } int Bnd::cmp(const Key& key) const { int place; // debug int ret; do { int k = key.m_val.cmp(m_val, m_val.m_numattrs); if (k != 0) { place = 1; ret = k; break; } if (m_side != 0) { place = 2; ret = (-1) * m_side; break; } place = 3; ret = 0; require(m_val.m_numattrs == 0); } while (0); ll3("bnd: " << *this << " cmp key: " << key << " ret: " << ret << " place: " << place); return ret; } int Bnd::cmp(const Bnd& bnd2) { int place; // debug int ret; const Bnd& bnd1 = *this; const Val& val1 = bnd1.m_val; const Val& val2 = bnd2.m_val; const uint numattrs1 = val1.m_numattrs; const uint numattrs2 = val2.m_numattrs; const uint n = (numattrs1 < numattrs2 ? numattrs1 : numattrs2); do { int k = val1.cmp(val2, n); if (k != 0) { place = 1; ret = k; break; } if (numattrs1 < numattrs2) { place = 2; ret = (+1) * bnd1.m_side; break; } if (numattrs1 > numattrs2) { place = 3; ret = (-1) * bnd1.m_side; break; } if (bnd1.m_side < bnd2.m_side) { place = 4; ret = -1; break; } if (bnd1.m_side > bnd2.m_side) { place = 5; ret = +1; break; } place = 6; ret = 0; } while (0); ll3("bnd: " << *this << " cmp bnd: " << bnd2 << " ret: " << ret << " place: " << place); return ret; } int Bnd::type(uint colno) const { int t; require(colno < m_val.m_numattrs && (m_side == -1 || m_side == +1)); require(m_lohi == 0 || m_lohi == 1); if (m_lohi == 0) { if (colno + 1 < m_val.m_numattrs) t = 0; // LE else if (m_side == -1) t = 0; // LE else t = 1; // LT } else { if (colno + 1 < m_val.m_numattrs) t = 2; // GE else if (m_side == +1) t = 2; // GE else t = 3; // GT } return t; } void Bnd::fromib(const NdbIndexScanOperation::IndexBound& ib, uint j) { Val& val = m_val; val.fromib(ib, j); const uint numattrs = (j == 0 ? ib.low_key_count : ib.high_key_count); const bool inclusive = (j == 0 ? ib.low_inclusive : ib.high_inclusive); if (numattrs == 0) { m_side = 0; } else { m_side = (j == 0 ? (inclusive ? -1 : +1) : (inclusive ? +1 : -1)); } m_lohi = j; } // stats values struct Stval { Uint32 rir_v2; double rir; double rpk[g_numattrs]; bool empty; char rule[NdbIndexStat::RuleBufferBytes]; Stval(); }; static NdbOut& operator<<(NdbOut& out, const Stval& st) { out << "rir_v2: " << st.rir_v2; out << " rir_v4: " << st.rir; out << " rpk:[ "; for (uint k = 0; k < g_opts.attrs; k++) { if (k != 0) out << " "; out << st.rpk[k]; } out << " ]"; out << " " << (st.empty ? "E" : "N"); out << " " << st.rule; return out; } Stval::Stval() { rir_v2 = 0; rir = 0.0; for (uint k = 0; k < g_numattrs; k++) rpk[k] = 0.0; empty = false; strcpy(rule, "-"); } // ranges struct Rng { Bnd m_bnd[2]; Int32 m_rowcount; // stats v2 double errpct; // stats v4 Stval m_st_scan; // exact stats computed from keys in range Stval m_st_stat; // interpolated kernel stats via g_is Rng(); uint minattrs() const; uint maxattrs() const; bool iseq() const; bool isempty() const; void copy(const Rng& rng2); int cmp(const Key& key) const; // -1,0,+1 = key is before,in,after range uint rowcount() const; void fromib(const NdbIndexScanOperation::IndexBound& ib); private: Rng& operator=(const Rng&); Rng(const Rng&); }; static NdbOut& operator<<(NdbOut& out, const Rng& rng) { out << rng.m_bnd[0] << " " << rng.m_bnd[1]; if (rng.m_rowcount != -1) out << " rows: " << rng.m_rowcount; return out; } Rng::Rng() { m_bnd[0].m_lohi = 0; m_bnd[1].m_lohi = 1; m_rowcount = -1; } uint Rng::minattrs() const { return std::min(m_bnd[0].m_val.m_numattrs, m_bnd[1].m_val.m_numattrs); } uint Rng::maxattrs() const { return std::max(m_bnd[0].m_val.m_numattrs, m_bnd[1].m_val.m_numattrs); } bool Rng::iseq() const { return minattrs() == maxattrs() && m_bnd[0].m_val.cmp(m_bnd[1].m_val, minattrs()) == 0 && m_bnd[0].m_side < m_bnd[1].m_side; } bool Rng::isempty() const { return m_bnd[0].isempty() && m_bnd[1].isempty(); } void Rng::copy(const Rng& rng2) { m_bnd[0].copy(rng2.m_bnd[0]); m_bnd[1].copy(rng2.m_bnd[1]); m_rowcount = rng2.m_rowcount; } int Rng::cmp(const Key& key) const { int place; // debug int ret; do { int k; k = m_bnd[0].cmp(key); if (k < 0) { place = 1; ret = -1; break; } k = m_bnd[1].cmp(key); if (k > 0) { place = 2; ret = +1; break; } place = 3; ret = 0; } while (0); ll3("rng: " << *this << " cmp key: " << key << " ret: " << ret << " place: " << place); return ret; } uint Rng::rowcount() const { ll3("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_opts.rows; int ret; int j; do { j = (hi + lo) / 2; require(lo < j && j < hi); ret = cmp(g_keys[g_sortkeys[j]]); 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; } // verify is expensive due to makeranges() multiple tries const bool verify = (urandom(10) == 0); const int lo = std::max(lim[0], 0); const int hi = std::min(lim[1], (int)g_opts.rows - 1); if (verify) { int pos = -1; // before, within, after for (i = 0; i < (int)g_opts.rows; i++) { int k = cmp(g_keys[g_sortkeys[i]]); if (k < 0) require(i < lo); else if (k == 0) require(lo <= i && i <= hi); else require(i > hi); require(pos <= k); if (pos < k) pos = k; } } // result require(hi - lo + 1 >= 0); uint count = hi - lo + 1; ll3("rowcount: " << count << " lim: " << lim[0] << " " << lim[1]); return count; } void Rng::fromib(const NdbIndexScanOperation::IndexBound& ib) { for (uint j = 0; j <= 1; j++) { Bnd& bnd = m_bnd[j]; bnd.fromib(ib, j); } } static Rng* g_rnglist = 0; static void freeranges() { delete [] g_rnglist; g_rnglist = 0; } static void allocranges() { freeranges(); g_rnglist = new Rng [g_opts.ops]; require(g_rnglist != 0); } static void makeranges() { ll1("makeranges"); const uint mintries = 20; const uint maxtries = 80; const uint fudgefac = 10; for (uint i = 0; i < g_opts.ops; i++) { const bool eqpart = (urandom(100) < g_opts.eqscans); const bool eqfull = eqpart && (urandom(100) < g_opts.eqscans); Rng rng; // candidate uint j; for (j = 0; j < maxtries; j++) { Rng rng2; if (!eqpart) { rng2.m_bnd[0].make(0); rng2.m_bnd[1].make(0); } else { const uint mincnt = eqfull ? g_opts.attrs : 1; rng2.m_bnd[0].make(mincnt); rng2.m_bnd[1].copy(rng2.m_bnd[0]); rng2.m_bnd[0].m_side = -1; rng2.m_bnd[1].m_side = +1; require(rng2.iseq()); } rng2.m_rowcount = (Int32)rng2.rowcount(); // 0-discard 1-replace or accept 2-accept int action = 0; do { // first candidate if (rng.m_rowcount == -1) { action = 1; break; } require(rng.m_rowcount != -1); // prefer some bounds if (rng2.isempty()) { if (urandom(fudgefac) != 0) action = 0; else action = 1; break; } // prefer some rows if (rng2.m_rowcount == 0) { action = 0; break; } // accept if row count under given pct require((uint)rng2.m_rowcount <= g_opts.rows); if (100 * (uint)rng2.m_rowcount <= g_opts.scanpct * g_opts.rows) { if (urandom(fudgefac) != 0) { action = 2; break; } } // replace if less rows if (rng2.m_rowcount < rng.m_rowcount) { if (urandom(fudgefac) != 0) { action = 1; break; } } } while (0); if (action != 0) { rng.copy(rng2); if (action == 2 || j >= mintries) break; } } g_rnglist[i].copy(rng); ll2("rng " << i << ": " << rng << " tries: " << j); } } // verify ranges via range scans static int setbounds(const Rng& rng) { // currently must do each attr in order ll3("setbounds: " << rng); uint i; const Bnd (&bnd)[2] = rng.m_bnd; for (i = 0; i < g_numattrs; i++) { const Uint32 no = i; // index attribute number uint j; int type[2] = { -1, -1 }; // determine inclusivity (boundtype) of upper+lower bounds on this col. // -1 == no bound on the col. for (j = 0; j <= 1; j++) { if (no < bnd[j].m_val.m_numattrs) type[j] = bnd[j].type(no); } for (j = 0; j <= 1; j++) { int t = type[j]; if (t == -1) continue; if (no + 1 < bnd[j].m_val.m_numattrs) t &= ~(uint)1; // strict bit is set on last bound only const Val& val = bnd[j].m_val; const void* addr = 0; if (no == 0) addr = ! val.b_null ? (const void*)&val.b : 0; 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 require(false); ll3("setBound attr:" << no << " type:" << t << " val: " << val); chkdb(g_rangescan_op->setBound(no, t, addr) == 0); } } return 0; } static int scanrange(const Rng& rng) { ll3("scanrange: " << rng); 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(rng) == 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++) { Key& key = g_keys[i]; key.m_flag = false; // not scanned } 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 = rng.cmp(key); chkrc(k == 0); chkrc(key.m_flag == false); key.m_flag = true; count++; } g_ndb->closeTransaction(g_con); g_con = 0; g_rangescan_op = 0; for (i = 0; i < g_opts.rows; i++) { Key& key = g_keys[i]; int k = rng.cmp(key); if (k != 0) // not in range chkrc(key.m_flag == false); else chkrc(key.m_flag == true); key.m_flag = -1; // forget } require((uint)rng.m_rowcount == count); return 0; } static int scanranges() { ll1("scanranges"); for (uint i = 0; i < g_opts.ops; i++) { const Rng& rng = g_rnglist[i]; chkrc(scanrange(rng) == 0); } return 0; } // stats v4 update static int definestat() { ll1("definestat"); require(g_is != 0 && g_ind != 0 && g_tab != 0); chkdb(g_is->set_index(*g_ind, *g_tab) == 0); return 0; } static int updatestat() { ll1("updatestat"); if (urandom(2) == 0) { g_dic = g_ndb->getDictionary(); chkdb(g_dic->updateIndexStat(*g_ind, *g_tab) == 0); g_dic = 0; } else { chkdb(g_is->update_stat(g_ndb_sys) == 0); } return 0; } static int readstat() { ll1("readstat"); NdbIndexStat::Head head; chkdb(g_is->read_head(g_ndb_sys) == 0); g_is->get_head(head); chkrc(head.m_found == true); chkrc(head.m_sampleVersion != 0); ll1("readstat:" << " sampleVersion: " << head.m_sampleVersion << " sampleCount: " << head.m_sampleCount); NdbIndexStat::CacheInfo infoQuery; chkdb(g_is->read_stat(g_ndb_sys) == 0); g_is->move_cache(); g_is->get_cache_info(infoQuery, NdbIndexStat::CacheQuery); ll1("readstat: cache bytes: " << infoQuery.m_totalBytes); return 0; } // test polling after updatestat static int startlistener() { ll1("startlistener"); chkdb(g_is->create_listener(g_ndb_sys) == 0); chkdb(g_is->execute_listener(g_ndb_sys) == 0); return 0; } static int runlistener() { ll1("runlistener"); int ret; chkdb((ret = g_is->poll_listener(g_ndb_sys, 10000)) != -1); chkrc(ret == 1); // one event is expected chkdb((ret = g_is->next_listener(g_ndb_sys)) != -1); chkrc(ret == 1); chkdb((ret = g_is->next_listener(g_ndb_sys)) != -1); chkrc(ret == 0); return 0; } static int stoplistener() { ll1("stoplistener"); chkdb(g_is->drop_listener(g_ndb_sys) != -1); return 0; } // stats queries // exact stats from scan results static void queryscan(Rng& rng) { ll3("queryscan"); uint rir; uint unq[g_numattrs]; rir = 0; for (uint k = 0; k < g_opts.attrs; k++) unq[0] = 0; Key prevkey; for (uint i = 0; i < g_opts.rows; i++) { const Key& key = g_keys[g_sortkeys[i]]; int res = rng.cmp(key); if (res != 0) continue; rir++; if (rir == 1) { for (uint k = 0; k < g_opts.attrs; k++) unq[k] = 1; } else { uint num_eq = ~0; int res = prevkey.m_val.cmp(key.m_val, g_opts.attrs, &num_eq); if (res == 0) require(num_eq == g_opts.attrs); else { require(res < 0); require(num_eq < g_opts.attrs); unq[num_eq]++; // propagate down for (uint k = num_eq + 1; k < g_opts.attrs; k++) unq[k]++; } } prevkey.m_val.copy(key.m_val); } require(rng.m_rowcount != -1); require((uint)rng.m_rowcount == rir); Stval& st = rng.m_st_scan; st.rir_v2 = rir; st.rir = rir == 0 ? 1.0 : (double)rir; for (uint k = 0; k < g_opts.attrs; k++) { if (rir == 0) st.rpk[k] = 1.0; else { require(rir >= unq[k]); require(unq[k] != 0); st.rpk[k] = (double)rir / (double)unq[k]; } } st.empty = (rir == 0); ll2("queryscan: " << st); } /* This method initialises the passed in IndexBound * to represent the range passed in. * It assumes that the storage pointed to by low_key * and high_key in the passed IndexBound can be overwritten * and is long enough to store the data */ static int initialiseIndexBound(const Rng& rng, NdbIndexScanOperation::IndexBound& ib, my_record* low_key, my_record* high_key) { ll3("initialiseIndexBound: " << rng); uint i; const Bnd (&bnd)[2] = rng.m_bnd; Uint32 colsInBound[2]= {0, 0}; bool boundInclusive[2]= {false, false}; memset(&ib, 0xf1, sizeof(ib)); memset(low_key, 0xf2, sizeof(*low_key)); memset(high_key, 0xf3, sizeof(*high_key)); // Clear nullbit storage low_key->m_null_bm = 0; high_key->m_null_bm = 0; for (i = 0; i < g_numattrs; i++) { const Uint32 no = i; // index attribute number uint j; int type[2] = { -1, -1 }; // determine inclusivity (boundtype) of upper+lower bounds on this col. // -1 == no bound on the col. for (j = 0; j <= 1; j++) { if (no < bnd[j].m_val.m_numattrs) type[j] = bnd[j].type(no); } for (j = 0; j <= 1; j++) { /* Get ptr to key storage space for this bound */ my_record* keyBuf= (j==0) ? low_key : high_key; int t = type[j]; if (t == -1) continue; colsInBound[j]++; if (no + 1 >= bnd[j].m_val.m_numattrs) // Last column in bound, inclusive if GE or LE (or EQ) // i.e. bottom bit of boundtype is clear boundInclusive[j]= !(t & 1); const Val& val = bnd[j].m_val; if (no == 0) { if (! val.b_null) keyBuf->m_b= val.b; if (g_b_nullable) keyBuf->m_null_bm |= ((val.b_null?1:0) << g_ndbrec_b_nb_offset); } else if (no == 1) { if (! val.c_null) memcpy(&keyBuf->m_c[0], (const void*)&val.c, 1+ g_charlen); if (g_c_nullable) keyBuf->m_null_bm |= ((val.c_null?1:0) << g_ndbrec_c_nb_offset); } else if (no == 2) { if (! val.d_null) keyBuf->m_d= val.d; if (g_d_nullable) keyBuf->m_null_bm |= ((val.d_null?1:0) << g_ndbrec_d_nb_offset); } else require(false); ll3("initialiseIndexBound attr:" << no << " type:" << t << " val: " << val); } } /* Now have everything we need to initialise the IndexBound */ ib.low_key = (char*)low_key; ib.low_key_count= colsInBound[0]; ib.low_inclusive= boundInclusive[0]; ib.high_key = (char*)high_key; ib.high_key_count= colsInBound[1]; ib.high_inclusive= boundInclusive[1]; ib.range_no= 0; ll3(" indexBound low_key_count=" << ib.low_key_count << " low_inc=" << ib.low_inclusive << " high_key_count=" << ib.high_key_count << " high_inc=" << ib.high_inclusive); ll3(" low bound b=" << *((Uint32*) &ib.low_key[g_ndbrec_b_offset]) << " d=" << *((Uint16*) &ib.low_key[g_ndbrec_d_offset]) << " first byte=" << ib.low_key[0]); ll3(" high bound b=" << *((Uint32*) &ib.high_key[g_ndbrec_b_offset]) << " d=" << *((Uint16*) &ib.high_key[g_ndbrec_d_offset]) << " first byte=" << ib.high_key[0]); // verify by reverse { Rng rng; rng.fromib(ib); require(rng.m_bnd[0].cmp(bnd[0]) == 0); require(rng.m_bnd[1].cmp(bnd[1]) == 0); } return 0; } static int querystat_v2(Rng& rng) { ll3("querystat_v2"); /* Create IndexBound and key storage space */ NdbIndexScanOperation::IndexBound ib; my_record low_key; my_record high_key; chkdb((g_con = g_ndb->startTransaction()) != 0); chkrc(initialiseIndexBound(rng, ib, &low_key, &high_key) == 0); Uint64 count = ~(Uint64)0; chkdb(g_is->records_in_range(g_ind, g_con, g_ind_rec, g_tab_rec, &ib, 0, &count, 0) == 0); g_ndb->closeTransaction(g_con); g_con = 0; g_rangescan_op = 0; Stval& st = rng.m_st_stat; chkrc(count < (1 << 30)); st.rir_v2 = (Uint32)count; ll2("querystat_v2: " << st.rir_v2 << " rows"); return 0; } static int querystat(Rng& rng) { ll3("querystat"); // set up range Uint8 bound_lo_buffer[NdbIndexStat::BoundBufferBytes]; Uint8 bound_hi_buffer[NdbIndexStat::BoundBufferBytes]; NdbIndexStat::Bound bound_lo(g_is, bound_lo_buffer); NdbIndexStat::Bound bound_hi(g_is, bound_hi_buffer); NdbIndexStat::Range range(bound_lo, bound_hi); // convert to IndexBound (like in mysqld) NdbIndexScanOperation::IndexBound ib; my_record low_key; my_record high_key; chkrc(initialiseIndexBound(rng, ib, &low_key, &high_key) == 0); chkrc(g_is->convert_range(range, g_ind_rec, &ib) == 0); // index stat query Uint8 stat_buffer[NdbIndexStat::StatBufferBytes]; NdbIndexStat::Stat stat(stat_buffer); chkdb(g_is->query_stat(range, stat) == 0); // save result Stval& st = rng.m_st_stat; g_is->get_rir(stat, &st.rir); for (uint k = 0; k < g_opts.attrs; k++) { g_is->get_rpk(stat, k, &st.rpk[k]); } g_is->get_empty(stat, &st.empty); g_is->get_rule(stat, st.rule); ll2("querystat: " << st); return 0; } static int queryranges() { ll2("queryranges"); for (uint i = 0; i < g_opts.ops; i++) { Rng& rng = g_rnglist[i]; ll1("rng " << i << ": " << rng); // exact stats queryscan(rng); // interpolated stats chkrc(querystat_v2(rng) == 0); chkrc(querystat(rng) == 0); const Stval& st1 = rng.m_st_scan; const Stval& st2 = rng.m_st_stat; // if rir v2 is zero then it is exact chkrc(st2.rir_v2 != 0 || st1.rir_v2 == 0); } return 0; } // general statistics methods struct Stats : public NDBT_Stats { Stats(); void add(double x2); void add(const Stats& sum2); }; static NdbOut& operator<<(NdbOut& out, const Stats& st) { out << "count: " << st.getCount() << " min: " << st.getMin() << " max: " << st.getMax() << " mean: " << st.getMean() << " stddev: " << st.getStddev(); return out; } Stats::Stats() { } void Stats::add(double x2) { addObservation(x2); } void Stats::add(const Stats& st2) { *this += st2; } // error statistics scan vs stat struct Sterr { Stats rir_v2; Stats rir; Stats rpk[g_numattrs]; Sterr(); void add(const Sterr& st2); }; static NdbOut& operator<<(NdbOut& out, const Sterr& st) { out << "rir_v2: " << st.rir_v2 << endl; out << "rir_v4: " << st.rir; for (uint k = 0; k < g_opts.attrs; k++) { out << endl << "rpk[" << k << "]: " << st.rpk[k]; } return out; } Sterr::Sterr() { } void Sterr::add(const Sterr& st2) { rir_v2.add(st2.rir_v2); rir.add(st2.rir); for (uint k = 0; k < g_opts.attrs; k++) { rpk[k].add(st2.rpk[k]); } } static void sumrange(const Rng& rng, Sterr& st) { const Stval& st1 = rng.m_st_scan; const Stval& st2 = rng.m_st_stat; // rir_v2 error as pct of total rows { double rows = (double)g_opts.rows; double x1 = (double)st1.rir_v2; double x2 = (double)st2.rir_v2; double x3 = 100.0 * (x2 - x1) / rows; st.rir_v2.add(x3); } // rir error as pct of total rows { double rows = (double)g_opts.rows; double x1 = st1.rir; double x2 = st2.rir; double x3 = 100.0 * (x2 - x1) / rows; st.rir.add(x3); } // rpk errors as plain diff for (uint k = 0; k < g_opts.attrs; k++) { double x1 = st1.rpk[k]; double x2 = st2.rpk[k]; double x3 = (x2 - x1); st.rpk[k].add(x3); } } static void sumranges(Sterr& st) { for (uint i = 0; i < g_opts.ops; i++) { const Rng& rng = g_rnglist[i]; sumrange(rng, st); } } // loop and final stats static Sterr g_sterr; static void loopstats() { Sterr st; sumranges(st); if (g_opts.loops != 1) { ll0("=== loop " << g_loop << " summary ==="); ll0(st); } // accumulate g_sterr.add(st); } static int loopdumps() { char file[200]; if (g_opts.dump == 0) return 0; { BaseString::snprintf(file, sizeof(file), "%s.key.%d", g_opts.dump, g_loop); FILE* f = 0; chker((f = fopen(file, "w")) != 0); fprintf(f, "a"); for (uint k = 0; k < g_opts.attrs; k++) { if (k == 0) fprintf(f, ",b_null,b"); else if (k == 1) fprintf(f, ",c_null,c"); else if (k == 2) fprintf(f, ",d_null,d"); else require(false); } fprintf(f, "\n"); for (uint i = 0; i < g_opts.rows; i++) { const Key& key = g_keys[g_sortkeys[i]]; const Val& val = key.m_val; fprintf(f, "%u", i); for (uint k = 0; k < g_opts.attrs; k++) { if (k == 0) { fprintf(f, ",%d,", val.b_null); if (!val.b_null) fprintf(f, "%u", val.b); } else if (k == 1) { fprintf(f, ",%d,", val.c_null); if (!val.c_null) fprintf(f, "%.*s", val.c[0], &val.c[1]); } else if (k == 2) { fprintf(f, ",%d,", val.d_null); if (!val.d_null) fprintf(f, "%u", val.d); } else { require(false); } } fprintf(f, "\n"); } chker(fclose(f) == 0); } { BaseString::snprintf(file, sizeof(file), "%s.range.%d", g_opts.dump, g_loop); FILE* f = 0; chker((f = fopen(file, "w")) != 0); fprintf(f, "op"); for (uint j = 0; j <= 1; j++) { const char* suf = (j == 0 ? "_lo" : "_hi"); fprintf(f, ",attrs%s", suf); for (uint k = 0; k < g_opts.attrs; k++) { if (k == 0) fprintf(f, ",b_null%s,b%s", suf, suf); else if (k == 1) fprintf(f, ",c_null%s,c%s", suf, suf); else if (k == 2) fprintf(f, ",d_null%s,d%s", suf, suf); else require(false); } fprintf(f, ",side%s", suf); } fprintf(f, "\n"); for (uint i = 0; i < g_opts.ops; i++) { const Rng& rng = g_rnglist[i]; fprintf(f, "%u", i); for (uint j = 0; j <= 1; j++) { const Bnd& bnd = rng.m_bnd[j]; const Val& val = bnd.m_val; fprintf(f, ",%u", val.m_numattrs); for (uint k = 0; k < g_opts.attrs; k++) { if (k >= val.m_numattrs) fprintf(f, ",,"); else if (k == 0) { fprintf(f, ",%d,", val.b_null); if (!val.b_null) fprintf(f, "%u", val.b); } else if (k == 1) { fprintf(f, ",%d,", val.c_null); if (!val.c_null) fprintf(f, "%.*s", val.c[0], &val.c[1]); } else if (k == 2) { fprintf(f, ",%d,", val.d_null); if (!val.d_null) fprintf(f, "%u", val.d); } else { require(false); } } fprintf(f, ",%d", bnd.m_side); } fprintf(f, "\n"); } chker(fclose(f) == 0); } { BaseString::snprintf(file, sizeof(file), "%s.stat.%d", g_opts.dump, g_loop); FILE* f = 0; chker((f = fopen(file, "w")) != 0); fprintf(f, "op"); for (uint j = 0; j <= 1; j++) { const char* suf = (j == 0 ? "_scan" : "_stat"); fprintf(f, ",rir_v2%s", suf); fprintf(f, ",rir%s", suf); for (uint k = 0; k < g_opts.attrs; k++) { fprintf(f, ",rpk_%u%s", k, suf); } fprintf(f, ",empty%s", suf); if (j == 1) fprintf(f, ",rule%s", suf); } fprintf(f, "\n"); for (uint i = 0; i < g_opts.ops; i++) { const Rng& rng = g_rnglist[i]; fprintf(f, "%u", i); for (uint j = 0; j <= 1; j++) { const Stval& st = (j == 0 ? rng.m_st_scan : rng.m_st_stat); fprintf(f, ",%u", st.rir_v2); fprintf(f, ",%.2f", st.rir); for (uint k = 0; k < g_opts.attrs; k++) { fprintf(f, ",%.2f", st.rpk[k]); } fprintf(f, ",%d", st.empty); if (j == 1) fprintf(f, ",%s", st.rule); } fprintf(f, "\n"); } chker(fclose(f) == 0); } return 0; } static void finalstats() { ll0("=== summary ==="); ll0(g_sterr); } static int runtest() { ll1("sizeof Val: " << sizeof(Val)); ll1("sizeof Key: " << sizeof(Key)); ll1("sizeof Bnd: " << sizeof(Bnd)); ll1("sizeof Rng: " << sizeof(Rng)); uint seed = g_opts.seed; if (seed != 1) { // not loop number if (seed == 0) { // random seed = 2 + NdbHost_GetProcessId(); } ll0("random seed is " << seed); srand(seed); } else { ll0("random seed is " << "loop number"); } 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); allockeys(); allocranges(); chkrc(createtable() == 0); chkrc(createindex() == 0); chkrc(createNdbRecords() == 0); chkrc(definestat() == 0); chkrc(startlistener() == 0); for (g_loop = 0; g_opts.loops == 0 || g_loop < g_opts.loops; g_loop++) { ll0("=== loop " << g_loop << " ==="); uint seed = g_opts.seed; if (seed == 1) { // loop number seed = g_loop; srand(seed); } makekeys(); chkrc_break(loaddata(g_loop != 0) == 0); makeranges(); chkrc_break(scanranges() == 0); chkrc_break(updatestat() == 0); chkrc_break(runlistener() == 0); chkrc_break(readstat() == 0); chkrc_break(queryranges() == 0); loopstats(); chkrc_break(loopdumps() == 0); } finalstats(); chkrc(stoplistener() == 0); if (!g_opts.keeptable) chkrc(droptable() == 0); freeranges(); freekeys(); return 0; } static int doconnect() { g_ncc = new Ndb_cluster_connection(); require(g_ncc != 0); chkdb(g_ncc->connect(30) == 0); g_ndb = new Ndb(g_ncc, "TEST_DB"); require(g_ndb != 0); chkdb(g_ndb->init() == 0 && g_ndb->waitUntilReady(30) == 0); g_ndb_sys = new Ndb(g_ncc, "mysql"); require(g_ndb_sys != 0); chkdb(g_ndb_sys->init() == 0 && g_ndb_sys->waitUntilReady(30) == 0); g_is = new NdbIndexStat; require(g_is != 0); return 0; } static void dodisconnect() { delete g_is; delete g_ndb_sys; delete g_ndb; delete g_ncc; } static struct my_option my_long_options[] = { NDB_STD_OPTS("testIndexStat"), { "loglevel", NDB_OPT_NOSHORT, "Logging level in this program 0-3 (default 0)", (uchar **)&g_opts.loglevel, (uchar **)&g_opts.loglevel, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "seed", NDB_OPT_NOSHORT, "Random seed (default 0=random, 1=loop number)", (uchar **)&g_opts.seed, (uchar **)&g_opts.seed, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "loops", NDB_OPT_NOSHORT, "Number of test loops (default 1, 0=forever)", (uchar **)&g_opts.loops, (uchar **)&g_opts.loops, 0, GET_INT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0 }, { "rows", NDB_OPT_NOSHORT, "Number of rows (default 10000)", (uchar **)&g_opts.rows, (uchar **)&g_opts.rows, 0, GET_UINT, REQUIRED_ARG, 100000, 0, 0, 0, 0, 0 }, { "ops", NDB_OPT_NOSHORT,"Number of index scans per loop (default 100)", (uchar **)&g_opts.ops, (uchar **)&g_opts.ops, 0, GET_UINT, REQUIRED_ARG, 1000, 0, 0, 0, 0, 0 }, { "nullkeys", NDB_OPT_NOSHORT, "Pct nulls in each key attribute (default 10)", (uchar **)&g_opts.nullkeys, (uchar **)&g_opts.nullkeys, 0, GET_UINT, REQUIRED_ARG, 10, 0, 0, 0, 0, 0 }, { "rpk", NDB_OPT_NOSHORT, "Avg records per full key (default 10)", (uchar **)&g_opts.rpk, (uchar **)&g_opts.rpk, 0, GET_UINT, REQUIRED_ARG, 10, 0, 0, 0, 0, 0 }, { "rpkvar", NDB_OPT_NOSHORT, "Vary rpk by factor (default 10, none 1)", (uchar **)&g_opts.rpkvar, (uchar **)&g_opts.rpkvar, 0, GET_UINT, REQUIRED_ARG, 10, 0, 0, 0, 0, 0 }, { "scanpct", NDB_OPT_NOSHORT, "Preferred max pct of total rows per scan (default 10)", (uchar **)&g_opts.scanpct, (uchar **)&g_opts.scanpct, 0, GET_UINT, REQUIRED_ARG, 5, 0, 0, 0, 0, 0 }, { "eqscans", NDB_OPT_NOSHORT, "Pct scans for partial/full equality (default 30)", (uchar **)&g_opts.eqscans, (uchar **)&g_opts.eqscans, 0, GET_UINT, REQUIRED_ARG, 50, 0, 0, 0, 0, 0 }, { "keeptable", NDB_OPT_NOSHORT, "Do not drop table at exit", (uchar **)&g_opts.keeptable, (uchar **)&g_opts.keeptable, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "abort", NDB_OPT_NOSHORT, "Dump core on any error", (uchar **)&g_opts.abort, (uchar **)&g_opts.abort, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "dump", NDB_OPT_NOSHORT, "Write CSV files name.* of keys,ranges,stats", (uchar **)&g_opts.dump, (uchar **)&g_opts.dump, 0, GET_STR, REQUIRED_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 short_usage_sub() { ndb_short_usage_sub(NULL); } static void usage() { ndbout << my_progname << ": ordered index stats test" << endl; } static int checkoptions() { chkrc(g_opts.rows != 0); chkrc(g_opts.nullkeys <= 100); chkrc(g_opts.rpk != 0); g_opts.rpk = std::min(g_opts.rpk, g_opts.rows); chkrc(g_opts.rpkvar != 0); chkrc(g_opts.scanpct <= 100); chkrc(g_opts.eqscans <= 100); // set value limits g_lim_val.all_nullable = false; g_lim_bnd.all_nullable = true; g_lim_val.b_min = g_opts.rows; g_lim_val.b_max = 2 * g_opts.rows; g_lim_bnd.b_min = 90 * g_lim_val.b_min / 100; g_lim_bnd.b_max = 110 * g_lim_val.b_max / 100; g_lim_val.c_char = "bcd"; g_lim_bnd.c_char = "abcde"; g_lim_val.d_min = 100; g_lim_val.d_max = 200; g_lim_bnd.d_min = 0; g_lim_bnd.d_max = 300; return 0; } static int docreate_stat_tables() { if (g_is->check_systables(g_ndb_sys) == 0) return 0; ll1("check_systables: " << g_is->getNdbError()); ll0("create stat tables"); chkdb(g_is->create_systables(g_ndb_sys) == 0); g_has_created_stat_tables = true; return 0; } static int dodrop_stat_tables() { if (g_has_created_stat_tables == false) return 0; ll0("drop stat tables"); chkdb(g_is->drop_systables(g_ndb_sys) == 0); return 0; } static int docreate_stat_events() { if (g_is->check_sysevents(g_ndb_sys) == 0) return 0; ll1("check_sysevents: " << g_is->getNdbError()); ll0("create stat events"); chkdb(g_is->create_sysevents(g_ndb_sys) == 0); g_has_created_stat_events = true; return 0; } static int dodrop_stat_events() { if (g_has_created_stat_events == false) return 0; ll0("drop stat events"); chkdb(g_is->drop_sysevents(g_ndb_sys) == 0); return 0; } static int docreate_sys_objects() { require(g_is != 0 && g_ndb_sys != 0); chkrc(docreate_stat_tables() == 0); chkrc(docreate_stat_events() == 0); return 0; } static int dodrop_sys_objects() { require(g_is != 0 && g_ndb_sys != 0); chkrc(dodrop_stat_events() == 0); chkrc(dodrop_stat_tables() == 0); return 0; } int main(int argc, char** argv) { ndb_init(); my_progname = strchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; uint i; ndbout << my_progname; for (i = 1; i < (uint)argc; i++) ndbout << " " << argv[i]; ndbout << endl; int ret; ndb_opt_set_usage_funcs(short_usage_sub, usage); ret = handle_options(&argc, &argv, my_long_options, ndb_std_get_one_option); if (ret != 0 || argc != 0) { ll0("wrong args"); return NDBT_ProgramExit(NDBT_WRONGARGS); } if (checkoptions() == -1) { ll0("invalid args"); return NDBT_ProgramExit(NDBT_WRONGARGS); } if (doconnect() == -1) { ll0("connect failed"); return NDBT_ProgramExit(NDBT_FAILED); } if (docreate_sys_objects() == -1) { ll0("failed to check or create stat tables and events"); goto failed; } if (runtest() == -1) { ll0("test failed"); goto failed; } if (dodrop_sys_objects() == -1) { ll0("failed to drop created stat tables or events"); goto failed; } dodisconnect(); return NDBT_ProgramExit(NDBT_OK); failed: (void)dodrop_sys_objects(); dodisconnect(); return NDBT_ProgramExit(NDBT_FAILED); }