package com.foursquare.heapaudit; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; class HeapSettings { // The synchronized keyword is used on the parse method mostly to enforce a // memory barrier across all of the global variables being set in the parse // logic. static synchronized void parse(String args) throws ClassNotFoundException, FileNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException { timeout = -1; threaded = false; conditional = false; enabled = true; lock = null; output = System.out; recorderClass = HeapQuantile.class; toSuppressAuditing.clear(); toAvoidAuditing.clear(); toIncludeAuditing.clear(); toDebugAuditing.clear(); toTraceAuditing.clear(); toInjectRecorder.clear(); toAvoidAuditing.addAll(Arrays.asList(new Pattern("java/lang/ThreadLocal"), new Pattern("org/objectweb/asm/.+"), new Pattern("com/foursquare/heapaudit/HeapCollection.*"), new Pattern("com/foursquare/heapaudit/HeapRecorder.*"), new Pattern("com/foursquare/heapaudit/HeapUtil.*"), new Pattern("[$].*"), new Pattern("java/.+"), new Pattern("javax/.+"), new Pattern("org/jcp/.+"), new Pattern("org/xml/.+"), new Pattern("com/apple/.+"), new Pattern("apple/.+"), new Pattern("com/sun/.+"), new Pattern("sun/.+"), new Pattern("oracle/.+"), new Pattern("jrockit/.+"))); // actually do transform these classes, despite the matches in toAvoidAuditing toIncludeAuditing.addAll(Arrays.asList(new Pattern("java/util/.+") // what can safely go here? It's unknown, but // java/.+ causes all kinds of trouble. It's not // actually known that java/util/.+ is safe )); if (args != null) { for (String arg: args.split("[ #]")) { if ((arg.length() < 2) || (arg.charAt(0) != '-')) { throw new IllegalArgumentException(arg); } final String value = (arg.length() > 2) ? arg.substring(2) : null; switch (arg.charAt(1)) { case 'X': if (value.startsWith("timeout=")) { timeout = Integer.parseInt(value.substring(8)); } else if (value.startsWith("output=")) { FileOutputStream stream = new FileOutputStream(value.substring(7)); lock = stream.getChannel(); output = value.length() > 0 ? new PrintStream(stream) : System.out; } else if (value.startsWith("recorder=")) { String[] recorder = value.substring(9).split("@"); ClassLoader loader = new URLClassLoader(new URL[] { new URL("file:" + recorder[1]) }); recorderClass = loader.loadClass(recorder[0]).asSubclass(HeapSummary.class); } else if (value.equals("threaded")) { threaded = true; } else if (value.equals("conditional")) { conditional = true; } else if (value.startsWith("delay=")) { // NOTE: The enabled variable is NOT declared volatile. // The synchronized keyword surrounding setting the // enabled variable causes the compiler to insert memory // barriers before and after modifying the value, thus // flushing the change across threads on all processors. enabled = false; Thread thread = new Thread() { public void run() { try { sleep(Long.parseLong(value.substring(6))); } catch (java.lang.InterruptedException e) { output.println(e); } synchronized (this) { HeapSettings.enabled = true; } } }; thread.start(); } break; case 'S': toSuppressAuditing.add(new Pattern(value)); break; case 'A': toAvoidAuditing.add(new Pattern(value)); break; case 'D': toDebugAuditing.add(new Pattern(value)); break; case 'I': toInjectRecorder.add(new Pattern(value)); break; case 'T': toTraceAuditing.add(new Pattern(value)); break; default: throw new IllegalArgumentException(arg); } } } } // The timeout specifies how many milliseconds to wait before exiting from // the dynamic use case. static int timeout = -1; // The threaded setting determines whether to extend all local recorders // from the parent thread to the child thread. static boolean threaded = false; // The conditional setting determines whether to optimize for tradeoffs by // adding extra bytecode instructions to check and potentially skip the code // paths for executing the recording logic. If HeapAudit is expected to // always have at least one recorder present, then setting conditional to // false can avoid the checks. static boolean conditional = false; // The enabled setting determines whether to send the allocations to the // recorders. static boolean enabled = true; static FileChannel lock = null; static PrintStream output = System.out; // The following specifies the class of the dynamically injected recorder. static Class<? extends HeapSummary> recorderClass = null; private final static ArrayList<Pattern> toSuppressAuditing = new ArrayList<Pattern>(); private final static ArrayList<Pattern> toAvoidAuditing = new ArrayList<Pattern>(); // override avoidance private final static ArrayList<Pattern> toIncludeAuditing = new ArrayList<Pattern>(); private final static ArrayList<Pattern> toDebugAuditing = new ArrayList<Pattern>(); private final static ArrayList<Pattern> toTraceAuditing = new ArrayList<Pattern>(); private final static ArrayList<Pattern> toInjectRecorder = new ArrayList<Pattern>(); private static boolean should(ArrayList<Pattern> patterns, String classPath, String methodName) { for (Pattern pattern: patterns) { if (classPath.matches(pattern.classPattern)) { if ((methodName == null) || (pattern.methodPattern == null) || methodName.matches(pattern.methodPattern)) { return true; } } } return false; } static boolean shouldSuppressAuditing(String classPath, String methodName) { return should(toSuppressAuditing, classPath, methodName); } static boolean shouldAvoidAuditing(String classPath, String methodName) { return should(toAvoidAuditing, classPath, methodName) && (! should(toIncludeAuditing, classPath, methodName)); } static boolean shouldDebugAuditing(String classPath, String methodName) { return should(toDebugAuditing, classPath, methodName); } static boolean shouldTraceAuditing(String classPath, String methodName) { return should(toTraceAuditing, classPath, methodName); } static boolean shouldInjectRecorder(String classPath, String methodName) { return should(toInjectRecorder, classPath, methodName); } private static class Pattern { public Pattern(String pattern) { String[] parts = pattern.split("@"); classPattern = parts[0]; methodPattern = (parts.length > 1) ? parts[1] : null; } public final String classPattern; public final String methodPattern; } }