changeset 1737:92fa2e05b10e

Color code method names in the profiler Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-August/015349.html
author Omair Majid <omajid@redhat.com>
date Wed, 19 Aug 2015 10:47:55 -0400
parents 8b6f962afffc
children bafe5e5ab55b
files common/core/src/main/java/com/redhat/thermostat/common/utils/MethodDescriptorConverter.java common/core/src/main/java/com/redhat/thermostat/common/utils/StringUtils.java common/core/src/test/java/com/redhat/thermostat/common/utils/MethodDescriptorConverterTest.java common/core/src/test/java/com/redhat/thermostat/common/utils/StringUtilsTest.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingVmDeadLockView.java vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatter.java vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatterTest.java vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommandTest.java vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResult.java vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParser.java vm-profiler/client-core/src/test/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParserTest.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java
diffstat 12 files changed, 171 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/MethodDescriptorConverter.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/MethodDescriptorConverter.java	Wed Aug 19 10:47:55 2015 -0400
@@ -53,6 +53,37 @@
 
     private static final Map<Character, String> lookupTable = new HashMap<>();
 
+    public static class MethodDeclaration {
+
+        private final String name;
+        private final List<String> parameters;
+        private final String returnType;
+
+        public MethodDeclaration(String name, List<String> parameters, String returnType) {
+            this.name = name;
+            this.parameters = parameters;
+            this.returnType = returnType;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public List<String> getParameters() {
+            return parameters;
+        }
+
+        public String getReturnType() {
+            return returnType;
+        }
+
+        @Override
+        public String toString() {
+            String parameters = StringUtils.join(", ", this.parameters);
+            return returnType + " " + name + "(" + parameters + ")";
+        }
+    }
+
     static {
         lookupTable.put('Z', "boolean");
         lookupTable.put('B', "byte");
@@ -70,6 +101,10 @@
     }
 
     public static String toJavaType(String methodName, String descriptor) {
+        return toJavaDeclaration(methodName, descriptor).toString();
+    }
+
+    public static MethodDeclaration toJavaDeclaration(String methodName, String descriptor) {
         final int NOT_FOUND = -1;
 
         int start = descriptor.indexOf('(');
@@ -79,13 +114,12 @@
         }
 
         String parameterPart = descriptor.substring(start+1, end);
-        List<String> decodedParameters = convertParameters(parameterPart);
-        String parameters = StringUtils.join(", ", decodedParameters);
+        List<String> parameters = convertParameters(parameterPart);
 
         String returnPart = descriptor.substring(end+1);
         String returnType = DescriptorConverter.toJavaType(returnPart, lookupTable);
 
-        return returnType + " " + methodName.replace('/', '.') + "(" + parameters + ")";
+        return new MethodDeclaration(methodName.replace('/', '.'), parameters, returnType);
     }
 
     private static List<String> convertParameters(String parameterPart) {
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/StringUtils.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/StringUtils.java	Wed Aug 19 10:47:55 2015 -0400
@@ -77,5 +77,18 @@
 
         return result.toString();
     }
+
+    /** Make a string usable as html by escaping everything dangerous inside it */
+    public static String htmlEscape(String in) {
+        // https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
+        in = in.replaceAll("&", "&amp;");
+        in = in.replaceAll("<", "&lt;");
+        in = in.replaceAll(">", "&gt;");
+        in = in.replaceAll("\"", "&quot;");
+        in = in.replaceAll("'", "&#x27;");
+        in = in.replaceAll("/", "&#x2F;");
+        return in;
+    }
+
 }
 
--- a/common/core/src/test/java/com/redhat/thermostat/common/utils/MethodDescriptorConverterTest.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/common/core/src/test/java/com/redhat/thermostat/common/utils/MethodDescriptorConverterTest.java	Wed Aug 19 10:47:55 2015 -0400
@@ -78,4 +78,19 @@
         assertEquals(expected, result);
     }
 
+    @Test (expected=IllegalArgumentException.class)
+    public void verifyDescriptorsWithoutParenthesisThrowsExceptions() {
+        MethodDescriptorConverter.toJavaType("foo");
+    }
+
+    @Test (expected=IllegalArgumentException.class)
+    public void verifyDescriptorWithBadClassCausesException() {
+        MethodDescriptorConverter.toJavaType("foo(Ljava/lang/Object)"); // no ';' matching 'L'
+    }
+
+    @Test (expected=IllegalArgumentException.class)
+    public void verifyUnrecognizedDescriptorCausesException() {
+        MethodDescriptorConverter.toJavaType("foo(X)");
+    }
+
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/utils/StringUtilsTest.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/common/core/src/test/java/com/redhat/thermostat/common/utils/StringUtilsTest.java	Wed Aug 19 10:47:55 2015 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.utils;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -62,4 +63,18 @@
             return Arrays.asList(items);
         }
     }
+
+    @Test
+    public void testHtmlEscape() {
+        String plainText = "hello";
+        assertEquals(plainText, StringUtils.htmlEscape(plainText));
+
+        String funnyCharacters = "<a h=\"f\">b</a>";
+        String escapedFunnyCharacters = StringUtils.htmlEscape(funnyCharacters);
+        assertFalse(escapedFunnyCharacters.contains("<"));
+        assertFalse(escapedFunnyCharacters.contains(">"));
+        assertFalse(escapedFunnyCharacters.contains("\""));
+        assertFalse(escapedFunnyCharacters.contains("/"));
+    }
+
 }
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingVmDeadLockView.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingVmDeadLockView.java	Wed Aug 19 10:47:55 2015 -0400
@@ -67,6 +67,7 @@
 import com.redhat.thermostat.client.swing.components.ThermostatScrollBar;
 import com.redhat.thermostat.client.swing.components.ThermostatScrollPane;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
+import com.redhat.thermostat.common.utils.StringUtils;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.thread.client.common.DeadlockParser;
 import com.redhat.thermostat.thread.client.common.DeadlockParser.Information;
@@ -251,7 +252,7 @@
     private static String getThreadTooltip(DeadlockParser.Thread thread) {
         return translate.localize(
                 LocaleResources.DEADLOCK_THREAD_TOOLTIP,
-                htmlEscape(thread.waitingOn.name),
+                StringUtils.htmlEscape(thread.waitingOn.name),
                 stackTraceToHtmlString(thread.stackTrace))
             .getContents();
     }
@@ -259,22 +260,11 @@
     private static String stackTraceToHtmlString(List<String> items) {
         StringBuilder result = new StringBuilder();
         for (String item : items) {
-            result.append(htmlEscape(item)).append("<br/>");
+            result.append(StringUtils.htmlEscape(item)).append("<br/>");
         }
         return result.toString();
     }
 
-    private static String htmlEscape(String in) {
-        // https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
-        in = in.replaceAll("&", "&amp;");
-        in = in.replaceAll("<", "&lt;");
-        in = in.replaceAll(">", "&gt;");
-        in = in.replaceAll("\"", "&quot;");
-        in = in.replaceAll("'", "&#x27;");
-        in = in.replaceAll("/", "&#x2F;");
-        return in;
-    }
-
     private static String getEdgeTooltip(DeadlockParser.Thread thread, Map<String, String> idToLabel) {
         return translate.localize(
                 LocaleResources.DEADLOCK_EDGE_TOOLTIP,
--- a/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatter.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatter.java	Wed Aug 19 10:47:55 2015 -0400
@@ -61,7 +61,7 @@
     public void addMethodInfo(MethodInfo methodInfo) {
         printLine(String.format("%4f", methodInfo.percentageTime),
                 String.valueOf(methodInfo.totalTimeInMillis),
-                methodInfo.name);
+                methodInfo.decl.toString());
     }
 
     public void format(OutputStream output) {
--- a/vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatterTest.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileResultFormatterTest.java	Wed Aug 19 10:47:55 2015 -0400
@@ -40,10 +40,12 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
+import java.util.Arrays;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.common.utils.MethodDescriptorConverter.MethodDeclaration;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResult.MethodInfo;
 
@@ -83,7 +85,8 @@
         long time = 1;
         double percentage = 100;
 
-        MethodInfo info = new MethodInfo(methodName, time, percentage);
+        MethodDeclaration decl = new MethodDeclaration("Class.method", Arrays.<String>asList(), "void");
+        MethodInfo info = new MethodInfo(decl, time, percentage);
 
         formatter.addMethodInfo(info);
         formatter.format(out);
--- a/vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommandTest.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-cli/src/test/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommandTest.java	Wed Aug 19 10:47:55 2015 -0400
@@ -164,7 +164,7 @@
         AgentInformation agentInfo = mock(AgentInformation.class);
         when(agentsDao.getAgentInformation(AGENT)).thenReturn(agentInfo);
 
-        String data = "1000000 foo\n3000000 bar";
+        String data = "1000000 foo()V\n3000000 bar(I)I";
         ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
         when(profileDao.loadLatestProfileData(VM)).thenReturn(in);
 
@@ -177,7 +177,7 @@
         cmd.run(ctx);
 
         assertEquals("% Time    Time (ms) Method Name\n" +
-                     "75.000000 3         bar\n" +
-                     "25.000000 1         foo\n", cmdCtxFactory.getOutput());
+                     "75.000000 3         int bar(int)\n" +
+                     "25.000000 1         void foo()\n", cmdCtxFactory.getOutput());
     }
 }
--- a/vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResult.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResult.java	Wed Aug 19 10:47:55 2015 -0400
@@ -39,16 +39,18 @@
 import java.util.Collections;
 import java.util.List;
 
+import com.redhat.thermostat.common.utils.MethodDescriptorConverter.MethodDeclaration;
+
 public class ProfilingResult {
 
     public static class MethodInfo {
 
-        public final String name;
+        public final MethodDeclaration decl;
         public final long totalTimeInMillis;
         public final double percentageTime;
 
-        public MethodInfo(String name, long totalTime, double percentageTime) {
-            this.name = name;
+        public MethodInfo(MethodDeclaration decl, long totalTime, double percentageTime) {
+            this.decl = decl;
             this.totalTimeInMillis = totalTime;
             this.percentageTime = percentageTime;
         }
--- a/vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParser.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParser.java	Wed Aug 19 10:47:55 2015 -0400
@@ -45,7 +45,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.TimeUnit;
@@ -54,8 +53,23 @@
 
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.MethodDescriptorConverter;
+import com.redhat.thermostat.common.utils.MethodDescriptorConverter.MethodDeclaration;
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResult.MethodInfo;
 
+/**
+ * Results are expected to be in this format, one result per line:
+ *
+ * <pre>
+ * [methodTime] [method]
+ * </pre>
+ *
+ * Where {@code methodTime} is the total time, in nanoseconds, that the method
+ * took. {@code method} is the method name followed by the method
+ * descriptor, as defined by the <a href=
+ * "http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3">
+ * Java Language</a>. An example is {@code foo(I)I} to indicate
+ * {@code int foo(int)}.
+ */
 public class ProfilingResultParser {
 
     private static final Logger logger = LoggingUtils.getLogger(ProfilingResultParser.class);
@@ -92,8 +106,8 @@
 
         for (Entry<String, Long> entry : results.entrySet()) {
             String methodNameAndDescriptor = entry.getKey();
-            String methodName = prettify(methodNameAndDescriptor);
-            MethodInfo method = new MethodInfo(methodName, entry.getValue(), (entry.getValue() * 1.0 / totalTime) * 100);
+            MethodDeclaration declaration = breakDownMethod(methodNameAndDescriptor);
+            MethodInfo method = new MethodInfo(declaration, entry.getValue(), (entry.getValue() * 1.0 / totalTime) * 100);
             info.add(method);
         }
 
@@ -107,14 +121,15 @@
         return new ProfilingResult(info);
     }
 
-    private String prettify(String name) {
+    private MethodDeclaration breakDownMethod(String name) {
         int startDescriptor = name.indexOf('(');
         if (startDescriptor == -1) {
             // handle malformed method descriptor by returning it as it is
-            return name;
+            return new MethodDeclaration(name, new ArrayList<String>(), "");
         }
         String methodClassName = name.substring(0, startDescriptor);
-        return MethodDescriptorConverter.toJavaType(methodClassName, name.substring(startDescriptor));
+
+        return MethodDescriptorConverter.toJavaDeclaration(methodClassName, name.substring(startDescriptor));
     }
 
 }
--- a/vm-profiler/client-core/src/test/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParserTest.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-core/src/test/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParserTest.java	Wed Aug 19 10:47:55 2015 -0400
@@ -50,7 +50,7 @@
 
     @Test
     public void parsesCorrectly() throws Exception {
-        String data = "1000000 foo\n2000000 bar";
+        String data = "1000000 foo()V\n2000000 bar()I";
         ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
 
         ProfilingResult result = new ProfilingResultParser().parse(in);
@@ -58,11 +58,11 @@
         List<MethodInfo> methods = result.getMethodInfo();
 
         MethodInfo method0 = methods.get(0);
-        assertEquals("foo", method0.name);
+        assertEquals("void foo()", method0.decl.toString());
         assertEquals(1, method0.totalTimeInMillis);
 
         MethodInfo method1 = methods.get(1);
-        assertEquals("bar", method1.name);
+        assertEquals("int bar()", method1.decl.toString());
         assertEquals(2, method1.totalTimeInMillis);
     }
 }
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java	Mon Mar 30 09:55:30 2015 -0400
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java	Wed Aug 19 10:47:55 2015 -0400
@@ -37,10 +37,12 @@
 package com.redhat.thermostat.vm.profiler.client.swing.internal;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
 import java.awt.Component;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Vector;
@@ -69,8 +71,11 @@
 import com.redhat.thermostat.client.swing.components.ThermostatScrollPane;
 import com.redhat.thermostat.client.swing.components.ThermostatTable;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
+import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.utils.StringUtils;
+import com.redhat.thermostat.common.utils.MethodDescriptorConverter.MethodDeclaration;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResult;
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResult.MethodInfo;
@@ -337,7 +342,7 @@
 
         for (MethodInfo methodInfo: results.getMethodInfo()) {
             Object[] data = new Object[] {
-                    methodInfo.name,
+                    syntaxHighlightMethod(methodInfo.decl),
                     methodInfo.percentageTime,
                     methodInfo.totalTimeInMillis,
             };
@@ -345,6 +350,44 @@
         }
     }
 
+    private String syntaxHighlightMethod(MethodDeclaration decl) {
+        final Color METHOD_COLOR = Palette.PALE_RED.getColor();
+        final Color PARAMETER_COLOR = Palette.AZUREUS.getColor();
+        final Color RETURN_TYPE_COLOR = Palette.SKY_BLUE.getColor();
+
+        String highlightedName = htmlColorText(decl.getName(), METHOD_COLOR);
+        String highlightedReturnType = htmlColorText(decl.getReturnType(), RETURN_TYPE_COLOR);
+
+        StringBuilder toReturn = new StringBuilder();
+        toReturn.append("<html>");
+        toReturn.append("<pre>");
+
+        toReturn.append(highlightedReturnType);
+        toReturn.append(" ");
+        toReturn.append("<b>");
+        toReturn.append(highlightedName);
+        toReturn.append("</b>");
+        toReturn.append("(");
+
+        ArrayList<String> parameters = new ArrayList<>();
+        for (String parameter : decl.getParameters()) {
+            parameters.add(htmlColorText(parameter, PARAMETER_COLOR));
+        }
+
+        toReturn.append(StringUtils.join(",", parameters));
+
+        toReturn.append(")");
+        toReturn.append("</pre>");
+        toReturn.append("<html>");
+        return toReturn.toString();
+
+    }
+
+    private String htmlColorText(String unescapedText, Color color) {
+        return "<font color='" + ("#" + Integer.toHexString(color.getRGB() & 0x00ffffff)) + "'>"
+                + StringUtils.htmlEscape(unescapedText) + "</font>";
+    }
+
     @Override
     public Component getUiComponent() {
         return mainContainer;
@@ -362,14 +405,16 @@
                 window.setVisible(true);
 
                 List<MethodInfo> data = new ArrayList<>();
-                data.add(new MethodInfo("foo", 1000, 1.0));
-                data.add(new MethodInfo("foo2", 10001, 100001));
-                data.add(new MethodInfo("bar", 200, 3.5));
-                data.add(new MethodInfo("baz", 100000, 9.8));
-                data.add(new MethodInfo("spam", 5000, 0.99999));
+                data.add(new MethodInfo(new MethodDeclaration("foo", list("int"), "int"), 1000, 1.0));
+                data.add(new MethodInfo(new MethodDeclaration("bar", list("foo.bar.Baz", "int"), "Bar"), 100000, 100));
                 ProfilingResult results = new ProfilingResult(data);
                 view.setProfilingDetailData(results);
             }
+
+            private List<String> list(String... args) {
+                return Arrays.asList(args);
+            }
         });
     }
+
 }