001/* 002 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.debug.internal; 024 025import java.io.*; 026import java.util.*; 027import java.util.concurrent.*; 028 029import com.oracle.graal.debug.*; 030 031public final class DebugScope implements Debug.Scope { 032 033 private final class IndentImpl implements Indent { 034 035 private static final String INDENTATION_INCREMENT = " "; 036 037 final String indent; 038 final IndentImpl parentIndent; 039 040 IndentImpl(IndentImpl parentIndent) { 041 this.parentIndent = parentIndent; 042 this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT); 043 } 044 045 private void printScopeName(StringBuilder str) { 046 if (logScopeName) { 047 if (parentIndent != null) { 048 parentIndent.printScopeName(str); 049 } 050 str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator()); 051 logScopeName = false; 052 } 053 } 054 055 public void log(int logLevel, String msg, Object... args) { 056 if (isLogEnabled(logLevel)) { 057 StringBuilder str = new StringBuilder(); 058 printScopeName(str); 059 str.append(indent); 060 String result = args.length == 0 ? msg : String.format(msg, args); 061 String lineSep = System.lineSeparator(); 062 str.append(result.replace(lineSep, lineSep.concat(indent))); 063 str.append(lineSep); 064 output.append(str); 065 lastUsedIndent = this; 066 } 067 } 068 069 IndentImpl indent() { 070 lastUsedIndent = new IndentImpl(this); 071 return lastUsedIndent; 072 } 073 074 @Override 075 public void close() { 076 if (parentIndent != null) { 077 lastUsedIndent = parentIndent; 078 } 079 } 080 } 081 082 private static final ThreadLocal<DebugScope> instanceTL = new ThreadLocal<>(); 083 private static final ThreadLocal<DebugScope> lastClosedTL = new ThreadLocal<>(); 084 private static final ThreadLocal<DebugConfig> configTL = new ThreadLocal<>(); 085 private static final ThreadLocal<Throwable> lastExceptionThrownTL = new ThreadLocal<>(); 086 087 private final DebugScope parent; 088 private final DebugConfig parentConfig; 089 private final boolean sandbox; 090 private IndentImpl lastUsedIndent; 091 private boolean logScopeName; 092 093 private final Object[] context; 094 095 private DebugValueMap valueMap; 096 097 private String qualifiedName; 098 private final String unqualifiedName; 099 100 private static final char SCOPE_SEP = '.'; 101 102 private boolean meterEnabled; 103 private boolean timeEnabled; 104 private boolean memUseTrackingEnabled; 105 private boolean verifyEnabled; 106 107 private int currentDumpLevel; 108 private int currentLogLevel; 109 110 private PrintStream output; 111 112 public static DebugScope getInstance() { 113 DebugScope result = instanceTL.get(); 114 if (result == null) { 115 DebugScope topLevelDebugScope = new DebugScope(Thread.currentThread()); 116 instanceTL.set(topLevelDebugScope); 117 return topLevelDebugScope; 118 } else { 119 return result; 120 } 121 } 122 123 public static DebugConfig getConfig() { 124 return configTL.get(); 125 } 126 127 static final Object[] EMPTY_CONTEXT = new Object[0]; 128 129 private DebugScope(Thread thread) { 130 this(thread.getName(), null, false); 131 computeValueMap(thread.getName()); 132 DebugValueMap.registerTopLevel(getValueMap()); 133 } 134 135 private DebugScope(String unqualifiedName, DebugScope parent, boolean sandbox, Object... context) { 136 this.parent = parent; 137 this.sandbox = sandbox; 138 this.parentConfig = getConfig(); 139 this.context = context; 140 this.unqualifiedName = unqualifiedName; 141 if (parent != null) { 142 logScopeName = !unqualifiedName.equals(""); 143 } else { 144 logScopeName = true; 145 } 146 147 this.output = TTY.out; 148 assert context != null; 149 } 150 151 private void computeValueMap(String name) { 152 if (parent != null) { 153 for (DebugValueMap child : parent.getValueMap().getChildren()) { 154 if (child.getName().equals(name)) { 155 this.valueMap = child; 156 return; 157 } 158 } 159 this.valueMap = new DebugValueMap(name); 160 parent.getValueMap().addChild(this.valueMap); 161 } else { 162 this.valueMap = new DebugValueMap(name); 163 } 164 } 165 166 public void close() { 167 instanceTL.set(parent); 168 configTL.set(parentConfig); 169 lastClosedTL.set(this); 170 } 171 172 public boolean isDumpEnabled(int dumpLevel) { 173 assert dumpLevel > 0; 174 return currentDumpLevel >= dumpLevel; 175 } 176 177 /** 178 * Enable dumping at the new {@code dumpLevel} for the remainder of enclosing scopes. This only 179 * works if a {@link TopLevelDebugConfig} was installed at a higher scope. 180 * 181 * @param dumpLevel 182 */ 183 public static void setDumpLevel(int dumpLevel) { 184 TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel"); 185 if (config != null) { 186 config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel); 187 recursiveUpdateFlags(); 188 } 189 } 190 191 /** 192 * Enable logging at the new {@code logLevel} for the remainder of enclosing scopes. This only 193 * works if a {@link TopLevelDebugConfig} was installed at a higher scope. 194 * 195 * @param logLevel 196 */ 197 public static void setLogLevel(int logLevel) { 198 TopLevelDebugConfig config = fetchTopLevelDebugConfig("setLogLevel"); 199 if (config != null) { 200 config.override(DelegatingDebugConfig.Level.LOG, logLevel); 201 config.delegate(DelegatingDebugConfig.Feature.LOG_METHOD); 202 recursiveUpdateFlags(); 203 } 204 } 205 206 private static void recursiveUpdateFlags() { 207 DebugScope c = DebugScope.getInstance(); 208 while (c != null) { 209 c.updateFlags(); 210 c = c.parent; 211 } 212 } 213 214 private static TopLevelDebugConfig fetchTopLevelDebugConfig(String msg) { 215 DebugConfig config = getConfig(); 216 if (config instanceof TopLevelDebugConfig) { 217 return (TopLevelDebugConfig) config; 218 } else { 219 if (config == null) { 220 TTY.println("DebugScope.%s ignored because debugging is disabled", msg); 221 } else { 222 TTY.println("DebugScope.%s ignored because top level delegate config missing", msg); 223 } 224 return null; 225 } 226 } 227 228 public boolean isVerifyEnabled() { 229 return verifyEnabled; 230 } 231 232 public boolean isLogEnabled(int logLevel) { 233 assert logLevel > 0; 234 return currentLogLevel >= logLevel; 235 } 236 237 public boolean isMeterEnabled() { 238 return meterEnabled; 239 } 240 241 public boolean isTimeEnabled() { 242 return timeEnabled; 243 } 244 245 public boolean isMemUseTrackingEnabled() { 246 return memUseTrackingEnabled; 247 } 248 249 public void log(int logLevel, String msg, Object... args) { 250 if (isLogEnabled(logLevel)) { 251 getLastUsedIndent().log(logLevel, msg, args); 252 } 253 } 254 255 public void dump(int dumpLevel, Object object, String formatString, Object... args) { 256 if (isDumpEnabled(dumpLevel)) { 257 DebugConfig config = getConfig(); 258 if (config != null) { 259 String message = String.format(formatString, args); 260 for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { 261 dumpHandler.dump(object, message); 262 } 263 } 264 } 265 } 266 267 /** 268 * This method exists mainly to allow a debugger (e.g., Eclipse) to force dump a graph. 269 */ 270 public static void forceDump(Object object, String format, Object... args) { 271 DebugConfig config = getConfig(); 272 if (config != null) { 273 String message = String.format(format, args); 274 for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { 275 dumpHandler.dump(object, message); 276 } 277 } else { 278 TTY.println("Forced dump ignored because debugging is disabled - use -G:Dump=xxx option"); 279 } 280 } 281 282 /** 283 * @see Debug#verify(Object, String) 284 */ 285 public void verify(Object object, String formatString, Object... args) { 286 if (isVerifyEnabled()) { 287 DebugConfig config = getConfig(); 288 if (config != null) { 289 String message = String.format(formatString, args); 290 for (DebugVerifyHandler handler : config.verifyHandlers()) { 291 handler.verify(object, message); 292 } 293 } 294 } 295 } 296 297 /** 298 * Creates and enters a new debug scope which is either a child of the current scope or a 299 * disjoint top level scope. 300 * 301 * @param name the name of the new scope 302 * @param sandboxConfig the configuration to use for a new top level scope, or null if the new 303 * scope should be a child scope 304 * @param newContextObjects objects to be appended to the debug context 305 * @return the new scope which will be exited when its {@link #close()} method is called 306 */ 307 public DebugScope scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) { 308 DebugScope newScope = null; 309 if (sandboxConfig != null) { 310 newScope = new DebugScope(name.toString(), this, true, newContextObjects); 311 configTL.set(sandboxConfig); 312 } else { 313 newScope = this.createChild(name.toString(), newContextObjects); 314 } 315 instanceTL.set(newScope); 316 newScope.updateFlags(); 317 return newScope; 318 } 319 320 public RuntimeException handle(Throwable e) { 321 DebugScope lastClosed = lastClosedTL.get(); 322 assert lastClosed.parent == this : "Debug.handle() used with no matching Debug.scope(...) or Debug.sandbox(...)"; 323 if (e != lastExceptionThrownTL.get()) { 324 RuntimeException newException = null; 325 instanceTL.set(lastClosed); 326 try (DebugScope s = lastClosed) { 327 newException = s.interceptException(e); 328 } 329 assert instanceTL.get() == this; 330 assert lastClosed == lastClosedTL.get(); 331 if (newException == null) { 332 lastExceptionThrownTL.set(e); 333 } else { 334 lastExceptionThrownTL.set(newException); 335 throw newException; 336 } 337 } 338 if (e instanceof Error) { 339 throw (Error) e; 340 } 341 if (e instanceof RuntimeException) { 342 throw (RuntimeException) e; 343 } 344 throw new RuntimeException(e); 345 } 346 347 private void updateFlags() { 348 DebugConfig config = getConfig(); 349 if (config == null) { 350 meterEnabled = false; 351 memUseTrackingEnabled = false; 352 timeEnabled = false; 353 verifyEnabled = false; 354 355 currentDumpLevel = 0; 356 357 // Be pragmatic: provide a default log stream to prevent a crash if the stream is not 358 // set while logging 359 output = TTY.out; 360 } else { 361 meterEnabled = config.isMeterEnabled(); 362 memUseTrackingEnabled = config.isMemUseTrackingEnabled(); 363 timeEnabled = config.isTimeEnabled(); 364 verifyEnabled = config.isVerifyEnabled(); 365 output = config.output(); 366 currentDumpLevel = config.getDumpLevel(); 367 currentLogLevel = config.getLogLevel(); 368 } 369 } 370 371 private RuntimeException interceptException(final Throwable e) { 372 final DebugConfig config = getConfig(); 373 if (config != null) { 374 try (DebugScope s = scope("InterceptException", null, e)) { 375 return config.interceptException(e); 376 } catch (Throwable t) { 377 return new RuntimeException("Exception while intercepting exception", t); 378 } 379 } 380 return null; 381 } 382 383 private DebugValueMap getValueMap() { 384 if (valueMap == null) { 385 computeValueMap(unqualifiedName); 386 } 387 return valueMap; 388 } 389 390 long getCurrentValue(int index) { 391 return getValueMap().getCurrentValue(index); 392 } 393 394 void setCurrentValue(int index, long l) { 395 getValueMap().setCurrentValue(index, l); 396 } 397 398 private DebugScope createChild(String newName, Object[] newContext) { 399 return new DebugScope(newName, this, false, newContext); 400 } 401 402 public Iterable<Object> getCurrentContext() { 403 final DebugScope scope = this; 404 return new Iterable<Object>() { 405 406 @Override 407 public Iterator<Object> iterator() { 408 return new Iterator<Object>() { 409 410 DebugScope currentScope = scope; 411 int objectIndex; 412 413 @Override 414 public boolean hasNext() { 415 selectScope(); 416 return currentScope != null; 417 } 418 419 private void selectScope() { 420 while (currentScope != null && currentScope.context.length <= objectIndex) { 421 currentScope = currentScope.sandbox ? null : currentScope.parent; 422 objectIndex = 0; 423 } 424 } 425 426 @Override 427 public Object next() { 428 selectScope(); 429 if (currentScope != null) { 430 return currentScope.context[objectIndex++]; 431 } 432 throw new IllegalStateException("May only be called if there is a next element."); 433 } 434 435 @Override 436 public void remove() { 437 throw new UnsupportedOperationException("This iterator is read only."); 438 } 439 }; 440 } 441 }; 442 } 443 444 public static <T> T call(Callable<T> callable) { 445 try { 446 return callable.call(); 447 } catch (Exception e) { 448 if (e instanceof RuntimeException) { 449 throw (RuntimeException) e; 450 } else { 451 throw new RuntimeException(e); 452 } 453 } 454 } 455 456 public void setConfig(DebugConfig newConfig) { 457 configTL.set(newConfig); 458 updateFlags(); 459 } 460 461 public String getQualifiedName() { 462 if (qualifiedName == null) { 463 if (parent == null) { 464 qualifiedName = unqualifiedName; 465 } else { 466 qualifiedName = parent.getQualifiedName() + SCOPE_SEP + unqualifiedName; 467 } 468 } 469 return qualifiedName; 470 } 471 472 public Indent pushIndentLogger() { 473 lastUsedIndent = getLastUsedIndent().indent(); 474 return lastUsedIndent; 475 } 476 477 public IndentImpl getLastUsedIndent() { 478 if (lastUsedIndent == null) { 479 if (parent != null) { 480 lastUsedIndent = new IndentImpl(parent.getLastUsedIndent()); 481 } else { 482 lastUsedIndent = new IndentImpl(null); 483 } 484 } 485 return lastUsedIndent; 486 } 487}