Mercurial > hg > release > thermostat-1.6
view vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java @ 2001:1f5dc3abcf16
Sort profiler results table by execution time
Backport of 4e6f50297303 from HEAD. PR3046
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019927.html
author | Alex Macdonald <almacdon@redhat.com> |
---|---|
date | Wed, 29 Jun 2016 15:01:50 -0400 |
parents | 59385cdc7d0d |
children | 8658e633a5a2 |
line wrap: on
line source
/* * Copyright 2012-2016 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ 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.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Vector; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.ListSelectionModel; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.WindowConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; import com.redhat.thermostat.client.swing.EdtHelper; import com.redhat.thermostat.client.swing.IconResource; import com.redhat.thermostat.client.swing.NonEditableTableModel; import com.redhat.thermostat.client.swing.SwingComponent; import com.redhat.thermostat.client.swing.components.ActionToggleButton; import com.redhat.thermostat.client.swing.components.FontAwesomeIcon; import com.redhat.thermostat.client.swing.components.HeaderPanel; import com.redhat.thermostat.client.swing.components.Icon; import com.redhat.thermostat.client.swing.components.ThermostatScrollPane; import com.redhat.thermostat.client.swing.components.ThermostatTable; import com.redhat.thermostat.client.swing.components.ThermostatTableRenderer; 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.MethodDescriptorConverter.MethodDeclaration; import com.redhat.thermostat.common.utils.StringUtils; import com.redhat.thermostat.shared.locale.LocalizedString; 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; public class SwingVmProfileView extends VmProfileView implements SwingComponent { private static final Icon START_ICON = IconResource.SAMPLE.getIcon(); private static final Icon STOP_ICON = new FontAwesomeIcon('\uf28e', START_ICON.getIconHeight()); private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer(); private static final double SPLIT_PANE_RATIO = 0.3; private static final int COLUMN_METHOD_NAME = 0; private static final int COLUMN_METHOD_PERCENTAGE = 1; private static final int COLUMN_METHOD_TIME = 2; private final CopyOnWriteArrayList<ActionListener<ProfileAction>> listeners = new CopyOnWriteArrayList<>(); private HeaderPanel mainContainer; private JTabbedPane tabPane; private ActionToggleButton toggleButton; private DefaultListModel<Profile> listModel; private JList<Profile> profileList; private ThermostatTable profileTable; private DefaultTableModel tableModel; private JLabel currentStatusLabel; private boolean viewControlsEnabled = true; static class ProfileItemRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value instanceof Profile) { Profile profile = (Profile) value; value = translator.localize(LocaleResources.PROFILER_LIST_ITEM, profile.name, new Date(profile.timeStamp).toString()).getContents(); } return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } } public SwingVmProfileView() { listModel = new DefaultListModel<>(); tabPane = new JTabbedPane(); toggleButton = new ActionToggleButton(START_ICON, STOP_ICON, translator.localize( LocaleResources.START_PROFILING)); toggleButton.setName("TOGGLE_PROFILING_BUTTON"); toggleButton.toggleText(false); toggleButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { JToggleButton button = (JToggleButton) e.getSource(); if (button.isSelected()) { fireProfileAction(ProfileAction.START_PROFILING); } else { fireProfileAction(ProfileAction.STOP_PROFILING); } } }); mainContainer = new HeaderPanel(translator.localize(LocaleResources.PROFILER_HEADING)); new ComponentVisibilityNotifier().initialize(mainContainer, notifier); JPanel contentContainer = new JPanel(new BorderLayout()); mainContainer.setContent(contentContainer); mainContainer.addToolBarButton(toggleButton); JComponent actionsPanel = createStatusPanel(); contentContainer.add(actionsPanel, BorderLayout.PAGE_START); JComponent profilingResultsPanel = createInformationPanel(); contentContainer.add(profilingResultsPanel, BorderLayout.CENTER); } private JPanel createStatusPanel() { GridBagLayout layout = new GridBagLayout(); JPanel statusPanel = new JPanel(layout); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.anchor = GridBagConstraints.PAGE_START; constraints.weightx = 1.0; constraints.gridy = 0; constraints.gridx = 0; constraints.gridwidth = 3; constraints.ipady = 5; String wrappedText = "<html>" + translator.localize(LocaleResources.PROFILER_DESCRIPTION).getContents() + "</html>"; JLabel descriptionLabel = new JLabel(wrappedText); statusPanel.add(descriptionLabel, constraints); constraints.gridy = 1; constraints.gridx = 0; constraints.gridwidth = 1; currentStatusLabel = new JLabel("Current Status: {0}"); currentStatusLabel.setName("CURRENT_STATUS_LABEL"); statusPanel.add(currentStatusLabel, constraints); return statusPanel; } private JComponent createInformationPanel() { profileList = new JList<>(listModel); profileList.setName("PROFILE_LIST"); profileList.setCellRenderer(new ProfileItemRenderer()); profileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); profileList.addListSelectionListener(new ListSelectionListener() { private Profile oldValue = null; @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } Profile newValue = profileList.getSelectedValue(); if (oldValue == null || !oldValue.equals(newValue)) { oldValue = newValue; fireProfileAction(ProfileAction.PROFILE_SELECTED); } } }); ThermostatScrollPane profileListPane = new ThermostatScrollPane(profileList); Vector<String> columnNames = new Vector<>(); columnNames.add(translator.localize(LocaleResources.PROFILER_RESULTS_METHOD).getContents()); columnNames.add(translator.localize(LocaleResources.PROFILER_RESULTS_PERCENTAGE_TIME).getContents()); columnNames.add(translator.localize(LocaleResources.PROFILER_RESULTS_TIME, "ms").getContents()); tableModel = new NonEditableTableModel(columnNames, 0) { @Override public java.lang.Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case COLUMN_METHOD_NAME: return MethodDeclaration.class; case COLUMN_METHOD_PERCENTAGE: return Double.class; case COLUMN_METHOD_TIME: return Long.class; default: throw new AssertionError("Unknown column index"); } } }; final SimpleTextRenderer simpleRenderer = new SimpleTextRenderer(); final PlainTextMethodDeclarationRenderer plainRenderer = new PlainTextMethodDeclarationRenderer(); final SyntaxHighlightedMethodDeclarationRenderer colorRenderer = new SyntaxHighlightedMethodDeclarationRenderer(); profileTable = new ThermostatTable(tableModel) { public javax.swing.table.TableCellRenderer getCellRenderer(int row, int column) { if (column == COLUMN_METHOD_NAME) { if(tableModel.getRowCount() == 1 && tableModel.getValueAt(0,0).equals( translator.localize(LocaleResources.PROFILER_NO_RESULTS).getContents())) { return simpleRenderer; } else if (profileTable.isCellSelected(row, column)) { return plainRenderer; } else { return colorRenderer; } } return super.getCellRenderer(row, column); } }; List <RowSorter.SortKey> sortKeys = new ArrayList<>(); sortKeys.add(new RowSorter.SortKey(COLUMN_METHOD_TIME, SortOrder.DESCENDING)); profileTable.getRowSorter().setSortKeys(sortKeys); profileTable.setName("METHOD_TABLE"); tabPane.addTab(translator.localize(LocaleResources.PROFILER_RESULTS_TABLE).getContents(), profileTable.wrap()); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, profileListPane, tabPane); splitPane.setDividerLocation(SPLIT_PANE_RATIO); splitPane.setResizeWeight(0.5); return splitPane; } @Override public void addProfileActionListener(ActionListener<ProfileAction> listener) { listeners.add(listener); } @Override public void removeProfileActionlistener(ActionListener<ProfileAction> listener) { listeners.remove(listener); } private void fireProfileAction(final ProfileAction action) { new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { try { ActionEvent<ProfileAction> event = new ActionEvent<>(this, action); for (ActionListener<ProfileAction> listener : listeners) { listener.actionPerformed(event); } } catch (Throwable t) { t.printStackTrace(); } return null; } }.execute(); } @Override public void setProfilingState(final ProfilingState profilingState) { final String status, buttonLabel; if (!viewControlsEnabled) { status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_DEAD).getContents(); buttonLabel = translator.localize(LocaleResources.START_PROFILING).getContents(); } else if (profilingState == ProfilingState.STOPPING || profilingState == ProfilingState.STARTED) { status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_ACTIVE).getContents(); buttonLabel = translator.localize(LocaleResources.STOP_PROFILING).getContents(); } else { status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_INACTIVE).getContents(); buttonLabel = translator.localize(LocaleResources.START_PROFILING).getContents(); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { currentStatusLabel.setText(status); if (!viewControlsEnabled) { toggleButton.setToggleActionState(ProfilingState.DISABLED); } else { toggleButton.setToggleActionState(profilingState); } toggleButton.setText(buttonLabel); } }); } @Override public void setViewControlsEnabled(boolean enabled) { this.viewControlsEnabled = enabled; if (!enabled) { setProfilingState(ProfilingState.DISABLED); } } @Override public void setAvailableProfilingRuns(final List<Profile> data) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { DefaultListModel<Profile> listModel = (DefaultListModel<Profile>) profileList.getModel(); updateSwingModel(data, listModel); } /** * Update the swing model based on the provided data, adding new * items and removing no-longer-present items from the swing model */ private void updateSwingModel(final List<Profile> data, DefaultListModel<Profile> model) { for (Profile profile : data) { if (!model.contains(profile)) { model.addElement(profile); } } List<Profile> toRemove = new ArrayList<>(); Enumeration<Profile> e = model.elements(); while (e.hasMoreElements()) { Profile profile = e.nextElement(); if (!data.contains(profile)) { toRemove.add(profile); } } for (Profile profile : toRemove) { model.removeElement(profile); } } }); } @Override public Profile getSelectedProfile() { try { return new EdtHelper().callAndWait(new Callable<Profile>() { @Override public Profile call() throws Exception { if (profileList.isSelectionEmpty()) { throw new AssertionError("Selection is empty"); } return profileList.getSelectedValue(); } }); } catch (InvocationTargetException | InterruptedException e) { e.printStackTrace(); return null; } } @Override public void setProfilingDetailData(final ProfilingResult results) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // delete all existing data tableModel.setRowCount(0); if (results.getMethodInfo().size() == 0) { String noResultsMessage = translator.localize( LocaleResources.PROFILER_NO_RESULTS).getContents(); tableModel.addRow(new Object[] { noResultsMessage, null, null }); return; } for (MethodInfo methodInfo: results.getMethodInfo()) { Object[] data = new Object[] { methodInfo.decl, methodInfo.percentageTime, methodInfo.totalTimeInMillis, }; tableModel.addRow(data); } } }); } @Override public void addTabToTabbedPane(final LocalizedString title, final Component component) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { tabPane.addTab(title.getContents(), component); } }); } @Override public Component getUiComponent() { return mainContainer; } static class SimpleTextRenderer extends ThermostatTableRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (!(value instanceof String)) { throw new AssertionError("Unexpected value"); } return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } } static class PlainTextMethodDeclarationRenderer extends ThermostatTableRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (!(value instanceof MethodDeclaration)) { throw new AssertionError("Unexpected value"); } String plainText = ((MethodDeclaration) value).toString(); return super.getTableCellRendererComponent(table, plainText, isSelected, hasFocus, row, column); } } static class SyntaxHighlightedMethodDeclarationRenderer extends ThermostatTableRenderer { static final Color METHOD_COLOR = Palette.PALE_RED.getColor(); static final Color PARAMETER_COLOR = Palette.VIOLET.getColor(); static final Color RETURN_TYPE_COLOR = Palette.GRANITA_ORANGE.getColor(); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (!(value instanceof MethodDeclaration)) { throw new AssertionError("Unexpected value"); } String syntaxHighlightedText = syntaxHighlightMethod((MethodDeclaration) value); return super.getTableCellRendererComponent(table, syntaxHighlightedText, isSelected, hasFocus, row, column); } private String syntaxHighlightMethod(MethodDeclaration decl) { 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(); } /** * Package-private for testing purposes. */ static String htmlColorText(String unescapedText, Color color) { String hexColorString = "#" + Integer.toHexString(color.getRGB() & 0x00ffffff); return "<font color='" + hexColorString + "'>" + StringUtils.htmlEscape(unescapedText) + "</font>"; } } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JFrame window = new JFrame(); SwingVmProfileView view = new SwingVmProfileView(); window.add(view.getUiComponent()); window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); window.pack(); window.setVisible(true); List<MethodInfo> data = new ArrayList<>(); 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); } }); } }