1 /*
2 Copyright (C) 2004-2006, 2008 MySQL AB, 2008, 2009 Sun Microsystems, Inc.
3 All rights reserved. Use is subject to license terms.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation. The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License, version 2.0, for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 #include <NDBT.hpp>
27 #include <NDBT_Test.hpp>
28 #include <HugoTransactions.hpp>
29 #include <UtilTransactions.hpp>
30 #include <random.h>
31 #include <getarg.h>
32
33 struct Parameter {
34 const char * name;
35 unsigned value;
36 unsigned min;
37 unsigned max;
38 };
39
40 #define P_BATCH 0
41 #define P_PARRA 1
42 #define P_LOCK 2
43 #define P_FILT 3
44 #define P_BOUND 4
45 #define P_ACCESS 5
46 #define P_FETCH 6
47 #define P_ROWS 7
48 #define P_LOOPS 8
49 #define P_CREATE 9
50 #define P_MULTI 11
51
52 #define P_MAX 12
53
54 /* Note that this tool can only be run against Hugo tables with an integer
55 * primary key
56 */
57
58 static
59 Parameter
60 g_paramters[] = {
61 { "batch", 0, 0, 1 }, // 0, 15
62 { "parallelism", 0, 0, 1 }, // 0, 1
63 { "lock", 0, 0, 2 }, // read, exclusive, dirty
64 { "filter", 0, 0, 3 }, // Use ScanFilter to return : all, none, 1, 100
65 { "range", 0, 0, 3 }, // Use IndexBounds to return : all, none, 1, 100
66 // For range==3, Multiple index scans are used with a number of ranges specified
67 // per scan (Number is defined by multi read range.
68 { "access", 0, 0, 2 }, // Table, Index or Ordered Index scan
69 { "fetch", 0, 0, 1 }, // nextResult fetchAllowed. No, yes
70 { "size", 1000000, 1, ~0 }, // Num rows to operate on
71 { "iterations", 3, 1, ~0 }, // Num times to repeat tests
72 { "create_drop", 1, 0, 2 }, // Whether to recreate the table
73 { "data", 1, 0, 1 }, // Ignored currently
74 { "multi read range", 1000, 1, ~0 } // Number of ranges to use in MRR access (range=3)
75 };
76
77 static Ndb* g_ndb = 0;
78 static const NdbDictionary::Table * g_table;
79 static const NdbDictionary::Index * g_index;
80 static char g_tablename[256];
81 static char g_indexname[256];
82 static const NdbRecord * g_table_record;
83 static const NdbRecord * g_index_record;
84
85 int create_table();
86 int run_scan();
87
88 int
main(int argc,const char ** argv)89 main(int argc, const char** argv){
90 ndb_init();
91 int verbose = 1;
92 int optind = 0;
93
94 struct getargs args[1+P_MAX] = {
95 { "verbose", 'v', arg_flag, &verbose, "Print verbose status", "verbose" }
96 };
97 const int num_args = 1 + P_MAX;
98 int i;
99 for(i = 0; i<P_MAX; i++){
100 args[i+1].long_name = g_paramters[i].name;
101 args[i+1].short_name = * g_paramters[i].name;
102 args[i+1].type = arg_integer;
103 args[i+1].value = &g_paramters[i].value;
104 BaseString tmp;
105 tmp.assfmt("min: %d max: %d", g_paramters[i].min, g_paramters[i].max);
106 args[i+1].help = strdup(tmp.c_str());
107 args[i+1].arg_help = 0;
108 }
109
110 if(getarg(args, num_args, argc, argv, &optind)) {
111 arg_printusage(args, num_args, argv[0], "tabname1 tabname2 ...");
112 return NDBT_WRONGARGS;
113 }
114
115 myRandom48Init((long)NdbTick_CurrentMillisecond());
116
117 Ndb_cluster_connection con;
118 if(con.connect(12, 5, 1))
119 {
120 return NDBT_ProgramExit(NDBT_FAILED);
121 }
122
123 g_ndb = new Ndb(&con, "TEST_DB");
124 if(g_ndb->init() != 0){
125 g_err << "init() failed" << endl;
126 goto error;
127 }
128 if(g_ndb->waitUntilReady() != 0){
129 g_err << "Wait until ready failed" << endl;
130 goto error;
131 }
132 for(i = optind; i<argc; i++){
133 const char * T = argv[i];
134 g_info << "Testing " << T << endl;
135 BaseString::snprintf(g_tablename, sizeof(g_tablename), "%s", T);
136 BaseString::snprintf(g_indexname, sizeof(g_indexname), "IDX_%s", T);
137 if(create_table())
138 goto error;
139 if(g_paramters[P_CREATE].value != 2 && run_scan())
140 goto error;
141 }
142
143 if(g_ndb) delete g_ndb;
144 return NDBT_OK;
145 error:
146 if(g_ndb) delete g_ndb;
147 return NDBT_FAILED;
148 }
149
150 int
create_table()151 create_table(){
152 NdbDictionary::Dictionary* dict = g_ndb->getDictionary();
153 assert(dict);
154 if(g_paramters[P_CREATE].value){
155 g_ndb->getDictionary()->dropTable(g_tablename);
156 const NdbDictionary::Table * pTab = NDBT_Tables::getTable(g_tablename);
157 assert(pTab);
158 NdbDictionary::Table copy = * pTab;
159 copy.setLogging(false);
160 if(dict->createTable(copy) != 0){
161 g_err << "Failed to create table: " << g_tablename << endl;
162 return -1;
163 }
164
165 NdbDictionary::Index x(g_indexname);
166 x.setTable(g_tablename);
167 x.setType(NdbDictionary::Index::OrderedIndex);
168 x.setLogging(false);
169 for (unsigned k = 0; k < (unsigned) copy.getNoOfColumns(); k++){
170 if(copy.getColumn(k)->getPrimaryKey()){
171 x.addColumnName(copy.getColumn(k)->getName());
172 }
173 }
174
175 if(dict->createIndex(x) != 0){
176 g_err << "Failed to create index: " << endl;
177 return -1;
178 }
179 }
180 g_table = dict->getTable(g_tablename);
181 g_index = dict->getIndex(g_indexname, g_tablename);
182 assert(g_table);
183 assert(g_index);
184
185 /* Obtain NdbRecord instances for the table and index */
186 {
187 NdbDictionary::RecordSpecification spec[ NDB_MAX_ATTRIBUTES_IN_TABLE ];
188
189 Uint32 offset=0;
190 Uint32 cols= g_table->getNoOfColumns();
191 for (Uint32 colNum=0; colNum<cols; colNum++)
192 {
193 const NdbDictionary::Column* col= g_table->getColumn(colNum);
194 Uint32 colLength= col->getLength();
195
196 spec[colNum].column= col;
197 spec[colNum].offset= offset;
198
199 offset+= colLength;
200
201 spec[colNum].nullbit_byte_offset= offset++;
202 spec[colNum].nullbit_bit_in_byte= 0;
203 }
204
205 g_table_record= dict->createRecord(g_table,
206 &spec[0],
207 cols,
208 sizeof(NdbDictionary::RecordSpecification));
209
210 assert(g_table_record);
211 }
212 {
213 NdbDictionary::RecordSpecification spec[ NDB_MAX_ATTRIBUTES_IN_TABLE ];
214
215 Uint32 offset=0;
216 Uint32 cols= g_index->getNoOfColumns();
217 for (Uint32 colNum=0; colNum<cols; colNum++)
218 {
219 /* Get column from the underlying table */
220 // TODO : Add this mechanism to dict->createRecord
221 // TODO : Add NdbRecord queryability methods so that an NdbRecord can
222 // be easily built and later used to read out data.
223 const NdbDictionary::Column* col=
224 g_table->getColumn(g_index->getColumn(colNum)->getName());
225 Uint32 colLength= col->getLength();
226
227 spec[colNum].column= col;
228 spec[colNum].offset= offset;
229
230 offset+= colLength;
231
232 spec[colNum].nullbit_byte_offset= offset++;
233 spec[colNum].nullbit_bit_in_byte= 0;
234 }
235
236 g_index_record= dict->createRecord(g_index,
237 &spec[0],
238 cols,
239 sizeof(NdbDictionary::RecordSpecification));
240
241 assert(g_index_record);
242 }
243
244
245 if(g_paramters[P_CREATE].value)
246 {
247 int rows = g_paramters[P_ROWS].value;
248 HugoTransactions hugoTrans(* g_table);
249 if (hugoTrans.loadTable(g_ndb, rows)){
250 g_err.println("Failed to load %s with %d rows",
251 g_table->getName(), rows);
252 return -1;
253 }
254 }
255
256 return 0;
257 }
258
259 inline
err(NdbError e)260 void err(NdbError e){
261 ndbout << e << endl;
262 }
263
264 int
setEqBound(NdbIndexScanOperation * isop,const NdbRecord * key_record,Uint32 value,Uint32 rangeNum)265 setEqBound(NdbIndexScanOperation *isop,
266 const NdbRecord *key_record,
267 Uint32 value,
268 Uint32 rangeNum)
269 {
270 Uint32 space[2];
271 space[0]= value;
272 space[1]= 0; // Null bit set to zero.
273
274 NdbIndexScanOperation::IndexBound ib;
275 ib.low_key= ib.high_key= (char*) &space;
276 ib.low_key_count= ib.high_key_count= 1;
277 ib.low_inclusive= ib.high_inclusive= true;
278 ib.range_no= rangeNum;
279
280 return isop->setBound(key_record, ib);
281 }
282
283 int
run_scan()284 run_scan(){
285 int iter = g_paramters[P_LOOPS].value;
286 NDB_TICKS start1, stop;
287 int sum_time= 0;
288
289 Uint32 sample_rows = 0;
290 int tot_rows = 0;
291 NDB_TICKS sample_start = NdbTick_CurrentMillisecond();
292
293 Uint32 tot = g_paramters[P_ROWS].value;
294
295 if(g_paramters[P_BOUND].value >= 2 || g_paramters[P_FILT].value == 2)
296 iter *= g_paramters[P_ROWS].value;
297
298 NdbScanOperation * pOp = 0;
299 NdbIndexScanOperation * pIOp = 0;
300 NdbConnection * pTrans = 0;
301 int check = 0;
302
303 for(int i = 0; i<iter; i++){
304 start1 = NdbTick_CurrentMillisecond();
305 pTrans = pTrans ? pTrans : g_ndb->startTransaction();
306 if(!pTrans){
307 g_err << "Failed to start transaction" << endl;
308 err(g_ndb->getNdbError());
309 return -1;
310 }
311
312 int par = g_paramters[P_PARRA].value;
313 int bat = g_paramters[P_BATCH].value;
314 NdbScanOperation::LockMode lm;
315 switch(g_paramters[P_LOCK].value){
316 case 0:
317 lm = NdbScanOperation::LM_CommittedRead;
318 break;
319 case 1:
320 lm = NdbScanOperation::LM_Read;
321 break;
322 case 2:
323 lm = NdbScanOperation::LM_Exclusive;
324 break;
325 default:
326 abort();
327 }
328
329 NdbScanOperation::ScanOptions options;
330 bzero(&options, sizeof(options));
331
332 options.optionsPresent=
333 NdbScanOperation::ScanOptions::SO_SCANFLAGS |
334 NdbScanOperation::ScanOptions::SO_PARALLEL |
335 NdbScanOperation::ScanOptions::SO_BATCH;
336
337 bool ord= g_paramters[P_ACCESS].value == 2;
338 bool mrr= (g_paramters[P_ACCESS].value != 0) &&
339 (g_paramters[P_BOUND].value == 3);
340
341 options.scan_flags|=
342 ( ord ? NdbScanOperation::SF_OrderBy:0 ) |
343 ( mrr ? NdbScanOperation::SF_MultiRange:0 );
344 options.parallel= par;
345 options.batch= bat;
346
347 switch(g_paramters[P_FILT].value){
348 case 0: // All
349 break;
350 case 1: // None
351 break;
352 case 2: // 1 row
353 default: {
354 assert(g_table->getNoOfPrimaryKeys() == 1); // only impl. so far
355 abort();
356 #if 0
357 int tot = g_paramters[P_ROWS].value;
358 int row = rand() % tot;
359 NdbInterpretedCode* ic= new NdbInterpretedCode(g_table);
360 NdbScanFilter filter(ic);
361 filter.begin(NdbScanFilter::AND);
362 filter.eq(0, row);
363 filter.end();
364
365 options.scan_flags|= NdbScanOperation::SF_Interpreted;
366 options.interpretedCode= ⁣
367 break;
368 #endif
369 }
370 }
371
372 if(g_paramters[P_ACCESS].value == 0){
373 pOp = pTrans->scanTable(g_table_record,
374 lm,
375 NULL, // Mask
376 &options,
377 sizeof(NdbScanOperation::ScanOptions));
378 assert(pOp);
379 } else {
380 pOp= pIOp= pTrans->scanIndex(g_index_record,
381 g_table_record,
382 lm,
383 NULL, // Mask
384 NULL, // First IndexBound
385 &options,
386 sizeof(NdbScanOperation::ScanOptions));
387 if (pIOp == NULL)
388 {
389 err(pTrans->getNdbError());
390 abort();
391 }
392
393 assert(pIOp);
394
395 switch(g_paramters[P_BOUND].value){
396 case 0: // All
397 break;
398 case 1: // None
399 check= setEqBound(pIOp, g_index_record, 0, 0);
400 assert(check == 0);
401 break;
402 case 2: { // 1 row
403 default:
404 assert(g_table->getNoOfPrimaryKeys() == 1); // only impl. so far
405 int tot = g_paramters[P_ROWS].value;
406 int row = rand() % tot;
407
408 check= setEqBound(pIOp, g_index_record, row, 0);
409 assert(check == 0);
410 break;
411 }
412 case 3: { // read multi
413 int multi = g_paramters[P_MULTI].value;
414 int tot = g_paramters[P_ROWS].value;
415 int rangeStart= i;
416 for(; multi > 0 && i < iter; --multi, i++)
417 {
418 int row = rand() % tot;
419 /* Set range num relative to this set of bounds */
420 check= setEqBound(pIOp, g_index_record, row, i- rangeStart);
421 if (check != 0)
422 {
423 err(pIOp->getNdbError());
424 abort();
425 }
426 assert(check == 0);
427 }
428 break;
429 }
430 }
431 }
432 assert(pOp);
433
434 assert(check == 0);
435
436 int rows = 0;
437 check = pTrans->execute(NoCommit);
438 assert(check == 0);
439 int fetch = g_paramters[P_FETCH].value;
440
441 const char * result_row_ptr;
442
443 while((check = pOp->nextResult(&result_row_ptr, true, false)) == 0){
444 do {
445 rows++;
446 } while(!fetch && ((check = pOp->nextResult(&result_row_ptr, false, false)) == 0));
447 if(check == -1){
448 err(pTrans->getNdbError());
449 return -1;
450 }
451 assert(check == 2);
452 }
453
454 if(check == -1){
455 err(pTrans->getNdbError());
456 return -1;
457 }
458 assert(check == 1);
459
460 pTrans->close();
461 pTrans = 0;
462
463 stop = NdbTick_CurrentMillisecond();
464
465 int time_passed= (int)(stop - start1);
466 sample_rows += rows;
467 sum_time+= time_passed;
468 tot_rows+= rows;
469
470 if(sample_rows >= tot)
471 {
472 int sample_time = (int)(stop - sample_start);
473 g_info << "Found " << sample_rows << " rows" << endl;
474 g_err.println("Time: %d ms = %u rows/sec", sample_time,
475 (1000*sample_rows)/sample_time);
476 sample_rows = 0;
477 sample_start = stop;
478 }
479 }
480
481 g_err.println("Avg time: %d ms = %u rows/sec", sum_time/tot_rows,
482 (1000*tot_rows)/sum_time);
483 return 0;
484 }
485