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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
<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">
|
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||||
<output url="file://$MODULE_DIR$/target/classes" />
|
<output url="file://$MODULE_DIR$/target/classes" />
|
||||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
<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/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/test/resources" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
|
@ -15,9 +29,39 @@
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="Maven: org.ow2.asm:asm:4.1" level="project" />
|
<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.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: 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: 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" 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>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
16
pom.xml
16
pom.xml
|
|
@ -19,6 +19,13 @@
|
||||||
<artifactId>javassist</artifactId>
|
<artifactId>javassist</artifactId>
|
||||||
<version>3.21.0-GA</version>
|
<version>3.21.0-GA</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.nanohttpd</groupId>
|
||||||
|
<artifactId>nanohttpd</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
@ -32,9 +39,10 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nanohttpd</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>nanohttpd</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
<version>2.3.1</version>
|
<version>1.5.3.RELEASE</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
@ -44,7 +52,7 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<testSourceDirectory>src/deploy/java</testSourceDirectory>
|
<testSourceDirectory>src/testapp/java</testSourceDirectory>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
|
import perfix.instrument.Instrumentor;
|
||||||
import perfix.server.HTTPServer;
|
import perfix.server.HTTPServer;
|
||||||
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
@ -16,15 +17,15 @@ public class Agent {
|
||||||
private static final String INCLUDES_PROPERTY = "perfix.includes";
|
private static final String INCLUDES_PROPERTY = "perfix.includes";
|
||||||
|
|
||||||
private static final String DEFAULT_PORT = "2048";
|
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);
|
System.out.println(MESSAGE);
|
||||||
|
|
||||||
int port = Integer.parseInt(System.getProperty(PORT_PROPERTY, DEFAULT_PORT));
|
int port = Integer.parseInt(System.getProperty(PORT_PROPERTY, DEFAULT_PORT));
|
||||||
|
|
||||||
new ClassInstrumentor(determineIncludes()).instrumentCode(inst);
|
Instrumentor.create(determineIncludes()).instrumentCode(instrumentation);
|
||||||
|
|
||||||
new HTTPServer(port).start();
|
new HTTPServer(port).start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* contains start and stop time for method/query/servlet
|
||||||
|
*/
|
||||||
public class MethodInvocation {
|
public class MethodInvocation {
|
||||||
private final long t0;
|
private final long t0;
|
||||||
private final String name;
|
private final String name;
|
||||||
private long t1;
|
long t1;
|
||||||
|
|
||||||
private MethodInvocation(String name) {
|
MethodInvocation(String name) {
|
||||||
t0 = System.nanoTime();
|
t0 = System.nanoTime();
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
this.name = name;
|
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() {
|
String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
@ -34,4 +24,5 @@ public class MethodInvocation {
|
||||||
long getDuration() {
|
long getDuration() {
|
||||||
return t1 - t0;
|
return t1 - t0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@ package perfix;
|
||||||
public class MutableBoolean {
|
public class MutableBoolean {
|
||||||
private boolean value;
|
private boolean value;
|
||||||
|
|
||||||
MutableBoolean(boolean value) {
|
public MutableBoolean(boolean value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(boolean value) {
|
public void set(boolean value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean get() {
|
public boolean get() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
|
@ -9,32 +8,27 @@ import java.util.concurrent.atomic.LongAdder;
|
||||||
public class Registry {
|
public class Registry {
|
||||||
|
|
||||||
private static final Map<String, List<MethodInvocation>> methods = new ConcurrentHashMap<>();
|
private static final Map<String, List<MethodInvocation>> methods = new ConcurrentHashMap<>();
|
||||||
private static final double NANO_2_MILLI = 1000000D;
|
private static final Map<String, Set<String>> callstack = new ConcurrentHashMap<>();
|
||||||
private static final String HEADER1 = "Invoked methods, by duration desc:";
|
private static final ThreadLocal<String> currentMethod = new ThreadLocal<>();
|
||||||
private static final String HEADER2 = "MethodInvocation name;#Invocations;Total duration;Average Duration";
|
|
||||||
private static final String FOOTER = "----------------------------------------";
|
|
||||||
|
|
||||||
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);
|
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() {
|
public static SortedMap<Long, Report> sortedMethodsByDuration() {
|
||||||
SortedMap<Long, Report> sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder());
|
SortedMap<Long, Report> sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder());
|
||||||
methods.forEach((name, measurements) -> {
|
methods.forEach((name, measurements) -> {
|
||||||
|
|
@ -46,6 +40,11 @@ public class Registry {
|
||||||
});
|
});
|
||||||
return sortedByTotal;
|
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 javassist.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.instrument.Instrumentation;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
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_EXECUTE_METHOD = "execute";
|
||||||
private static final String JAVASQL_EXECUTEQUERY_METHOD = "executeQuery";
|
private static final String JAVASQL_EXECUTEQUERY_METHOD = "executeQuery";
|
||||||
private static final String JAVASQL_EXECUTEUPDATE_METHOD = "executeUpdate";
|
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_PREPAREDSTATEMENT_INTERFACE = "java.sql.PreparedStatement";
|
||||||
private static final String JAVASQL_CONNECTION_INTERFACE = "java.sql.Connection";
|
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_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 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_SQLSTATEMENT_FIELD = "_perfixSqlStatement";
|
||||||
private static final String PERFIX_SETSQL_METHOD = "setSqlForPerfix";
|
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) {
|
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
|
||||||
this.includes = includes;
|
super(includes, classPool);
|
||||||
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. */
|
/* 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 {
|
try {
|
||||||
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
|
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
|
||||||
} catch (NotFoundException e1) {
|
} 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
|
* 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 */
|
* 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 {
|
try {
|
||||||
stream(classToInstrument.getDeclaredMethods(JAVASQL_PREPARESTATEMENT_METHODNAME)).forEach(method -> {
|
stream(classToInstrument.getDeclaredMethods(JAVASQL_PREPARESTATEMENT_METHODNAME)).forEach(method -> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -137,7 +84,7 @@ class ClassInstrumentor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
private byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
||||||
try {
|
try {
|
||||||
addPerfixStatementField(preparedStatementClass);
|
addPerfixStatementField(preparedStatementClass);
|
||||||
addPerfixStatementSetter(preparedStatementClass);
|
addPerfixStatementSetter(preparedStatementClass);
|
||||||
|
|
@ -165,7 +112,7 @@ class ClassInstrumentor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
||||||
try {
|
try {
|
||||||
//instrument executeQuery to record query duration
|
//instrument executeQuery to record query duration
|
||||||
stream(classToInstrument.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
|
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 {
|
private void addPerfixStatementField(CtClass preparedStatementClass) throws CannotCompileException {
|
||||||
// add a String field that will contain the statement
|
// add a String field that will contain the statement
|
||||||
CtField perfixSqlField = new CtField(stringClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
|
CtField perfixSqlField = new CtField(stringClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
|
||||||
|
|
@ -281,4 +143,32 @@ class ClassInstrumentor {
|
||||||
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
|
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
|
||||||
preparedStatementImplClass.addMethod(setSqlForPerfix);
|
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 {
|
try {
|
||||||
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
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) {
|
} catch (IOException ioe) {
|
||||||
System.err.println("Couldn't start server:\n" + ioe);
|
System.err.println(" --- Couldn't start Perfix http server:\n" + ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response serve(IHTTPSession session) {
|
public Response serve(IHTTPSession session) {
|
||||||
String uri = session.getUri();
|
String uri = session.getUri();
|
||||||
if (uri.equals("/report")) {
|
switch (uri) {
|
||||||
return perfixMetrics();
|
case "/report":
|
||||||
} else {
|
return perfixMetrics();
|
||||||
return serveStaticContent(uri);
|
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 {
|
private String readFile(InputStream stream) throws IOException {
|
||||||
int nbytes = stream.available();
|
int nbytes = stream.available();
|
||||||
byte[] bytes = new byte[nbytes];
|
byte[] bytes = new byte[nbytes];
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
package perfix.server.json;
|
package perfix.server.json;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import javassist.*;
|
||||||
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.CannotCompileException;
|
import java.util.*;
|
||||||
import javassist.ClassPool;
|
|
||||||
import javassist.CtClass;
|
import static java.util.Arrays.asList;
|
||||||
import javassist.CtField;
|
|
||||||
import javassist.CtMethod;
|
|
||||||
import javassist.CtNewMethod;
|
|
||||||
import javassist.Modifier;
|
|
||||||
import javassist.NotFoundException;
|
|
||||||
|
|
||||||
public class SynthSerializerFactory implements SerializerFactory {
|
public class SynthSerializerFactory implements SerializerFactory {
|
||||||
private static final String STRING = "java.lang.String";
|
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 SHORT = "java.lang.Short";
|
||||||
private static final String INTEGER = "java.lang.Integer";
|
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));
|
STRING));
|
||||||
|
|
||||||
private static final String COLLECTION = "java.util.Collection";
|
private static final String COLLECTION = "java.util.Collection";
|
||||||
private static final String LIST = "java.util.List";
|
private static final String LIST = "java.util.List";
|
||||||
private static final String SET = "java.util.Set";
|
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 Map<String, JSONSerializer<?>> serializers = new HashMap<>();
|
||||||
private static final String ROOT_PACKAGE = "serializer.";
|
private static final String ROOT_PACKAGE = "serializer.";
|
||||||
|
|
||||||
private final ClassPool pool = ClassPool.getDefault();
|
private final ClassPool pool = ClassPool.getDefault();
|
||||||
private CtClass serializerBase;
|
|
||||||
private final Map<String, CtClass> primitiveWrappers = new HashMap<String, CtClass>();
|
private final Map<String, CtClass> primitiveWrappers = new HashMap<String, CtClass>();
|
||||||
|
private CtClass serializerBase;
|
||||||
|
|
||||||
public SynthSerializerFactory() {
|
SynthSerializerFactory() {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
|
||||||
|
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
try {
|
try {
|
||||||
serializerBase = pool.get(JSONSerializer.class.getName());
|
serializerBase = pool.get(JSONSerializer.class.getName());
|
||||||
|
|
@ -68,9 +61,7 @@ public class SynthSerializerFactory implements SerializerFactory {
|
||||||
try {
|
try {
|
||||||
CtClass beanClass = pool.get(beanjavaClass.getName());
|
CtClass beanClass = pool.get(beanjavaClass.getName());
|
||||||
|
|
||||||
JSONSerializer<T> jsonSerializer = createSerializer(beanClass);
|
return createSerializer(beanClass);
|
||||||
|
|
||||||
return jsonSerializer;
|
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
throw new SerializerCreationException(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
|
* create method source, compile it and add it to the class under construction
|
||||||
*/
|
*/
|
||||||
private void addToJsonStringMethod(CtClass beanClass, CtClass serializerClass) throws NotFoundException, CannotCompileException {
|
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 {
|
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);
|
interfaces.add(beanClass);
|
||||||
for (CtClass interfaze : interfaces) {
|
for (CtClass interfaze : interfaces) {
|
||||||
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)) {
|
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 {
|
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);
|
interfaces.add(beanClass);
|
||||||
for (CtClass interfaze : interfaces) {
|
return interfaces.stream().anyMatch(i -> mapInterfaces.contains(i.getName()));
|
||||||
if (interfaze.getName().equals(MAP)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -346,22 +333,22 @@ public class SynthSerializerFactory implements SerializerFactory {
|
||||||
|
|
||||||
String asPrimitive(String name) {
|
String asPrimitive(String name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "int":
|
case "int":
|
||||||
return "I";
|
return "I";
|
||||||
case "byte":
|
case "byte":
|
||||||
return "B";
|
return "B";
|
||||||
case "float":
|
case "float":
|
||||||
return "F";
|
return "F";
|
||||||
case "long":
|
case "long":
|
||||||
return "J";
|
return "J";
|
||||||
case "boolean":
|
case "boolean":
|
||||||
return "Z";
|
return "Z";
|
||||||
case "char":
|
case "char":
|
||||||
return "C";
|
return "C";
|
||||||
case "double":
|
case "double":
|
||||||
return "D";
|
return "D";
|
||||||
case "short":
|
case "short":
|
||||||
return "S";
|
return "S";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
@ -369,8 +356,4 @@ public class SynthSerializerFactory implements SerializerFactory {
|
||||||
String innerClassName(String name) {
|
String innerClassName(String name) {
|
||||||
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[\\]", "");
|
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[\\]", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
|
|
||||||
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,14 @@
|
||||||
<table></table>
|
<table></table>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
d3.json("/report").then(function (data) {
|
function loadData() {
|
||||||
// render the table(s)
|
d3.json("/report").then(function (data) {
|
||||||
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
|
// render the table(s)
|
||||||
});
|
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData();
|
||||||
</script>
|
</script>
|
||||||
|
<button type="button" onclick="loadData()">refresh</button>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
function tabulate(data, columns) {
|
function tabulate(data, columns) {
|
||||||
|
d3.select('table').remove();
|
||||||
var table = d3.select('body').append('table')
|
var table = d3.select('body').append('table')
|
||||||
var thead = table.append('thead')
|
var thead = table.append('thead')
|
||||||
var tbody = table.append('tbody');
|
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.sql.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class Main {
|
public class App {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("Perfix Test Application is running. Make sure the agent is active.");
|
System.out.println("Perfix Test Application is running. Make sure the agent is active.");
|
||||||
String includesProperty = System.getProperty("perfix.includes");
|
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