/*
* 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.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
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.MMultiLineTableCellRenderer;
import net.bull.javamelody.swing.table.MTable;
import net.bull.javamelody.swing.util.MWaitCursor;
/**
* Panel des statistiques.
* @author Emeric Vernat
*/
class StatisticsPanel extends MelodyPanel {
static final ImageIcon PLUS_ICON = ImageIconCache.getImageIcon("bullets/plus.png");
static final ImageIcon MINUS_ICON = ImageIconCache.getImageIcon("bullets/minus.png");
private static final long serialVersionUID = 1L;
@SuppressWarnings("all")
private final CounterRequestAggregation counterRequestAggregation;
private final Counter counter;
private final Range range;
private final StatisticsTablePanel tablePanel;
private final JPanel mainPanel;
private StatisticsPanel detailsPanel;
private CounterErrorPanel lastErrorsPanel;
StatisticsPanel(RemoteCollector remoteCollector, Counter counter, Range range,
boolean includeGraph) {
this(remoteCollector, counter, range, null, includeGraph);
}
private StatisticsPanel(RemoteCollector remoteCollector, Counter counter, Range range,
CounterRequestAggregation counterRequestAggregation, boolean includeGraph) {
super(remoteCollector);
assert counter != null;
assert range != null;
this.counter = counter;
this.range = range;
if (counter.getRequestsCount() == 0) {
this.tablePanel = null;
this.counterRequestAggregation = null;
} else {
if (counterRequestAggregation == null) {
this.counterRequestAggregation = new CounterRequestAggregation(counter);
} else {
this.counterRequestAggregation = counterRequestAggregation;
}
this.tablePanel = new StatisticsTablePanel(getRemoteCollector(), counter,
this.counterRequestAggregation, includeGraph);
}
this.mainPanel = new JPanel(new BorderLayout());
this.mainPanel.setOpaque(false);
add(this.mainPanel, BorderLayout.NORTH);
}
public void showGlobalRequests() {
if (counter.getRequestsCount() == 0) {
final JLabel noRequestsLabel = createNoRequestsLabel();
mainPanel.add(noRequestsLabel, BorderLayout.CENTER);
} else {
List<CounterRequest> requests = counterRequestAggregation.getRequests();
final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
if (isErrorAndNotJobCounter()) {
// il y a au moins une "request" d'erreur puisque la liste n'est pas vide
assert !requests.isEmpty();
requests = Collections.singletonList(requests.get(0));
final MTable<CounterRequest> myTable = tablePanel.getTable();
addMouseClickedListener(myTable);
} else {
requests = Arrays.asList(globalRequest,
counterRequestAggregation.getWarningRequest(),
counterRequestAggregation.getSevereRequest());
}
showRequests(requests);
final JPanel requestsSizeAndButtonsPanel = createRequestsSizeAndButtonsPanel();
mainPanel.add(requestsSizeAndButtonsPanel, BorderLayout.EAST);
}
}
void showDetailRequests() {
if (detailsPanel == null) {
final boolean includeGraph = CounterRequestTable.isRequestGraphDisplayed(counter);
detailsPanel = new StatisticsPanel(getRemoteCollector(), counter, range,
counterRequestAggregation, includeGraph);
detailsPanel.setVisible(false);
final List<CounterRequest> requests = counterRequestAggregation.getRequests();
detailsPanel.showRequests(requests);
final MTable<CounterRequest> myTable = detailsPanel.tablePanel.getTable();
// ajoute un renderer multi-lignes pour la colonne "name" dans le tableau de "Détails",
// ce qui est utile pour les requêtes sql ou les erreurs http par exemple
myTable.setColumnCellRenderer("name", new MMultiLineTableCellRenderer());
// invokeLater nécessaire pour que les dimensions et l'affichage soient corrects
// avec le MMultiLineTableCellRenderer (tester par exemple l'erreur de compilation dans la page jsp avec tomcat)
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Utilities.adjustTableHeight(myTable);
}
});
addMouseClickedListener(myTable);
add(detailsPanel, BorderLayout.CENTER);
}
detailsPanel.setVisible(!detailsPanel.isVisible());
detailsPanel.validate();
}
void showRequestsAggregatedOrFilteredByClassName(String requestId,
final MButton detailsButton) {
final List<CounterRequest> requests = new CounterRequestAggregation(counter)
.getRequestsAggregatedOrFilteredByClassName(requestId);
tablePanel.setList(requests);
add(tablePanel, BorderLayout.CENTER);
final MTable<CounterRequest> myTable = tablePanel.getTable();
if (detailsButton != null) {
// le double-clique sur une ligne de la table des requêtes aggrégrées par classe
// permet d'afficher la table des requêtes filtrées par la classe sélectionnée
myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
detailsButton.setEnabled(myTable.getSelectedObject() != null);
}
});
detailsButton.setEnabled(myTable.getSelectedObject() != null);
myTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
detailsButton.doClick();
}
}
});
detailsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
actionCounterSummaryPerClass(true);
}
});
} else {
// et sinon, le double-clique sur la table des requêtes filtrées pour une classe
// permet d'afficher le détail d'une requête
addMouseClickedListener(myTable);
}
}
private void addMouseClickedListener(final MTable<CounterRequest> myTable) {
myTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
final MWaitCursor waitCursor = new MWaitCursor(myTable);
final CounterRequest request = myTable.getSelectedObject();
try {
showRequestDetail(request);
} catch (final IOException ex) {
showException(ex);
} finally {
waitCursor.restore();
}
}
}
});
}
void showLastErrors() {
if (lastErrorsPanel == null) {
lastErrorsPanel = new CounterErrorPanel(getRemoteCollector(), counter);
lastErrorsPanel.setVisible(false);
add(lastErrorsPanel, BorderLayout.SOUTH);
}
lastErrorsPanel.setVisible(!lastErrorsPanel.isVisible());
lastErrorsPanel.validate();
}
private JLabel createNoRequestsLabel() {
final String key;
if (isJobCounter()) {
key = "Aucun_job";
} else if (isErrorCounter()) {
key = "Aucune_erreur";
} else {
key = "Aucune_requete";
}
return new JLabel(' ' + getString(key));
}
private void showRequests(List<CounterRequest> requests) {
tablePanel.setList(requests);
Utilities.adjustTableHeight(tablePanel.getTable());
mainPanel.add(tablePanel, BorderLayout.NORTH);
}
private JPanel createRequestsSizeAndButtonsPanel() {
final CounterRequest globalRequest = this.counterRequestAggregation.getGlobalRequest();
final long end;
if (range.getEndDate() != null) {
// l'utilisateur a choisi une période personnalisée de date à date,
// donc la fin est peut-être avant la date du jour
end = Math.min(range.getEndDate().getTime(), System.currentTimeMillis());
} else {
end = System.currentTimeMillis();
}
// delta ni négatif ni à 0
final long deltaMillis = Math.max(end - counter.getStartDate().getTime(), 1);
final long hitsParMinute = 60 * 1000 * globalRequest.getHits() / deltaMillis;
// Rq : si serveur utilisé de 8h à 20h (soit 12h) on peut multiplier par 2 ces hits par minute indiqués
// pour avoir une moyenne sur les heures d'activité sans la nuit
final String nbKey;
if (isJobCounter()) {
nbKey = "nb_jobs";
} else if (isErrorCounter()) {
nbKey = "nb_erreurs";
} else {
nbKey = "nb_requetes";
}
final DecimalFormat integerFormat = I18N.createIntegerFormat();
final String text = getFormattedString(nbKey, integerFormat.format(hitsParMinute),
integerFormat.format(counterRequestAggregation.getRequests().size()));
final JPanel panel = Utilities.createButtonsPanel(new JLabel(text));
if (counter.isBusinessFacadeCounter()) {
panel.add(createCounterSummaryPerClassButton());
panel.add(createRuntimeDependenciesButton());
}
panel.add(createDetailsButton());
if (isErrorCounter()) {
panel.add(createLastErrorsButton());
}
if (range.getPeriod() == Period.TOUT) {
panel.add(createClearCounterButton());
}
return panel;
}
private MButton createCounterSummaryPerClassButton() {
final MButton counterSummaryPerClassButton = new MButton(getString("Resume_par_classe"));
counterSummaryPerClassButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
actionCounterSummaryPerClass(false);
}
});
return counterSummaryPerClassButton;
}
private MButton createRuntimeDependenciesButton() {
final MButton runtimeDependenciesButton = new MButton(getString("Dependencies"),
ImageIconCache.getImageIcon("pdf.png"));
runtimeDependenciesButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
actionRuntimeDependencies();
} catch (final IOException ex) {
showException(ex);
}
}
});
return runtimeDependenciesButton;
}
private MButton createDetailsButton() {
final MButton detailsButton = new MButton(getString("Details"), PLUS_ICON);
detailsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showDetailRequests();
changePlusMinusIcon(detailsButton);
}
});
return detailsButton;
}
private MButton createLastErrorsButton() {
final MButton lastErrorsButton = new MButton(getString("Dernieres_erreurs"), PLUS_ICON);
lastErrorsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showLastErrors();
changePlusMinusIcon(lastErrorsButton);
}
});
return lastErrorsButton;
}
private MButton createClearCounterButton() {
final MButton clearCounterButton = new MButton(getString("Reinitialiser"));
clearCounterButton.setToolTipText(getFormattedString("Vider_stats", counter.getName()));
final Counter myCounter = counter;
clearCounterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (confirm(getFormattedString("confirm_vider_stats", myCounter.getName()))) {
actionClearCounter(myCounter);
}
}
});
return clearCounterButton;
}
final void actionClearCounter(Counter myCounter) {
try {
final String message = getRemoteCollector().executeActionAndCollectData(
Action.CLEAR_COUNTER, myCounter.getName(), null, null, null, null);
showMessage(message);
MainPanel.refreshMainTabFromChild(this);
} catch (final IOException ex) {
showException(ex);
}
}
final void actionCounterSummaryPerClass(boolean detailsForAClass) {
final String requestId;
if (detailsForAClass) {
requestId = tablePanel.getTable().getSelectedObject().getId();
} else {
requestId = null;
}
final CounterSummaryPerClassPanel panel = new CounterSummaryPerClassPanel(
getRemoteCollector(), counter, range, requestId);
MainPanel.addOngletFromChild(this, panel);
}
final void actionRuntimeDependencies() throws IOException {
final File tempFile = createTempFileForPdf();
final PdfOtherReport pdfOtherReport = createPdfOtherReport(tempFile);
try {
pdfOtherReport.writeRuntimeDependencies(counter, range);
} finally {
pdfOtherReport.close();
}
Desktop.getDesktop().open(tempFile);
}
final void changePlusMinusIcon(MButton detailsButton) {
if (detailsButton.getIcon() == PLUS_ICON) {
detailsButton.setIcon(MINUS_ICON);
} else {
detailsButton.setIcon(PLUS_ICON);
}
}
private boolean isErrorCounter() {
return counter.isErrorCounter();
}
private boolean isJobCounter() {
return counter.isJobCounter();
}
private boolean isErrorAndNotJobCounter() {
return isErrorCounter() && !isJobCounter();
}
void showRequestDetail(CounterRequest request) throws IOException {
final CounterRequestDetailPanel panel = new CounterRequestDetailPanel(getRemoteCollector(),
request);
MainPanel.addOngletFromChild(this, panel);
}
}