call stack
This commit is contained in:
parent
678b122c33
commit
4c6b40fc2e
17 changed files with 722 additions and 402 deletions
48
agent.iml
48
agent.iml
|
|
@ -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>
|
||||
16
pom.xml
16
pom.xml
|
|
@ -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,7 +52,7 @@
|
|||
</properties>
|
||||
|
||||
<build>
|
||||
<testSourceDirectory>src/deploy/java</testSourceDirectory>
|
||||
<testSourceDirectory>src/testapp/java</testSourceDirectory>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package perfix;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
|
@ -9,32 +8,27 @@ 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());
|
||||
methods.forEach((name, measurements) -> {
|
||||
|
|
@ -46,6 +40,11 @@ public class Registry {
|
|||
});
|
||||
return sortedByTotal;
|
||||
}
|
||||
|
||||
//work in progress
|
||||
// public static Map<String, Set<Report>> getCallStack() {
|
||||
// callstack.forEach((name,children)->{
|
||||
//
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
105
src/main/java/perfix/instrument/ClassInstrumentor.java
Normal file
105
src/main/java/perfix/instrument/ClassInstrumentor.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
65
src/main/java/perfix/instrument/Instrumentor.java
Normal file
65
src/main/java/perfix/instrument/Instrumentor.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +1,35 @@
|
|||
package perfix;
|
||||
package perfix.instrument;
|
||||
|
||||
import javassist.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
class ClassInstrumentor {
|
||||
public class JdbcInstrumentor extends Instrumentor {
|
||||
|
||||
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_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_PREPARED_STATEMENT_CLASSNAME = "java.sql.PreparedStatement";
|
||||
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 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;
|
||||
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
|
||||
super(includes, classPool);
|
||||
}
|
||||
|
||||
/* Need to enhance interface to be able to set a statement (string) for perfix. */
|
||||
private byte[] instrumentJdbcPreparedStatement(CtClass preparedStatementInterface, byte[] uninstrumentedByteCode) {
|
||||
byte[] instrumentJdbcPreparedStatement(CtClass preparedStatementInterface, byte[] uninstrumentedByteCode) {
|
||||
try {
|
||||
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
|
||||
} catch (NotFoundException e1) {
|
||||
|
|
@ -106,7 +53,7 @@ class ClassInstrumentor {
|
|||
*
|
||||
* 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) {
|
||||
byte[] instrumentJdbcConnection(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
||||
try {
|
||||
stream(classToInstrument.getDeclaredMethods(JAVASQL_PREPARESTATEMENT_METHODNAME)).forEach(method -> {
|
||||
try {
|
||||
|
|
@ -137,7 +84,7 @@ class ClassInstrumentor {
|
|||
}
|
||||
|
||||
/* */
|
||||
private byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
||||
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
||||
try {
|
||||
addPerfixStatementField(preparedStatementClass);
|
||||
addPerfixStatementSetter(preparedStatementClass);
|
||||
|
|
@ -165,7 +112,7 @@ class ClassInstrumentor {
|
|||
}
|
||||
|
||||
|
||||
private byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
||||
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
||||
try {
|
||||
//instrument executeQuery to record query duration
|
||||
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
|
||||
|
|
@ -182,91 +129,6 @@ class ClassInstrumentor {
|
|||
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
|
@ -281,4 +143,32 @@ class ClassInstrumentor {
|
|||
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;
|
||||
}
|
||||
}
|
||||
41
src/main/java/perfix/instrument/ServletInstrumentor.java
Normal file
41
src/main/java/perfix/instrument/ServletInstrumentor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
141
src/main/resources/tree.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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");
|
||||
34
src/testapp/java/testperfix/web/WebApp.java
Normal file
34
src/testapp/java/testperfix/web/WebApp.java
Normal 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!";
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue