Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Energy, onHostDestruction: ensured ptr existence
[simgrid.git] / contrib / psg / src / peersim / rangesim / RangeSimulator.java
1 /*
2  * Copyright (c) 2003-2005 The BISON Project
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16  *
17  */
18
19 package peersim.rangesim;
20
21 import java.io.*;
22 import java.util.*;
23
24 import peersim.*;
25 import peersim.config.*;
26 import peersim.core.*;
27 import peersim.util.*;
28
29 /**
30  * This class is the main class for the Range Simulator. A range is
31  * a collection of values <em>S</em> to be assigned to a variable 
32  * <em>v</em>. The Range Simulator invokes the standard Peersim 
33  * simulator once for each distinct value. If multiple ranges 
34  * <em>S1, S2, ..., Sn</em> are specified, the standard Peersim
35  * simulator is invoked for each element in 
36  * <em>S1 * S2 * ... * Sn</em>.  
37  * <p>  
38  * Ranges are specified with the following syntax:
39 <pre>
40 range.[id] [var];[range]
41 </pre>
42  * where:
43  * <UL>
44  * <LI> {@value #PAR_RANGE} is the prefix for all range
45  * specifications;</LI>
46  * <LI> <code>id</code> is an identifier; since they are not referred anywhere else,
47  * consecutive numbers are a good choice for range identifiers;</LI>
48  * <LI> <code>var</code> is a variable parameter </LI>
49  * <LI> <code>range</code> describes the collection of values to be associated
50  * to <code>var</code>, whose syntax and semantics is defined in 
51  * {@link peersim.util.StringListParser}. </LI>
52  * </UL>
53  * Examples of range specifications are the following:
54 <pre>
55 range.0 SIZE;2^10:2^18|*2
56 range.1 K;20:30
57 range.2 CHURN;0.05,0.10,0.20 
58 </pre>
59  * With this specification, the collection of values associated to 
60  * <code>SIZE</code> is {2^10,2^11,...,2^18}; <code>K</code> contains 
61  * {20,21,22,...,30}, while <code>CHURN</code> contains just the
62  * specified values.
63  * <p>
64  * Values can be specified as constant expressions (like 2^10, (5+10), etc.)
65  * but variables cannot be used. 
66  * <p>
67  * A separate Java virtual machine is invoked to run each of the
68  * experiments. An attempt is done to run the same JVM version as
69  * the one running the Range Simulator; if this is not possible
70  * (for example due to path problems), the command shell mechanism
71  * is used to run the first JVM version found in the path.
72  * </p>
73  * It is possible to specify options for the forked JVM using the
74  * {@value #PAR_JVM} parameter on the command line. For example,
75  * a command line like this:
76 <pre>
77 java peersim.rangesim.RangeSimulator config.file jvm.options=-Xmx256m
78 </pre>
79  * can be used to run the forked JVM with a maximum heap of 256MB.
80  * <p>
81  * The new JVM inherits the same classpath as the JVM running the
82  * RangeSimulator. The {@value #PAR_JVM} parameter can be used to
83  * specify additional classpath specification.
84  * 
85  * @author Alberto Montresor
86  * @version $Revision: 1.11 $
87  */
88 public class RangeSimulator implements ProcessHandler
89 {
90
91 // --------------------------------------------------------------------------
92 // Configuration parameters
93 // --------------------------------------------------------------------------
94
95 /**
96  * This is the prefix of the config properties whose value vary during
97  * a set of experiments.
98  * @config
99  */
100 private static final String PAR_RANGE = "range";
101
102 /**
103  * This config property can be used to set options in the JVMs that
104  * are forked to execute experiments with different configuration
105  * parameters.
106  * @config
107  */
108 public static final String PAR_JVM = "jvm.options";
109
110
111 // --------------------------------------------------------------------------
112 // Static variables
113 // --------------------------------------------------------------------------
114
115 /** Names of range parameters */
116 private String[] pars;
117
118 /** Values to be simulated, for each parameter */
119 private String[][] values;
120
121 /** The jvm options to be used when creating jvms */
122 private String[] jvmoptions;
123
124 /** Command line arguments */
125 private String[] args;
126
127 /** The current process that is executed */
128 private Process p;
129
130
131 // --------------------------------------------------------------------------
132 // Main
133 // --------------------------------------------------------------------------
134
135 /**
136  * Main method of the system.
137  */
138 public static void main(String[] args)
139 {
140         RangeSimulator r = new RangeSimulator(args);
141         r.run();
142 }
143
144 //--------------------------------------------------------------------------
145 // Constructor
146 //--------------------------------------------------------------------------
147
148 public RangeSimulator(String[] args)
149 {
150
151         // Check if there are no arguments or there is an explicit --help
152         // flag; if so, print the usage of the class
153         if (args.length == 0 || args[0].equals("--help")) {
154                 usage();
155                 System.exit(101);
156         }
157
158         this.args = args.clone();
159
160         // Read property file
161         System.err.println("Simulator: loading configuration");
162         Properties properties = new ParsedProperties(args);
163         Configuration.setConfig(properties);
164         
165         // Read jvm options and separate them in different strings
166         String opt = Configuration.getString(PAR_JVM, null);
167         if (opt == null)
168                 jvmoptions = new String[0];
169         else
170                 jvmoptions = opt.split(" ");
171
172         // Parse range parameters
173         parseRanges();
174
175 }
176
177 /**
178  * Main method to be executed
179  */
180 public void run()
181 {
182         // Shutdown thread management
183         ProcessManager t = new ProcessManager();
184         t.addThread(this);
185         Runtime.getRuntime().addShutdownHook(t);
186
187         // Executes experiments; report short messages about exceptions that are
188         // handled by the configuration mechanism.
189         try {
190                 doExperiments(args);
191         } catch (MissingParameterException e) {
192                 Runtime.getRuntime().removeShutdownHook(t);
193                 System.err.println(e + "");
194                 System.exit(101);
195         } catch (IllegalParameterException e) {
196                 Runtime.getRuntime().removeShutdownHook(t);
197                 System.err.println(e + "");
198                 System.exit(101);
199         }
200         Runtime.getRuntime().removeShutdownHook(t);
201         System.exit(0);
202 }
203
204 // --------------------------------------------------------------------
205
206 /**
207  * Parses a collection of range specifications and returns the set of
208  * parameter that will change during the simulation and the values that
209  * will be used for those parameters.
210  */
211 private void parseRanges()
212 {
213         // Get ranges
214         String[] ranges = Configuration.getNames(PAR_RANGE);
215
216         // Start is the first element in which ranges are stored
217         int start;
218         
219         // If there is an explicit simulation.experiment or there are no 
220         // ranges, put an experiment range at the beginning of the values.
221         // Otherwise, just use the ranges.
222         if (Configuration.contains(Simulator.PAR_EXPS) || ranges.length == 0) {
223                 pars = new String[ranges.length + 1];
224                 values = new String[ranges.length + 1][];
225                 pars[0] = "EXP";
226                 values[0] = StringListParser.parseList("1:"
227                                 + Configuration.getInt(Simulator.PAR_EXPS, 1));
228                 start = 1;
229         } else {
230                 pars = new String[ranges.length];
231                 values = new String[ranges.length][];
232                 start = 0;
233         }
234
235         for (int i = start; i < pars.length; i++) {
236                 String[] array = Configuration.getString(ranges[i-start]).split(";");
237                 if (array.length != 2) {
238                         throw new IllegalParameterException(ranges[i],
239                                         " should be formatted as <parameter>;<value list>");
240                 }
241                 pars[i] = array[0];
242                 values[i] = StringListParser.parseList(array[1]);
243         }
244 }
245
246 // --------------------------------------------------------------------
247
248 /**
249  * Selects the next set of values by incrementing the specified index
250  * array. The index array is treated as a vector of digits; the first is
251  * managed managed as a vector of digits.
252  */
253 private void nextValues(int[] idx, String[][] values)
254 {
255         idx[idx.length - 1]++;
256         for (int j = idx.length - 1; j > 0; j--) {
257                 if (idx[j] == values[j].length) {
258                         idx[j] = 0;
259                         idx[j - 1]++;
260                 }
261         }
262 }
263
264 // --------------------------------------------------------------------
265
266 private void doExperiments(String[] args)
267 {
268
269         // Configure the java parameter for exception
270         String filesep = System.getProperty("file.separator");
271         String classpath = System.getProperty("java.class.path");
272         String javapath = System.getProperty("java.home") + filesep + "bin" + filesep
273                         + "java";
274         ArrayList<String> list = new ArrayList<String>(20);
275         list.add(javapath);
276         list.add("-cp");
277         list.add(classpath);
278         
279         // Add the jvm options
280         for (int i=0; i < jvmoptions.length; i++)
281                 list.add(jvmoptions[i]);
282         
283         // The class to be run in the forked JVM
284         list.add("peersim.Simulator");
285         
286         // Parameters specified on the command line
287         for (int i=0; i < args.length; i++) {
288                 list.add(args[i]);
289         }
290         
291         // Since multiple experiments are managed here, the value
292         // of standard variable for multiple experiments is changed to 1
293         list.add(Simulator.PAR_EXPS+"=1");
294
295         // Activate redirection to separate stdout from stderr
296         list.add(Simulator.PAR_REDIRECT+"="+TaggedOutputStream.class.getCanonicalName());
297         int startlog = list.size();
298         list.add(""); 
299         
300         // Create a placeholder for the seed
301         int startseed = list.size();
302         list.add("");
303         
304         // Create placeholders for the range parameters
305         int startpar = list.size();
306         for (int i=0; i < values.length; i++)
307                 list.add("");
308                 
309         // Execute with different values
310         int[] idx = new int[values.length]; // Initialized to 0
311         while (idx[0] < values[0].length) {
312
313                 // Configure the argument string array
314                 for (int j = 0; j < pars.length; j++) {
315                         list.set(startpar + j, pars[j] + "=" + values[j][idx[j]]);
316                 }
317
318                 // Fill the log placeholder
319                 StringBuffer log = new StringBuffer();
320                 for (int j = 0; j < pars.length; j++) {
321                         log.append(pars[j]);
322                         log.append(" ");
323                         log.append(values[j][idx[j]]);
324                         log.append(" ");
325                 }
326                 list.set(startlog, Simulator.PAR_REDIRECT+"."+
327                                 TaggedOutputStream.PAR_RANGES+"="+log);
328
329                 // Fill the seed place holder
330                 long seed = CommonState.r.nextLong();
331                 list.set(startseed, CommonState.PAR_SEED+"="+seed);
332
333                 System.err.println("Experiment: " + log);
334                 
335                 executeProcess(list);
336
337                 // Increment values
338                 nextValues(idx, values);
339         
340         }
341 }
342
343 //--------------------------------------------------------------------
344
345 /**
346  * Execute the "command line" represented by this String list.
347  * The first argument is the process to be executed. We try
348  * to run the same JVM as the current one. If not possible,
349  * we use the first java command found in the path.
350  */
351 private void executeProcess(List<String> list)
352 {
353         // Prepare the argument array for process forking
354         String[] newargs = new String[list.size()];
355
356         // Execute a new JVM
357         try {
358                 ProcessBuilder pb = new ProcessBuilder(list.toArray(newargs));
359                 pb.redirectErrorStream(true);
360                 p = pb.start();
361         } catch (IOException e1) {
362                 try {
363                         list.set(0, "java");
364                         ProcessBuilder pb = new ProcessBuilder(list.toArray(newargs));
365                         pb.redirectErrorStream(true);
366                         p = pb.start();
367                 } catch (IOException e2) {
368                         System.err.println("Unable to launch a Java virtual machine");
369                         System.exit(1);
370                 }
371         }
372
373         // Read the output from the process and redirect it to System.out
374         // and System.err.
375         BufferedReader toprint = new BufferedReader(new InputStreamReader(p
376                         .getInputStream()));
377         String line;
378         while ((line = getLine(toprint)) != null) {
379                 if (line.length() == 0) {
380                         System.out.println();
381                 } else {
382                         int last = line.charAt(line.length()-1);
383                         if (last != TaggedOutputStream.TAG) {
384                                 System.err.println(line);
385                         } else {
386                                 line = line.substring(0, line.length()-1);
387                                 System.out.println(line);
388                         }
389                 }
390         }
391
392         // We close all the files and we destroy the process. They are not 
393         // cleaned when the process is closed. See:
394         // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
395         // http://www.thescripts.com/forum/thread18019.html
396         try {
397                 p.getErrorStream().close();
398                 p.getInputStream().close();
399                 p.getOutputStream().close();
400                 p.destroy();
401         } catch (IOException e) {
402                 e.printStackTrace();
403         }
404
405         // The static variable p (used also by ShutdownThread) is back to
406         // null - no process must be killed on shutdown.
407         p = null;
408         
409         
410
411 }
412
413 //--------------------------------------------------------------------
414
415 private static String getLine(BufferedReader toprint)
416 {
417         try {
418                 return toprint.readLine();
419         } catch (IOException e) {
420                 // If we get here, this means that the forked process has
421                 // been killed by the shutdown thread. We just exit without
422           // printing this exception.
423                 System.exit(1);
424                 return null; // Never reached, but needed.
425         }
426 }
427
428
429 // --------------------------------------------------------------------
430
431 private static void usage()
432 {
433         System.err.println("Usage:");
434         System.err.println("  peersim.RangeSimulator <configfile> [property]*");
435 }
436
437 //--------------------------------------------------------------------
438
439 RangeSimulator()
440 {
441 }
442
443 /**
444  * Stop the process executing the external java virtual machine.
445  */
446 public void doStop()
447 {
448         if (p != null)
449                 p.destroy();
450 }
451
452 /**
453  * Wait until the java virtual machine has terminated; it won't be
454  * used in this class, but you never know.
455  */
456 public void join() throws InterruptedException
457 {
458         p.waitFor();
459 }
460
461 }