001 package toolbus.viewer;
002
003 import java.io.IOException;
004 import java.lang.management.ManagementFactory;
005 import java.lang.management.MemoryMXBean;
006 import java.lang.management.ThreadInfo;
007 import java.lang.management.ThreadMXBean;
008 import java.util.Collections;
009 import java.util.HashMap;
010 import java.util.Iterator;
011 import java.util.LinkedList;
012 import java.util.List;
013 import java.util.Map;
014
015 import toolbus.SocketConnectionHandler;
016 import toolbus.StateElement;
017 import toolbus.TBTermFactory;
018 import toolbus.ToolBus;
019 import toolbus.exceptions.ToolBusException;
020 import toolbus.logging.ILogger;
021 import toolbus.logging.IToolBusLoggerConstants;
022 import toolbus.logging.LoggerFactory;
023 import toolbus.parsercup.PositionInformation;
024 import toolbus.process.ProcessCall;
025 import toolbus.process.ProcessInstance;
026 import toolbus.process.debug.ExecutionResult;
027 import toolbus.tool.ToolInstance;
028 import toolbus.util.collections.ConcurrentHashSet;
029 import aterm.ATerm;
030 import aterm.ATermList;
031
032 /**
033 * A specialized version of the ToolBus, which executes the process logic in debug mode. Viewer
034 * implementations need to use this ToolBus instead of the regular one.
035 *
036 * @author Arnold Lankamp
037 */
038 public class DebugToolBus extends ToolBus{
039 private volatile int currentState = IViewerConstants.UNKNOWN_STATE;
040
041 private volatile boolean running = false;
042 private final Object processLock = new Object();
043 private volatile boolean workHasArrived = false;
044
045 private volatile boolean doRun = false;
046 private volatile boolean doStep = false;
047
048 private volatile boolean breakWhileStepping;
049
050 private final IViewer viewer;
051
052 private final ConcurrentHashSet<Integer> processInstanceBreakPoints;
053 private final ConcurrentHashSet<String> processBreakPoints;
054 private final ConcurrentHashSet<StateElement> stateElementBreakPoints;
055 private final Map<String, List<Integer>> sourceCodeBreakPoints;
056
057 private final PerformanceInformationHandler performanceInformationRetriever;
058
059 private long nextTime;
060
061 /**
062 * Constructor.
063 *
064 * @param args
065 * The arguments the ToolBus need to start (like include path and main script name).
066 * @param viewer
067 * The viewer that is attached to this ToolBus.
068 * @param performanceMonitor
069 * The tool performance monitor to use (optional argument; pass null in case
070 * monitoring is not required).
071 */
072 public DebugToolBus(String[] args, IViewer viewer, IPerformanceMonitor performanceMonitor){
073 super(args);
074
075 this.viewer = viewer;
076
077 this.performanceInformationRetriever = (performanceMonitor != null) ? new PerformanceInformationHandler(performanceMonitor): null;
078
079 processInstanceBreakPoints = new ConcurrentHashSet<Integer>();
080 processBreakPoints = new ConcurrentHashSet<String>();
081 stateElementBreakPoints = new ConcurrentHashSet<StateElement>();
082 sourceCodeBreakPoints = new HashMap<String, List<Integer>>();
083
084 nextTime = 0;
085
086 breakWhileStepping = true;
087 }
088
089 /**
090 * @see toolbus.ToolBus#addProcess(ProcessCall)
091 */
092 public ProcessInstance addProcess(ProcessCall call) throws ToolBusException{
093 ProcessInstance pi = super.addProcess(call);
094 viewer.processInstanceStarted(pi);
095 return pi;
096 }
097
098 /**
099 * Terminates the ToolBus.
100 *
101 * @see toolbus.ToolBus#shutdown(ATerm)
102 */
103 public void shutdown(ATerm msg){
104 viewer.toolbusTerminating();
105
106 super.shutdown(msg);
107
108 running = false;
109
110 synchronized(processLock){
111 processLock.notify();
112 }
113 }
114
115 /**
116 * @see toolbus.ToolBus#setNextTime(long)
117 */
118 public void setNextTime(long next){
119 //System.err.println("setNextTime: " + next);
120 long currentTime = getRunTime();
121 if(nextTime < currentTime || (next < nextTime && next > currentTime)){
122 nextTime = next;
123 }
124 //System.err.println("setNextTime: set to " + nextTime);
125 }
126
127 public void prepare(){
128 if(nerrors > 0){
129 System.err.println("ToolBus cannot continue execution due to errors in Tscript");
130 return;
131 }
132
133 viewer.toolbusStarting();
134
135 connectionHandler = new SocketConnectionHandler(this);
136
137 try{
138 int userSpecifiedPort = propertyManager.getUserSpecifiedPort();
139 if(userSpecifiedPort == -1){
140 connectionHandler.initialize();
141 }else{
142 connectionHandler.initialize(userSpecifiedPort);
143 }
144 }catch(IOException ioex){
145 LoggerFactory.log("Unable initialize the ToolBus connection handler.", ioex, ILogger.FATAL, IToolBusLoggerConstants.COMMUNICATION);
146 throw new RuntimeException(ioex);
147 }
148 portNumber = connectionHandler.getPort();
149
150 Thread tbConnectionHandler = new Thread(connectionHandler);
151 tbConnectionHandler.setName("ToolBus connection handler");
152 tbConnectionHandler.start();
153
154 System.out.println("The ToolBus server allocated port (" + portNumber + ")");
155 }
156
157 /**
158 * The main process loop of the ToolBus.
159 *
160 * @see toolbus.ToolBus#execute()
161 */
162 public void execute(){
163 ProcessInstance pi = null;
164 ProcessInstanceIterator processesIterator = new ProcessInstanceIterator(processes);
165 running = true;
166 try{
167 boolean work = false;
168 boolean reset = false;
169 long currentNextTime = 0;
170 workHasArrived = true; // This is just to get things started.
171
172 PROCESSLOOP: do{
173 synchronized(processLock){
174 while((!workHasArrived && running) || !(doStep || doRun)){
175 fireStateChange(IViewerConstants.WAITING_STATE);
176
177 long blockTime = nextTime - getRunTime(); // Recalculate the delay before sleeping.
178 if(blockTime > 0){
179 try{
180 processLock.wait(blockTime);
181 }catch(InterruptedException irex){
182 // Just ignore this, it's not harmfull.
183 }
184 workHasArrived = true;
185 }else if(currentNextTime != nextTime){ // If the nextTime changed and the blockTime is zero or less, don't block as there might be work to do.
186 workHasArrived = true;
187 currentNextTime = nextTime;
188 }else{
189 try{
190 processLock.wait();
191 }catch(InterruptedException irex){
192 // Just ignore this, it's not harmfull.
193 }
194
195 if(performanceInformationRetriever != null) performanceInformationRetriever.handleRetrievedData(); // Handle the arrived performance stats before continueing to execute the process logic.
196 }
197
198 if(shuttingDown) return; // Stop executing if a shutdown is triggered.
199 }
200 currentNextTime = nextTime;
201
202 if(doStep) fireStateChange(IViewerConstants.STEPPING_STATE);
203 else if(doRun) fireStateChange(IViewerConstants.RUNNING_STATE);
204 else fireStateChange(IViewerConstants.UNKNOWN_STATE);
205 }
206
207 WORKLOOP: do{
208 work = false;
209 workHasArrived = false;
210
211 if(!processesIterator.hasNext()){
212 processesIterator.reset();
213 Collections.rotate(processes, 1);
214
215 reset = true;
216 }else{
217 reset = false; // We might start iterating in the middle of the collections, so ensure we do at least a full one if we're running.
218 }
219
220 while(processesIterator.hasNext() && (doRun || doStep)){
221 if(shuttingDown){
222 return;
223 }
224
225 pi = processesIterator.next();
226 ExecutionResult executionResult = pi.debugStep();
227 boolean stepSuccessFull = (executionResult != null);
228 boolean wasStep = doStep;
229
230 if(wasStep && stepSuccessFull){
231 doStep = false; // Consume the step.
232 }
233
234 String processName = pi.getProcessName();
235 int processId = pi.getProcessId();
236 if(!wasStep || breakWhileStepping){
237 if(stepSuccessFull){
238 if(processInstanceBreakPoints.contains(new Integer(processId)) || processBreakPoints.contains(processName)){
239 viewer.processBreakPointHit(pi);
240 }
241 StateElement executedStateElement = executionResult.stateElement; // Ignore the Eclipse warning here, since it's bogus.
242 if(stateElementBreakPoints.contains(executedStateElement)){
243 viewer.stateElementBreakPointHit(executedStateElement);
244 }
245 PositionInformation posInfo = executedStateElement.getPosInfo();
246 if(posInfo != null){
247 String filename = posInfo.getFileName();
248 List<Integer> lineNumbers = sourceCodeBreakPoints.get(filename);
249 if(lineNumbers != null){
250 int beginLineNumber = posInfo.getBeginLine();
251 int endLineNumber = posInfo.getEndLine();
252
253 Iterator<Integer> lineNumberIterator = lineNumbers.iterator();
254 synchronized(lineNumbers){
255 while(lineNumberIterator.hasNext()){
256 int lineNumber = lineNumberIterator.next().intValue();
257 if(lineNumber >= beginLineNumber && lineNumber <= endLineNumber){
258 viewer.sourceBreakPointHit(executedStateElement);
259 break;
260 }
261 }
262 }
263 }
264 }
265 }
266 }
267
268 if(pi.isTerminated()){
269 processesIterator.remove();
270 pi.terminate();
271
272 viewer.processInstanceTerminated(pi);
273
274 if(processes.size() == 0) break PROCESSLOOP;
275 }
276 work |= stepSuccessFull;
277
278 if(stepSuccessFull){
279 if(wasStep){
280 viewer.stepExecuted(pi, executionResult.stateElement, executionResult.partners); // Ignore the Eclipse warning here, since it's bogus.
281 break WORKLOOP;
282 }
283 }
284 }
285
286 if(performanceInformationRetriever != null) performanceInformationRetriever.handleRetrievedData(); // Handle the arrived performance stats before continueing the process.
287 }while((doRun && (work || !reset)) || (doStep && !reset));
288 workHasArrived |= work; // If we did something, set this to ensure we can release the lock when needed.
289 }while(running);
290 }catch(ToolBusException e){
291 error("Process " + (pi != null ? pi.getProcessName() : "?"), e.getMessage());
292 e.printStackTrace();
293 }
294
295 if(!shuttingDown) shutdown(tbfactory.make("ToolBus halted"));
296 }
297
298 /**
299 * @see toolbus.ToolBus#workArrived()
300 */
301 public void workArrived(ToolInstance toolInstance, byte operation){
302 if(performanceInformationRetriever != null){
303 performanceInformationRetriever.toolCommunicationTriggered(toolInstance, operation);
304 }
305
306 if(!workHasArrived){
307 synchronized(processLock){
308 fireStateChange(IViewerConstants.READY_STATE);
309
310 workHasArrived = true;
311
312 processLock.notify();
313 }
314 }
315 }
316
317 /**
318 * Gathers performance statistics related to JVM the current ToolBus is running in.
319 *
320 * @return Performance statictics.
321 */
322 public ATerm getToolBusPerformanceStats(){
323 // Type stuff (for standard compliance only).
324 ATerm toolData = tbfactory.makeAppl(tbfactory.makeAFun("unsupported-operation", 0, false));
325
326 // Memory stuff
327 MemoryMXBean mmxb = ManagementFactory.getMemoryMXBean();
328 long heapMemoryUsage = mmxb.getHeapMemoryUsage().getUsed();
329 long nonHeapMemoryUsage = mmxb.getNonHeapMemoryUsage().getUsed();
330
331 ATerm heapUsage = tbfactory.makeAppl(tbfactory.makeAFun("heap-usage", 1, false), tbfactory.makeInt(((int) (heapMemoryUsage / 1024))));
332 ATerm nonHeapUsage = tbfactory.makeAppl(tbfactory.makeAFun("non-heap-usage", 1, false), tbfactory.makeInt(((int) (nonHeapMemoryUsage / 1024))));
333
334 ATerm memory = tbfactory.makeAppl(tbfactory.makeAFun("memory-usage", 2, false), heapUsage, nonHeapUsage);
335
336 // Thread stuff
337 ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
338
339 ATerm threads;
340
341 long[] threadIds = tmxb.getAllThreadIds();
342 int nrOfThreads = threadIds.length;
343 try{
344 ATermList threadsList = tbfactory.makeList();
345 for(int i = 0; i < nrOfThreads; i++){
346 ThreadInfo ti = tmxb.getThreadInfo(threadIds[i]);
347 if(ti != null){
348 String threadName = ti.getThreadName();
349 long userTime = tmxb.getThreadUserTime(threadIds[i]);
350 long systemTime = tmxb.getThreadCpuTime(threadIds[i]) - userTime;
351
352 if((userTime + systemTime) <= 0) continue;
353
354 ATerm userTimeTerm = tbfactory.makeAppl(tbfactory.makeAFun("user-time", 1, false), tbfactory.makeInt(((int) (userTime / 1000000))));
355 ATerm systemTimeTerm = tbfactory.makeAppl(tbfactory.makeAFun("system-time", 1, false), tbfactory.makeInt(((int) (systemTime / 1000000))));
356 ATerm thread = tbfactory.makeAppl(tbfactory.makeAFun(threadName, 2, false), userTimeTerm, systemTimeTerm);
357
358 threadsList = tbfactory.makeList(thread, threadsList);
359 }
360 }
361
362 threads = tbfactory.makeAppl(tbfactory.makeAFun("threads", 1, false), threadsList);
363 }catch(UnsupportedOperationException uoex){
364 threads = tbfactory.make("threads(unsupported-operation)");
365 LoggerFactory.log("Thread time profiling is not supported by this JVM.", ILogger.ERROR, IToolBusLoggerConstants.EXECUTE);
366 }
367
368 return tbfactory.makeAppl(tbfactory.makeAFun("performance-stats", 3, false), toolData, memory, threads);
369 }
370
371 /**
372 * Notifies the debug toolbus that it should execute normally.
373 */
374 public void doRun(){
375 synchronized(processLock){
376 workHasArrived = true; // This should not be needed; it's just here to ensure we will escape the wait loop.
377
378 doStep = false;
379 doRun = true;
380
381 fireStateChange(IViewerConstants.RUNNING_STATE);
382
383 processLock.notify();
384 }
385 }
386
387 /**
388 * Notifies the debug toolbus that it should suspend its execution.
389 */
390 public void doStop(){
391 synchronized(processLock){
392 fireStateChange(IViewerConstants.STOPPING_STATE);
393
394 doRun = false;
395 doStep = false;
396
397 processLock.notify(); // This causes the state to be updated properly.
398 }
399 }
400
401 /**
402 * Notifies the debug toolbus that it should execute one step.
403 */
404 public void doStep(){
405 synchronized(processLock){
406 workHasArrived = true; // This should not be needed; it's just here to ensure we will escape the wait loop.
407
408 doRun = false;
409 doStep = true;
410
411 fireStateChange(IViewerConstants.STEPPING_STATE);
412
413 processLock.notify();
414 }
415 }
416
417 /**
418 * Requests the termination of the ToolBus.
419 */
420 public void doTerminate(){
421 shutdown(TBTermFactory.getInstance().EmptyList);
422 }
423
424 /**
425 * Sends a notification to the attached Viewer that the ToolBus's state changed.
426 *
427 * @param state
428 * The state we changed to.
429 */
430 private void fireStateChange(int state){
431 if(state != currentState){
432 currentState = state;
433 viewer.updateState(state);
434 }
435 }
436
437 /**
438 * Tells the debug toolbus whether or not it should fire 'breakPointHit' events while stepping.
439 * This parameter is set to true by default.
440 *
441 * @param breakWhileStepping
442 * The flag that indicates whether or not to break on breakpoints while stepping.
443 */
444 public void setBreakWhileStepping(boolean breakWhileStepping){
445 this.breakWhileStepping = breakWhileStepping;
446 }
447
448 /**
449 * Adds a breakpoint for the process instance with the given id. When the debug toolbus executes
450 * a state element in the associated process instance, the attached Viewer will be notified.
451 *
452 * @param processId
453 * The id of the process instance we want to add a breakpoint for.
454 */
455 public void addProcessInstanceBreakPoint(int processId){
456 processInstanceBreakPoints.put(new Integer(processId));
457 }
458
459 /**
460 * Removes the breakpoint on the process instance with the given id (if present).
461 *
462 * @param processId
463 * The id of the process instance we want to remove the breakpoint for.
464 */
465 public void removeProcessInstanceBreakPoint(int processId){
466 processInstanceBreakPoints.remove(new Integer(processId));
467 }
468
469 /**
470 * Adds a breakpoint for all the process instances whos type is identified by the given name.
471 * When the debug toolbus executes a state element in one of those process instances, the
472 * attached Viewer will be notified.
473 *
474 * @param processName
475 * The name that identifies the type of process instances we want to add breakpoints
476 * for.
477 */
478 public void addProcessBreakPoint(String processName){
479 processBreakPoints.put(processName);
480 }
481
482 /**
483 * Removes the breakpoints for the process instances whos type is identified by the given name
484 * (if present).
485 *
486 * @param processName
487 * The name that identifies the type of process instance we want to remove the
488 * breakpoints for.
489 */
490 public void removeProcessBreakPoint(String processName){
491 processBreakPoints.remove(processName);
492 }
493
494 /**
495 * Adds a breakpoint on the given state element. When the debug toolbus executes the given state
496 * element, the attached Viewer will be notified.
497 *
498 * @param stateElement
499 * The state element we want to add a breakpoint for.
500 */
501 public void addStateElementBreakPoint(StateElement stateElement){
502 stateElementBreakPoints.put(stateElement);
503 }
504
505 /**
506 * Removes the breakpoint from the given state element (if present).
507 *
508 * @param stateElement
509 * The state element we want to remove the breakpoint from.
510 */
511 public void removeStateElementBreakPoint(StateElement stateElement){
512 stateElementBreakPoints.remove(stateElement);
513 }
514
515 /**
516 * Adds a breakpoint on the given sourcecode coordinates. When the debug toolbus executes a
517 * state element which's position information matches the sourcecode coordiates, the attached
518 * Viewer will be notified.
519 *
520 * @param filename
521 * The name of the sourcefile
522 * @param lineNumber
523 * The line number to add the breakpoint on. Note that sourcecode line numbers start
524 * counting at 0.
525 */
526 public void addSourceCodeBreakPoint(String filename, int lineNumber){
527 synchronized(sourceCodeBreakPoints){
528 List<Integer> lineNumbers = sourceCodeBreakPoints.get(filename);
529 if(lineNumbers == null){
530 lineNumbers = new LinkedList<Integer>();
531 sourceCodeBreakPoints.put(filename, lineNumbers);
532 }
533 synchronized(lineNumbers){
534 lineNumbers.add(Integer.valueOf(lineNumber));
535 }
536 }
537 }
538
539 /**
540 * Removes the breakpoint from the given source code coordinates (if present).
541 *
542 * @param filename
543 * The name of the sourcefile
544 * @param lineNumber
545 * The line number to remove the breakpoint for.
546 */
547 public void removeSourceCodeBreakPoint(String filename, int lineNumber){
548 synchronized(sourceCodeBreakPoints){
549 List<Integer> lineNumbers = sourceCodeBreakPoints.get(filename);
550 synchronized(lineNumbers){
551 lineNumbers.remove(Integer.valueOf(lineNumber));
552 }
553 if(lineNumbers.isEmpty()){
554 sourceCodeBreakPoints.remove(filename);
555 }
556 }
557 }
558
559 /**
560 * Initiates the monitoring of the tool associated with the given tool key (in case performance
561 * monitoring is enabled for this debug ToolBus).
562 *
563 * @param toolKey
564 * The tool key associated with the tool we want to monitor.
565 */
566 public void startMonitoringTool(ATerm toolKey){
567 performanceInformationRetriever.startMonitoringTool(toolKey);
568 }
569
570 /**
571 * Stops monitoring the tool associated with the given tool key.
572 *
573 * @param toolKey
574 * The tool key associated with the tool which we want to stop monitoring.
575 */
576 public void stopMonitoringTool(ATerm toolKey){
577 performanceInformationRetriever.startMonitoringTool(toolKey);
578 }
579
580 /**
581 * Initiates the monitoring of the given tool type (in case performance monitoring is enabled
582 * for this debug ToolBus).
583 *
584 * @param toolName
585 * The tool type of tool we want to monitor.
586 */
587 public void startMonitorToolType(String toolName){
588 performanceInformationRetriever.startMonitorToolType(toolName);
589 }
590
591 /**
592 * Stops monitoring tools of the given type.
593 *
594 * @param toolName
595 * The type of tool which we want to stop monitoring.
596 */
597 public void stopMonitoringToolType(String toolName){
598 performanceInformationRetriever.stopMonitoringToolType(toolName);
599 }
600 }