/* Copyright (c) 2006, 2013, 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 #include #include #include #include #include #include #include #undef min #undef max #define min(a, b) ((a) <= (b) ? (a) : (b)) #define max(a, b) ((a) >= (b) ? (a) : (b)) struct Opts { int loglevel; uint seed; uint attrs; uint loops; uint rows; uint ops; uint nullkeys; uint rpk; uint rpkvar; uint scanpct; uint eqscans; my_bool keeptable; my_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 const Uint32 g_ndbrecord_bytes=sizeof(my_record); 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 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, 0); } 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 min(m_bnd[0].m_val.m_numattrs, m_bnd[1].m_val.m_numattrs); } uint Rng::maxattrs() const { return 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 = max(lim[0], 0); const int hi = 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 + (ushort)getpid(); } 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(loaddata(g_loop != 0) == 0); makeranges(); chkrc(scanranges() == 0); chkrc(updatestat() == 0); chkrc(runlistener() == 0); chkrc(readstat() == 0); chkrc(queryranges() == 0); loopstats(); chkrc(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 } }; const char* load_default_groups[] = { "mysql_cluster", 0 }; static void short_usage_sub() { ndb_short_usage_sub(NULL); } static void usage() { ndbout << my_progname << ": ordered index stats test" << endl; ndb_usage(short_usage_sub, load_default_groups, my_long_options); } static int checkoptions() { chkrc(g_opts.rows != 0); chkrc(g_opts.nullkeys <= 100); chkrc(g_opts.rpk != 0); g_opts.rpk = 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); }