Mercurial > hg > release > thermostat-1.4
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
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("&", "&"); + in = in.replaceAll("<", "<"); + in = in.replaceAll(">", ">"); + in = in.replaceAll("\"", """); + in = in.replaceAll("'", "'"); + in = in.replaceAll("/", "/"); + 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("&", "&"); - in = in.replaceAll("<", "<"); - in = in.replaceAll(">", ">"); - in = in.replaceAll("\"", """); - in = in.replaceAll("'", "'"); - in = in.replaceAll("/", "/"); - 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); + } }); } + }