* removed React for the UI
* removed TinyHttpd for the backend service * fixed errors * reduced footprint * improved performance and responsiveness
This commit is contained in:
parent
b814c44a0c
commit
29e71c0f96
12 changed files with 520 additions and 82 deletions
12
README.md
12
README.md
|
|
@ -33,4 +33,14 @@ Pretty basic profiling tool for JVM's
|
|||
# DISCLAIMER:
|
||||
This has only been tested on oracle java8 in spring-boot using tomcat web-container (and apache dbcp)
|
||||
|
||||
That said I should mention that the callstack view is pretty slow, which is caused by the gui, not the backend. I guess I'll replace it with a static vanilla.js app.
|
||||
Javassist raises the following error:
|
||||
```
|
||||
WARNING: An illegal reflective access operation has occurred
|
||||
WARNING: Illegal reflective access by javassist.util.proxy.SecurityActions (file:/Users/Shautvast/.m2/repository/org/javassist/javassist/3.26.0-GA/javassist-3.26.0-GA.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
|
||||
WARNING: Please consider reporting this to the maintainers of javassist.util.proxy.SecurityActions
|
||||
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
|
||||
WARNING: All illegal access operations will be denied in a future release
|
||||
```
|
||||
But it works at least up until java 15.
|
||||
|
||||
I cannot fix this issue, but I'm working to replace javassist as a dependency.
|
||||
9
pom.xml
9
pom.xml
|
|
@ -19,24 +19,17 @@
|
|||
<artifactId>javassist</artifactId>
|
||||
<version>3.26.0-GA</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.nanohttpd</groupId>
|
||||
<artifactId>nanohttpd</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
|
|
|
|||
|
|
@ -1,76 +1,138 @@
|
|||
package perfix.server;
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import perfix.Registry;
|
||||
import perfix.server.json.Serializer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class HTTPServer extends NanoHTTPD {
|
||||
|
||||
public class HTTPServer implements HttpHandler {
|
||||
private static final Logger log = Logger.getLogger("perfix");
|
||||
private final int port;
|
||||
|
||||
public HTTPServer(int port) {
|
||||
super(port);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
||||
try {
|
||||
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
||||
System.out.println(" --- Perfix http server running. Point your browser to http://localhost:" + getListeningPort() + "/");
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("localhost", port), 0);
|
||||
server.createContext("/", this);
|
||||
server.setExecutor(Executors.newFixedThreadPool(2));
|
||||
server.start();
|
||||
System.out.println(" --- Perfix http server running. Point your browser to http://localhost:" + port + "/");
|
||||
} catch (IOException ioe) {
|
||||
System.err.println(" --- Couldn't start Perfix http server:\n" + ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response serve(IHTTPSession session) {
|
||||
String uri = session.getUri();
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
String uri = exchange.getRequestURI().toString();
|
||||
InputStream response = null;
|
||||
switch (uri) {
|
||||
case "/report":
|
||||
return perfixMetrics();
|
||||
setContentTypeJson(exchange);
|
||||
response = toStream(perfixMetrics());
|
||||
break;
|
||||
case "/callstack":
|
||||
return perfixCallstack();
|
||||
setContentTypeJson(exchange);
|
||||
response = toStream(perfixCallstack());
|
||||
break;
|
||||
case "/clear":
|
||||
return clear();
|
||||
setContentTypeJson(exchange);
|
||||
response = toStream(clear());
|
||||
break;
|
||||
default:
|
||||
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "NOT FOUND");
|
||||
response = staticContent(exchange, uri);
|
||||
}
|
||||
OutputStream outputStream = exchange.getResponseBody();
|
||||
exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
|
||||
exchange.getResponseHeaders().add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
int length = response.available();
|
||||
exchange.sendResponseHeaders(200, length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
outputStream.write(response.read());
|
||||
}
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void setContentTypeJson(HttpExchange exchange) {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
private InputStream staticContent(HttpExchange exchange, String uri) {
|
||||
if (uri.equals("/")) {
|
||||
uri = "/index.html";
|
||||
}
|
||||
InputStream resource = getClass().getResourceAsStream(uri);
|
||||
if (resource != null) {
|
||||
String mimeType;
|
||||
if (uri.endsWith("css")) {
|
||||
mimeType = "text/css";
|
||||
} else if (uri.endsWith("js")) {
|
||||
mimeType = "application/ecmascript";
|
||||
} else {
|
||||
mimeType = "text/html";
|
||||
}
|
||||
exchange.getResponseHeaders().add("Content-Type", mimeType);
|
||||
return resource;
|
||||
|
||||
} else {
|
||||
return toStream(notFound());
|
||||
}
|
||||
}
|
||||
|
||||
private Response perfixMetrics() {
|
||||
private String notFound() {
|
||||
return "NOT FOUND";
|
||||
}
|
||||
|
||||
|
||||
private String perfixMetrics() {
|
||||
try {
|
||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values()))));
|
||||
return Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return newFixedLengthResponse(e.toString());
|
||||
log.severe(e.toString());
|
||||
return 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 InputStream toStream(String text) {
|
||||
return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private Response perfixCallstack() {
|
||||
private String perfixCallstack() {
|
||||
try {
|
||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
|
||||
return Serializer.toJSONString(Registry.getCallStack());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return newFixedLengthResponse(e.toString());
|
||||
log.severe(e.toString());
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private Response clear() {
|
||||
private String clear() {
|
||||
Registry.clear();
|
||||
try {
|
||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
|
||||
return Serializer.toJSONString(Registry.getCallStack());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return newFixedLengthResponse(e.toString());
|
||||
log.severe(e.toString());
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
package perfix.server.json;
|
||||
|
||||
public interface SerializerFactory {
|
||||
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass);
|
||||
<T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package perfix.server.json;
|
|||
import javassist.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
public class SynthSerializerFactory implements SerializerFactory {
|
||||
private static final String STRING = "java.lang.String";
|
||||
|
|
@ -17,15 +20,15 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
private static final String SHORT = "java.lang.Short";
|
||||
private static final String INTEGER = "java.lang.Integer";
|
||||
|
||||
private final static Set<String> wrappersAndString = new HashSet<String>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
|
||||
STRING));
|
||||
private final static Set<String> wrappersAndString = unmodifiableSet(new HashSet<String>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
|
||||
STRING)));
|
||||
|
||||
private static final String COLLECTION = "java.util.Collection";
|
||||
private static final String LIST = "java.util.List";
|
||||
private static final String SET = "java.util.Set";
|
||||
private static final List<String> mapInterfaces = asList("java.util.Map", "java.util.concurrent.ConcurrentHashMap");
|
||||
private static final List<String> mapInterfaces = Collections.unmodifiableList(asList("java.util.Map", "java.util.concurrent.ConcurrentHashMap"));
|
||||
|
||||
private static final Map<String, JSONSerializer<?>> serializers = new HashMap<>();
|
||||
private static final ConcurrentMap<String, JSONSerializer<?>> serializers = new ConcurrentHashMap<>();
|
||||
private static final String ROOT_PACKAGE = "serializer.";
|
||||
|
||||
private final ClassPool pool = ClassPool.getDefault();
|
||||
|
|
@ -47,43 +50,31 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
}
|
||||
}
|
||||
|
||||
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
||||
try {
|
||||
CtClass beanClass = pool.get(beanjavaClass.getName());
|
||||
|
||||
return createSerializer(beanClass);
|
||||
} 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));
|
||||
}
|
||||
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
||||
String serializerName = createSerializerName(beanjavaClass);
|
||||
return (JSONSerializer<T>) serializers.computeIfAbsent(serializerName, key -> {
|
||||
try {
|
||||
return tryCreateSerializer(beanClass);
|
||||
} catch (NotFoundException | CannotCompileException | ReflectiveOperationException e) {
|
||||
throw new SerializerCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> JSONSerializer<T> tryCreateSerializer(CtClass beanClass) throws NotFoundException, CannotCompileException, ReflectiveOperationException {
|
||||
CtClass serializerClass = pool.makeClass(createSerializerName(beanClass), serializerBase);
|
||||
CtClass beanClass = pool.get(beanjavaClass.getName());
|
||||
CtClass serializerClass = pool.makeClass(serializerName, serializerBase);
|
||||
|
||||
addToJsonStringMethod(beanClass, serializerClass);
|
||||
|
||||
JSONSerializer<T> jsonSerializer = createSerializerInstance(serializerClass);
|
||||
return createSerializerInstance(serializerClass);
|
||||
|
||||
serializers.put(createSerializerName(beanClass), jsonSerializer);
|
||||
return jsonSerializer;
|
||||
} catch (NotFoundException | CannotCompileException | ReflectiveOperationException e) {
|
||||
e.printStackTrace();
|
||||
throw new SerializerCreationException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
String body = createToJSONStringMethodSource(beanClass);
|
||||
serializerClass.addMethod(CtNewMethod.make(body, serializerClass));
|
||||
}
|
||||
|
|
@ -146,7 +137,8 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
/*
|
||||
* 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 {
|
||||
private String addGetterCallers(CtClass beanClass, String source, List<CtMethod> getters) throws
|
||||
NotFoundException {
|
||||
int index = 0;
|
||||
source += "\treturn ";
|
||||
source += "\"{";
|
||||
|
|
@ -161,8 +153,9 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> JSONSerializer<T> createSerializerInstance(CtClass serializerClass) throws CannotCompileException, ReflectiveOperationException {
|
||||
return (JSONSerializer<T>) serializerClass.toClass().getConstructor().newInstance();
|
||||
private <T> JSONSerializer<T> createSerializerInstance(CtClass serializerClass) throws
|
||||
CannotCompileException, ReflectiveOperationException {
|
||||
return (JSONSerializer<T>) pool.toClass(serializerClass).getConstructor().newInstance();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -170,25 +163,30 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
*
|
||||
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
|
||||
*/
|
||||
public String createSerializerName(CtClass beanClass) {
|
||||
public String createSerializerName(Class<?> beanClass) {
|
||||
return createSerializerName(beanClass.getName());
|
||||
}
|
||||
|
||||
public String createSerializerName(String name) {
|
||||
return ROOT_PACKAGE + name.replaceAll("\\[\\]", "Array") + "Serializer";
|
||||
return ROOT_PACKAGE + name.replaceAll("\\[]", "Array") + "Serializer";
|
||||
}
|
||||
|
||||
private boolean isCollection(CtClass beanClass) throws NotFoundException {
|
||||
List<CtClass> interfaces = new ArrayList<>(asList(beanClass.getInterfaces()));
|
||||
interfaces.add(beanClass);
|
||||
boolean is = interfaces.stream().anyMatch(interfaze -> interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET));
|
||||
return is;
|
||||
return interfaces.stream()
|
||||
.map(CtClass::getName)
|
||||
.anyMatch(interfaze -> interfaze.equals(COLLECTION) || interfaze.equals(LIST) || interfaze.equals(SET));
|
||||
}
|
||||
|
||||
private boolean isMap(CtClass beanClass) throws NotFoundException {
|
||||
List<CtClass> interfaces = new ArrayList<>(asList(beanClass.getInterfaces()));
|
||||
interfaces.add(beanClass);
|
||||
return interfaces.stream().anyMatch(i -> mapInterfaces.contains(i.getName()));
|
||||
if (mapInterfaces.contains(beanClass.getName())) {
|
||||
return true;
|
||||
} else {
|
||||
return Arrays.stream(beanClass.getInterfaces())
|
||||
.map(CtClass::getName)
|
||||
.anyMatch(mapInterfaces::contains);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -218,7 +216,8 @@ public class SynthSerializerFactory implements SerializerFactory {
|
|||
return source;
|
||||
}
|
||||
|
||||
private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter, String source, CtClass returnType) {
|
||||
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(";
|
||||
|
|
|
|||
87
src/main/resources/css/app.css
Normal file
87
src/main/resources/css/app.css
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
body {
|
||||
background:white;
|
||||
font: normal 12px/150% Arial, Helvetica, sans-serif;
|
||||
padding:2px;
|
||||
}
|
||||
|
||||
#callstack-view{
|
||||
padding: 10px;
|
||||
border: 1px solid #006699;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#datagrid table {
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#datagrid {
|
||||
padding: 10px;
|
||||
font: normal 12px Arial, Helvetica, sans-serif;
|
||||
background: #fff;
|
||||
border: 1px solid #006699;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#datagrid table thead th:first-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#datagrid table tbody tr {
|
||||
color: #00496B;
|
||||
border-left: 1px solid #E1EEF4;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#datagrid table tbody .alt td {
|
||||
background: #E1EEF4;
|
||||
}
|
||||
|
||||
#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 tbody tr: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;
|
||||
}
|
||||
86
src/main/resources/css/tree.css
Normal file
86
src/main/resources/css/tree.css
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
.tree, .tree ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style:none
|
||||
}
|
||||
.tree ul {
|
||||
margin-left:1em;
|
||||
position:relative
|
||||
}
|
||||
.tree ul ul {
|
||||
margin-left:.5em
|
||||
}
|
||||
.tree ul:before {
|
||||
content:"";
|
||||
display:block;
|
||||
width:0;
|
||||
position:absolute;
|
||||
top:0;
|
||||
bottom:0;
|
||||
left:0;
|
||||
border-left:1px solid
|
||||
}
|
||||
.tree li {
|
||||
margin:0;
|
||||
padding:0 1em;
|
||||
line-height:2em;
|
||||
color:#369;
|
||||
position:relative
|
||||
}
|
||||
.tree ul li:before {
|
||||
content:"";
|
||||
display:block;
|
||||
width:10px;
|
||||
height:0;
|
||||
border-top:1px solid;
|
||||
margin-top:-1px;
|
||||
position:absolute;
|
||||
top:1em;
|
||||
left:0
|
||||
}
|
||||
.tree ul li:last-child:before {
|
||||
background:#fff;
|
||||
height:auto;
|
||||
top:1em;
|
||||
bottom:0
|
||||
}
|
||||
.indicator {
|
||||
margin-right:5px;
|
||||
}
|
||||
.tree li a {
|
||||
text-decoration: none;
|
||||
color:#369;
|
||||
}
|
||||
.tree li button, .tree li button:active, .tree li button:focus {
|
||||
text-decoration: none;
|
||||
color:#369;
|
||||
border:none;
|
||||
background:transparent;
|
||||
margin:0;
|
||||
padding:0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.glyphicon {
|
||||
position: relative;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.glyphicon-plus-sign::before {
|
||||
content: "+";
|
||||
}
|
||||
|
||||
.glyphicon-min-sign::before {
|
||||
content: "-";
|
||||
/*transform: translate(0,-5px);*/
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible{
|
||||
display: block;
|
||||
}
|
||||
25
src/main/resources/index.html
Normal file
25
src/main/resources/index.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Perfix</title>
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<link rel="stylesheet" href="/css/tree.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="table-view">
|
||||
<h1>List of invocations</h1>
|
||||
<label for="name-filter">filter name </label><input id="name-filter" type="text" onkeyup="update_filter()">
|
||||
<div id="datagrid"></div>
|
||||
</div>
|
||||
<div id="callstack-view">
|
||||
<h1>Callstack</h1>
|
||||
<div id="datatree" class="tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/ecmascript" src="/js/axios.min.js"></script>
|
||||
<script type="application/ecmascript" src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
172
src/main/resources/js/app.js
Normal file
172
src/main/resources/js/app.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
let name_filter = "";
|
||||
let datatable, callstack_data;
|
||||
const datagrid = document.getElementById("datagrid");
|
||||
const datatree = document.getElementById("datatree");
|
||||
|
||||
function refresh_data_grid() {
|
||||
if (datagrid.firstChild) {
|
||||
datagrid.removeChild(datagrid.firstChild);
|
||||
}
|
||||
|
||||
const table = append_table(datagrid);
|
||||
let row, name;
|
||||
table.thead().tr()
|
||||
.th("name")
|
||||
.th("invocations")
|
||||
.th("total duration")
|
||||
.th("average");
|
||||
const tbody = table.tbody();
|
||||
for (let i = 0; i < datatable.data.length; i++) {
|
||||
row = datatable.data[i];
|
||||
name = row.name;
|
||||
if (name_filter.length === 0 || name.includes(name_filter)) {
|
||||
tbody.tr()
|
||||
.td(name)
|
||||
.td(row["invocations"])
|
||||
.td(row["totalDuration"] / 1000000 + " ms")
|
||||
.td(row["average"] / 1000000 + " ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update_filter() {
|
||||
name_filter = document.getElementById("name-filter").value;
|
||||
if (datatable) {
|
||||
refresh_data_grid();
|
||||
}
|
||||
}
|
||||
|
||||
function append_table(parent) {
|
||||
const table = document.createElement("table");
|
||||
parent.appendChild(table);
|
||||
|
||||
return {
|
||||
thead: function () {
|
||||
let thead = document.createElement("thead");
|
||||
table.appendChild(thead);
|
||||
return {
|
||||
tr: function () {
|
||||
let new_tr = document.createElement("tr");
|
||||
thead.appendChild(new_tr);
|
||||
|
||||
let th = function (text) {
|
||||
let new_td = document.createElement("th");
|
||||
new_td.innerText = text;
|
||||
new_tr.appendChild(new_td);
|
||||
return {
|
||||
th
|
||||
}
|
||||
}
|
||||
return {
|
||||
th
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tbody: function () {
|
||||
let tbody = document.createElement("tbody");
|
||||
table.appendChild(tbody);
|
||||
return {
|
||||
tr: function () {
|
||||
let new_tr = document.createElement("tr");
|
||||
tbody.appendChild(new_tr);
|
||||
|
||||
let td = function (text) {
|
||||
let new_td = document.createElement("td");
|
||||
new_td.innerText = text;
|
||||
new_tr.appendChild(new_td);
|
||||
return {
|
||||
td
|
||||
}
|
||||
}
|
||||
return {
|
||||
td
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tr: function () {
|
||||
let new_tr = document.createElement("tr");
|
||||
table.appendChild(new_tr);
|
||||
|
||||
let td = function (text) {
|
||||
let new_td = document.createElement("td");
|
||||
new_td.innerText = text;
|
||||
new_tr.appendChild(new_td);
|
||||
return {
|
||||
td
|
||||
}
|
||||
}
|
||||
return {
|
||||
td
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (function tabular_view() {
|
||||
|
||||
// }());
|
||||
|
||||
function appendChildren(parent, nodes) {
|
||||
let node;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
node = nodes[i];
|
||||
let li = document.createElement("li");
|
||||
|
||||
let branch = document.createElement("i");
|
||||
if (node.children.length > 0) {
|
||||
branch.setAttribute("class", "indicator glyphicon glyphicon-plus-sign");
|
||||
branch.onclick = function (event) {
|
||||
let icon = event.currentTarget.parentElement.firstChild;
|
||||
let ul_to_toggle = icon.nextSibling.nextSibling;
|
||||
|
||||
if (ul_to_toggle.getAttribute("class") === "visible") {
|
||||
icon.setAttribute("class", "indicator glyphicon glyphicon-plus-sign");
|
||||
ul_to_toggle.setAttribute("class", "hidden");
|
||||
} else {
|
||||
icon.setAttribute("class", "indicator glyphicon glyphicon-min-sign");
|
||||
ul_to_toggle.setAttribute("class", "visible");
|
||||
}
|
||||
};
|
||||
}
|
||||
let label = document.createElement("a");
|
||||
label.innerText = Math.floor(node["invocation"].duration / 1000) / 1000 + " ms " + node.name;
|
||||
li.appendChild(branch);
|
||||
li.appendChild(label);
|
||||
|
||||
let ul = document.createElement("ul");
|
||||
ul.setAttribute("class", "hidden");
|
||||
li.appendChild(ul);
|
||||
|
||||
appendChildren(ul, node.children);
|
||||
|
||||
parent.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
function refresh_data_tree() {
|
||||
let datatree = document.getElementById("datatree");
|
||||
let new_div = document.createElement("div");
|
||||
new_div.setAttribute("class", "callstack-tree");
|
||||
datatree.appendChild(new_div);
|
||||
appendChildren(new_div, callstack_data);
|
||||
}
|
||||
|
||||
(function main() {
|
||||
update_filter();
|
||||
|
||||
axios.get('http://localhost:2048/report')
|
||||
.then(response => {
|
||||
datatable = response;
|
||||
refresh_data_grid();
|
||||
});
|
||||
|
||||
axios.get('http://localhost:2048/callstack')
|
||||
.then(response => {
|
||||
callstack_data = response.data;
|
||||
refresh_data_tree();
|
||||
});
|
||||
}());
|
||||
|
||||
3
src/main/resources/js/axios.min.js
vendored
Normal file
3
src/main/resources/js/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/js/axios.min.map
Normal file
1
src/main/resources/js/axios.min.map
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -68,7 +68,7 @@ public class App {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (level < 1000) {
|
||||
if (level < 10) {
|
||||
someOtherMethod(level + 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue