001 package nl.cwi.sen1.tunit; 002 003 import java.lang.management.ManagementFactory; 004 import java.lang.management.MemoryMXBean; 005 import java.lang.management.ThreadInfo; 006 import java.lang.management.ThreadMXBean; 007 import java.net.InetAddress; 008 import java.util.ArrayList; 009 import java.util.Collections; 010 import java.util.HashMap; 011 import java.util.HashSet; 012 import java.util.List; 013 import java.util.Map; 014 import java.util.Set; 015 016 import toolbus.ToolBus; 017 import toolbus.adapter.AbstractTool; 018 import toolbus.adapter.ToolBridge; 019 import toolbus.logging.ILogger; 020 import toolbus.logging.IToolBusLoggerConstants; 021 import toolbus.logging.LoggerFactory; 022 import aterm.ATerm; 023 import aterm.ATermAppl; 024 import aterm.ATermList; 025 import aterm.pure.PureFactory; 026 027 public class TestToolBridge extends ToolBridge{ 028 public final static long DEFAULTTIMEOUT = 10000; 029 030 private final Object completionLock = new Object(); 031 private final WaitLock expectLock = new WaitLock(); 032 private final WaitLock actionLock = new WaitLock(); 033 034 private final TUnitTestCase testCase; 035 036 private final PureFactory termFactory; 037 private final AbstractTool tool; 038 039 private final Map<String, WaitLock> expectedAckEvents; 040 private final Map<ATerm, ATerm> expectedEvals; 041 private final Set<ATerm> expectedDos; 042 043 private final List<ATerm> actionQueue; 044 private volatile boolean expecting; 045 046 private final boolean verbose; 047 048 public TestToolBridge(TUnitTestCase testCase, PureFactory termFactory, AbstractTool tool, String toolName, int toolID, InetAddress host, int port, boolean verbose){ 049 super(termFactory, toolName, toolID, host, port); 050 this.testCase = testCase; 051 this.termFactory = termFactory; 052 this.tool = tool; 053 054 expectedAckEvents = Collections.synchronizedMap(new HashMap<String, WaitLock>()); 055 expectedEvals = Collections.synchronizedMap(new HashMap<ATerm, ATerm>()); 056 expectedDos = Collections.synchronizedSet(new HashSet<ATerm>()); 057 058 actionQueue = Collections.synchronizedList(new ArrayList<ATerm>()); 059 expecting = false; 060 061 this.verbose = verbose; 062 } 063 064 public TestToolBridge(TUnitTestCase testCase, PureFactory termFactory, AbstractTool tool, String toolName, int toolID, ClassLoader classLoader, ToolBus toolbus, boolean verbose){ 065 super(termFactory, toolName, toolID, toolbus); 066 067 this.testCase = testCase; 068 this.termFactory = termFactory; 069 this.tool = tool; 070 071 expectedAckEvents = Collections.synchronizedMap(new HashMap<String, WaitLock>()); 072 expectedEvals = Collections.synchronizedMap(new HashMap<ATerm, ATerm>()); 073 expectedDos = Collections.synchronizedSet(new HashSet<ATerm>()); 074 075 actionQueue = Collections.synchronizedList(new ArrayList<ATerm>()); 076 expecting = false; 077 078 this.verbose = verbose; 079 } 080 081 public void receive(byte operation, ATerm aTerm){ 082 if(operation == ACKEVENT){ 083 String eventName = ((ATermAppl)((ATermList) aTerm).getFirst()).getAFun().toString(); 084 WaitLock eventLock = expectedAckEvents.remove(eventName); 085 086 synchronized(completionLock){ 087 completionLock.notifyAll(); 088 } 089 090 if(eventLock == null) testCase.testFailed("Tool "+getToolName()+": unexpected ack event: "+aTerm); 091 092 synchronized(eventLock){ 093 eventLock.notified = true; 094 eventLock.notify(); 095 } 096 } 097 098 if(verbose) System.err.println("Tool "+getToolName()+" recieved (" + operation + ") " + aTerm); 099 100 super.receive(operation, aTerm); 101 } 102 103 public boolean checkSignature(ATerm signature){ 104 return true; 105 } 106 107 public void doDo(ATerm aTerm){ 108 synchronized(actionLock){ 109 if(expecting){ 110 if(!expectedDos.contains(aTerm)) testCase.testFailed("Tool "+getToolName()+": unexpected do: "+aTerm); 111 }else{ 112 actionQueue.add(aTerm); 113 114 long waitToMillis = System.currentTimeMillis() + DEFAULTTIMEOUT; 115 while(!actionLock.notified && System.currentTimeMillis() < waitToMillis){ 116 try{ 117 long timeToWait = waitToMillis - System.currentTimeMillis(); 118 if(timeToWait > 0) actionLock.wait(timeToWait); 119 }catch(InterruptedException irex){/* Ignore this.*/} 120 } 121 if(!expectedDos.contains(aTerm)) testCase.testFailed("Tool "+getToolName()+": unexpected do: "+aTerm); 122 } 123 actionLock.notified = false; 124 } 125 126 synchronized(expectLock){ 127 expectLock.notified = true; 128 expectLock.notify(); 129 } 130 } 131 132 public ATerm doEval(ATerm aTerm){ 133 ATerm value; 134 135 synchronized(actionLock){ 136 if(expecting){ 137 value = expectedEvals.get(aTerm); 138 139 if(value == null) testCase.testFailed("Tool "+getToolName()+": unexpected eval: "+aTerm); 140 }else{ 141 actionQueue.add(aTerm); 142 143 long waitToMillis = System.currentTimeMillis() + DEFAULTTIMEOUT; 144 while(!actionLock.notified && System.currentTimeMillis() < waitToMillis){ 145 try{ 146 long timeToWait = waitToMillis - System.currentTimeMillis(); 147 if(timeToWait > 0) actionLock.wait(timeToWait); 148 }catch(InterruptedException irex){/* Ignore this.*/} 149 } 150 151 value = expectedEvals.get(aTerm); 152 153 if(value == null) testCase.testFailed("Tool "+getToolName()+": unexpected eval: "+aTerm); 154 } 155 156 actionLock.notified = false; 157 } 158 159 synchronized(expectLock){ 160 expectLock.notified = true; 161 expectLock.notify(); 162 } 163 164 return value; 165 } 166 167 public ATerm doGetPerformanceStats(){ 168 return getPerformanceStats(); 169 } 170 171 public void doReceiveAckEvent(ATerm aTerm){ 172 tool.receiveAckEvent(aTerm); 173 } 174 175 public void doTerminate(ATerm aTerm){ 176 tool.receiveTerminate(aTerm); 177 } 178 179 public void sendEvent(final ATerm event, final long timeout){ 180 final WaitLock eventLock = new WaitLock(); 181 final String eventName = ((ATermAppl) event).getAFun().toString(); 182 183 if(verbose) System.err.println("Tool "+getToolName()+" sends event: "+event); 184 185 expectedAckEvents.put(eventName, eventLock); 186 187 postEvent(event); 188 189 new Thread(){ 190 public void run(){ 191 long waitToMillis = System.currentTimeMillis() + timeout; 192 193 synchronized(eventLock){ 194 while(!eventLock.notified){ 195 try{ 196 long timeToWait = waitToMillis - System.currentTimeMillis(); 197 if(timeToWait > 0) eventLock.wait(timeToWait); 198 }catch(InterruptedException irex){/* Ignore this. */} 199 200 if(System.currentTimeMillis() >= waitToMillis){ 201 expectedAckEvents.remove(eventName); 202 synchronized(completionLock){ 203 completionLock.notifyAll(); 204 } 205 206 testCase.testFailed("Tool "+getToolName()+": timed out while waiting for ack event of: "+event); 207 } 208 } 209 } 210 } 211 }.start(); 212 } 213 214 public ATerm sendRequest(final ATerm request, ATerm expectedResponse, long timeout){ 215 final Object requestLock = new Object(); 216 217 if(verbose) System.err.println("Tool "+getToolName()+" sends request "+request); 218 219 class RequestPoster extends Thread{ 220 public volatile ATerm response = null; 221 222 public void run(){ 223 response = postRequest(request); 224 synchronized(requestLock){ 225 requestLock.notify(); 226 } 227 } 228 } 229 RequestPoster requestPoster = new RequestPoster(); 230 requestPoster.start(); 231 232 long waitToMillis = System.currentTimeMillis() + timeout; 233 synchronized(requestLock){ 234 while(requestPoster.response == null){ 235 try{ 236 long timeToWait = waitToMillis - System.currentTimeMillis(); 237 if(timeToWait > 0) requestLock.wait(timeToWait); 238 }catch(InterruptedException irex){/* Ignore this. */} 239 240 if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for response of: "+request); 241 } 242 } 243 244 ATerm response = requestPoster.response; 245 246 if(response != expectedResponse) testCase.testFailed("Tool "+getToolName()+": received an unexpected response. Expected: "+expectedResponse+", got: "+response); 247 248 return response; 249 } 250 251 public void registerForDo(ATerm action){ 252 expectedDos.add(action); 253 } 254 255 public void registerForEval(ATerm action, ATerm expectedResult){ 256 expectedEvals.put(action, expectedResult); 257 } 258 259 public void expectAction(long timeout){ 260 if(expectedDos.size() + expectedEvals.size() == 0) testCase.testFailed("Can't expect nothing."); 261 262 if(verbose) System.err.println("Tool "+getToolName()+" expects eval's: "+expectedEvals+" and do's: "+expectedDos); 263 264 synchronized(actionLock){ 265 expecting = true; 266 267 if(!actionQueue.isEmpty()){ 268 actionLock.notified = true; 269 actionLock.notify(); 270 } 271 } 272 273 long waitToMillis = System.currentTimeMillis() + timeout; 274 synchronized(expectLock){ 275 while(!expectLock.notified){ 276 try{ 277 long timeToWait = waitToMillis - System.currentTimeMillis(); 278 if(timeToWait > 0) expectLock.wait(timeToWait); 279 }catch(InterruptedException irex){/* Ignore this. */} 280 281 if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for evals and/or do's."); 282 } 283 expectLock.notified = false; 284 } 285 286 synchronized(actionLock){ 287 actionQueue.clear(); 288 expecting = false; 289 } 290 291 expectedDos.clear(); 292 expectedEvals.clear(); 293 } 294 295 public void waitForCompletion(long timeout){ 296 long waitToMillis = System.currentTimeMillis() + timeout; 297 298 synchronized(completionLock){ 299 while(!expectedAckEvents.isEmpty()){ 300 try{ 301 long timeToWait = waitToMillis - System.currentTimeMillis(); 302 if(timeToWait > 0) completionLock.wait(timeToWait); 303 }catch(InterruptedException irex){/* Ignore this. */} 304 305 if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for ack event(s) for: "+expectedAckEvents); 306 } 307 } 308 309 if(!actionQueue.isEmpty()) testCase.testFailed("Tool "+getToolName()+": unexpected queued eval or do detected while waiting for completion."+actionQueue); 310 } 311 312 private static class WaitLock{ 313 public volatile boolean notified = false; 314 } 315 316 /** 317 * Gathers performance statistics about this tool, like memory usage and the user-/system-time 318 * spend per thread. 319 * 320 * @return Performance statictics. 321 */ 322 private ATerm getPerformanceStats(){ 323 String type = getType(); 324 325 // Type stuff 326 ATerm remote = termFactory.makeAppl(termFactory.makeAFun(type, 0, true)); 327 ATerm toolType = termFactory.makeAppl(termFactory.makeAFun("type", 1, false), remote); 328 329 ATerm java = termFactory.makeAppl(termFactory.makeAFun("Java", 0, true)); 330 ATerm toolLanguage = termFactory.makeAppl(termFactory.makeAFun("language", 1, false), java); 331 332 ATerm toolData = termFactory.makeAppl(termFactory.makeAFun("tool", 2, false), toolType, toolLanguage); 333 334 // Memory stuff 335 MemoryMXBean mmxb = ManagementFactory.getMemoryMXBean(); 336 long heapMemoryUsage = mmxb.getHeapMemoryUsage().getUsed(); 337 long nonHeapMemoryUsage = mmxb.getNonHeapMemoryUsage().getUsed(); 338 339 ATerm heapUsage = termFactory.makeAppl(termFactory.makeAFun("heap-usage", 1, false), termFactory.makeInt(((int) (heapMemoryUsage / 1024)))); 340 ATerm nonHeapUsage = termFactory.makeAppl(termFactory.makeAFun("non-heap-usage", 1, false), termFactory.makeInt(((int) (nonHeapMemoryUsage / 1024)))); 341 342 ATerm memory = termFactory.makeAppl(termFactory.makeAFun("memory-usage", 2, false), heapUsage, nonHeapUsage); 343 344 // Thread stuff 345 ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup(); 346 Thread[] relevantThreads = new Thread[currentThreadGroup.activeCount() * 2 + 10]; // Create an array that's more then big enough. 347 currentThreadGroup.enumerate(relevantThreads); 348 349 Set<Long> relevantThreadIds = new HashSet<Long>(); 350 for(int i = 0; i < relevantThreads.length; i++){ 351 Thread thread = relevantThreads[i]; 352 if(thread != null){ 353 relevantThreadIds.add(new Long(thread.getId())); 354 } 355 } 356 357 ThreadMXBean tmxb = ManagementFactory.getThreadMXBean(); 358 359 ATerm threads; 360 361 long[] threadIds = tmxb.getAllThreadIds(); 362 int nrOfThreads = threadIds.length; 363 try{ 364 ATermList threadsList = termFactory.makeList(); 365 for(int i = 0; i < nrOfThreads; i++){ 366 long threadId = threadIds[i]; 367 if(relevantThreadIds.contains(new Long(threadId))){ // Only list the info if we're interested in it. 368 ThreadInfo ti = tmxb.getThreadInfo(threadId); 369 if(ti != null){ 370 String threadName = ti.getThreadName(); 371 long userTime = tmxb.getThreadUserTime(threadIds[i]); 372 long systemTime = tmxb.getThreadCpuTime(threadIds[i]) - userTime; 373 374 if((userTime + systemTime) <= 0) continue; 375 376 ATerm userTimeTerm = termFactory.makeAppl(termFactory.makeAFun("user-time", 1, false), termFactory.makeInt(((int) (userTime / 1000000)))); 377 ATerm systemTimeTerm = termFactory.makeAppl(termFactory.makeAFun("system-time", 1, false), termFactory.makeInt(((int) (systemTime / 1000000)))); 378 ATerm thread = termFactory.makeAppl(termFactory.makeAFun(threadName, 2, false), userTimeTerm, systemTimeTerm); 379 380 threadsList = termFactory.makeList(thread, threadsList); 381 } 382 } 383 } 384 385 threads = termFactory.makeAppl(termFactory.makeAFun("threads", 1, false), threadsList); 386 }catch(UnsupportedOperationException uoex){ 387 threads = termFactory.make("threads(unsupported-operation)"); 388 LoggerFactory.log("Thread time profiling is not supported by this JVM.", ILogger.ERROR, IToolBusLoggerConstants.TOOL); 389 } 390 391 return termFactory.makeAppl(termFactory.makeAFun("performance-stats", 3, false), toolData, memory, threads); 392 } 393 }