diff --git a/src/main/java/perfix/MethodInvocation.java b/src/main/java/perfix/MethodInvocation.java index 7b519af..92e4588 100644 --- a/src/main/java/perfix/MethodInvocation.java +++ b/src/main/java/perfix/MethodInvocation.java @@ -4,25 +4,19 @@ package perfix; * contains start and stop time for method/query/servlet */ public class MethodInvocation { - private final long t0; - private final String name; - long t1; + private final long timestamp; + long duration; MethodInvocation(String name) { - t0 = System.nanoTime(); - if (name != null) { - this.name = name; - } else { - this.name = ""; - } + timestamp = System.nanoTime(); + } - String getName() { - return name; + public long getDuration() { + return duration; } - long getDuration() { - return t1 - t0; + public void registerEndingTime(long t1) { + duration = t1 - timestamp; } - } diff --git a/src/main/java/perfix/MethodNode.java b/src/main/java/perfix/MethodNode.java index ff70e57..5be85f6 100644 --- a/src/main/java/perfix/MethodNode.java +++ b/src/main/java/perfix/MethodNode.java @@ -8,14 +8,14 @@ public class MethodNode { public final String name; public final List children; public MethodNode parent; - public Report report; + private MethodInvocation invocation; public MethodNode(String name) { this.name = name; this.children = new ArrayList<>(); } - public void addChild(MethodNode child){ + public void addChild(MethodNode child) { children.add(child); } @@ -23,10 +23,6 @@ public class MethodNode { return name; } - public Report getReport() { - return report; - } - public List getChildren() { return children; } @@ -51,5 +47,11 @@ public class MethodNode { return Objects.hash(name); } + public MethodInvocation getInvocation() { + return invocation; + } + public void setInvocation(MethodInvocation invocation) { + this.invocation = invocation; + } } diff --git a/src/main/java/perfix/Registry.java b/src/main/java/perfix/Registry.java index 21b7c41..b074ea0 100644 --- a/src/main/java/perfix/Registry.java +++ b/src/main/java/perfix/Registry.java @@ -5,11 +5,9 @@ import perfix.instrument.Util; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.atomic.LongAdder; public class Registry { - private static final Map> methods = new ConcurrentHashMap<>(); private static final List callstack = new ArrayList<>(); private static final ThreadLocal currentMethod = new ThreadLocal<>(); @@ -26,7 +24,7 @@ public class Registry { @SuppressWarnings("unused") public static MethodInvocation start(String name) { MethodInvocation methodInvocation = new MethodInvocation(name); - MethodNode newNode = new MethodNode(methodInvocation.getName()); + MethodNode newNode = new MethodNode(name); MethodNode parent = currentMethod.get(); if (parent != null) { @@ -52,44 +50,43 @@ public class Registry { @SuppressWarnings("unused") public static void stop(MethodInvocation methodInvocation) { if (methodInvocation != null) { - methodInvocation.t1 = System.nanoTime(); - methods.computeIfAbsent(methodInvocation.getName(), key -> new ArrayList<>()).add(methodInvocation); + methodInvocation.registerEndingTime(System.nanoTime()); } MethodNode methodNode = currentMethod.get(); - if (methodNode != null) { - currentMethod.set(methodNode.parent); - } + methodNode.setInvocation(methodInvocation); + + currentMethod.set(methodNode.parent); } public static SortedMap sortedMethodsByDuration() { + //walk the stack to group methods by their name + Map> methods = new ConcurrentHashMap<>(); + collectInvocationsPerMethodName(methods, callstack); + + //gather invocations by method name and calculate statistics SortedMap sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder()); methods.forEach((name, measurements) -> { - LongAdder totalDuration = new LongAdder(); - measurements.stream() + long totalDuration = measurements.stream() .filter(Objects::nonNull) - .forEach(m -> totalDuration.add(m.getDuration())); - sortedByTotal.put(totalDuration.longValue(), new Report(name, measurements.size(), totalDuration.longValue())); + .mapToLong(MethodInvocation::getDuration).sum(); + sortedByTotal.put(totalDuration, new Report(name, measurements.size(), totalDuration)); }); return sortedByTotal; } + private static void collectInvocationsPerMethodName(Map> invocations, List nodes) { + nodes.forEach(methodNode -> { + invocations.computeIfAbsent(methodNode.getName(), key -> new ArrayList<>()).add(methodNode.getInvocation()); + collectInvocationsPerMethodName(invocations, methodNode.children); + }); + + } + public static List getCallStack() { - addReport(callstack); return callstack; } - private static void addReport(List callstack) { - callstack.forEach(methodNode -> { - LongAdder totalDuration = new LongAdder(); - List methodInvocations = methods.get(methodNode.name); - methodInvocations.forEach(methodInvocation -> totalDuration.add(methodInvocation.getDuration())); - methodNode.report = new Report(methodNode.name, methodInvocations.size(), totalDuration.longValue()); - addReport(methodNode.children); - }); - } - public static void clear() { - methods.clear(); callstack.clear(); } } diff --git a/src/main/java/perfix/server/json/SynthSerializerFactory.java b/src/main/java/perfix/server/json/SynthSerializerFactory.java index 3a8c4ad..880acbf 100644 --- a/src/main/java/perfix/server/json/SynthSerializerFactory.java +++ b/src/main/java/perfix/server/json/SynthSerializerFactory.java @@ -102,7 +102,7 @@ public class SynthSerializerFactory implements SerializerFactory { /* * Creates the source, handling the for JSON different types of classes */ - private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException { + private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException { String source = "public String handle(Object object){\n"; if (beanClass.isArray()) { source += "\tObject[] array=(Object[])object;\n"; @@ -191,14 +191,10 @@ public class SynthSerializerFactory implements SerializerFactory { } private boolean isCollection(CtClass beanClass) throws NotFoundException { - List interfaces = new ArrayList(asList(beanClass.getInterfaces())); + List interfaces = new ArrayList<>(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; + boolean is = interfaces.stream().anyMatch(interfaze -> interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)); + return is; } private boolean isMap(CtClass beanClass) throws NotFoundException { @@ -212,7 +208,7 @@ public class SynthSerializerFactory implements SerializerFactory { */ 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 += ":"; source += jsonValue(classToSerialize, getter); return source; } diff --git a/src/testapp/java/testperfix/cmdline/App.java b/src/testapp/java/testperfix/cmdline/App.java index c604911..7c9900b 100644 --- a/src/testapp/java/testperfix/cmdline/App.java +++ b/src/testapp/java/testperfix/cmdline/App.java @@ -22,7 +22,7 @@ public class App { try { someJdbcStatementMethod(); someJdbcPreparedStatementMethod(); - someOtherMethod(); + someOtherMethod(0); TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); @@ -31,7 +31,7 @@ public class App { private static void someJdbcStatementMethod() { try { - BasicDataSource dataSource=new BasicDataSource(); + BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:mem:default"); dataSource.setUsername("sa"); @@ -61,11 +61,15 @@ public class App { } } - private static void someOtherMethod() { + private static void someOtherMethod(int level) { try { TimeUnit.NANOSECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } + + if (level < 1000) { + someOtherMethod(level + 1); + } } } diff --git a/ui/src/App.css b/ui/src/App.css index 47910a0..bc993bf 100644 --- a/ui/src/App.css +++ b/ui/src/App.css @@ -26,35 +26,15 @@ body { left:-20px; } - - .tree li input ~ ul{ display: none; height: 0; } -/* -.tree input { - background: url(toggle-small-expand.png) no-repeat; - height: 1em; - - - content: "\00a0\00a0\00a0"; - overflow: hidden; - width: 16px; - height: 16px; -} - -.tree input:checked{ - background: url(toggle-small.png) no-repeat; -}*/ .tree li input:checked ~ ul{ display: inline; } - -/* .tree li input:checked { display: block; margin: 0 0 0.125em; 2px} */ -/* .tree li input:checked li:last-child { margin: 0 0 0.063em; 1px } */ - + .tree ul {margin-left:1em} /* (indentation/2) */ .tree li:before { diff --git a/ui/src/Tree.js b/ui/src/Tree.js index b3dd3d5..c15cfa6 100644 --- a/ui/src/Tree.js +++ b/ui/src/Tree.js @@ -24,10 +24,9 @@ class Tree extends Component { return (
    {children.map( r => -
  • - {Math.floor(r.report.average / 1000) / 1000} ms   - - {r.report.invocations} inv.   - {r.report.name} +
  • + {Math.floor(r.invocation.duration / 1000) / 1000} ms   + {r.name} {this.renderChildren(r.children)}
  • )}