/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.bull.javamelody; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.bull.javamelody.swing.MButton; import net.bull.javamelody.swing.Utilities; import net.bull.javamelody.swing.table.MDefaultTableCellRenderer; import net.bull.javamelody.swing.table.MTable; import net.bull.javamelody.swing.table.MTableScrollPane; /** * Panel des threads. * @author Emeric Vernat */ class ThreadInformationsPanel extends MelodyPanel { private static final long serialVersionUID = 1L; private final JavaInformations javaInformations; @SuppressWarnings("all") private final List<ThreadInformations> threadInformationsList; private final boolean stackTraceEnabled; private final boolean cpuTimeEnabled; private final MTable<ThreadInformations> table; private class NameTableCellRenderer extends MDefaultTableCellRenderer { private static final long serialVersionUID = 1L; NameTableCellRenderer() { super(); } @Override public Component getTableCellRendererComponent(JTable jtable, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // tooltip selon la stack-trace if (row == -1) { setToolTipText(null); } else { final MTable<ThreadInformations> myTable = getTable(); final ThreadInformations threadInformations = myTable.getList() .get(myTable.convertRowIndexToModel(row)); setToolTipText(convertStackTraceToHtml(threadInformations.getName(), threadInformations.getStackTrace())); } // et texte selon la valeur (nom du thread) return super.getTableCellRendererComponent(jtable, value, isSelected, hasFocus, row, column); } } private class StateTableCellRenderer extends MDefaultTableCellRenderer { private static final long serialVersionUID = 1L; StateTableCellRenderer() { super(); } @Override public Component getTableCellRendererComponent(JTable jtable, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // icône correspondant à l'état if (row == -1) { setIcon(null); } else { final MTable<ThreadInformations> myTable = getTable(); final ThreadInformations threadInformations = myTable.getList() .get(myTable.convertRowIndexToModel(row)); setIcon(ImageIconCache.getImageIcon("bullets/" + HtmlThreadInformationsReport.getStateIcon(threadInformations))); } // et texte selon la valeur (libellé de l'état) return super.getTableCellRendererComponent(jtable, value, isSelected, hasFocus, row, column); } } ThreadInformationsPanel(RemoteCollector remoteCollector, JavaInformations javaInformations) { super(remoteCollector); assert javaInformations != null; this.javaInformations = javaInformations; this.threadInformationsList = javaInformations.getThreadInformationsList(); this.stackTraceEnabled = javaInformations.isStackTraceEnabled(); this.cpuTimeEnabled = !threadInformationsList.isEmpty() && threadInformationsList.get(0).getCpuTimeMillis() != -1; final MTableScrollPane<ThreadInformations> scrollPane = createScrollPane(); this.table = scrollPane.getTable(); table.setList(threadInformationsList); Utilities.adjustTableHeight(table); add(scrollPane, BorderLayout.NORTH); final JLabel label = new JLabel(' ' + getString("Temps_threads")); add(label, BorderLayout.WEST); final JPanel buttonsPanel = createButtonsPanel(); add(buttonsPanel, BorderLayout.EAST); } private MTableScrollPane<ThreadInformations> createScrollPane() { final MTableScrollPane<ThreadInformations> tableScrollPane = new MTableScrollPane<>(); final MTable<ThreadInformations> myTable = tableScrollPane.getTable(); myTable.addColumn("name", getString("Thread")); myTable.addColumn("daemon", getString("Demon")); myTable.addColumn("priority", getString("Priorite")); myTable.addColumn("state", getString("Etat")); if (stackTraceEnabled) { myTable.addColumn("executedMethod", getString("Methode_executee")); } if (cpuTimeEnabled) { myTable.addColumn("cpuTimeMillis", getString("Temps_cpu")); myTable.addColumn("userTimeMillis", getString("Temps_user")); } myTable.setColumnCellRenderer("state", new StateTableCellRenderer()); myTable.setColumnCellRenderer("name", new NameTableCellRenderer()); return tableScrollPane; } private JPanel createButtonsPanel() { final JPanel buttonsPanel = Utilities.createButtonsPanel(); final MButton openButton = new MButton(getString("Ouvrir"), ImageIconCache.getImageIcon("action_open.png")); buttonsPanel.add(openButton); openButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final ThreadInformations threadInformations = getTable().getSelectedObject(); showStackTraceInPopup(threadInformations); } }); table.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { openButton.doClick(); } } }); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { final ThreadInformations threadInformations = getTable().getSelectedObject(); openButton.setEnabled(threadInformations != null); } }); openButton.setEnabled(false); if (Parameters.isSystemActionsEnabled()) { final MButton killThreadButton = new MButton(getString("Tuer"), ImageIconCache.getImageIcon("stop.png")); getTable().getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { final ThreadInformations threadInformations = getTable().getSelectedObject(); killThreadButton.setEnabled(threadInformations != null); if (threadInformations != null) { killThreadButton.setToolTipText( getFormattedString("kill_thread", threadInformations.getName())); } else { killThreadButton.setToolTipText(null); } } }); killThreadButton.setEnabled(getTable().getSelectedObject() != null); killThreadButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final ThreadInformations threadInformations = getTable().getSelectedObject(); if (threadInformations != null && confirm(getFormattedString("confirm_kill_thread", threadInformations.getName()))) { actionKillThread(threadInformations); } } }); buttonsPanel.add(killThreadButton); } if (stackTraceEnabled) { final MButton dumpThreadsButton = new MButton(getString("Dump_threads_en_texte"), ImageIconCache.getImageIcon("text.png")); dumpThreadsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { actionDumpThreads(); } }); buttonsPanel.add(dumpThreadsButton); } return buttonsPanel; } final void actionKillThread(ThreadInformations threadInformations) { try { final String message = getRemoteCollector().executeActionAndCollectData( Action.KILL_THREAD, null, null, threadInformations.getGlobalThreadId(), null, null); showMessage(message); MainPanel.refreshMainTabFromChild(this); } catch (final IOException ex) { showException(ex); } } final void actionDumpThreads() { try { final ThreadsDumpPanel panel = new ThreadsDumpPanel(getRemoteCollector(), javaInformations); MainPanel.addOngletFromChild(this, panel); } catch (final IOException ex) { showException(ex); } } final void showStackTraceInPopup(ThreadInformations threadInformations) { final StringBuilder sb = new StringBuilder(); sb.append(threadInformations.getName()); final List<StackTraceElement> stackTrace = threadInformations.getStackTrace(); if (stackTrace != null && !stackTrace.isEmpty()) { // même si stackTraceEnabled, ce thread n'a pas forcément de stack-trace sb.append('\n'); for (final StackTraceElement stackTraceElement : stackTrace) { sb.append(stackTraceElement); sb.append('\n'); } } final String title = threadInformations.getName(); final String text = sb.toString(); Utilities.showTextInPopup(this, title, text); } static String convertStackTraceToHtml(String description, List<StackTraceElement> stackTrace) { if (stackTrace != null && !stackTrace.isEmpty()) { // même si stackTraceEnabled, ce thread n'a pas forcément de stack-trace final StringBuilder sb = new StringBuilder(); sb.append("<html>"); sb.append(description); sb.append("<br/>"); for (final StackTraceElement stackTraceElement : stackTrace) { sb.append(stackTraceElement); sb.append("<br/>"); } return sb.toString(); } return null; } JLabel createThreadDeadlocksLabel() { final StringBuilder sb = new StringBuilder(); sb.append(" "); sb.append(getString("Threads_deadlocks")); String separator = " "; for (final ThreadInformations thread : threadInformationsList) { if (thread.isDeadlocked()) { sb.append(separator); sb.append(thread.getName()); separator = ", "; } } final JLabel label = new JLabel(sb.toString()); label.setForeground(Color.RED); label.setFont(label.getFont().deriveFont(Font.BOLD)); // séparateur avec composants au-dessus label.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); return label; } MTable<ThreadInformations> getTable() { return table; } }