call stack

This commit is contained in:
Sander Hautvast 2018-05-31 09:01:31 +02:00
parent 678b122c33
commit 4c6b40fc2e
17 changed files with 722 additions and 402 deletions

View file

@ -1,12 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots />
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/java" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/deploy/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testapp/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
@ -15,9 +29,39 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.ow2.asm:asm:4.1" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.21.0-GA" level="project" />
<orderEntry type="library" name="Maven: org.nanohttpd:nanohttpd:2.3.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.h2database:h2:1.4.195" level="project" />
<orderEntry type="library" name="Maven: org.nanohttpd:nanohttpd:2.3.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-autoconfigure:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-logging:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: ch.qos.logback:logback-classic:1.1.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: ch.qos.logback:logback-core:1.1.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.slf4j:slf4j-api:1.7.22" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.slf4j:jcl-over-slf4j:1.7.25" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.slf4j:log4j-over-slf4j:1.7.25" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-core:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.yaml:snakeyaml:1.17" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:1.5.3.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.14" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.14" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.14" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hibernate:hibernate-validator:5.3.5.Final" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: javax.validation:validation-api:1.1.0.Final" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jboss.logging:jboss-logging:3.3.0.Final" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.fasterxml:classmate:1.3.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.8.8" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.8.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.fasterxml.jackson.core:jackson-core:2.8.8" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-web:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-aop:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-beans:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-context:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-webmvc:4.3.8.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-expression:4.3.8.RELEASE" level="project" />
</component>
</module>

18
pom.xml
View file

@ -19,6 +19,13 @@
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
<dependency>
<groupId>org.nanohttpd</groupId>
<artifactId>nanohttpd</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@ -32,9 +39,10 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nanohttpd</groupId>
<artifactId>nanohttpd</artifactId>
<version>2.3.1</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -44,9 +52,9 @@
</properties>
<build>
<testSourceDirectory>src/deploy/java</testSourceDirectory>
<testSourceDirectory>src/testapp/java</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>

View file

@ -1,5 +1,6 @@
package perfix;
import perfix.instrument.Instrumentor;
import perfix.server.HTTPServer;
import java.lang.instrument.Instrumentation;
@ -16,15 +17,15 @@ public class Agent {
private static final String INCLUDES_PROPERTY = "perfix.includes";
private static final String DEFAULT_PORT = "2048";
private static final String MESSAGE = " --- Perfix agent active --- ";
private static final String MESSAGE = " --- Perfix agent active";
public static void premain(String agentArgs, Instrumentation inst) {
public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println(MESSAGE);
int port = Integer.parseInt(System.getProperty(PORT_PROPERTY, DEFAULT_PORT));
new ClassInstrumentor(determineIncludes()).instrumentCode(inst);
Instrumentor.create(determineIncludes()).instrumentCode(instrumentation);
new HTTPServer(port).start();
}

View file

@ -1,11 +1,14 @@
package perfix;
/**
* contains start and stop time for method/query/servlet
*/
public class MethodInvocation {
private final long t0;
private final String name;
private long t1;
long t1;
private MethodInvocation(String name) {
MethodInvocation(String name) {
t0 = System.nanoTime();
if (name != null) {
this.name = name;
@ -14,19 +17,6 @@ public class MethodInvocation {
}
}
public static MethodInvocation start(String name) {
return new MethodInvocation(name);
}
public static void stop(MethodInvocation methodInvocation) {
methodInvocation.stop();
}
public void stop() {
t1 = System.nanoTime();
Registry.add(this);
}
String getName() {
return name;
}
@ -34,4 +24,5 @@ public class MethodInvocation {
long getDuration() {
return t1 - t0;
}
}

View file

@ -3,15 +3,15 @@ package perfix;
public class MutableBoolean {
private boolean value;
MutableBoolean(boolean value) {
public MutableBoolean(boolean value) {
this.value = value;
}
void set(boolean value) {
public void set(boolean value) {
this.value = value;
}
boolean get() {
public boolean get() {
return value;
}

View file

@ -1,6 +1,5 @@
package perfix;
import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
@ -9,31 +8,26 @@ import java.util.concurrent.atomic.LongAdder;
public class Registry {
private static final Map<String, List<MethodInvocation>> methods = new ConcurrentHashMap<>();
private static final double NANO_2_MILLI = 1000000D;
private static final String HEADER1 = "Invoked methods, by duration desc:";
private static final String HEADER2 = "MethodInvocation name;#Invocations;Total duration;Average Duration";
private static final String FOOTER = "----------------------------------------";
private static final Map<String, Set<String>> callstack = new ConcurrentHashMap<>();
private static final ThreadLocal<String> currentMethod = new ThreadLocal<>();
static void add(MethodInvocation methodInvocation) {
@SuppressWarnings("unused")
public static MethodInvocation start(String name) {
MethodInvocation methodInvocation = new MethodInvocation(name);
String parent = currentMethod.get();
if (parent != null) {
callstack.computeIfAbsent(parent, k -> new HashSet<>()).add(methodInvocation.getName());
}
currentMethod.set(methodInvocation.getName());
return methodInvocation;
}
@SuppressWarnings("unused")
public static void stop(MethodInvocation methodInvocation) {
methodInvocation.t1 = System.nanoTime();
methods.computeIfAbsent(methodInvocation.getName(), key -> new ArrayList<>()).add(methodInvocation);
}
public static void report(PrintStream out) {
out.println(HEADER1);
out.println(HEADER2);
sortedMethodsByDuration().entrySet().stream()
.map(entry -> createReportLine(entry.getValue()))
.forEach(out::println);
out.println(FOOTER);
out.flush();
}
private static String createReportLine(Report report) {
return report.getName() + ";"
+ report.getInvocations() + ";"
+ (long) (report.getTotalDuration() / NANO_2_MILLI) + ";"
+ (long) (report.getAverage() / NANO_2_MILLI);
}
public static SortedMap<Long, Report> sortedMethodsByDuration() {
SortedMap<Long, Report> sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder());
@ -46,6 +40,11 @@ public class Registry {
});
return sortedByTotal;
}
//work in progress
// public static Map<String, Set<Report>> getCallStack() {
// callstack.forEach((name,children)->{
//
// });
// }
}

View file

@ -0,0 +1,105 @@
package perfix.instrument;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import perfix.MutableBoolean;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.List;
import static java.util.Arrays.stream;
public class ClassInstrumentor extends Instrumentor {
private static final String JAVA_INNERCLASS_SEPARATOR = "$";
JdbcInstrumentor jdbcInstrumentor;
ServletInstrumentor servletInstrumentor;
ClassInstrumentor(List<String> includes, ClassPool classPool) {
super(includes, classPool);
try {
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
stringClass = classpool.get(JAVA_STRING);
} catch (NotFoundException e) {
//suppress TODO implement trace
}
}
public void instrumentCode(Instrumentation inst) {
inst.addTransformer((classLoader, resource, aClass, protectionDomain, uninstrumentedByteCode)
-> instrumentCodeForClass(includes, resource, uninstrumentedByteCode));
}
private byte[] instrumentCodeForClass(List<String> includes, String resource, byte[] uninstrumentedByteCode) {
if (!isInnerClass(resource)) {
try {
CtClass ctClass = getCtClassForResource(resource);
if (servletInstrumentor.isServletImpl(resource)){
return servletInstrumentor.instrumentServlet(ctClass, uninstrumentedByteCode);
}
if (jdbcInstrumentor.isJdbcStatementImpl(resource, ctClass)) {
return jdbcInstrumentor.instrumentJdbcStatement(ctClass, uninstrumentedByteCode);
}
if (jdbcInstrumentor.isJdbcConnectionImpl(resource, ctClass)) {
return jdbcInstrumentor.instrumentJdbcConnection(ctClass, uninstrumentedByteCode);
}
if (jdbcInstrumentor.isJdbcPreparedStatement(resource)) {
return jdbcInstrumentor.instrumentJdbcPreparedStatement(ctClass, uninstrumentedByteCode);
}
if (jdbcInstrumentor.isJdbcPreparedStatementImpl(resource, ctClass)) {
return jdbcInstrumentor.instrumentJdbcPreparedStatementImpl(ctClass, uninstrumentedByteCode);
}
if (shouldInclude(resource, includes)) {
return instrumentMethods(ctClass, uninstrumentedByteCode);
}
} catch (Exception ex) {
//suppress
}
}
return uninstrumentedByteCode;
}
/* for regular classes that require instrumentation instrument all methods to record duration*/
private byte[] instrumentMethods(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
if (!classToInstrument.isInterface()) {
stream(classToInstrument.getDeclaredMethods()).forEach(this::instrumentMethod);
try {
return bytecode(classToInstrument);
} catch (IOException | CannotCompileException e) {
//suppress
}
}
return uninstrumentedByteCode;
}
private CtClass getCtClassForResource(String resource) throws NotFoundException {
return getCtClass(resource.replaceAll("/", "."));
}
private boolean shouldInclude(String resource, List<String> excludes) {
MutableBoolean included = new MutableBoolean(false);
excludes.forEach(include -> {
if (resource.startsWith(include)) {
included.set(true);
}
});
return included.get();
}
private boolean isInnerClass(String resource) {
return resource.contains(JAVA_INNERCLASS_SEPARATOR);
}
}

View file

@ -0,0 +1,65 @@
package perfix.instrument;
import javassist.*;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.List;
public abstract class Instrumentor {
static final String JAVA_STRING = "java.lang.String";
static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
static final String JAVASSIST_RETURNVALUE = "$_";
final ClassPool classpool;
final List<String> includes;
CtClass perfixMethodInvocationClass;
CtClass stringClass;
Instrumentor(List<String> includes, ClassPool classPool) {
this.includes = includes;
this.classpool = classPool;
try {
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
stringClass = classpool.get(JAVA_STRING);
} catch (NotFoundException e) {
//suppress TODO implement trace
}
}
public static Instrumentor create(List<String> includes) {
ClassPool classPool = ClassPool.getDefault();
ClassInstrumentor classInstrumentor = new ClassInstrumentor(includes, classPool);
classInstrumentor.jdbcInstrumentor = new JdbcInstrumentor(includes, classPool);
classInstrumentor.servletInstrumentor = new ServletInstrumentor(includes, classPool);
return classInstrumentor;
}
public void instrumentCode(Instrumentation inst){}
void instrumentMethod(CtMethod methodToinstrument) {
instrumentMethod(methodToinstrument, "\"" + methodToinstrument.getLongName() + "\"");
}
/* record times at beginning and end of method body*/
void instrumentMethod(CtMethod methodToinstrument, String metricName) {
try {
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.start(" + metricName + ");");
methodToinstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
byte[] bytecode(CtClass classToInstrument) throws IOException, CannotCompileException {
classToInstrument.detach();
return classToInstrument.toBytecode();
}
CtClass getCtClass(String classname) throws NotFoundException {
return classpool.get(classname);
}
}

View file

@ -1,284 +1,174 @@
package perfix;
import javassist.*;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.List;
import static java.util.Arrays.stream;
class ClassInstrumentor {
private static final String JAVA_INNERCLASS_SEPARATOR = "$";
private static final String JAVA_STRING = "java.lang.String";
private static final String JAVASQL_PACKAGE = "java/sql";
private static final String JAVASQL_STATEMENT_INTERFACE = "java.sql.Statement";
private static final String JAVASQL_EXECUTE_METHOD = "execute";
private static final String JAVASQL_EXECUTEQUERY_METHOD = "executeQuery";
private static final String JAVASQL_EXECUTEUPDATE_METHOD = "executeUpdate";
private static final String JAVASQL_PREPAREDSTATEMENT_INTERFACE = "java.sql.PreparedStatement";
private static final String JAVASQL_CONNECTION_INTERFACE = "java.sql.Connection";
private static final String JAVASQL_PREPARED_STATEMENT_CLASSNAME = "java.sql.PreparedStatement";
private static final String JAVASQL_PREPAREDSTATEMENT_RESOURCENAME = "java/sql/PreparedStatement";
private static final String JAVASQL_PREPARESTATEMENT_METHODNAME = "prepareStatement";
private static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
private static final String JAVASSIST_RETURNVALUE = "$_";
private static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
private static final String PERFIX_SQLSTATEMENT_FIELD = "_perfixSqlStatement";
private static final String PERFIX_SETSQL_METHOD = "setSqlForPerfix";
private ClassPool classpool;
private List<String> includes;
private CtClass perfixMethodInvocationClass;
private CtClass stringClass;
ClassInstrumentor(List<String> includes) {
this.includes = includes;
this.classpool = ClassPool.getDefault();
try {
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
stringClass = classpool.get(JAVA_STRING);
} catch (NotFoundException e) {
//suppress TODO implement trace
}
}
void instrumentCode(Instrumentation inst) {
inst.addTransformer((classLoader, resource, aClass, protectionDomain, uninstrumentedByteCode)
-> instrumentCodeForClass(includes, resource, uninstrumentedByteCode));
}
private byte[] instrumentCodeForClass(List<String> includes, String resource, byte[] uninstrumentedByteCode) {
if (!isInnerClass(resource)) {
try {
CtClass ctClass = getCtClassForResource(resource);
if (isJdbcStatementImpl(resource, ctClass)) {
return instrumentJdbcStatement(ctClass, uninstrumentedByteCode);
}
if (isJdbcConnectionImpl(resource, ctClass)) {
return instrumentJdbcConnection(ctClass, uninstrumentedByteCode);
}
if (isJdbcPreparedStatement(resource)) {
return instrumentJdbcPreparedStatement(ctClass, uninstrumentedByteCode);
}
if (isJdbcPreparedStatementImpl(resource, ctClass)) {
return instrumentJdbcPreparedStatementImpl(ctClass, uninstrumentedByteCode);
}
if (shouldInclude(resource, includes)) {
return instrumentMethods(ctClass, uninstrumentedByteCode);
}
} catch (Exception ex) {
//suppress
}
}
return uninstrumentedByteCode;
}
/* Need to enhance interface to be able to set a statement (string) for perfix. */
private byte[] instrumentJdbcPreparedStatement(CtClass preparedStatementInterface, byte[] uninstrumentedByteCode) {
try {
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
} catch (NotFoundException e1) {
try {
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
preparedStatementInterface.addMethod(setSqlForPerfix);
} catch (CannotCompileException e2) {
return uninstrumentedByteCode;
}
}
try {
return bytecode(preparedStatementInterface);
} catch (CannotCompileException | IOException e) {
return uninstrumentedByteCode;
}
}
/* Prepared statement methods that actually execute sql don't have the statement in their parameters (unlike java.sql.Statement)
* instead every jdbc vendor has a specific String field in their PreparedStatement impl that contains the statement.
*
* To circumvent vendor specifics in perfix, the first argument for prepareStatement (the sql String) is intercepted here
* and injected into the PreparedStatement instance under a fixed name, whatever the implementation type */
private byte[] instrumentJdbcConnection(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
stream(classToInstrument.getDeclaredMethods(JAVASQL_PREPARESTATEMENT_METHODNAME)).forEach(method -> {
try {
// The sql statement String that is the first argument for this method is injected into PreparedStatementImpl
// using a name known (only) to perfix, so that it can fetch it later in that class (instance)
// this way no JDBC implementor specific code is needed
CtClass preparedStatementInterface = classpool.get(JAVASQL_PREPARED_STATEMENT_CLASSNAME);
try {
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
} catch (NotFoundException e1) {
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
preparedStatementInterface.addMethod(setSqlForPerfix);
}
method.insertAfter(JAVASSIST_RETURNVALUE + "." + PERFIX_SETSQL_METHOD + "(" + JAVASSIST_FIRST_ARGUMENT_NAME + ");"); //$_ is result instance, $1 is first argument
} catch (CannotCompileException | NotFoundException e) {
// suppress
e.printStackTrace();
}
}
);
return bytecode(classToInstrument);
} catch (NotFoundException | CannotCompileException | IOException e) {
// suppress
}
return uninstrumentedByteCode;
}
/* */
private byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
try {
addPerfixStatementField(preparedStatementClass);
addPerfixStatementSetter(preparedStatementClass);
// instrument execute to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
// instrument executeQuery to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
// instrument executeUpdate to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
return bytecode(preparedStatementClass);
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
return uninstrumentedByteCode;
}
}
private byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
//instrument executeQuery to record query duration
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
instrumentMethod(method, JAVASSIST_FIRST_ARGUMENT_NAME)
);
//instrument executeUpdate to record query duration
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
instrumentMethod(method, JAVASSIST_FIRST_ARGUMENT_NAME)
);
return bytecode(classToInstrument);
} catch (Exception e) {
return uninstrumentedByteCode;
}
}
/* for regular classes that require instrumentation instrument all methods to record duration*/
private byte[] instrumentMethods(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
if (!classToInstrument.isInterface()) {
stream(classToInstrument.getDeclaredMethods()).forEach(this::instrumentMethod);
try {
return bytecode(classToInstrument);
} catch (IOException | CannotCompileException e) {
//suppress
}
}
return uninstrumentedByteCode;
}
private CtClass getCtClassForResource(String resource) throws NotFoundException {
return getCtClass(resource.replaceAll("/", "."));
}
private CtClass getCtClass(String classname) throws NotFoundException {
return classpool.get(classname);
}
private void instrumentMethod(CtMethod methodToinstrument) {
instrumentMethod(methodToinstrument, "\"" + methodToinstrument.getLongName() + "\"");
}
/* record times at beginning and end of method body*/
private void instrumentMethod(CtMethod methodToinstrument, String metricName) {
try {
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
methodToinstrument.insertBefore("_perfixmethod = perfix.MethodInvocation.start(" + metricName + ");");
methodToinstrument.insertAfter("perfix.MethodInvocation.stop(_perfixmethod);");
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
private boolean shouldInclude(String resource, List<String> excludes) {
MutableBoolean included = new MutableBoolean(false);
excludes.forEach(include -> {
if (resource.startsWith(include)) {
included.set(true);
}
});
return included.get();
}
private boolean isInnerClass(String resource) {
return resource.contains(JAVA_INNERCLASS_SEPARATOR);
}
private boolean isJdbcStatementImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_STATEMENT_INTERFACE) && !i.getName().equals(JAVASQL_PREPARED_STATEMENT_CLASSNAME));
}
return false;
}
private boolean isJdbcPreparedStatement(String resource) {
return resource.equals(JAVASQL_PREPAREDSTATEMENT_RESOURCENAME);
}
private boolean isJdbcPreparedStatementImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_PREPAREDSTATEMENT_INTERFACE));
}
return false;
}
private boolean isJdbcConnectionImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_CONNECTION_INTERFACE));
}
return false;
}
private byte[] bytecode(CtClass classToInstrument) throws IOException, CannotCompileException {
classToInstrument.detach();
return classToInstrument.toBytecode();
}
private void addPerfixStatementField(CtClass preparedStatementClass) throws CannotCompileException {
// add a String field that will contain the statement
CtField perfixSqlField = new CtField(stringClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
perfixSqlField.setModifiers(Modifier.PRIVATE);
preparedStatementClass.addField(perfixSqlField);
}
private void addPerfixStatementSetter(CtClass preparedStatementImplClass) throws CannotCompileException {
// add setter for the new field
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementImplClass);
setSqlForPerfix.setModifiers(Modifier.PUBLIC);
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
preparedStatementImplClass.addMethod(setSqlForPerfix);
}
}
package perfix.instrument;
import javassist.*;
import java.io.IOException;
import java.util.List;
import static java.util.Arrays.stream;
public class JdbcInstrumentor extends Instrumentor {
private static final String JAVASQL_EXECUTE_METHOD = "execute";
private static final String JAVASQL_EXECUTEQUERY_METHOD = "executeQuery";
private static final String JAVASQL_EXECUTEUPDATE_METHOD = "executeUpdate";
private static final String JAVASQL_PACKAGE = "java/sql";
private static final String JAVASQL_STATEMENT_INTERFACE = "java.sql.Statement";
private static final String JAVASQL_PREPAREDSTATEMENT_INTERFACE = "java.sql.PreparedStatement";
private static final String JAVASQL_CONNECTION_INTERFACE = "java.sql.Connection";
private static final String JAVASQL_PREPAREDSTATEMENT_RESOURCENAME = "java/sql/PreparedStatement";
private static final String JAVASQL_PREPARED_STATEMENT_CLASSNAME = "java.sql.PreparedStatement";
private static final String JAVASQL_PREPARESTATEMENT_METHODNAME = "prepareStatement";
private static final String PERFIX_SQLSTATEMENT_FIELD = "_perfixSqlStatement";
private static final String PERFIX_SETSQL_METHOD = "setSqlForPerfix";
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
super(includes, classPool);
}
/* Need to enhance interface to be able to set a statement (string) for perfix. */
byte[] instrumentJdbcPreparedStatement(CtClass preparedStatementInterface, byte[] uninstrumentedByteCode) {
try {
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
} catch (NotFoundException e1) {
try {
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
preparedStatementInterface.addMethod(setSqlForPerfix);
} catch (CannotCompileException e2) {
return uninstrumentedByteCode;
}
}
try {
return bytecode(preparedStatementInterface);
} catch (CannotCompileException | IOException e) {
return uninstrumentedByteCode;
}
}
/* Prepared statement methods that actually execute sql don't have the statement in their parameters (unlike java.sql.Statement)
* instead every jdbc vendor has a specific String field in their PreparedStatement impl that contains the statement.
*
* To circumvent vendor specifics in perfix, the first argument for prepareStatement (the sql String) is intercepted here
* and injected into the PreparedStatement instance under a fixed name, whatever the implementation type */
byte[] instrumentJdbcConnection(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
stream(classToInstrument.getDeclaredMethods(JAVASQL_PREPARESTATEMENT_METHODNAME)).forEach(method -> {
try {
// The sql statement String that is the first argument for this method is injected into PreparedStatementImpl
// using a name known (only) to perfix, so that it can fetch it later in that class (instance)
// this way no JDBC implementor specific code is needed
CtClass preparedStatementInterface = classpool.get(JAVASQL_PREPARED_STATEMENT_CLASSNAME);
try {
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
} catch (NotFoundException e1) {
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
preparedStatementInterface.addMethod(setSqlForPerfix);
}
method.insertAfter(JAVASSIST_RETURNVALUE + "." + PERFIX_SETSQL_METHOD + "(" + JAVASSIST_FIRST_ARGUMENT_NAME + ");"); //$_ is result instance, $1 is first argument
} catch (CannotCompileException | NotFoundException e) {
// suppress
e.printStackTrace();
}
}
);
return bytecode(classToInstrument);
} catch (NotFoundException | CannotCompileException | IOException e) {
// suppress
}
return uninstrumentedByteCode;
}
/* */
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
try {
addPerfixStatementField(preparedStatementClass);
addPerfixStatementSetter(preparedStatementClass);
// instrument execute to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
// instrument executeQuery to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
// instrument executeUpdate to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
);
return bytecode(preparedStatementClass);
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
return uninstrumentedByteCode;
}
}
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
//instrument executeQuery to record query duration
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
instrumentMethod(method, JAVASSIST_FIRST_ARGUMENT_NAME)
);
//instrument executeUpdate to record query duration
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
instrumentMethod(method, JAVASSIST_FIRST_ARGUMENT_NAME)
);
return bytecode(classToInstrument);
} catch (Exception e) {
return uninstrumentedByteCode;
}
}
private void addPerfixStatementField(CtClass preparedStatementClass) throws CannotCompileException {
// add a String field that will contain the statement
CtField perfixSqlField = new CtField(stringClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
perfixSqlField.setModifiers(Modifier.PRIVATE);
preparedStatementClass.addField(perfixSqlField);
}
private void addPerfixStatementSetter(CtClass preparedStatementImplClass) throws CannotCompileException {
// add setter for the new field
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementImplClass);
setSqlForPerfix.setModifiers(Modifier.PUBLIC);
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
preparedStatementImplClass.addMethod(setSqlForPerfix);
}
boolean isJdbcStatementImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_STATEMENT_INTERFACE) && !i.getName().equals(JAVASQL_PREPARED_STATEMENT_CLASSNAME));
}
return false;
}
boolean isJdbcPreparedStatement(String resource) {
return resource.equals(JAVASQL_PREPAREDSTATEMENT_RESOURCENAME);
}
boolean isJdbcPreparedStatementImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_PREPAREDSTATEMENT_INTERFACE));
}
return false;
}
boolean isJdbcConnectionImpl(String resource, CtClass ctClass) throws NotFoundException {
if (!resource.startsWith(JAVASQL_PACKAGE)) {
return stream(ctClass.getInterfaces())
.anyMatch(i -> i.getName().equals(JAVASQL_CONNECTION_INTERFACE));
}
return false;
}
}

View file

@ -0,0 +1,41 @@
package perfix.instrument;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import java.util.List;
import static java.util.Arrays.stream;
public class ServletInstrumentor extends Instrumentor {
private static final String JAVA_SERVLET_SERVICE_METHOD = "service";
private static final String JAVA_SERVLET_CLASS = "javax/servlet/http/HttpServlet";
ServletInstrumentor(List<String> includes, ClassPool classPool) {
super(includes, classPool);
}
public boolean isServletImpl(String resource) {
return resource.equals(JAVA_SERVLET_CLASS);
}
public byte[] instrumentServlet(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
stream(classToInstrument.getDeclaredMethods(JAVA_SERVLET_SERVICE_METHOD)).forEach(methodToInstrument -> {
try {
methodToInstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
methodToInstrument.insertBefore("_perfixmethod = perfix.Registry.start($1.getRequestURI());");
methodToInstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
} catch (CannotCompileException e) {
}
});
return bytecode(classToInstrument);
} catch (Exception e) {
return uninstrumentedByteCode;
}
}
}

View file

@ -19,19 +19,22 @@ public class HTTPServer extends NanoHTTPD {
try {
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
System.out.println("\nHttpServer running! Point your browser to http://localhost:2048/ \n");
System.out.println(" --- Perfix http server running. Point your browser to http://localhost:" + getListeningPort() + "/");
} catch (IOException ioe) {
System.err.println("Couldn't start server:\n" + ioe);
System.err.println(" --- Couldn't start Perfix http server:\n" + ioe);
}
}
@Override
public Response serve(IHTTPSession session) {
String uri = session.getUri();
if (uri.equals("/report")) {
return perfixMetrics();
} else {
return serveStaticContent(uri);
switch (uri) {
case "/report":
return perfixMetrics();
case "/callstack":
return perfixCallstack();
default:
return serveStaticContent(uri);
}
}
@ -59,6 +62,15 @@ public class HTTPServer extends NanoHTTPD {
}
}
private Response perfixCallstack() {
try {
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack()));
} catch (Exception e) {
e.printStackTrace();
return newFixedLengthResponse(e.toString());
}
}
private String readFile(InputStream stream) throws IOException {
int nbytes = stream.available();
byte[] bytes = new byte[nbytes];

View file

@ -1,21 +1,10 @@
package perfix.server.json;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javassist.*;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import java.util.*;
import static java.util.Arrays.asList;
public class SynthSerializerFactory implements SerializerFactory {
private static final String STRING = "java.lang.String";
@ -28,25 +17,29 @@ public class SynthSerializerFactory implements SerializerFactory {
private static final String SHORT = "java.lang.Short";
private static final String INTEGER = "java.lang.Integer";
private final static Set<String> wrappersAndString = new HashSet<String>(Arrays.asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
private final static Set<String> wrappersAndString = new HashSet<String>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
STRING));
private static final String COLLECTION = "java.util.Collection";
private static final String LIST = "java.util.List";
private static final String SET = "java.util.Set";
private static final String MAP = "java.util.Map";
private static final List<String> mapInterfaces = asList("java.util.Map", "java.util.concurrent.ConcurrentHashMap");
private static final Map<String, JSONSerializer<?>> serializers = new HashMap<>();
private static final String ROOT_PACKAGE = "serializer.";
private final ClassPool pool = ClassPool.getDefault();
private CtClass serializerBase;
private final Map<String, CtClass> primitiveWrappers = new HashMap<String, CtClass>();
private CtClass serializerBase;
public SynthSerializerFactory() {
SynthSerializerFactory() {
init();
}
private static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
}
void init() {
try {
serializerBase = pool.get(JSONSerializer.class.getName());
@ -68,9 +61,7 @@ public class SynthSerializerFactory implements SerializerFactory {
try {
CtClass beanClass = pool.get(beanjavaClass.getName());
JSONSerializer<T> jsonSerializer = createSerializer(beanClass);
return jsonSerializer;
return createSerializer(beanClass);
} catch (NotFoundException e) {
throw new SerializerCreationException(e);
}
@ -104,7 +95,8 @@ public class SynthSerializerFactory implements SerializerFactory {
* create method source, compile it and add it to the class under construction
*/
private void addToJsonStringMethod(CtClass beanClass, CtClass serializerClass) throws NotFoundException, CannotCompileException {
serializerClass.addMethod(CtNewMethod.make(createToJSONStringMethodSource(beanClass), serializerClass));
String body = createToJSONStringMethodSource(beanClass);
serializerClass.addMethod(CtNewMethod.make(body, serializerClass));
}
/*
@ -133,9 +125,9 @@ public class SynthSerializerFactory implements SerializerFactory {
/*
* Any Collection is converted to an array, after which code is generated to handle the single elements.
*
*
* A subserializer is created for every single element, but most of the time it will be the same cached instance.
*
*
* The generated code fills a StringBuilder. The values are generated by the subserializers
*/
private String handleArray(CtClass beanClass) {
@ -192,7 +184,7 @@ public class SynthSerializerFactory implements SerializerFactory {
/*
* custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes
*
*
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
*/
public String createSerializerName(CtClass beanClass) {
@ -204,7 +196,7 @@ public class SynthSerializerFactory implements SerializerFactory {
}
private boolean isCollection(CtClass beanClass) throws NotFoundException {
List<CtClass> interfaces = new ArrayList<CtClass>(Arrays.asList(beanClass.getInterfaces()));
List<CtClass> interfaces = new ArrayList<CtClass>(asList(beanClass.getInterfaces()));
interfaces.add(beanClass);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)) {
@ -215,14 +207,9 @@ public class SynthSerializerFactory implements SerializerFactory {
}
private boolean isMap(CtClass beanClass) throws NotFoundException {
List<CtClass> interfaces = new ArrayList<CtClass>(Arrays.asList(beanClass.getInterfaces()));
List<CtClass> interfaces = new ArrayList<>(asList(beanClass.getInterfaces()));
interfaces.add(beanClass);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(MAP)) {
return true;
}
}
return false;
return interfaces.stream().anyMatch(i -> mapInterfaces.contains(i.getName()));
}
/*
@ -346,22 +333,22 @@ public class SynthSerializerFactory implements SerializerFactory {
String asPrimitive(String name) {
switch (name) {
case "int":
return "I";
case "byte":
return "B";
case "float":
return "F";
case "long":
return "J";
case "boolean":
return "Z";
case "char":
return "C";
case "double":
return "D";
case "short":
return "S";
case "int":
return "I";
case "byte":
return "B";
case "float":
return "F";
case "long":
return "J";
case "boolean":
return "Z";
case "char":
return "C";
case "double":
return "D";
case "short":
return "S";
}
return "";
}
@ -369,8 +356,4 @@ public class SynthSerializerFactory implements SerializerFactory {
String innerClassName(String name) {
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[\\]", "");
}
static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
}
}

View file

@ -10,9 +10,14 @@
<table></table>
</body>
<script>
d3.json("/report").then(function (data) {
// render the table(s)
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
});
function loadData() {
d3.json("/report").then(function (data) {
// render the table(s)
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
});
}
loadData();
</script>
<button type="button" onclick="loadData()">refresh</button>
</html>

View file

@ -1,4 +1,5 @@
function tabulate(data, columns) {
d3.select('table').remove();
var table = d3.select('body').append('table')
var thead = table.append('thead')
var tbody = table.append('tbody');

141
src/main/resources/tree.js Normal file
View file

@ -0,0 +1,141 @@
var m = [20, 120, 20, 120],
w = 1280 - m[1] - m[3],
h = 800 - m[0] - m[2],
i = 0,
root;
var tree = d3.layout.tree()
.size([h, w]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var vis = d3.select("#body").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
d3.json("flare.json", function(json) {
root = json;
root.x0 = h / 2;
root.y0 = 0;
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
// Initialize the display to show a few nodes.
root.children.forEach(toggleAll);
toggle(root.children[1]);
toggle(root.children[1].children[2]);
toggle(root.children[9]);
toggle(root.children[9].children[0]);
update(root);
});
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", function(d) { toggle(d); update(d); });
nodeEnter.append("svg:circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("svg:text")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}

View file

@ -1,9 +1,9 @@
package testperfix;
package testperfix.cmdline;
import java.sql.*;
import java.util.concurrent.TimeUnit;
public class Main {
public class App {
public static void main(String[] args) {
System.out.println("Perfix Test Application is running. Make sure the agent is active.");
String includesProperty = System.getProperty("perfix.includes");

View file

@ -0,0 +1,34 @@
package testperfix.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class WebApp {
public static void main(String[] args) {
String includesProperty = System.getProperty("perfix.includes");
if (includesProperty == null || !includesProperty.equals("testperfix")) {
System.out.println("Start me with -javaagent:target/agent-0.1-SNAPSHOT.jar -Dperfix.includes=testperfix");
System.out.println("Exiting now");
System.exit(0);
}
SpringApplication.run(WebApp.class, args);
System.out.println("Perfix Test Web Application is running");
}
@RequestMapping("/greetings")
public String index() {
return greetings();
}
private String greetings() {
return "Greetings from Spring Boot!";
}
}