removed UI and added bound variable inspection
This commit is contained in:
parent
14e759f7fb
commit
78f8d406f4
17 changed files with 193 additions and 423 deletions
|
|
@ -19,10 +19,9 @@
|
||||||
<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/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" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/testapp/java" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
||||||
2
pom.xml
2
pom.xml
|
|
@ -4,7 +4,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>perfix</groupId>
|
<groupId>perfix</groupId>
|
||||||
<artifactId>agent</artifactId>
|
<artifactId>perfix-agent</artifactId>
|
||||||
<version>0.1-SNAPSHOT</version>
|
<version>0.1-SNAPSHOT</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,13 @@ public class Registry {
|
||||||
});
|
});
|
||||||
return sortedByTotal;
|
return sortedByTotal;
|
||||||
}
|
}
|
||||||
//work in progress
|
|
||||||
// public static Map<String, Set<Report>> getCallStack() {
|
//work in progress
|
||||||
// callstack.forEach((name,children)->{
|
public static Map<String, Set<Report>> getCallStack() {
|
||||||
//
|
callstack.forEach((name, children) -> {
|
||||||
// });
|
|
||||||
// }
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import javassist.NotFoundException;
|
||||||
import perfix.MutableBoolean;
|
import perfix.MutableBoolean;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
|
|
@ -30,9 +32,14 @@ public class ClassInstrumentor extends Instrumentor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void instrumentCode(Instrumentation inst) {
|
public void instrumentCode(Instrumentation inst) {
|
||||||
inst.addTransformer((classLoader, resource, aClass, protectionDomain, uninstrumentedByteCode)
|
inst.addTransformer(new ClassFileTransformer() {
|
||||||
-> instrumentCodeForClass(includes, resource, uninstrumentedByteCode));
|
@Override
|
||||||
|
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
|
||||||
|
return instrumentCodeForClass(includes, className, classfileBuffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] instrumentCodeForClass(List<String> includes, String resource, byte[] uninstrumentedByteCode) {
|
private byte[] instrumentCodeForClass(List<String> includes, String resource, byte[] uninstrumentedByteCode) {
|
||||||
|
|
@ -40,10 +47,10 @@ public class ClassInstrumentor extends Instrumentor {
|
||||||
try {
|
try {
|
||||||
CtClass ctClass = getCtClassForResource(resource);
|
CtClass ctClass = getCtClassForResource(resource);
|
||||||
|
|
||||||
if (servletInstrumentor.isServletImpl(resource)){
|
if (servletInstrumentor.isServletImpl(resource)) {
|
||||||
return servletInstrumentor.instrumentServlet(ctClass, uninstrumentedByteCode);
|
return servletInstrumentor.instrumentServlet(ctClass, uninstrumentedByteCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jdbcInstrumentor.isJdbcStatementImpl(resource, ctClass)) {
|
if (jdbcInstrumentor.isJdbcStatementImpl(resource, ctClass)) {
|
||||||
return jdbcInstrumentor.instrumentJdbcStatement(ctClass, uninstrumentedByteCode);
|
return jdbcInstrumentor.instrumentJdbcStatement(ctClass, uninstrumentedByteCode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,28 @@ import java.util.List;
|
||||||
|
|
||||||
public abstract class Instrumentor {
|
public abstract class Instrumentor {
|
||||||
static final String JAVA_STRING = "java.lang.String";
|
static final String JAVA_STRING = "java.lang.String";
|
||||||
|
static final String JAVA_HASHMAP = "java.util.HashMap";
|
||||||
static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
|
static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
|
||||||
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
|
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
|
||||||
static final String JAVASSIST_RETURNVALUE = "$_";
|
static final String JAVASSIST_RETURNVALUE = "$_";
|
||||||
|
|
||||||
final ClassPool classpool;
|
final ClassPool classpool;
|
||||||
final List<String> includes;
|
final List<String> includes;
|
||||||
CtClass perfixMethodInvocationClass;
|
protected CtClass stringClass;
|
||||||
CtClass stringClass;
|
protected CtClass hashMapClass;
|
||||||
|
protected CtClass perfixMethodInvocationClass;
|
||||||
|
|
||||||
Instrumentor(List<String> includes, ClassPool classPool) {
|
Instrumentor(List<String> includes, ClassPool classPool) {
|
||||||
this.includes = includes;
|
this.includes = includes;
|
||||||
this.classpool = classPool;
|
this.classpool = classPool;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
|
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
|
||||||
stringClass = classpool.get(JAVA_STRING);
|
stringClass = classpool.get(JAVA_STRING);
|
||||||
|
hashMapClass = classPool.get(JAVA_HASHMAP);
|
||||||
|
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
//suppress TODO implement trace
|
//suppress TODO implement trace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +42,8 @@ public abstract class Instrumentor {
|
||||||
return classInstrumentor;
|
return classInstrumentor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void instrumentCode(Instrumentation inst){}
|
public void instrumentCode(Instrumentation inst) {
|
||||||
|
}
|
||||||
|
|
||||||
void instrumentMethod(CtMethod methodToinstrument) {
|
void instrumentMethod(CtMethod methodToinstrument) {
|
||||||
instrumentMethod(methodToinstrument, "\"" + methodToinstrument.getLongName() + "\"");
|
instrumentMethod(methodToinstrument, "\"" + methodToinstrument.getLongName() + "\"");
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ package perfix.instrument;
|
||||||
import javassist.*;
|
import javassist.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
|
|
||||||
|
|
@ -22,10 +25,15 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
|
|
||||||
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 static CtClass statementTextClass = null;
|
||||||
|
|
||||||
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
|
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
|
||||||
super(includes, classPool);
|
super(includes, classPool);
|
||||||
|
try {
|
||||||
|
statementTextClass = classpool.get("perfix.instrument.StatementText");
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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. */
|
||||||
|
|
@ -33,17 +41,20 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
try {
|
try {
|
||||||
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
|
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
|
||||||
} catch (NotFoundException e1) {
|
} catch (NotFoundException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
try {
|
try {
|
||||||
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
|
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
|
||||||
preparedStatementInterface.addMethod(setSqlForPerfix);
|
preparedStatementInterface.addMethod(setSqlForPerfix);
|
||||||
|
|
||||||
} catch (CannotCompileException e2) {
|
} catch (CannotCompileException e2) {
|
||||||
|
e2.printStackTrace();
|
||||||
return uninstrumentedByteCode;
|
return uninstrumentedByteCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return bytecode(preparedStatementInterface);
|
return bytecode(preparedStatementInterface);
|
||||||
} catch (CannotCompileException | IOException e) {
|
} catch (CannotCompileException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
return uninstrumentedByteCode;
|
return uninstrumentedByteCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +90,7 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
return bytecode(classToInstrument);
|
return bytecode(classToInstrument);
|
||||||
} catch (NotFoundException | CannotCompileException | IOException e) {
|
} catch (NotFoundException | CannotCompileException | IOException e) {
|
||||||
// suppress
|
// suppress
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return uninstrumentedByteCode;
|
return uninstrumentedByteCode;
|
||||||
}
|
}
|
||||||
|
|
@ -86,24 +98,53 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
/* */
|
/* */
|
||||||
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
|
||||||
try {
|
try {
|
||||||
addPerfixStatementField(preparedStatementClass);
|
addPerfixFields(preparedStatementClass);
|
||||||
addPerfixStatementSetter(preparedStatementClass);
|
addPerfixStatementSetter(preparedStatementClass);
|
||||||
|
|
||||||
// instrument execute to record query duration
|
// instrument execute to record query duration
|
||||||
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTE_METHOD)).forEach(method ->
|
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTE_METHOD)).forEach(method ->
|
||||||
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
|
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
|
||||||
);
|
);
|
||||||
|
|
||||||
// instrument executeQuery to record query duration
|
// instrument executeQuery to record query duration
|
||||||
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
|
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
|
||||||
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
|
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
|
||||||
);
|
);
|
||||||
|
|
||||||
// instrument executeUpdate to record query duration
|
// instrument executeUpdate to record query duration
|
||||||
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
|
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
|
||||||
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
|
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
getDeclaredMethods(preparedStatementClass, "setString", "setObject", "setDate", "setTime", "setTimestamp")
|
||||||
|
.forEach(method -> {
|
||||||
|
try {
|
||||||
|
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, \"\'\"+$2+\"\'\");");
|
||||||
|
} catch (CannotCompileException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
getDeclaredMethods(preparedStatementClass,
|
||||||
|
"setInt", "setFloat", "setDouble", "setBoolean", "setLong", "setShort")
|
||||||
|
.forEach(method -> {
|
||||||
|
try {
|
||||||
|
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, $2);");
|
||||||
|
} catch (CannotCompileException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
getDeclaredMethods(preparedStatementClass, "setNull")
|
||||||
|
.forEach(method -> {
|
||||||
|
try {
|
||||||
|
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, \"[NULL]\");");
|
||||||
|
} catch (CannotCompileException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return bytecode(preparedStatementClass);
|
return bytecode(preparedStatementClass);
|
||||||
} catch (NotFoundException | CannotCompileException | IOException e) {
|
} catch (NotFoundException | CannotCompileException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
@ -111,6 +152,21 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Stream<CtMethod> getDeclaredMethods(CtClass preparedStatementClass, String... methodnames) {
|
||||||
|
List<CtMethod> methods = new ArrayList<>();
|
||||||
|
for (String methodname : methodnames) {
|
||||||
|
try {
|
||||||
|
methods.addAll(Arrays.asList(preparedStatementClass.getDeclaredMethods(methodname)));
|
||||||
|
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -124,14 +180,15 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
);
|
);
|
||||||
return bytecode(classToInstrument);
|
return bytecode(classToInstrument);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
return uninstrumentedByteCode;
|
return uninstrumentedByteCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPerfixStatementField(CtClass preparedStatementClass) throws CannotCompileException {
|
private void addPerfixFields(CtClass preparedStatementClass) throws CannotCompileException, NotFoundException {
|
||||||
// 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(statementTextClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
|
||||||
perfixSqlField.setModifiers(Modifier.PRIVATE);
|
perfixSqlField.setModifiers(Modifier.PRIVATE);
|
||||||
preparedStatementClass.addField(perfixSqlField);
|
preparedStatementClass.addField(perfixSqlField);
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +197,7 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
// add setter for the new field
|
// add setter for the new field
|
||||||
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementImplClass);
|
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementImplClass);
|
||||||
setSqlForPerfix.setModifiers(Modifier.PUBLIC);
|
setSqlForPerfix.setModifiers(Modifier.PUBLIC);
|
||||||
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
|
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "= new perfix.instrument.StatementText(" + JAVASSIST_FIRST_ARGUMENT_NAME + ");");
|
||||||
preparedStatementImplClass.addMethod(setSqlForPerfix);
|
preparedStatementImplClass.addMethod(setSqlForPerfix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
67
src/main/java/perfix/instrument/StatementText.java
Normal file
67
src/main/java/perfix/instrument/StatementText.java
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package perfix.instrument;
|
||||||
|
|
||||||
|
public class StatementText {
|
||||||
|
|
||||||
|
private static final char BOUNDVAR_MARK = '?';
|
||||||
|
|
||||||
|
private final Object[] vars;
|
||||||
|
private final StringBuilder sql;
|
||||||
|
|
||||||
|
public StatementText(String sql) {
|
||||||
|
vars = new Object[countVars(sql)];
|
||||||
|
this.sql = new StringBuilder(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, Object value) {
|
||||||
|
if (index < 1 || index > vars.length + 1) {
|
||||||
|
throw new IndexOutOfBoundsException("" + index);
|
||||||
|
}
|
||||||
|
vars[index - 1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, int value){
|
||||||
|
set(index, Integer.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, long value){
|
||||||
|
set(index, Long.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, double value){
|
||||||
|
set(index, Double.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, boolean value){
|
||||||
|
set(index, Boolean.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, float value){
|
||||||
|
set(index, Float.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, short value){
|
||||||
|
set(index, Short.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
int found = 0;
|
||||||
|
for (int i = 0; i < sql.length(); i++) {
|
||||||
|
if (sql.charAt(i) == BOUNDVAR_MARK) {
|
||||||
|
sql.deleteCharAt(i);
|
||||||
|
sql.insert(i, vars[found++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countVars(String sql) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < sql.length(); i++) {
|
||||||
|
if (sql.charAt(i) == BOUNDVAR_MARK) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,58 +33,34 @@ public class HTTPServer extends NanoHTTPD {
|
||||||
return perfixMetrics();
|
return perfixMetrics();
|
||||||
case "/callstack":
|
case "/callstack":
|
||||||
return perfixCallstack();
|
return perfixCallstack();
|
||||||
default:
|
default: return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "NOT FOUND");
|
||||||
return serveStaticContent(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response serveStaticContent(String uri) {
|
|
||||||
if (uri.equals("/")) {
|
|
||||||
uri = "/index.html";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
InputStream stream = getClass().getResourceAsStream(uri);
|
|
||||||
if (stream == null) {
|
|
||||||
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "resource not found");
|
|
||||||
}
|
|
||||||
return newFixedLengthResponse(Response.Status.OK, determineContentType(uri), readFile(stream));
|
|
||||||
} catch (IOException e) {
|
|
||||||
return newFixedLengthResponse(e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response perfixMetrics() {
|
private Response perfixMetrics() {
|
||||||
try {
|
try {
|
||||||
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values())));
|
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values()))));
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return newFixedLengthResponse(e.toString());
|
return newFixedLengthResponse(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response addCors(Response response) {
|
||||||
|
response.addHeader("Access-Control-Allow-Origin","*");
|
||||||
|
response.addHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private Response perfixCallstack() {
|
private Response perfixCallstack() {
|
||||||
try {
|
try {
|
||||||
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack()));
|
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return newFixedLengthResponse(e.toString());
|
return newFixedLengthResponse(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readFile(InputStream stream) throws IOException {
|
|
||||||
int nbytes = stream.available();
|
|
||||||
byte[] bytes = new byte[nbytes];
|
|
||||||
stream.read(bytes);
|
|
||||||
return new String(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String determineContentType(String uri) {
|
|
||||||
if (uri.endsWith(".js")) {
|
|
||||||
return "application/javascript";
|
|
||||||
} else if (uri.endsWith(".css")) {
|
|
||||||
return "text/css";
|
|
||||||
} else {
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
src/main/resources/d3.js
vendored
2
src/main/resources/d3.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,23 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Perfix console</title>
|
|
||||||
<script src="/d3.js"></script>
|
|
||||||
<script src="/perfix.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
</head>
|
|
||||||
<body class="datagrid">
|
|
||||||
<table></table>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
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,106 +0,0 @@
|
||||||
.datagrid table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid {
|
|
||||||
font: normal 12px/150% Arial, Helvetica, sans-serif;
|
|
||||||
background: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #006699;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-moz-border-radius: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table td, .datagrid table th {
|
|
||||||
padding: 3px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table thead th {
|
|
||||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F));
|
|
||||||
background: -moz-linear-gradient(center top, #006699 5%, #00557F 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#006699', endColorstr='#00557F');
|
|
||||||
background-color: #006699;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-left: 1px solid #0070A8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table thead th:first-child {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tbody td {
|
|
||||||
color: #00496B;
|
|
||||||
border-left: 1px solid #E1EEF4;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tbody .alt td {
|
|
||||||
background: #E1EEF4;
|
|
||||||
color: #00496B;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tbody td:first-child {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tbody tr:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot td div {
|
|
||||||
border-top: 1px solid #006699;
|
|
||||||
background: #E1EEF4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot td {
|
|
||||||
padding: 0;
|
|
||||||
font-size: 12px
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot td div {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot td ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot li a {
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 8px;
|
|
||||||
margin: 1px;
|
|
||||||
color: #FFFFFF;
|
|
||||||
border: 1px solid #006699;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-moz-border-radius: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F));
|
|
||||||
background: -moz-linear-gradient(center top, #006699 5%, #00557F 100%);
|
|
||||||
background-color: #006699;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datagrid table tfoot ul.active, .datagrid table tfoot ul a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
border-color: #006699;
|
|
||||||
color: #FFFFFF;
|
|
||||||
background: none;
|
|
||||||
background-color: #00557F;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dhtmlx_window_active, div.dhx_modal_cover_dv {
|
|
||||||
position: fixed !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
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');
|
|
||||||
|
|
||||||
thead.append('tr')
|
|
||||||
.selectAll('th')
|
|
||||||
.data(columns).enter()
|
|
||||||
.append('th')
|
|
||||||
.text(function (column) { return column; });
|
|
||||||
|
|
||||||
var rows = tbody.selectAll('tr')
|
|
||||||
.data(data)
|
|
||||||
.enter()
|
|
||||||
.append('tr');
|
|
||||||
|
|
||||||
rows.selectAll('td')
|
|
||||||
.data(function (row) {
|
|
||||||
return columns.map(function (column) {
|
|
||||||
return {column: column, value: row[column]};
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.enter()
|
|
||||||
.append('td')
|
|
||||||
.text(function (d) { return d.value; });
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "testperfix.Main.main(java.lang.String[])",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 1414878875,
|
|
||||||
"average": 1.414878875E9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testperfix.Main.run()",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 1414756159,
|
|
||||||
"average": 1.414756159E9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testperfix.Main.someJdbcStatentMethod()",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 399009103,
|
|
||||||
"average": 3.99009103E8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "select CURRENT_DATE() -- simple statement",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 99705675,
|
|
||||||
"average": 9.9705675E7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testperfix.Main.someJdbcPreparedStatementMethod()",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 5880308,
|
|
||||||
"average": 5880308.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "testperfix.Main.someOtherMethod()",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 1285359,
|
|
||||||
"average": 1285359.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "select CURRENT_DATE() -- prepared statement",
|
|
||||||
"invocations": 1,
|
|
||||||
"totalDuration": 98241,
|
|
||||||
"average": 98241.0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
14
src/test/java/StatementTextTest.java
Normal file
14
src/test/java/StatementTextTest.java
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import perfix.instrument.StatementText;
|
||||||
|
|
||||||
|
public class StatementTextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReplace(){
|
||||||
|
StatementText b = new StatementText("select ? from ?");
|
||||||
|
b.set(1, "alpha");
|
||||||
|
b.set(2, "beta");
|
||||||
|
Assert.assertEquals("select alpha from beta",b.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import org.junit.Test;
|
|
||||||
import perfix.MethodInvocation;
|
|
||||||
import perfix.Registry;
|
|
||||||
|
|
||||||
public class TestMethod {
|
|
||||||
@Test
|
|
||||||
public void testAddMethodToRegistry() {
|
|
||||||
MethodInvocation method = MethodInvocation.start("somename");
|
|
||||||
method.stop();
|
|
||||||
MethodInvocation method2 = MethodInvocation.start("somename");
|
|
||||||
method2.stop();
|
|
||||||
|
|
||||||
Registry.report(System.out);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -42,8 +42,11 @@ public class App {
|
||||||
private static void someJdbcPreparedStatementMethod() {
|
private static void someJdbcPreparedStatementMethod() {
|
||||||
try {
|
try {
|
||||||
Connection connection = DriverManager.getConnection("jdbc:h2:mem:default", "sa", "");
|
Connection connection = DriverManager.getConnection("jdbc:h2:mem:default", "sa", "");
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement("select CURRENT_DATE() -- prepared statement");
|
statement.execute("create table t (v varchar(2))");
|
||||||
|
statement.close();
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement("select * from t where v=? -- prepared statement");
|
||||||
|
preparedStatement.setDate(1, new Date(new java.util.Date().getTime()));
|
||||||
preparedStatement.executeQuery();
|
preparedStatement.executeQuery();
|
||||||
connection.close();
|
connection.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue