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= &ic;
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