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 }