html interface
This commit is contained in:
parent
5049459bbc
commit
678b122c33
24 changed files with 784 additions and 234 deletions
11
README.md
11
README.md
|
|
@ -2,15 +2,13 @@
|
||||||
Pretty basic profiling tool for JVM's
|
Pretty basic profiling tool for JVM's
|
||||||
|
|
||||||
# Highlights:
|
# Highlights:
|
||||||
* Provides method and SQL statement execution times. (Somehow it gives me more info than Java Mission Control)
|
|
||||||
* Meant for development time (after process stops, data is gone).
|
* Meant for development time (after process stops, data is gone).
|
||||||
* Minimalistic commandline interface (ssh).
|
* Minimal memory footprint (agent is 2.5 mb).
|
||||||
|
* Minimalistic commandline interface.
|
||||||
* Execution time is measured in nanoseconds, reported in milliseconds (this way the totals and averages are most precise, but also human readable).
|
* Execution time is measured in nanoseconds, reported in milliseconds (this way the totals and averages are most precise, but also human readable).
|
||||||
* No manual instrumentation necessary using loadtime bytecode manipulation (javassist).
|
* No manual instrumentation necessary using loadtime bytecode manipulation (javassist).
|
||||||
* No special jdbc configuration necessary (ie no wrapped jdbc driver).
|
* No special jdbc configuration necessary (ie no wrapped jdbc driver).
|
||||||
* The agent is also the server (unlike commercial tooling). This way there is no overhead in interprocess communication.
|
* The agent is also the server (unlike commercial tooling). This way there is no overhead in interprocess communication.
|
||||||
* Minimal memory footprint (agent is >only< 2.5 mb, it's still java right?).
|
|
||||||
* Overhead (in method execution time) not clear yet. I wouldn't use it in production.
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
* Agent that instruments loaded classes: -javaagent:<path>/perfix.jar
|
* Agent that instruments loaded classes: -javaagent:<path>/perfix.jar
|
||||||
|
|
@ -19,11 +17,12 @@ Pretty basic profiling tool for JVM's
|
||||||
<br/> * #invocations
|
<br/> * #invocations
|
||||||
<br/> * total execution time for the method in nanoseconds (this is also the sorting order)
|
<br/> * total execution time for the method in nanoseconds (this is also the sorting order)
|
||||||
<br/> * average time in nanoseconds per method (= total/#invocations)
|
<br/> * average time in nanoseconds per method (= total/#invocations)
|
||||||
* The ssh server starts on port 2048 by default. Use -Dperfix.port=... to adjust.
|
* The (ssh) server starts on port 2048 by default. Use -Dperfix.port=... to adjust.
|
||||||
|
|
||||||
|
|
||||||
# roadmap
|
# roadmap
|
||||||
* Finish jdbc query recording (CallableStatement)
|
* Overhead (in method execution time) not clear yet. I wouldn't use it in production.
|
||||||
|
* Finish jdbc query logging
|
||||||
* Make output format configurable
|
* Make output format configurable
|
||||||
* Implement password login (now any)
|
* Implement password login (now any)
|
||||||
* Add web interface (maybe)
|
* Add web interface (maybe)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<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/deploy/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" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
@ -17,8 +18,6 @@
|
||||||
<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.apache.sshd:sshd-core:1.7.0" level="project" />
|
<orderEntry type="library" name="Maven: org.nanohttpd:nanohttpd:2.3.1" level="project" />
|
||||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-nop:1.7.25" level="project" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
11
pom.xml
11
pom.xml
|
|
@ -32,14 +32,9 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.nanohttpd</groupId>
|
||||||
<artifactId>sshd-core</artifactId>
|
<artifactId>nanohttpd</artifactId>
|
||||||
<version>1.7.0</version>
|
<version>2.3.1</version>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-nop</artifactId>
|
|
||||||
<version>1.7.25</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ public class Main {
|
||||||
System.out.println("Exiting now");
|
System.out.println("Exiting now");
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
System.out.println("Now start ssh session on localhost on port 2048 (without closing the Test Application)");
|
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
import javassist.*;
|
import perfix.server.HTTPServer;
|
||||||
import perfix.server.SSHServer;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -28,7 +26,7 @@ public class Agent {
|
||||||
|
|
||||||
new ClassInstrumentor(determineIncludes()).instrumentCode(inst);
|
new ClassInstrumentor(determineIncludes()).instrumentCode(inst);
|
||||||
|
|
||||||
new SSHServer().startListeningOnSocket(port);
|
new HTTPServer(port).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> determineIncludes() {
|
private static List<String> determineIncludes() {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ public class MethodInvocation {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
} else {
|
} else {
|
||||||
this.name = "<UNKNOWN_BECAUSE_OF_ERROR>";
|
this.name = "<error occurred>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public class Registry {
|
||||||
static void add(MethodInvocation methodInvocation) {
|
static void add(MethodInvocation methodInvocation) {
|
||||||
methods.computeIfAbsent(methodInvocation.getName(), key -> new ArrayList<>()).add(methodInvocation);
|
methods.computeIfAbsent(methodInvocation.getName(), key -> new ArrayList<>()).add(methodInvocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void report(PrintStream out) {
|
public static void report(PrintStream out) {
|
||||||
out.println(HEADER1);
|
out.println(HEADER1);
|
||||||
out.println(HEADER2);
|
out.println(HEADER2);
|
||||||
|
|
@ -29,13 +29,13 @@ public class Registry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String createReportLine(Report report) {
|
private static String createReportLine(Report report) {
|
||||||
return report.name + ";"
|
return report.getName() + ";"
|
||||||
+ report.invocations + ";"
|
+ report.getInvocations() + ";"
|
||||||
+ (long) (report.totalDuration / NANO_2_MILLI) + ";"
|
+ (long) (report.getTotalDuration() / NANO_2_MILLI) + ";"
|
||||||
+ (long) (report.average() / NANO_2_MILLI);
|
+ (long) (report.getAverage() / NANO_2_MILLI);
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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) -> {
|
||||||
LongAdder totalDuration = new LongAdder();
|
LongAdder totalDuration = new LongAdder();
|
||||||
|
|
@ -47,19 +47,5 @@ public class Registry {
|
||||||
return sortedByTotal;
|
return sortedByTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Report {
|
|
||||||
final String name;
|
|
||||||
final int invocations;
|
|
||||||
final long totalDuration;
|
|
||||||
|
|
||||||
Report(String name, int invocations, long totalDuration) {
|
|
||||||
this.name = name;
|
|
||||||
this.invocations = invocations;
|
|
||||||
this.totalDuration = totalDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
double average() {
|
|
||||||
return (double) totalDuration / invocations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/main/java/perfix/Report.java
Normal file
31
src/main/java/perfix/Report.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package perfix;
|
||||||
|
|
||||||
|
public class Report {
|
||||||
|
private final String name;
|
||||||
|
private final int invocations;
|
||||||
|
private final long totalDuration;
|
||||||
|
private final double average;
|
||||||
|
|
||||||
|
Report(String name, int invocations, long totalDuration) {
|
||||||
|
this.name = name;
|
||||||
|
this.invocations = invocations;
|
||||||
|
this.totalDuration = totalDuration;
|
||||||
|
this.average = (double) totalDuration / invocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverage() {
|
||||||
|
return average;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInvocations() {
|
||||||
|
return invocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalDuration() {
|
||||||
|
return totalDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/main/java/perfix/server/HTTPServer.java
Normal file
78
src/main/java/perfix/server/HTTPServer.java
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package perfix.server;
|
||||||
|
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
import perfix.Registry;
|
||||||
|
import perfix.server.json.Serializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class HTTPServer extends NanoHTTPD {
|
||||||
|
|
||||||
|
|
||||||
|
public HTTPServer(int port) {
|
||||||
|
super(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
||||||
|
System.out.println("\nHttpServer running! Point your browser to http://localhost:2048/ \n");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
System.err.println("Couldn't start server:\n" + ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response serve(IHTTPSession session) {
|
||||||
|
String uri = session.getUri();
|
||||||
|
if (uri.equals("/report")) {
|
||||||
|
return perfixMetrics();
|
||||||
|
} else {
|
||||||
|
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() {
|
||||||
|
try {
|
||||||
|
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values())));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return newFixedLengthResponse(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readFile(InputStream stream) throws IOException {
|
||||||
|
int nbytes = stream.available();
|
||||||
|
byte[] bytes = new byte[nbytes];
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
import org.apache.sshd.common.PropertyResolverUtils;
|
|
||||||
import org.apache.sshd.server.ServerFactoryManager;
|
|
||||||
import org.apache.sshd.server.SshServer;
|
|
||||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
public class SSHServer implements Server, Runnable {
|
|
||||||
private static final String BANNER = "\n\nWelcome to Perfix!\n\n";
|
|
||||||
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
public void startListeningOnSocket(int port) {
|
|
||||||
this.port=port;
|
|
||||||
new Thread(this).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
SshServer sshd = SshServer.setUpDefaultServer();
|
|
||||||
|
|
||||||
PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, BANNER);
|
|
||||||
sshd.setPasswordAuthenticator((s, s1, serverSession) -> true);
|
|
||||||
sshd.setPort(port);
|
|
||||||
sshd.setShellFactory(new SshSessionFactory());
|
|
||||||
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(Paths.get("hostkey.ser")));
|
|
||||||
|
|
||||||
try {
|
|
||||||
sshd.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;){
|
|
||||||
try {
|
|
||||||
Thread.sleep(500);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
public interface Server {
|
|
||||||
void startListeningOnSocket(int port);
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
import org.apache.sshd.common.Factory;
|
|
||||||
import org.apache.sshd.server.Command;
|
|
||||||
import org.apache.sshd.server.CommandFactory;
|
|
||||||
|
|
||||||
public class SshSessionFactory
|
|
||||||
implements CommandFactory, Factory<Command> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command createCommand(String command) {
|
|
||||||
return new SshSessionInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command create() {
|
|
||||||
return createCommand("none");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
import org.apache.sshd.server.Command;
|
|
||||||
import org.apache.sshd.server.Environment;
|
|
||||||
import org.apache.sshd.server.ExitCallback;
|
|
||||||
import perfix.Registry;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
|
|
||||||
public class SshSessionInstance implements Command, Runnable {
|
|
||||||
|
|
||||||
private static final String ANSI_NEWLINE_CRLF = "\u001B[20h";
|
|
||||||
|
|
||||||
private InputStream input;
|
|
||||||
private OutputStream output;
|
|
||||||
private ExitCallback callback;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Environment env) {
|
|
||||||
new Thread(this, "PerfixShell").start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
output.write("press [enter] for report or [q] to quit\n".getBytes());
|
|
||||||
output.write((ANSI_NEWLINE_CRLF).getBytes());
|
|
||||||
output.flush();
|
|
||||||
|
|
||||||
boolean exit = false;
|
|
||||||
while (!exit) {
|
|
||||||
char c = (char) input.read();
|
|
||||||
if (c == 'q') {
|
|
||||||
exit = true;
|
|
||||||
} else if (c == '\n') {
|
|
||||||
Registry.report(new PrintStream(output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
callback.onExit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setErrorStream(OutputStream errOS) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setExitCallback(ExitCallback ec) {
|
|
||||||
callback = ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setInputStream(InputStream is) {
|
|
||||||
this.input = is;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOutputStream(OutputStream os) {
|
|
||||||
this.output = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
import perfix.Registry;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
public class TelnetServer implements Server {
|
|
||||||
public void startListeningOnSocket(int port) {
|
|
||||||
try {
|
|
||||||
ServerSocket serverSocket = new ServerSocket(port);
|
|
||||||
new Thread(() -> {
|
|
||||||
for (; ; ) {
|
|
||||||
try {
|
|
||||||
Socket client = serverSocket.accept();
|
|
||||||
|
|
||||||
PrintStream out = new PrintStream(client.getOutputStream());
|
|
||||||
out.println("press [enter] for report or [q and enter] to quit");
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.equals("q")) {
|
|
||||||
try {
|
|
||||||
client.close();
|
|
||||||
break;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Registry.report(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
21
src/main/java/perfix/server/json/JSONSerializer.java
Normal file
21
src/main/java/perfix/server/json/JSONSerializer.java
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
public abstract class JSONSerializer<T> {
|
||||||
|
protected abstract String handle(T object);
|
||||||
|
|
||||||
|
protected Formatter formatter = new Formatter();
|
||||||
|
|
||||||
|
public String toJSONString(T object) {
|
||||||
|
if (object == null) {
|
||||||
|
return "";
|
||||||
|
} else if (object instanceof Number || object instanceof Boolean) {
|
||||||
|
return "" + object.toString();
|
||||||
|
} else if (object instanceof CharSequence || object instanceof Character) {
|
||||||
|
return "\"" + object.toString() + "\"";
|
||||||
|
} else {
|
||||||
|
return handle(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/perfix/server/json/Serializer.java
Normal file
45
src/main/java/perfix/server/json/Serializer.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
public class Serializer {
|
||||||
|
private static SerializerFactory instance = new SynthSerializerFactory();
|
||||||
|
|
||||||
|
public static String toJSONString(boolean b) {
|
||||||
|
return Boolean.toString(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(short s) {
|
||||||
|
return Short.toString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(int i) {
|
||||||
|
return Integer.toString(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(float f) {
|
||||||
|
return Float.toString(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(double d) {
|
||||||
|
return Double.toString(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(long l) {
|
||||||
|
return Long.toString(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(char c) {
|
||||||
|
return "\"" + Character.toString(c) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> String toJSONString(T o) {
|
||||||
|
if (o == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
return instance.createSerializer((Class<T>) o.getClass()).toJSONString(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setInstance(SerializerFactory instance) {
|
||||||
|
Serializer.instance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class SerializerCreationException extends RuntimeException {
|
||||||
|
|
||||||
|
public SerializerCreationException(Throwable t) {
|
||||||
|
super(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
src/main/java/perfix/server/json/SerializerFactory.java
Normal file
5
src/main/java/perfix/server/json/SerializerFactory.java
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
public interface SerializerFactory {
|
||||||
|
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass);
|
||||||
|
}
|
||||||
376
src/main/java/perfix/server/json/SynthSerializerFactory.java
Normal file
376
src/main/java/perfix/server/json/SynthSerializerFactory.java
Normal file
|
|
@ -0,0 +1,376 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javassist.CannotCompileException;
|
||||||
|
import javassist.ClassPool;
|
||||||
|
import javassist.CtClass;
|
||||||
|
import javassist.CtField;
|
||||||
|
import javassist.CtMethod;
|
||||||
|
import javassist.CtNewMethod;
|
||||||
|
import javassist.Modifier;
|
||||||
|
import javassist.NotFoundException;
|
||||||
|
|
||||||
|
public class SynthSerializerFactory implements SerializerFactory {
|
||||||
|
private static final String STRING = "java.lang.String";
|
||||||
|
private static final String BOOLEAN = "java.lang.Boolean";
|
||||||
|
private static final String CHARACTER = "java.lang.Character";
|
||||||
|
private static final String BYTE = "java.lang.Byte";
|
||||||
|
private static final String DOUBLE = "java.lang.Double";
|
||||||
|
private static final String FLOAT = "java.lang.Float";
|
||||||
|
private static final String LONG = "java.lang.Long";
|
||||||
|
private static final String SHORT = "java.lang.Short";
|
||||||
|
private static final String INTEGER = "java.lang.Integer";
|
||||||
|
|
||||||
|
private final static Set<String> wrappersAndString = new HashSet<String>(Arrays.asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
|
||||||
|
STRING));
|
||||||
|
|
||||||
|
private static final String COLLECTION = "java.util.Collection";
|
||||||
|
private static final String LIST = "java.util.List";
|
||||||
|
private static final String SET = "java.util.Set";
|
||||||
|
private static final String MAP = "java.util.Map";
|
||||||
|
|
||||||
|
private static final Map<String, JSONSerializer<?>> serializers = new HashMap<>();
|
||||||
|
private static final String ROOT_PACKAGE = "serializer.";
|
||||||
|
|
||||||
|
private final ClassPool pool = ClassPool.getDefault();
|
||||||
|
private CtClass serializerBase;
|
||||||
|
private final Map<String, CtClass> primitiveWrappers = new HashMap<String, CtClass>();
|
||||||
|
|
||||||
|
public SynthSerializerFactory() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
try {
|
||||||
|
serializerBase = pool.get(JSONSerializer.class.getName());
|
||||||
|
|
||||||
|
primitiveWrappers.put("int", pool.get(INTEGER));
|
||||||
|
primitiveWrappers.put("short", pool.get(SHORT));
|
||||||
|
primitiveWrappers.put("byte", pool.get(BYTE));
|
||||||
|
primitiveWrappers.put("long", pool.get(LONG));
|
||||||
|
primitiveWrappers.put("float", pool.get(FLOAT));
|
||||||
|
primitiveWrappers.put("double", pool.get(DOUBLE));
|
||||||
|
primitiveWrappers.put("boolean", pool.get(BOOLEAN));
|
||||||
|
primitiveWrappers.put("char", pool.get(CHARACTER));
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new SerializerCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
||||||
|
try {
|
||||||
|
CtClass beanClass = pool.get(beanjavaClass.getName());
|
||||||
|
|
||||||
|
JSONSerializer<T> jsonSerializer = createSerializer(beanClass);
|
||||||
|
|
||||||
|
return jsonSerializer;
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new SerializerCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> JSONSerializer<T> createSerializer(CtClass beanClass) {
|
||||||
|
if (serializers.containsKey(createSerializerName(beanClass))) {
|
||||||
|
return (JSONSerializer<T>) serializers.get(createSerializerName(beanClass));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return tryCreateSerializer(beanClass);
|
||||||
|
} catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) {
|
||||||
|
throw new SerializerCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> JSONSerializer<T> tryCreateSerializer(CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException,
|
||||||
|
IllegalAccessException {
|
||||||
|
CtClass serializerClass = pool.makeClass(createSerializerName(beanClass), serializerBase);
|
||||||
|
|
||||||
|
addToJsonStringMethod(beanClass, serializerClass);
|
||||||
|
|
||||||
|
JSONSerializer<T> jsonSerializer = createSerializerInstance(serializerClass);
|
||||||
|
|
||||||
|
serializers.put(createSerializerName(beanClass), jsonSerializer);
|
||||||
|
return jsonSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create method source, compile it and add it to the class under construction
|
||||||
|
*/
|
||||||
|
private void addToJsonStringMethod(CtClass beanClass, CtClass serializerClass) throws NotFoundException, CannotCompileException {
|
||||||
|
serializerClass.addMethod(CtNewMethod.make(createToJSONStringMethodSource(beanClass), serializerClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates the source, handling the for JSON different types of classes
|
||||||
|
*/
|
||||||
|
private <T> String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException {
|
||||||
|
String source = "public String handle(Object object){\n";
|
||||||
|
if (beanClass.isArray()) {
|
||||||
|
source += "\tObject[] array=(Object[])object;\n";
|
||||||
|
source += handleArray(beanClass);
|
||||||
|
} else if (isCollection(beanClass)) {
|
||||||
|
source += "\tObject[] array=((java.util.Collection)object).toArray();\n";
|
||||||
|
source += handleArray(beanClass);
|
||||||
|
} else if (isMap(beanClass)) {
|
||||||
|
source += handleMap(beanClass);
|
||||||
|
} else if (!isPrimitiveOrWrapperOrString(beanClass)) {
|
||||||
|
List<CtMethod> getters = getGetters(beanClass);
|
||||||
|
if (shouldAddGetterCallers(getters)) {
|
||||||
|
source = addGetterCallers(beanClass, source, getters);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
source += "\treturn \"\";}";
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Any Collection is converted to an array, after which code is generated to handle the single elements.
|
||||||
|
*
|
||||||
|
* A subserializer is created for every single element, but most of the time it will be the same cached instance.
|
||||||
|
*
|
||||||
|
* The generated code fills a StringBuilder. The values are generated by the subserializers
|
||||||
|
*/
|
||||||
|
private String handleArray(CtClass beanClass) {
|
||||||
|
String source = "\tStringBuilder result=new StringBuilder(\"[\");\n";
|
||||||
|
source += "\tfor (int i=0; i<array.length; i++){\n";
|
||||||
|
source += "\t\tresult.append(" + Serializer.class.getName() + ".toJSONString(array[i]));\n";
|
||||||
|
source += "\t\tresult.append(\", \");\n";
|
||||||
|
source += "\t};\n";
|
||||||
|
source += "\tresult.setLength(result.length()-2);\n";
|
||||||
|
source += "\tresult.append(\"]\");\n";
|
||||||
|
source += "\treturn result.toString();\n";
|
||||||
|
source += "}";
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String handleMap(CtClass beanClass) {
|
||||||
|
String source = "StringBuilder result=new StringBuilder(\"{\");\n";
|
||||||
|
source += "\tfor (java.util.Iterator entries=((java.util.Map)object).entrySet().iterator();entries.hasNext();){\n";
|
||||||
|
source += "\t\tjava.util.Map.Entry entry=(java.util.Map.Entry)entries.next();\n";
|
||||||
|
source += "\t\tresult.append(\"\\\"\"+entry.getKey().toString()+\"\\\"\");\n";
|
||||||
|
source += "\t\tresult.append(\": \");\n";
|
||||||
|
source += "\t\tresult.append(" + Serializer.class.getName() + ".toJSONString(entry.getValue()));\n";
|
||||||
|
source += "\t\tresult.append(\", \");\n";
|
||||||
|
source += "\t};\n";
|
||||||
|
source += "\tresult.setLength(result.length()-2);\n";
|
||||||
|
source += "\tresult.append(\"}\");\n";
|
||||||
|
source += "\treturn result.toString();\n";
|
||||||
|
source += "}";
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the class contains fields for which public getters are available, then these will be called in the generated code.
|
||||||
|
*/
|
||||||
|
private String addGetterCallers(CtClass beanClass, String source, List<CtMethod> getters) throws NotFoundException {
|
||||||
|
int index = 0;
|
||||||
|
source += "\treturn ";
|
||||||
|
source += "\"{";
|
||||||
|
for (CtMethod getter : getters) {
|
||||||
|
source = addPair(beanClass, source, getter);
|
||||||
|
if (index++ < getters.size() - 1) {
|
||||||
|
source += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
source += "}\";\n}";
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> JSONSerializer<T> createSerializerInstance(CtClass serializerClass) throws InstantiationException, IllegalAccessException,
|
||||||
|
CannotCompileException {
|
||||||
|
return (JSONSerializer<T>) serializerClass.toClass().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes
|
||||||
|
*
|
||||||
|
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
|
||||||
|
*/
|
||||||
|
public String createSerializerName(CtClass beanClass) {
|
||||||
|
return createSerializerName(beanClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createSerializerName(String name) {
|
||||||
|
return ROOT_PACKAGE + name.replaceAll("\\[\\]", "Array") + "Serializer";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCollection(CtClass beanClass) throws NotFoundException {
|
||||||
|
List<CtClass> interfaces = new ArrayList<CtClass>(Arrays.asList(beanClass.getInterfaces()));
|
||||||
|
interfaces.add(beanClass);
|
||||||
|
for (CtClass interfaze : interfaces) {
|
||||||
|
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMap(CtClass beanClass) throws NotFoundException {
|
||||||
|
List<CtClass> interfaces = new ArrayList<CtClass>(Arrays.asList(beanClass.getInterfaces()));
|
||||||
|
interfaces.add(beanClass);
|
||||||
|
for (CtClass interfaze : interfaces) {
|
||||||
|
if (interfaze.getName().equals(MAP)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The JSON vernacular for key:value is pair...
|
||||||
|
*/
|
||||||
|
private String addPair(CtClass classToSerialize, String source, CtMethod getter) throws NotFoundException {
|
||||||
|
source += jsonKey(getter);
|
||||||
|
source += ": "; // what is the rule when it comes to spaces in json?
|
||||||
|
source += jsonValue(classToSerialize, getter);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* derive property key from getter
|
||||||
|
*/
|
||||||
|
private String jsonKey(CtMethod getter) {
|
||||||
|
return "\\\"" + toFieldName(getter.getName()) + "\\\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String jsonValue(CtClass classToSerialize, CtMethod getter) throws NotFoundException {
|
||||||
|
String source = "";
|
||||||
|
CtClass returnType = getter.getReturnType();
|
||||||
|
|
||||||
|
/* primitives are wrapped so the produced methods adhere to the JSONSerializer interface */
|
||||||
|
source = createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter, source, returnType);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter, String source, CtClass returnType) {
|
||||||
|
/* NB there does not seem to be auto(un))boxing nor generic types (or other jdk1.5 stuff) in javassist compileable code */
|
||||||
|
|
||||||
|
source += "\"+" + Serializer.class.getName() + ".toJSONString(";
|
||||||
|
|
||||||
|
// cast because of lack of generics
|
||||||
|
source += "(" + cast(regularClassname(classToSerialize.getName())) + "object)." + getter.getName() + "()";
|
||||||
|
|
||||||
|
source += ")+\"";
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* turns for example 'getValue' into 'value'
|
||||||
|
*/
|
||||||
|
private String toFieldName(String name) {
|
||||||
|
return name.substring(3, 4).toLowerCase() + (name.length() > 4 ? name.substring(4) : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String regularClassname(String name) {
|
||||||
|
return name.replaceAll("\\$", ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cast(String classToSerialize) {
|
||||||
|
return "(" + classToSerialize + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieves getter methods from a class
|
||||||
|
*/
|
||||||
|
private List<CtMethod> getGetters(CtClass beanClass) {
|
||||||
|
List<CtMethod> methods = new ArrayList<CtMethod>();
|
||||||
|
List<CtField> fields = getAllFields(beanClass);
|
||||||
|
for (CtField field : fields) {
|
||||||
|
try {
|
||||||
|
CtMethod method = beanClass.getMethod(getGetterMethod(field), getDescription(field));
|
||||||
|
if (Modifier.isPublic(method.getModifiers())) {
|
||||||
|
methods.add(method);
|
||||||
|
}
|
||||||
|
} catch (NotFoundException n) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getGetterMethod(CtField field) {
|
||||||
|
return "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CtField> getAllFields(CtClass beanClass) {
|
||||||
|
try {
|
||||||
|
List<CtField> allfields = new ArrayList<>();
|
||||||
|
for (CtField field : beanClass.getDeclaredFields()) {
|
||||||
|
allfields.add(field);
|
||||||
|
}
|
||||||
|
if (beanClass.getSuperclass() != null) {
|
||||||
|
return getAllFields(beanClass.getSuperclass(), allfields);
|
||||||
|
}
|
||||||
|
return allfields;
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new SerializerCreationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CtField> getAllFields(CtClass beanClass, List<CtField> allfields) {
|
||||||
|
for (CtField field : beanClass.getDeclaredFields()) {
|
||||||
|
allfields.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allfields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* is getter list is not empty then callers should be added
|
||||||
|
*/
|
||||||
|
boolean shouldAddGetterCallers(List<CtMethod> getters) {
|
||||||
|
return !getters.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDescription(CtField field) throws NotFoundException {
|
||||||
|
if (field.getType().isArray()) {
|
||||||
|
return "()[" + innerClassName(field.getType().getName()) + ";";
|
||||||
|
} else if (!field.getType().isPrimitive()) {
|
||||||
|
return "()" + innerClassName(field.getType().getName()) + ";";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return "()" + asPrimitive(field.getType().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String asPrimitive(String name) {
|
||||||
|
switch (name) {
|
||||||
|
case "int":
|
||||||
|
return "I";
|
||||||
|
case "byte":
|
||||||
|
return "B";
|
||||||
|
case "float":
|
||||||
|
return "F";
|
||||||
|
case "long":
|
||||||
|
return "J";
|
||||||
|
case "boolean":
|
||||||
|
return "Z";
|
||||||
|
case "char":
|
||||||
|
return "C";
|
||||||
|
case "double":
|
||||||
|
return "D";
|
||||||
|
case "short":
|
||||||
|
return "S";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String innerClassName(String name) {
|
||||||
|
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[\\]", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
|
||||||
|
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/main/resources/d3.js
vendored
Normal file
2
src/main/resources/d3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
18
src/main/resources/index.html
Normal file
18
src/main/resources/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!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>
|
||||||
|
d3.json("/report").then(function (data) {
|
||||||
|
// render the table(s)
|
||||||
|
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
106
src/main/resources/main.css
Executable file
106
src/main/resources/main.css
Executable file
|
|
@ -0,0 +1,106 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
28
src/main/resources/perfix.js
Normal file
28
src/main/resources/perfix.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
function tabulate(data, columns) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
44
src/main/resources/static.json
Normal file
44
src/main/resources/static.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
Loading…
Add table
Reference in a new issue