KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > requestplayer > ClientEmulator


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2002-2004 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Contact: c-jdbc@objectweb.org
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation; either version 2.1 of the License, or any later
10  * version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20  *
21  * Initial developer(s): Emmanuel Cecchet.
22  * Contributor(s): Julie Marguerite, Mathieu Peltier.
23  */

24
25 package org.objectweb.cjdbc.requestplayer;
26
27 import java.io.BufferedReader JavaDoc;
28 import java.io.FileReader JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.sql.Connection JavaDoc;
31 import java.sql.DriverManager JavaDoc;
32 import java.sql.SQLException JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.EmptyStackException JavaDoc;
35 import java.util.HashSet JavaDoc;
36 import java.util.Hashtable JavaDoc;
37 import java.util.Stack JavaDoc;
38 import java.util.StringTokenizer JavaDoc;
39
40 import org.apache.commons.cli.CommandLine;
41 import org.apache.commons.cli.CommandLineParser;
42 import org.apache.commons.cli.GnuParser;
43 import org.apache.commons.cli.HelpFormatter;
44 import org.apache.commons.cli.Option;
45 import org.apache.commons.cli.OptionGroup;
46 import org.apache.commons.cli.Options;
47 import org.apache.commons.cli.ParseException;
48 import org.objectweb.cjdbc.common.util.Stats;
49
50 /**
51  * C-JDBC client emulator. Reads SQL requests in a file and forwards them to the
52  * controller.
53  *
54  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
55  * @author <a HREF="mailto:julie.marguerite@inria.fr">Julie Marguerite </a>
56  * @author <a HREF="mailto:mathieu.peltier@inrialpes.fr">Mathieu Peltier </a>
57  * @version 1.0
58  */

59 public class ClientEmulator
60 {
61   /** Major version. */
62   public static final int MAJOR_VERSION = 1;
63
64   /** Minor version. */
65   public static final int MINOR_VERSION = 0;
66
67   /** Zero value. */
68   private static final Integer JavaDoc ZERO = new Integer JavaDoc(0);
69
70   /** Database URL. */
71   private String JavaDoc propUrl;
72
73   /** Database login. */
74   private String JavaDoc propUsername;
75
76   /** Database password. */
77   private String JavaDoc propPassword;
78
79   /** Access to the properties file. */
80   private RequestPlayerProperties requestPlayerProp = null;
81
82   /** To read the SQL requests in the file. */
83   protected BufferedReader JavaDoc sqlTrace = null;
84
85   /** Statistics concerning the <code>SELECT</code> requests. */
86   protected Stats selectStats = new Stats("Select");
87
88   /** Statistics concerning the unknown requests. */
89   protected Stats unknownStats = new Stats("Unknown");
90
91   /** Statistics concerning the <code>UPDATE</code> requests. */
92   protected Stats updateStats = new Stats("Update");
93
94   /** Statistics concerning the <code>INSERT</code> requests. */
95   protected Stats insertStats = new Stats("Insert");
96
97   /** Statistics concerning the <code>DELETE</code> requests. */
98   protected Stats deleteStats = new Stats("Delete");
99
100   /** Statistics concerning transaction begin. */
101   protected Stats beginStats = new Stats("Begin");
102
103   /** Statistics concerning transaction commit. */
104   protected Stats commitStats = new Stats("Commit");
105
106   /** Statistics concerning transaction rollback. */
107   protected Stats rollbackStats = new Stats("Rollback");
108
109   /** Statistics about get connection from driver */
110   protected Stats getConnectionStats = new Stats(
111                                                          "Get connection from driver");
112
113   /** Statistics about closing a connection */
114   protected Stats closeStats = new Stats(
115                                                          "Close connection");
116
117   /** Statistics about getting request from the log file */
118   protected Stats getRequestStats = new Stats(
119                                                          "Get requests from log file");
120
121   /** Number of requests. */
122   private int nbRequests = 0;
123
124   /** Max number of requests. */
125   private int maxRequests = 0;
126
127   /** Transaction id list. */
128   private Hashtable JavaDoc tidList = new Hashtable JavaDoc();
129
130   private HashSet JavaDoc ignoredTids = new HashSet JavaDoc();
131
132   /** Query timeout. */
133   private int timeout;
134
135   /** Type of connection management: standard, optimized or pooling. */
136   private int connectionType;
137
138   /** Stack of available connections (pool). */
139   private Stack JavaDoc freeConnections = null;
140
141   /** Connection pool size. */
142   private int poolSize;
143
144   /** Transaction id. */
145   private Integer JavaDoc transactionId;
146
147   /**
148    * Creates a new <code>ClientEmulator</code> instance. The program is
149    * stopped on any error reading the configuration files.
150    *
151    * @param configFile configuration file to used.
152    */

153   public ClientEmulator(String JavaDoc configFile)
154   {
155     // Initialization, check that all files are ok
156
requestPlayerProp = new RequestPlayerProperties(configFile);
157     if (!requestPlayerProp.checkPropertiesFile())
158       Runtime.getRuntime().exit(1);
159
160     propUrl = requestPlayerProp.getDatabaseURL();
161     propUsername = requestPlayerProp.getDatabaseUsername();
162     propPassword = requestPlayerProp.getDatabasePassword();
163
164     // Load the cjdbc driver
165
try
166     {
167       Class.forName(requestPlayerProp.getDatabaseDriver());
168     }
169     catch (Exception JavaDoc e)
170     {
171       System.out.println("Unable to load database driver '"
172           + requestPlayerProp.getDatabaseDriver() + "' (" + e + ")");
173       Runtime.getRuntime().exit(1);
174     }
175
176     connectionType = requestPlayerProp.getConnectionType();
177     if (connectionType == RequestPlayerProperties.POOLING_CONNECTION)
178     {
179       poolSize = requestPlayerProp.getPoolSize();
180       if (poolSize <= 0)
181       {
182         System.out.println("Connections pool size must be greater than 0.");
183         Runtime.getRuntime().exit(1);
184       }
185       freeConnections = new Stack JavaDoc();
186       initializeConnections();
187     }
188
189     int nbClients = requestPlayerProp.getNbClients();
190     if (nbClients <= 0)
191     {
192       System.out.println("Number of clients must be greater than 0.");
193       Runtime.getRuntime().exit(1);
194     }
195
196     timeout = requestPlayerProp.getTimeout();
197
198     try
199     {
200       String JavaDoc fileName = requestPlayerProp.getTraceFile();
201       sqlTrace = new BufferedReader JavaDoc(new FileReader JavaDoc(fileName));
202     }
203     catch (Exception JavaDoc e)
204     {
205       System.out.println("An error occured while opening trace file ("
206           + e.getMessage() + ")");
207       Runtime.getRuntime().exit(1);
208     }
209
210     maxRequests = requestPlayerProp.getNbRequests();
211
212     System.out.println("Creating " + nbClients + " threads.");
213     ClientThread[] threads = new ClientThread[nbClients];
214     for (int i = 0; i < nbClients; i++)
215       threads[i] = new ClientThread(i, this, connectionType);
216
217     MonitoringThread monitor = new MonitoringThread(this, 60000);
218     // Display stats every minute
219
monitor.start();
220
221     System.out.println("Starting threads.");
222     long start = System.currentTimeMillis();
223     for (int i = 0; i < nbClients; i++)
224       threads[i].start();
225     System.out.println("Done.");
226
227     for (int i = 0; i < nbClients; i++)
228     {
229       try
230       {
231         threads[i].join();
232       }
233       catch (java.lang.InterruptedException JavaDoc ie)
234       {
235         System.err.println("ClientEmulator: Thread " + i
236             + " has been interrupted.");
237       }
238     }
239     long end = System.currentTimeMillis();
240     monitor.setKilled(true);
241     monitor.interrupt();
242     System.out.println("Done\n");
243
244     try
245     {
246       sqlTrace.close();
247     }
248     catch (Exception JavaDoc ignore)
249     {
250     }
251
252     if (connectionType == RequestPlayerProperties.POOLING_CONNECTION)
253     {
254       try
255       {
256         finalizeConnections();
257       }
258       catch (SQLException JavaDoc e)
259       {
260         System.out.println("Failed to release connections from the pool.");
261       }
262     }
263
264     // Merge and display the stats
265
Stats globalStats = new Stats("Global");
266     try
267     {
268       globalStats.merge(selectStats);
269       globalStats.merge(insertStats);
270       globalStats.merge(updateStats);
271       globalStats.merge(deleteStats);
272       globalStats.merge(getConnectionStats);
273       globalStats.merge(beginStats);
274       globalStats.merge(commitStats);
275       globalStats.merge(rollbackStats);
276     }
277     catch (Exception JavaDoc e)
278     {
279       e.printStackTrace();
280     }
281
282     //For single line output
283
//System.out.println("Name Count Error Hits %Hits Min Max Avg Total
284
// Thoughput");
285
getRequestStats.displayOnStdout();
286     getConnectionStats.displayOnStdout();
287     closeStats.displayOnStdout();
288     beginStats.displayOnStdout();
289     commitStats.displayOnStdout();
290     rollbackStats.displayOnStdout();
291     selectStats.displayOnStdout();
292     insertStats.displayOnStdout();
293     updateStats.displayOnStdout();
294     deleteStats.displayOnStdout();
295     globalStats.displayOnStdout();
296     System.out.println("\nTotal execution time: " + (end - start) + " ms");
297     if (end - start != 0)
298     {
299       System.out.println("Average requests/second: "
300           + (globalStats.getCount() * 1000.0 / (end - start)));
301       System.out.println("Average requests/minute: "
302           + (globalStats.getCount() * 60000.0 / (end - start)));
303     }
304   }
305
306   /**
307    * Gets a new connection from the driver.
308    *
309    * @return a connection
310    */

311   public Connection JavaDoc getConnection()
312   {
313     // Connect to the database
314
try
315     {
316       return DriverManager.getConnection(propUrl, propUsername, propPassword);
317     }
318     catch (Exception JavaDoc e)
319     {
320       System.out.println("Unable to connect to database '"
321           + requestPlayerProp.getDatabaseURL() + "' (" + e + ")");
322     }
323     return null;
324   }
325
326   /**
327    * Initializes the pool of connections to the database. The caller must ensure
328    * that the driver has already been loaded else an exception will be thrown.
329    */

330   public synchronized void initializeConnections()
331   {
332     for (int i = 0; i < poolSize; i++)
333     {
334       // Get connections to the database
335
freeConnections.push(getConnection());
336     }
337   }
338
339   /**
340    * Closes a given connection.
341    *
342    * @param connection connection to close
343    */

344   public void closeConnection(Connection JavaDoc connection)
345   {
346     try
347     {
348       if (connection != null)
349         connection.close();
350     }
351     catch (Exception JavaDoc e)
352     {
353       System.out.println("Failed to close the connection (" + e + ")");
354     }
355   }
356
357   /**
358    * Gets a connection from the pool (round-robin).
359    *
360    * @return a <code>Connection</code> instance or <code>null</code> if no
361    * connection is available
362    */

363   public synchronized Connection JavaDoc getConnectionFromPool()
364   {
365     try
366     {
367       // Wait for a connection to be available
368
while (freeConnections.isEmpty())
369       {
370         try
371         {
372           wait();
373         }
374         catch (InterruptedException JavaDoc e)
375         {
376           System.out.println("Connection pool wait interrupted.");
377         }
378       }
379
380       Connection JavaDoc c = (Connection JavaDoc) freeConnections.pop();
381       return c;
382     }
383     catch (EmptyStackException JavaDoc e)
384     {
385       System.out.println("Out of connections.");
386       return null;
387     }
388   }
389
390   /**
391    * Releases a connection to the pool.
392    *
393    * @param connection the connection to release
394    */

395   public synchronized void releaseConnectionToPool(Connection JavaDoc connection)
396   {
397     boolean mustNotify = freeConnections.isEmpty();
398
399     freeConnections.push(connection);
400
401     // Wake up one thread waiting for a connection (if any)
402
if (mustNotify)
403       notify();
404   }
405
406   /**
407    * Releases all the connections to the database.
408    *
409    * @exception SQLException if an error occurs
410    */

411   public synchronized void finalizeConnections() throws SQLException JavaDoc
412   {
413     Connection JavaDoc c = null;
414     while (!freeConnections.isEmpty())
415     {
416       c = (Connection JavaDoc) freeConnections.pop();
417       c.close();
418     }
419   }
420
421   /**
422    * Gets the next SQL request from the trace file. Requests are executed in
423    * parallel for each separate transaction.
424    *
425    * @param tid transaction id
426    * @return a <code>String</code> containing the SQL request or
427    * <code>null</code> if no more requests are available (end of file
428    * or maximum number of requests reached)
429    */

430   public synchronized String JavaDoc parallelGetNextSQLRequest(int tid)
431   {
432     // Check if we have already stored requests corresponding to tid
433
ArrayList JavaDoc req = (ArrayList JavaDoc) tidList.get(new Integer JavaDoc(tid));
434     if (req != null)
435     {
436       String JavaDoc request = (String JavaDoc) req.remove(0);
437       if (req.isEmpty())
438         tidList.remove(new Integer JavaDoc(tid));
439
440       nbRequests++;
441       return request;
442     }
443
444     // We don't have anything ready, let's read the input file
445
while ((nbRequests <= maxRequests) || (maxRequests == 0))
446     {
447       String JavaDoc request = readRequest();
448
449       if ((request == null) || (transactionId == null)) // Should always be null
450
// together
451
return null;
452
453       // Does this entry match the requested tid ?
454
if (transactionId.intValue() == tid)
455       {
456         nbRequests++;
457         return request; // Yes
458
}
459       else
460       { // No, store this request
461
ArrayList JavaDoc requests = (ArrayList JavaDoc) tidList.get(transactionId);
462         if (requests == null)
463         {
464           requests = new ArrayList JavaDoc();
465           tidList.put(transactionId, requests);
466         }
467         requests.add(request);
468       }
469     }
470     return null;
471   }
472
473   /**
474    * Ignores all requests belonging to a specific transaction id. Only used for
475    * sequential request execution.
476    *
477    * @param tid the tid to ignore
478    */

479   public void ignoreTid(int tid)
480   {
481     ignoredTids.add(new Integer JavaDoc(tid));
482   }
483
484   /**
485    * Gets the next SQL request from the trace file. If the current request does
486    * not match the request transaction id, the current thread waits until the
487    * current request transaction id matches the requested one.
488    *
489    * @param tid transaction id
490    * @return a <code>String</code> containing the SQL request or
491    * <code>null</code> if no more requests are available (end of file
492    * or maximum number of requests reached)
493    */

494   public synchronized String JavaDoc sequentialGetNextSQLRequest(int tid)
495   {
496     String JavaDoc request;
497     do
498     {
499       request = readRequest();
500
501       if (request == null) // This is the end, wake up everybody
502
{
503         notifyAll();
504         return null;
505       }
506     }
507     while (ignoredTids.contains(transactionId));
508
509     // Does this entry match the requested tid ?
510
if (transactionId.intValue() == tid)
511       return request; // Yes
512
else
513     {
514       notifyAll(); // Wake up the others
515
while (request != null)
516       {
517         try
518         {
519           wait(1000);
520         }
521         catch (InterruptedException JavaDoc e)
522         {
523           System.err.println("sequentialGetNextSQLRequest wait interrupted");
524         }
525         // Is the current request for us ?
526
if (transactionId != null)
527           if (transactionId.intValue() != tid)
528           {
529             String JavaDoc myRequest = request;
530             request = readRequest(); // fetch the next request
531
notifyAll();
532             return myRequest;
533           }
534       }
535       notifyAll();
536       return null;
537     }
538   }
539
540   /**
541    * Must be called from a synchronized statement.
542    */

543   private String JavaDoc readRequest()
544   {
545     String JavaDoc request = null;
546     transactionId = null;
547
548     try
549     {
550       if ((nbRequests <= maxRequests) || (maxRequests == 0))
551         if ((request = sqlTrace.readLine()) != null)
552         {
553           // Expected format is: date vdbName requestType transactionId SQL
554
nbRequests++;
555
556           StringTokenizer JavaDoc requestTokenizer = new StringTokenizer JavaDoc(request, " ");
557           // String date = requestTokenizer.nextToken().trim();
558
requestTokenizer.nextToken();
559           // String virtualDatabase = requestTokenizer.nextToken().trim();
560
requestTokenizer.nextToken();
561           String JavaDoc type = requestTokenizer.nextToken().trim();
562           transactionId = new Integer JavaDoc(requestTokenizer.nextToken().trim());
563
564           String JavaDoc sql;
565           switch (type.charAt(0))
566           {
567             case 'B' :
568               // Begin
569
sql = "B " + transactionId;
570               transactionId = ZERO;
571               break;
572             case 'C' :
573             // Commit
574
case 'R' :
575               // Rollback
576
sql = type;
577               break;
578             default :
579               // return type+" "+SQL
580
sql = type
581                   + " "
582                   + request.substring(
583                       request.indexOf(" " + transactionId.toString() + " ")
584                           + transactionId.toString().length() + 1).trim();
585               break;
586           }
587           return sql.toString();
588         }
589     }
590     catch (IOException JavaDoc e)
591     {
592     }
593     return null;
594   }
595
596   /**
597    * Returns the <code>DELETE</code> requests statictics.
598    *
599    * @return a <code>Stats</code> instance.
600    */

601   public Stats getDeleteStats()
602   {
603     return deleteStats;
604   }
605
606   /**
607    * Returns the <code>INSERT</code> requests statictics.
608    *
609    * @return a <code>Stats</code> instance.
610    */

611   public Stats getInsertStats()
612   {
613     return insertStats;
614   }
615
616   /**
617    * Returns the <code>SELECT</code> requests statictics.
618    *
619    * @return a <code>Stats</code> instance.
620    */

621   public Stats getSelectStats()
622   {
623     return selectStats;
624   }
625
626   /**
627    * Returns the unknown requests statictics.
628    *
629    * @return a <code>Stats</code> instance.
630    */

631   public Stats getUnknownStats()
632   {
633     return unknownStats;
634   }
635
636   /**
637    * Returns the <code>UPDATE</code> requests statictics. *
638    *
639    * @return a <code>Stats</code> instance.
640    */

641   public Stats getUpdateStats()
642   {
643     return updateStats;
644   }
645
646   /**
647    * Returns the beginStats value.
648    *
649    * @return Returns the beginStats.
650    */

651   public Stats getBeginStats()
652   {
653     return beginStats;
654   }
655
656   /**
657    * Returns the closeStats value.
658    *
659    * @return Returns the closeStats.
660    */

661   public Stats getCloseStats()
662   {
663     return closeStats;
664   }
665
666   /**
667    * Returns the commitStats value.
668    *
669    * @return Returns the commitStats.
670    */

671   public Stats getCommitStats()
672   {
673     return commitStats;
674   }
675
676   /**
677    * Returns the freeConnections value.
678    *
679    * @return Returns the freeConnections.
680    */

681   public Stack JavaDoc getFreeConnections()
682   {
683     return freeConnections;
684   }
685
686   /**
687    * Returns the getConnectionStats value.
688    *
689    * @return Returns the getConnectionStats.
690    */

691   public Stats getGetConnectionStats()
692   {
693     return getConnectionStats;
694   }
695
696   /**
697    * Returns the getRequestStats value.
698    *
699    * @return Returns the getRequestStats.
700    */

701   public Stats getGetRequestStats()
702   {
703     return getRequestStats;
704   }
705
706   /**
707    * Returns the rollbackStats value.
708    *
709    * @return Returns the rollbackStats.
710    */

711   public Stats getRollbackStats()
712   {
713     return rollbackStats;
714   }
715
716   /**
717    * Returns the query timeout.
718    *
719    * @return <code>int</code> value.
720    */

721   public int getTimeout()
722   {
723     return timeout;
724   }
725
726   /**
727    * Main method. The available options are:
728    * <ul>
729    * <li><code>-h</code> or <code>--help</code> <code>&lt;port&gt;</code>:
730    * displays usage informations.</li>
731    * <li><code>-v</code> or <code>--version</code>: displays version
732    * informations.</li>
733    * <li><code>-f</code> or <code>--file</code>: allows to use a given
734    * configuration file instead of the default file.</li>
735    * </ul>
736    *
737    * @param args command line arguments (see above)
738    */

739   public static void main(String JavaDoc[] args)
740   {
741     // Create options object
742
Options options = createOptions();
743
744     // Parse command line
745
CommandLineParser parser = new GnuParser();
746     CommandLine commandLine = null;
747     try
748     {
749       commandLine = parser.parse(options, args);
750     }
751     catch (ParseException e)
752     {
753       System.err.println("Syntax error (" + e + ")");
754       printUsage(options);
755       Runtime.getRuntime().exit(1);
756     }
757
758     // Non-recognized options
759
int n = commandLine.getArgs().length;
760     for (int i = 0; i < n; i++)
761     {
762       System.err.println("Syntax error (unrecognized option: "
763           + commandLine.getArgs()[i] + ")");
764       printUsage(options);
765       Runtime.getRuntime().exit(1);
766     }
767
768     // Handle --help option
769
if (commandLine.hasOption('h'))
770     {
771       if (commandLine.getOptions().length > 1)
772         System.err.println("Syntax error");
773
774       printUsage(options);
775       Runtime.getRuntime().exit(1);
776     }
777
778     // Handle --version option
779
if (commandLine.hasOption('v'))
780     {
781       if (commandLine.getOptions().length > 1)
782       {
783         System.err.println("Syntax error");
784         printUsage(options);
785       }
786       else
787         System.out.println("C-JDBC request player version " + MAJOR_VERSION
788             + "." + MINOR_VERSION);
789
790       Runtime.getRuntime().exit(1);
791     }
792
793     // Handle -f option
794
if (commandLine.hasOption('f'))
795     {
796       if (commandLine.getOptions().length > 1)
797       {
798         System.err.println("Syntax error");
799         printUsage(options);
800       }
801       new ClientEmulator(commandLine.getOptionValue("f"));
802     }
803     else
804     {
805       new ClientEmulator(null);
806     }
807   }
808
809   /**
810    * Creates <code>Options</code> object that contains all available options
811    * that can be used launching C-JDBC request player.
812    *
813    * @return an <code>Options</code> instance
814    */

815   private static Options createOptions()
816   {
817     Options options = new Options();
818     OptionGroup group = new OptionGroup();
819
820     // help and verbose options
821
group.addOption(new Option("h", "help", false,
822         "Displays usage information."));
823     group.addOption(new Option("v", "version", false,
824         "Displays version information."));
825     options.addOptionGroup(group);
826
827     // file option
828
options
829         .addOption(new Option("f", "file", true,
830             "Allows to use a given configuration file instead of the default file."));
831     return options;
832   }
833
834   /**
835    * Displays usage message.
836    *
837    * @param options available command line options
838    */

839   private static void printUsage(Options options)
840   {
841     String JavaDoc header = "Launchs the C-JDBC request player: this tool reads SQL requests in a file and forwards them to the C-JDBC controller(s)."
842         + System.getProperty("line.separator") + "Options:";
843     String JavaDoc footer = "Statistics are displayed after the execution has finished. For more information, see the default configuration file contained in the <C_JDBC_HOME>/config/ directory.";
844
845     (new HelpFormatter()).printHelp(80, "requestplayer(.sh|.bat) [options]",
846         header, options, footer);
847   }
848 }
Popular Tags