1 | package de.uka.ipd.sdq.prototype.framework; |
2 | |
3 | import java.io.File; |
4 | import java.lang.reflect.Method; |
5 | import java.util.ArrayList; |
6 | import java.util.Collection; |
7 | import java.util.Date; |
8 | import java.util.List; |
9 | |
10 | import org.apache.log4j.ConsoleAppender; |
11 | import org.apache.log4j.Level; |
12 | import org.apache.log4j.PatternLayout; |
13 | |
14 | import de.uka.ipd.sdq.measurement.strategies.activeresource.DegreeOfAccuracyEnum; |
15 | import de.uka.ipd.sdq.probfunction.math.IProbabilityFunctionFactory; |
16 | import de.uka.ipd.sdq.probfunction.math.impl.DefaultRandomGenerator; |
17 | import de.uka.ipd.sdq.probfunction.math.impl.ProbabilityFunctionFactoryImpl; |
18 | import de.uka.ipd.sdq.prototype.framework.utils.CommandLineParser; |
19 | import de.uka.ipd.sdq.prototype.framework.utils.RmiRegistry; |
20 | import de.uka.ipd.sdq.prototype.framework.utils.RunProperties; |
21 | import de.uka.ipd.sdq.prototype.framework.utils.UserMenu; |
22 | import de.uka.ipd.sdq.sensorframework.dao.file.FileDAOFactory; |
23 | import de.uka.ipd.sdq.sensorframework.entities.Experiment; |
24 | import de.uka.ipd.sdq.sensorframework.entities.ExperimentRun; |
25 | import de.uka.ipd.sdq.sensorframework.entities.dao.IDAOFactory; |
26 | |
27 | import de.uka.ipd.sdq.simucomframework.variables.cache.StoExCache; |
28 | |
29 | /** |
30 | * Abstract parent class of the main class of a QoS prototype. The class includes static, |
31 | * i.e., not generator or model dependent, code like command line reading, taking measurements |
32 | * or setting up prototyped resources. |
33 | * |
34 | * @author Steffen Becker, Thomas Zolynski, Sebastian Lehrig |
35 | * |
36 | */ |
37 | public abstract class AbstractMain { |
38 | |
39 | /** |
40 | * Root logger of the whole application. Changing its configuration impacts all log output. |
41 | */ |
42 | protected static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getRootLogger(); |
43 | |
44 | /** |
45 | * Attributes for the measurements store |
46 | */ |
47 | private static IDAOFactory datasource = null; |
48 | private ExperimentRun expRun; |
49 | |
50 | /** |
51 | * Threads used to simulate users |
52 | */ |
53 | protected ArrayList<Thread> threads = new ArrayList<Thread>(); |
54 | protected RunProperties runProps; |
55 | |
56 | /** |
57 | * Main method to run the generated prototype. It implements the main workflow, i.e., |
58 | * parsing cmd line, setting up resources, datastores, RMI registry, etc. |
59 | * |
60 | * @param args Command line arguments given for the prototype according to Apache CLI's configuration |
61 | */ |
62 | protected void run(String[] args) { |
63 | runProps = CommandLineParser.parseCommandLine(args); |
64 | setupLogging(); |
65 | logger.info("Command line read. Logging initialised. Protocom starts its workflow now..."); |
66 | |
67 | logger.info("Reading allocation configuration. Callibrating container if needed"); |
68 | initAllocationStorage(); |
69 | AbstractAllocationStorage.initContainer(); |
70 | |
71 | DefaultRandomGenerator randomGen = new DefaultRandomGenerator(); |
72 | if (runProps.hasOption('E')) { |
73 | randomGen.setSeed(Long.parseLong(runProps.getOptionValue('E'))); |
74 | } |
75 | |
76 | logger.info("Initialising StoEx Cache " + (runProps.hasOption('E') ? " - Seed: " + runProps.getOptionValue('E') : "")); |
77 | |
78 | IProbabilityFunctionFactory probFunctionFactory = ProbabilityFunctionFactoryImpl.getInstance(); |
79 | probFunctionFactory.setRandomGenerator(randomGen); |
80 | StoExCache.initialiseStoExCache(probFunctionFactory); |
81 | |
82 | handleStartOption(); |
83 | } |
84 | |
85 | /** |
86 | * Handles the start option set via the M-parameter. |
87 | * - If M has no option value, the user menu is displayed |
88 | * - If M has a option value, the corresponding main method is invoked (currently this is an experimental feature) |
89 | * - If M is not set, the default main method is invoked |
90 | */ |
91 | private void handleStartOption() |
92 | { |
93 | if (runProps.hasOption('M')) |
94 | { |
95 | String mainClass = runProps.getOptionValue('M'); |
96 | Method mainMethod = getMain(mainClass); |
97 | |
98 | if (!mainMethodFound(mainMethod)) |
99 | { |
100 | List<Integer> itemIds = UserMenu.showUserMenu(getSystems()); |
101 | for (int itemId : itemIds) |
102 | { |
103 | handleMenuItem(itemId); |
104 | } |
105 | } |
106 | else |
107 | { |
108 | invokeMethod(mainMethod, new String[]{}); |
109 | } |
110 | } |
111 | else |
112 | { |
113 | startDefaultMain(); |
114 | } |
115 | } |
116 | |
117 | /** |
118 | * Executes the action that corresponds to itemId |
119 | * @param itemId The id of a menu item to execute |
120 | */ |
121 | private void handleMenuItem(int itemId) |
122 | { |
123 | if(itemId == 1) |
124 | { |
125 | // Start everything in local mode |
126 | logger.debug("Start: Start everything in local mode"); |
127 | RmiRegistry.startRegistry(); |
128 | AbstractAllocationStorage.setLocalMode(true); |
129 | setupResources(); |
130 | startDefaultMain(); |
131 | } |
132 | else if(itemId == 2) |
133 | { |
134 | // RmiRegistry |
135 | logger.debug("Start: RmiRegistry"); |
136 | RmiRegistry.main(null); |
137 | } |
138 | else if(itemId == 3) |
139 | { |
140 | // Usage Scenarios |
141 | logger.debug("Start: Usage Scenarios"); |
142 | |
143 | createExperiment(); |
144 | initMeasurement(); |
145 | } |
146 | else |
147 | { |
148 | int i = 4; |
149 | |
150 | // This data source is only used temporary by components for inner sensors and NOT for the usage scenario. |
151 | // This should be refactored into a better place, just like all in this class... |
152 | datasource = prepareDatasource(); |
153 | ExperimentManager.setExperiment(datasource.createExperimentDAO().addExperiment(runProps.getOptionValue('n'))); |
154 | |
155 | |
156 | // systems |
157 | String[][] systems = getSystems(); |
158 | for(String[] system : systems) |
159 | { |
160 | if(itemId == i) |
161 | { |
162 | logger.debug("Start: System "+system[0]); |
163 | invokeMethod(getMain(system[0]), getRMIRegistry()); |
164 | } |
165 | i++; |
166 | } |
167 | |
168 | // container |
169 | Collection<String> containers = AbstractAllocationStorage.getContainerIds(); |
170 | for(String containerId : containers) |
171 | { |
172 | if(itemId == i) |
173 | { |
174 | logger.debug("Start: Container "+AbstractAllocationStorage.getContainerName(containerId)); |
175 | Collection<Class<?>> components = AbstractAllocationStorage.getComponents(containerId); |
176 | AbstractAllocationStorage.setActiveContainer(containerId); |
177 | setupResources(); |
178 | |
179 | for(Class<?> component : components) |
180 | { |
181 | logger.debug("Start: Component "+component.getName()); |
182 | invokeMethod(getMain(component), getRMIRegistry()); |
183 | } |
184 | } |
185 | i++; |
186 | } |
187 | } |
188 | } |
189 | |
190 | |
191 | private String[] getRMIRegistry() { |
192 | if (runProps.hasOption("R")) { |
193 | return new String[]{ runProps.getOptionValue("R") }; |
194 | } |
195 | return new String[]{}; |
196 | } |
197 | |
198 | private Method getMain(Class<?> mainClass) { |
199 | if (mainClass == null) |
200 | return null; |
201 | try { |
202 | return mainClass.getMethod("main", String[].class); |
203 | } catch (Throwable e) { |
204 | } |
205 | |
206 | return null; |
207 | } |
208 | |
209 | private boolean mainMethodFound(Method mainMethod) { |
210 | logger.debug(mainMethod == null ? "Main method not found. Falling back" : "Main method passed on command line."); |
211 | return mainMethod != null; |
212 | } |
213 | |
214 | protected void initMeasurement() { |
215 | |
216 | if (runProps.hasOption('w')) { |
217 | logger.info("Please pin java runtime to a single processor if needed! Press a key to continue!"); |
218 | waitForKey(); |
219 | } |
220 | |
221 | // init threads if configuration is active server (not -P) or only |
222 | // warmup requested. |
223 | if (!runProps.hasOption('P') || runProps.hasOption('W')) { |
224 | initialiseThreads(ExperimentManager.getExperiment(), expRun); |
225 | } |
226 | |
227 | // run measurements if the configuration is neither passive nor warmup |
228 | // only. |
229 | if (!runProps.hasOption('P') && !runProps.hasOption('W')) { |
230 | |
231 | try { |
232 | |
233 | logger.info("Current time: " + new Date()); |
234 | startThreads(); |
235 | |
236 | stop(); |
237 | |
238 | } catch (RuntimeException e) { |
239 | throw e; |
240 | } finally { |
241 | logger.info("Current time: " + new Date()); |
242 | writeResultsAndClose(datasource); |
243 | } |
244 | } |
245 | |
246 | // close all running threads |
247 | System.exit(0); |
248 | } |
249 | |
250 | private void createExperiment() { |
251 | datasource = prepareDatasource(); |
252 | ExperimentManager.setExperiment(datasource.createExperimentDAO().addExperiment(runProps.getOptionValue('n'))); |
253 | expRun = ExperimentManager.addExperimentRun(); |
254 | logger.info("Created data source at event time " + (System.nanoTime() / Math.pow(10, 9))); |
255 | } |
256 | |
257 | private void startDefaultMain() { |
258 | createExperiment(); |
259 | |
260 | if (!runProps.hasOption('R')) { |
261 | initialiseSystems(); |
262 | } |
263 | initMeasurement(); |
264 | } |
265 | |
266 | protected abstract void initAllocationStorage(); |
267 | |
268 | private void invokeMethod(Method method, String[] params) { |
269 | try { |
270 | method.invoke(null, (Object) params); |
271 | } catch (Exception e) { |
272 | logger.error("Failed to run main method",e); |
273 | System.exit(-1); |
274 | } |
275 | } |
276 | |
277 | private Method getMain(String mainClass) { |
278 | if (mainClass == null) |
279 | return null; |
280 | try { |
281 | Class<?> cls = Class.forName(mainClass); |
282 | return cls.getMethod("main", String[].class); |
283 | } catch (Throwable e) { |
284 | logger.info("Failed to retrieve main class. Falling back to menu mode"); |
285 | } |
286 | return null; |
287 | } |
288 | |
289 | private static void writeResultsAndClose(IDAOFactory datasource) { |
290 | logger.info("Storing results..."); |
291 | datasource.createExperimentDAO().storeAll(); |
292 | datasource.finalizeAndClose(); |
293 | logger.info("...Done!"); |
294 | |
295 | // wait a little before closing down results writer |
296 | try { |
297 | Thread.sleep(1000); |
298 | } catch (InterruptedException e) { |
299 | logger.error("Failed to persist measurements", e); |
300 | System.exit(-1); |
301 | } |
302 | } |
303 | |
304 | private void startThreads() { |
305 | logger.info("Starting workload threads. "); |
306 | if (runProps.hasOption('m')) |
307 | logger.info("Taking " + runProps.getOptionValue('m') + " measurements"); |
308 | else |
309 | logger.info("Request a measurement stop by pressing any key!"); |
310 | for (Thread t : threads) { |
311 | t.start(); |
312 | } |
313 | } |
314 | |
315 | private void setupLogging() { |
316 | logger.removeAllAppenders(); |
317 | PatternLayout layout = new PatternLayout("%d{HH:mm} %-5p [%t]: %m%n"); |
318 | logger.addAppender(new ConsoleAppender(layout)); |
319 | if (runProps.hasOption('D')) |
320 | logger.setLevel(Level.DEBUG); |
321 | else |
322 | logger.setLevel(Level.INFO); |
323 | } |
324 | |
325 | private void waitForKey() { |
326 | try { |
327 | System.in.read(); |
328 | while (System.in.available() > 0) { |
329 | System.in.read(); |
330 | } |
331 | } catch (java.io.IOException e) { |
332 | logger.error("Failed to wait for key. " + e); |
333 | System.exit(-1); |
334 | } |
335 | } |
336 | |
337 | private void stop() { |
338 | if (!runProps.hasOption('m')) { |
339 | logger.debug("Request Thread stop"); |
340 | for (Thread t : threads) { |
341 | ((IStopable) t).requestStop(); |
342 | } |
343 | } |
344 | for (Thread t : threads) { |
345 | try { |
346 | t.join(); |
347 | } catch (InterruptedException e) { |
348 | e.printStackTrace(); |
349 | throw new RuntimeException(e); |
350 | } |
351 | } |
352 | } |
353 | |
354 | private IDAOFactory prepareDatasource() { |
355 | if (!checkDirectory(runProps.getOptionValue('d'))) { |
356 | String error = "Unable to find data directory. Ensure data directory exists and is writeable"; |
357 | logger.error(error); |
358 | throw new RuntimeException(error); |
359 | } |
360 | IDAOFactory datasource = new FileDAOFactory(runProps.getOptionValue('d')); |
361 | return datasource; |
362 | } |
363 | |
364 | private boolean checkDirectory(String path) { |
365 | File f = new File(path); |
366 | if (f.isDirectory() && f.canWrite()) |
367 | return true; |
368 | if (!f.exists()) { |
369 | return f.mkdir(); |
370 | } else { |
371 | return false; |
372 | } |
373 | } |
374 | |
375 | protected abstract void setupResources(); |
376 | |
377 | protected abstract void initialiseSystems(); |
378 | |
379 | protected abstract String[][] getSystems(); |
380 | |
381 | protected DegreeOfAccuracyEnum getAccuracy() |
382 | { |
383 | DegreeOfAccuracyEnum accuracy = DegreeOfAccuracyEnum.MEDIUM; |
384 | if(runProps.hasOption('a')) |
385 | { |
386 | try |
387 | { |
388 | String acc = runProps.getOptionValue('a').toUpperCase(); |
389 | accuracy = DegreeOfAccuracyEnum.valueOf( acc ); |
390 | logger.info("Using accuracy for calibration: " + acc); |
391 | } |
392 | catch(IllegalArgumentException e) |
393 | { |
394 | logger.warn("Calibration accuracy "+runProps.getOptionValue('a')+" not found! Using MEDIUM instead"); |
395 | } |
396 | } |
397 | else |
398 | { |
399 | logger.info("Using default accuracy for calibration: MEDIUM"); |
400 | } |
401 | |
402 | return accuracy; |
403 | } |
404 | |
405 | /** |
406 | * Initialise threads and perform warmup, if requested. |
407 | */ |
408 | protected abstract void initialiseThreads(Experiment exp, ExperimentRun expRun); |
409 | } |