1 /* -*- mode: java; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2  *  vim:expandtab:shiftwidth=4:tabstop=4:smarttab:
3  *
4  *  Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License, version 2.0,
8  *  as published by the Free Software Foundation.
9  *
10  *  This program is also distributed with certain software (including
11  *  but not limited to OpenSSL) that is licensed under separate terms,
12  *  as designated in a particular file or component or in included license
13  *  documentation.  The authors of MySQL hereby grant you an additional
14  *  permission to link the program and your derivative works with the
15  *  separately licensed software that they have included with MySQL.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License, version 2.0, for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
25  */
26 
27 package com.mysql.cluster.crund;
28 
29 import java.util.ArrayList;
30 import java.util.EnumSet;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 
35 /**
36  * This class benchmarks standard database operations over a series
37  * of transactions on an increasing data set.
38  * <p>
39  * The abstract database operations are variations of: Create,
40  * Read, Update, Navigate, and Delete -- hence, the benchmark's name: CRUND.
41  * <p>
42  * The actual operations are defined by subclasses to allow measuring the
43  * operation performance across different datastore implementations.
44  *
45  * @see <a href="http://www.urbandictionary.com/define.php?term=crund">Urban Dictionary: crund</a>
46  * <ol>
47  * <li> used to debase people who torture others with their illogical
48  * attempts to make people laugh;
49  * <li> reference to cracking obsolete jokes;
50  * <li> a dance form;
51  * <li> to hit hard or smash.
52  * </ol>
53  */
54 abstract public class CrundDriver extends Driver {
55 
56     enum XMode { INDY, EACH, BULK }
57 
58     // benchmark settings
59     protected final EnumSet< XMode > xMode = EnumSet.noneOf(XMode.class);
60     protected boolean renewConnection;
61     protected boolean renewOperations;
62     protected boolean logSumOfOps;
63     protected boolean allowExtendedPC;
64     protected int nOpsStart;
65     protected int nOpsEnd;
66     protected int nOpsScale;
67     protected int maxVarbinaryBytes;
68     protected int maxVarcharChars;
69     protected int maxBlobBytes;
70     protected int maxTextChars;
71     protected final Set<String> exclude = new HashSet<String>();
72     protected final Set<String> include = new HashSet<String>();
73 
74     // the name of the test currently being performed
75     protected String operationName;
76 
77     /** The errors for the current test */
78     protected StringBuilder errorBuffer;
79 
80     /** Throw an exception if an error is reported */
81     protected boolean failOnError;
82 
83     // ----------------------------------------------------------------------
84     // benchmark intializers/finalizers
85     // ----------------------------------------------------------------------
86 
init()87     protected void init() throws Exception {
88         out.println();
89         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
90         out.println("initializing benchmark ...");
91         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
92         super.init();
93 
94 /*
95         // XXX support multiple load instances
96         // initialize load classes
97         if (doJdbc) {
98             assert (jdbcLoad == null);
99             jdbcLoad = new JdbcLoad(this);
100             jdbcLoad.init();
101         }
102         if (doClusterj) {
103             assert (clusterjLoad == null);
104             clusterjLoad = new ClusterjLoad(this);
105             clusterjLoad.init();
106         }
107         if (doNdbjtie) {
108             assert (ndbjtieLoad == null);
109             ndbjtieLoad = new NdbjtieLoad(this);
110             ndbjtieLoad.init();
111         }
112 */
113         initLoad();
114     }
115 
close()116     protected void close() throws Exception {
117         out.println();
118         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
119         out.println("closing benchmark ...");
120         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
121 
122 /*
123         // XXX support multiple load instances
124         // close load classes
125         if (doJdbc) {
126             assert (jdbcLoad != null);
127             jdbcLoad.close();
128             jdbcLoad = null;
129         }
130         if (doClusterj) {
131             assert (clusterjLoad != null);
132             clusterjLoad.close();
133             clusterjLoad = null;
134         }
135         if (doNdbjtie) {
136             assert (ndbjtieLoad != null);
137             ndbjtieLoad.close();
138             ndbjtieLoad = null;
139         }
140 */
141         closeLoad();
142 
143         super.close();
144     }
145 
initProperties()146     protected void initProperties() {
147         super.initProperties();
148 
149         out.print("setting crund properties ...");
150 
151         final StringBuilder msg = new StringBuilder();
152         final String eol = System.getProperty("line.separator");
153 
154         // parse execution modes
155         final String[] xm = props.getProperty("xMode", "").split(",");
156         for (int i = 0; i < xm.length; i++) {
157             if (!"".equals(xm[i]))
158                 xMode.add(XMode.valueOf(XMode.class, xm[i]));
159         }
160 
161         renewConnection = parseBoolean("renewConnection", false);
162         renewOperations = parseBoolean("renewOperations", false);
163         logSumOfOps = parseBoolean("logSumOfOps", true);
164         allowExtendedPC = parseBoolean("allowExtendedPC", false);
165         failOnError = parseBoolean("failOnError", false);
166 
167         nOpsStart = parseInt("nOpsStart", 256);
168         if (nOpsStart < 1) {
169             msg.append("[ignored] nOpsStart:            " + nOpsStart + eol);
170             nOpsStart = 256;
171         }
172         nOpsEnd = parseInt("nOpsEnd", nOpsStart);
173         if (nOpsEnd < nOpsStart) {
174             msg.append("[ignored] nOpsEnd:              "+ nOpsEnd + eol);
175             nOpsEnd = nOpsStart;
176         }
177         nOpsScale = parseInt("nOpsScale", 2);
178         if (nOpsScale < 2) {
179             msg.append("[ignored] nOpsScale:            " + nOpsScale + eol);
180             nOpsScale = 2;
181         }
182 
183         maxVarbinaryBytes = parseInt("maxVarbinaryBytes", 100);
184         if (maxVarbinaryBytes < 0) {
185             msg.append("[ignored] maxVarbinaryBytes:    "
186                        + maxVarbinaryBytes + eol);
187             maxVarbinaryBytes = 100;
188         }
189         maxVarcharChars = parseInt("maxVarcharChars", 100);
190         if (maxVarcharChars < 0) {
191             msg.append("[ignored] maxVarcharChars:      "
192                        + maxVarcharChars + eol);
193             maxVarcharChars = 100;
194         }
195 
196         maxBlobBytes = parseInt("maxBlobBytes", 1000);
197         if (maxBlobBytes < 0) {
198             msg.append("[ignored] maxBlobBytes:         "
199                        + maxBlobBytes + eol);
200             maxBlobBytes = 1000;
201         }
202         maxTextChars = parseInt("maxTextChars", 1000);
203         if (maxTextChars < 0) {
204             msg.append("[ignored] maxTextChars:         "
205                        + maxTextChars + eol);
206             maxTextChars = 1000;
207         }
208 
209         // initialize exclude set
210         final String[] excludeProperty = props.getProperty("exclude", "").split(",");
211         for (int i = 0; i < excludeProperty.length; i++) {
212             String excludeTest = excludeProperty[i];
213             if (!excludeTest.isEmpty()) {
214                 exclude.add(excludeTest);
215             }
216         }
217 
218         // initialize include set
219         final String[] includeProperty = props.getProperty("include", "").split(",");
220         for (int i = 0; i < includeProperty.length; ++i) {
221             String includeTest = includeProperty[i];
222             if (!includeTest.isEmpty()) {
223                 include.add(includeTest);
224             }
225         }
226 
227         if (msg.length() == 0) {
228             out.println("    [ok: "
229                         + "nOps=" + nOpsStart + ".." + nOpsEnd + "]");
230         } else {
231             out.println();
232             out.print(msg.toString());
233         }
234     }
235 
printProperties()236     protected void printProperties() {
237         super.printProperties();
238 
239         out.println();
240         out.println("crund settings ...");
241         out.println("xMode:                          " + xMode);
242         out.println("renewConnection:                " + renewConnection);
243         out.println("renewOperations:                " + renewOperations);
244         out.println("logSumOfOps:                    " + logSumOfOps);
245         out.println("allowExtendedPC:                " + allowExtendedPC);
246         out.println("nOpsStart:                      " + nOpsStart);
247         out.println("nOpsEnd:                        " + nOpsEnd);
248         out.println("nOpsScale:                      " + nOpsScale);
249         out.println("maxVarbinaryBytes:              " + maxVarbinaryBytes);
250         out.println("maxVarcharChars:                " + maxVarcharChars);
251         out.println("maxBlobBytes:                   " + maxBlobBytes);
252         out.println("maxTextChars:                   " + maxTextChars);
253         out.println("exclude:                        " + exclude);
254         out.println("include:                        " + include);
255     }
256 
257     // ----------------------------------------------------------------------
258     // benchmark operations
259     // ----------------------------------------------------------------------
260 
261     // XXX move to generic load class
262     // a database operation to be benchmarked
263     protected abstract class Op {
264         final protected String name;
265 
Op(String name)266         public Op(String name) { this.name = name; }
267 
getName()268         public String getName() { return name; }
269 
run(int nOps)270         public abstract void run(int nOps) throws Exception;
271     };
272 
273     // XXX move to generic load class
274     // the list of database operations to be benchmarked
275     protected final List<Op> ops = new ArrayList<Op>();
276 
277     // manages list of database operations
initOperations()278     abstract protected void initOperations() throws Exception;
closeOperations()279     abstract protected void closeOperations() throws Exception;
280 
runTests()281     protected void runTests() throws Exception {
282         initConnections();
283         runLoads();
284         closeConnections();
285     }
286 
runLoads()287     protected void runLoads() throws Exception {
288 /*
289         // XXX support multiple load instances
290         if (doJdbc)
291             runLoads(jdbcLoad);
292         if (doClusterj)
293             runLoads(clusterjLoad);
294         if (doNdbjtie)
295             runLoads(ndbjtieLoad);
296 */
297         runLoad();
298     }
299 
runLoad()300     protected void runLoad() throws Exception {
301         assert (nOpsStart <= nOpsEnd && nOpsScale > 1);
302         for (int i = nOpsStart; i <= nOpsEnd; i *= nOpsScale) {
303             try {
304                 out.println();
305                 out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
306                 // XXX support multiple load instances
307                 //out.print("running load nOps = " + i + " on "
308                 //          + load.getDescriptor());
309                 out.println("running load [" + i + " nOps] on " + descr);
310                 out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
311                 runSeries(i);
312             } catch (Exception ex) {
313                 // already in rollback for database/orm exceptions
314                 throw ex;
315             }
316         }
317     }
318 
runSeries(int nOps)319     protected void runSeries(int nOps) throws Exception {
320         if (nRuns == 0)
321             return; // nothing to do
322 
323         for (int i = 1; i <= nRuns; i++) {
324             out.println();
325             out.println("------------------------------------------------------------");
326             out.println("run " + i + " of " + nRuns + " [" + nOps + " nOps]");
327             out.println("------------------------------------------------------------");
328             // XXX runLoad(load);
329             runLoad(nOps);
330         }
331 
332         // XXX support multiple load instances
333         //writeLogBuffers(load.getDescriptor());
334         writeLogBuffers(descr);
335         clearLogBuffers();
336     }
337 
runLoad(int nOps)338     protected void runLoad(int nOps) throws Exception {
339         // log buffers
340         if (logRealTime) {
341             rtimes.append(nOps);
342             ta = 0;
343         }
344         if (logMemUsage) {
345             musage.append(nOps);
346             ma = 0;
347         }
348 
349         // pre-run cleanup
350         if (renewConnection) {
351             // XXX move to generic load class?
352             closeOperations();
353             closeConnection();
354             initConnection();
355             initOperations();
356         } else if (renewOperations) {
357             closeOperations();
358             initOperations();
359         }
360         clearData();
361 
362         runSequence(nOps);
363 
364         if (logSumOfOps) {
365             out.println();
366             out.println("total");
367             if (logRealTime) {
368                 out.println("tx real time                    " + ta
369                             + "\tms");
370             }
371             if (logMemUsage) {
372                 out.println("net mem usage                   "
373                             + (ma >= 0 ? "+" : "") + ma
374                             + "\tKiB");
375             }
376         }
377 
378         // log buffers
379         if (logHeader) {
380             if (logSumOfOps) {
381                 header.append("\ttotal");
382             }
383             logHeader = false;
384         }
385         if (logRealTime) {
386             if (logSumOfOps) {
387                 rtimes.append("\t" + ta);
388             }
389             rtimes.append(endl);
390         }
391         if (logMemUsage) {
392             if (logSumOfOps) {
393                 musage.append("\t" + ma);
394             }
395             musage.append(endl);
396         }
397     }
398 
399     // XXX move to generic load class
runSequence(int nOps)400     protected void runSequence(int nOps) throws Exception {
401         for (Op op : ops) {
402             // pre-tx cleanup
403             if (!allowExtendedPC) {
404                 // effectively prevent caching beyond Tx scope by clearing
405                 // any data/result caches before the next transaction
406                 clearPersistenceContext();
407             }
408             runOperation(op, nOps);
409             reportErrors();
410         }
411     }
412 
413     // XXX move to generic load class
runOperation(Op op, int nOps)414     protected void runOperation(Op op, int nOps) throws Exception {
415         operationName = op.getName();
416         // if there is an include list and this test is included, or
417         // there is not an include list and this test is not excluded
418         if ((include.size() != 0 && include.contains(operationName))
419                 || (include.size() == 0 && !exclude.contains(operationName))) {
420             begin(operationName);
421             op.run(nOps);
422             finish(operationName);
423         }
424     }
425 
426     /** Add an error to the existing errors */
appendError(String where)427     protected void appendError(String where) {
428         if (errorBuffer == null) {
429             errorBuffer = new StringBuilder();
430         }
431         errorBuffer.append("Error in operation ");
432         errorBuffer.append(operationName);
433         errorBuffer.append(": ");
434         errorBuffer.append(where);
435         errorBuffer.append('\n');
436     }
437 
438     /** Report errors and reset the error buffer */
reportErrors()439     protected void reportErrors() {
440         if (errorBuffer != null) {
441             if (failOnError) {
442                 throw new RuntimeException(errorBuffer.toString());
443             }
444             System.out.println(errorBuffer.toString());
445             errorBuffer = null;
446         }
447     }
448     // XXX move to generic load class
449     // reports an error if a condition is not met
verify(boolean cond)450     static protected final void verify(boolean cond) {
451         //assert (cond);
452         if (!cond)
453             throw new RuntimeException("data verification failed.");
454     }
455 
456     // XXX move to generic load class
verify(int exp, int act)457     static protected final void verify(int exp, int act) {
458         if (exp != act)
459             throw new RuntimeException("data verification failed:"
460                                        + " expected = " + exp
461                                        + ", actual = " + act);
462     }
463 
464     // XXX move to generic load class
verify(String where, int exp, int act)465     protected final void verify(String where, int exp, int act) {
466         if (exp != act)
467             appendError(" data verification failed:"
468                     + " expected = " + exp
469                     + ", actual = " + act);
470     }
471 
472     // XXX move to generic load class
verify(String exp, String act)473     static protected final void verify(String exp, String act) {
474         if ((exp == null && act != null)
475             || (exp != null && !exp.equals(act)))
476             throw new RuntimeException("data verification failed:"
477                                        + " expected = '" + exp + "'"
478                                        + ", actual = '" + act + "'");
479     }
480 
481     // ----------------------------------------------------------------------
482     // helpers
483     // ----------------------------------------------------------------------
484 
485     // XXX move to generic load class
myString(int n)486     static final protected String myString(int n) {
487         final StringBuilder s = new StringBuilder();
488         switch (n) {
489         case 1:
490             s.append('i');
491             break;
492         case 2:
493             for (int i = 0; i < 10; i++) s.append('x');
494             break;
495         case 3:
496             for (int i = 0; i < 100; i++) s.append('c');
497             break;
498         case 4:
499             for (int i = 0; i < 1000; i++) s.append('m');
500             break;
501         case 5:
502             for (int i = 0; i < 10000; i++) s.append('X');
503             break;
504         case 6:
505             for (int i = 0; i < 100000; i++) s.append('C');
506             break;
507         case 7:
508             for (int i = 0; i < 1000000; i++) s.append('M');
509             break;
510         default:
511             throw new IllegalArgumentException("unsupported 10**n = " + n);
512         }
513         return s.toString();
514     }
515 
516     // XXX move to generic load class
myBytes(String s)517     static final protected byte[] myBytes(String s) {
518         final char[] c = s.toCharArray();
519         final int n = c.length;
520         final byte[] b = new byte[n];
521         for (int i = 0; i < n; i++) b[i] = (byte)c[i];
522         return b;
523     }
524 
525     // XXX move to generic load class
526     // some string and byte constants
527     static final protected String string1 = myString(1);
528     static final protected String string2 = myString(2);
529     static final protected String string3 = myString(3);
530     static final protected String string4 = myString(4);
531     static final protected String string5 = myString(5);
532     static final protected String string6 = myString(6);
533     static final protected String string7 = myString(7);
534     static final protected byte[] bytes1 = myBytes(string1);
535     static final protected byte[] bytes2 = myBytes(string2);
536     static final protected byte[] bytes3 = myBytes(string3);
537     static final protected byte[] bytes4 = myBytes(string4);
538     static final protected byte[] bytes5 = myBytes(string5);
539     static final protected byte[] bytes6 = myBytes(string6);
540     static final protected byte[] bytes7 = myBytes(string7);
541     static final protected String[] strings
542         = { string1, string2, string3, string4, string5, string6, string7 };
543     static final protected byte[][] bytes
544         = { bytes1, bytes2, bytes3, bytes4, bytes5, bytes6, bytes7 };
545 
546     // ----------------------------------------------------------------------
547     // datastore operations
548     // ----------------------------------------------------------------------
549 
initConnections()550     protected void initConnections() throws Exception {
551         out.println();
552         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
553         out.println("initializing connections ...");
554         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
555 
556 /*
557         // XXX support multiple load instances
558         if (doJdbc) {
559             assert (jdbcLoad != null);
560             jdbcLoad.initConnection();
561         }
562         if (doClusterj) {
563             assert (clusterjLoad != null);
564             clusterjLoad.initConnection();
565         }
566         if (doNdbjtie) {
567             assert (ndbjtieLoad != null);
568             ndbjtieLoad.initConnection();
569         }
570 */
571         initConnection();
572 
573         // XXX move to generic load class
574         initOperations();
575     }
576 
closeConnections()577     protected void closeConnections() throws Exception {
578         out.println();
579         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
580         out.println("closing connections ...");
581         out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
582 
583         // XXX move to generic load class
584         closeOperations();
585 
586         closeConnection();
587 /*
588         // XXX support multiple load instances
589         if (doJdbc) {
590             assert (jdbcLoad != null);
591             jdbcLoad.closeConnection();
592         }
593         if (doClusterj) {
594             assert (clusterjLoad != null);
595             clusterjLoad.closeConnection();
596         }
597         if (doNdbjtie) {
598             assert (ndbjtieLoad != null);
599             ndbjtieLoad.closeConnection();
600         }
601 */
602     }
603 
initLoad()604     abstract protected void initLoad() throws Exception;
closeLoad()605     abstract protected void closeLoad() throws Exception;
initConnection()606     abstract protected void initConnection() throws Exception;
closeConnection()607     abstract protected void closeConnection() throws Exception;
clearPersistenceContext()608     abstract protected void clearPersistenceContext() throws Exception;
clearData()609     abstract protected void clearData() throws Exception;
610 }
611