Skip to content

Commit

Permalink
[APROF-108] Tracking of calls to tracked methods via interface/super-…
Browse files Browse the repository at this point in the history
…classes fails when they are loaded in non-default class loader
  • Loading branch information
elizarov committed Feb 6, 2014
1 parent 844fa6d commit 084b60e
Show file tree
Hide file tree
Showing 30 changed files with 373 additions and 171 deletions.
12 changes: 11 additions & 1 deletion agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,20 @@
<name>Aprof Agent</name>

<dependencies>
<dependency>
<groupId>com.devexperts.aprof</groupId>
<artifactId>core</artifactId>
<version>28-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.devexperts.aprof</groupId>
<artifactId>transformer</artifactId>
<version>${project.version}</version>
<version>28-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.devexperts.aprof</groupId>
<artifactId>selftest</artifactId>
<version>28-SNAPSHOT</version>
</dependency>
</dependencies>

Expand Down
11 changes: 8 additions & 3 deletions core/src/main/java/com/devexperts/aprof/AProfAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ public void go() throws Exception {
AProfSizeUtil.init(inst);
AProfRegistry.init(config, resolverClass.newInstance());

Class<ClassFileTransformer> transformerClass = (Class<ClassFileTransformer>)classLoader.loadClass(TRANSFORMER_CLASS);
Constructor<ClassFileTransformer> transformerConstructor = transformerClass.getConstructor(Configuration.class);
ClassFileTransformer transformer = transformerConstructor.newInstance(config);
Class<TransformerAnalyzer> transformerClass = (Class<TransformerAnalyzer>)classLoader.loadClass(TRANSFORMER_CLASS);
Constructor<TransformerAnalyzer> transformerConstructor = transformerClass.getConstructor(Configuration.class);
TransformerAnalyzer transformer = transformerConstructor.newInstance(config);

// Analyze tracked classes in bootstrap classloader using transfers for class-hierarchy analysis
config.analyzeTrackedClasses(null, transformer);

// redefine all classes loader so far
redefine(transformer);

inst.addTransformer(transformer);
Expand Down
60 changes: 12 additions & 48 deletions core/src/main/java/com/devexperts/aprof/AProfTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,29 @@
package com.devexperts.aprof;

import java.io.*;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.URL;
import java.util.Locale;

import com.devexperts.aprof.dump.DumpFormatter;
import com.devexperts.aprof.dump.SnapshotRoot;
import com.devexperts.aprof.selftest.TestCase;
import com.devexperts.aprof.selftest.TestSuite;
import com.devexperts.aprof.util.FastOutputStreamWriter;
import com.devexperts.aprof.util.InnerJarClassLoader;

/**
* @author Denis Davydov
*/
public class AProfTools {

private static final String STARTUP_NOTICE = Version.full() +
public static final String STARTUP_NOTICE = Version.full() +
"\nThis program comes with ABSOLUTELY NO WARRANTY." +
"\nThis is free software, and you are welcome to redistribute it under certain conditions." +
"\nSource code and documentation are available at <http://code.devexperts.com/>.";

private static final String ENCODING = "UTF-8";

public static void main(final String[] args) throws IOException, ClassNotFoundException {
public static void main(final String[] args) throws Exception {
if (args.length > 0) {
String command = args[0].trim().toLowerCase(Locale.US);
if ("dump".equals(command)) {
Expand Down Expand Up @@ -88,15 +89,13 @@ private static String padr(String s, int len) {
return s;
}

private static void helpSelfTest() throws IOException {
PrintWriter out = new PrintWriter(new FastOutputStreamWriter(System.out), true);
out.println(STARTUP_NOTICE);
out.println();
out.println("Usage: java -ea -javaagent:aprof.jar -jar aprof.jar selftest [<test> ...]");
out.println("Where <test> is 'all' or one of tests:");
for (TestCase test : TestSuite.getTestCases()) {
out.println("\t" + test.name());
}
private static void runSelfTest(String[] args) throws Exception {
// run self-tests from an inner jar file
URL url = Thread.currentThread().getContextClassLoader().getResource("selftest.jar");
InnerJarClassLoader classLoader = new InnerJarClassLoader(url);
Class<?> testSuiteClass = classLoader.loadClass("com.devexperts.aprof.selftest.TestSuite");
Method mainMethod = testSuiteClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object)args);
}

private static void runDumpCommand(String[] args) throws IOException, ClassNotFoundException {
Expand Down Expand Up @@ -144,39 +143,4 @@ private static void runExportCommand(String[] args) throws IOException {
out.close();
}
}

private static void runSelfTest(String[] args) throws IOException {
if (args.length < 2) {
helpSelfTest();
return;
}
boolean ok = true;
for (int i = 1; i < args.length; i++) {
String testName = args[i].trim();
if ("all".equalsIgnoreCase(testName)) {
if (!TestSuite.testAllApplicableCases())
ok = false;
continue;
}
boolean done = false;
for (TestCase test : TestSuite.getTestCases()) {
if (test.name().equalsIgnoreCase(testName)) {
if (!TestSuite.testSingleCase(test))
ok = false;
done = true;
}
}
if (!done) {
helpSelfTest();
return;
}
}
if (ok) {
System.out.println("==== ALL TESTS HAVE PASSED");
System.exit(0);
} else {
System.out.println("==== SOME TESTS HAVE FAILED !!!");
System.exit(1);
}
}
}
9 changes: 6 additions & 3 deletions core/src/main/java/com/devexperts/aprof/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ public class Configuration {
@Description("Be verbose and log every class transformation.")
private boolean verbose = false;

@Description("Be verbose about class redefinitions too.")
@Description("Be verbose about class redefinitions.")
private boolean verbose_redefinition = false;

@Description("Be verbose about available tracked classes.")
private boolean verbose_tracked = false;

@Description("Dump aprof-instrumented class into a specified directory.")
private String dump_classes_dir = "";

Expand Down Expand Up @@ -242,8 +245,8 @@ public int getPort() {
return port;
}

public void reloadTrackedClasses() {
detailsConfig.reloadTrackedClasses();
public void analyzeTrackedClasses(ClassLoader loader, TransformerAnalyzer analyzer) {
detailsConfig.analyzeTrackedClasses(loader, analyzer, verbose_tracked);
}

public boolean isLocationTracked(String locationClass, String locationMethod) {
Expand Down
90 changes: 46 additions & 44 deletions core/src/main/java/com/devexperts/aprof/DetailsConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.io.*;
import java.util.*;

import com.devexperts.aprof.util.Log;

/**
* @author Dmitry Paraschenko
*/
Expand All @@ -29,29 +31,34 @@ class DetailsConfiguration {

private static final String ANY_METHOD = "*";

/** Class name --> set of method names. */
private Map<String, Set<String>> trackedLocations = new HashMap<String, Set<String>>();
/**
* Maps class name to a set of tracked method names.
*/
private final Map<String, Set<String>> trackedLocations = new HashMap<String, Set<String>>();

private final Set<String> remainingClasses = new LinkedHashSet<String>();

private boolean bootstrapLoaderAnalyzed;

private HashSet<String> remainingClasses = new HashSet<String>();
private boolean reloadTrackedClasses;
private ClassLoader lastAnalyzedLoader; // leak at most one class per class loader

public DetailsConfiguration() {}

public void loadFromResource() throws IOException {
public synchronized void loadFromResource() throws IOException {
loadFromStream(ClassLoader.getSystemResourceAsStream(RESOURCE));
for (String str : trackedLocations.keySet())
remainingClasses.add(str);
}

public void loadFromFile(String fileName) throws IOException {
public synchronized void loadFromFile(String fileName) throws IOException {
if (fileName == null || fileName.trim().length() == 0)
return;
loadFromStream(new FileInputStream(fileName));
for (String str : trackedLocations.keySet())
remainingClasses.add(str);
}

public void addClassMethods(String[] locations) throws IOException {
public synchronized void addClassMethods(String[] locations) throws IOException {
for (String location : locations) {
int pos = location.lastIndexOf('.');
if (pos < 0)
Expand All @@ -62,52 +69,47 @@ public void addClassMethods(String[] locations) throws IOException {
}
}

public void reloadTrackedClasses() {
reloadTrackedClasses = true;
}

public boolean isLocationTracked(String locationClass, String locationMethod) {
Map<String, Set<String>> tracked = getTrackedMethods();
public synchronized boolean isLocationTracked(String locationClass, String locationMethod) {
Map<String, Set<String>> tracked = trackedLocations;
Set<String> trackedMethods = tracked.get(locationClass);
return trackedMethods != null &&
(trackedMethods.contains(ANY_METHOD) || trackedMethods.contains(locationMethod));
}

private Map<String, Set<String>> getTrackedMethods() {
if (reloadTrackedClasses) {
reloadTrackedClasses = false;
ArrayList<String> processedClasses = null;
for (String className : remainingClasses) {
try {
Class clazz = Class.forName(className);
Set<String> methods = trackedLocations.get(className);
while (clazz != null && clazz != Object.class) {
addInterfaces(clazz, methods);
clazz = clazz.getSuperclass();
}
if (processedClasses == null)
processedClasses = new ArrayList<String>();
processedClasses.add(className);
} catch (ClassNotFoundException e) {
// do nothing
}
}
if (processedClasses != null) {
remainingClasses.removeAll(processedClasses);
}
/**
* Analyzes tracked classes in the specified class loader.
*/
public synchronized void analyzeTrackedClasses(ClassLoader loader, TransformerAnalyzer analyzer, boolean verbose) {
if (remainingClasses.isEmpty() || (loader == null ? bootstrapLoaderAnalyzed : loader == lastAnalyzedLoader))
return;
List<String> processedClasses = new ArrayList<String>();
for (String className : remainingClasses) {
Set<String> methods = trackedLocations.get(className);
List<String> parents = analyzer.getImmediateClassParents(className, loader);
if (parents == null)
continue;
if (verbose)
Log.out.println("Resolving tracked class: " + className);
for (String parent : parents)
analyzeClassRec(parent, loader, analyzer, methods);
processedClasses.add(className);
}
return trackedLocations;
remainingClasses.removeAll(processedClasses);
if (loader == null)
bootstrapLoaderAnalyzed = true;
else
lastAnalyzedLoader = loader;
}

private void addInterfaces(Class clazz, Set<String> classMethods) {
Set<String> methods = trackedLocations.get(clazz.getName());
private void analyzeClassRec(String className, ClassLoader loader, TransformerAnalyzer analyzer, Set<String> newMethods) {
Set<String> methods = trackedLocations.get(className);
if (methods == null)
trackedLocations.put(clazz.getName(), methods = new HashSet<String>());
methods.addAll(classMethods);
Class[] interfaces = clazz.getInterfaces();
if (interfaces != null)
for (Class anInterface : interfaces)
addInterfaces(anInterface, classMethods);
trackedLocations.put(className, methods = new HashSet<String>());
methods.addAll(newMethods);
List<String> parents = analyzer.getImmediateClassParents(className, loader);
if (parents != null)
for (String parent : parents)
analyzeClassRec(parent, loader, analyzer, newMethods);
}

private void loadFromStream(InputStream stream) throws IOException {
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/com/devexperts/aprof/TransformerAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.devexperts.aprof;

import java.lang.instrument.ClassFileTransformer;
import java.util.List;

/**
* Class transformer and analyzer. It is loaded in a separate class-loader to avoid ASM library version
* clashes with the target code and so it is referred to by this interface.
*
* @author Roman Elizarov
*/
public interface TransformerAnalyzer extends ClassFileTransformer {
/**
* Returns a list of immediate super-classes and super-interfaces of a given class or null if the class
* cannot be loaded in the specified class loader.
*/
public List<String> getImmediateClassParents(String className, ClassLoader loader);
}
3 changes: 3 additions & 0 deletions core/src/main/resources/details.config
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,6 @@ com.devexperts.aprof.selftest.TrackingTest

com.devexperts.aprof.selftest.TrackingDeepTest
trackedMethod

com.devexperts.aprof.selftest.TrackingIntfTrackedImpl
trackedMethod
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<modules>
<module>transformer</module>
<module>core</module>
<module>selftest</module>
<module>agent</module>
<module>benchmark</module>
</modules>
Expand Down
40 changes: 40 additions & 0 deletions selftest/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
~ Aprof - Java Memory Allocation Profiler
~ Copyright (C) 2002-2014 Devexperts LLC
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses />.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.devexperts.aprof</groupId>
<artifactId>aprof</artifactId>
<version>28-SNAPSHOT</version>
</parent>

<packaging>jar</packaging>

<artifactId>selftest</artifactId>
<name>Aprof Integration tests (selftest)</name>

<dependencies>
<dependency>
<groupId>com.devexperts.aprof</groupId>
<artifactId>core</artifactId>
<version>28-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
Loading

0 comments on commit 084b60e

Please sign in to comment.