/**
* Copyright Plugtree LLC
*
* 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 com.plugtree.solrmeter.view.statistic;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.ui.RectangleEdge;
import com.google.inject.Inject;
import com.plugtree.solrmeter.model.statistic.CacheData;
import com.plugtree.solrmeter.model.statistic.CacheHistoryStatistic;
import com.plugtree.solrmeter.util.ReflectionUtils;
import com.plugtree.solrmeter.view.I18n;
import com.plugtree.solrmeter.view.StatisticPanel;
import com.plugtree.solrmeter.view.component.InfoPanel;
import com.plugtree.solrmeter.view.component.RoundedBorderJPanel;
import com.plugtree.stressTestScope.StressTestScope;
/**
* This class will show the Cache history Statistic. This statistic will show a chart with the hit ratio
* of all caches together OR the data (lookups, hits, insertions and evictions) of a specific cache.
* @author tomas
*
*/
@StressTestScope
public class CacheHistoryPanel extends StatisticPanel implements ActionListener {
private static final long serialVersionUID = -154560067788983461L;
private final static Logger logger = Logger.getLogger(CacheHistoryPanel.class);
/**
* Just for i18n stuff
*/
private static final String PREFIX = "statistic.cacheStatistic.";
/**
* The dataset used in the chart.
*/
private DefaultXYDataset xyDataset = new DefaultXYDataset();
/**
* The model class for this statistic
*/
private CacheHistoryStatistic statistic;
/**
* The combo box where the user will chose the cache hi wants to see
*/
private JComboBox comboBoxCache;
/**
* Combo box to select wether to see the hit ratio information or a specific
* cache information
*/
private JComboBox whatToShowBoxCache;
/**
* Info Panel to display comulative lookups of the selected cache
*/
private InfoPanel cumulativeLookupsInfoPanel;
/**
* Info Panel to display comulative hits of the selected cache
*/
private InfoPanel cumulativeHitsInfoPanel;
/**
* Info Panel to display comulative hit ratio of the selected cache
*/
private InfoPanel cumulativeHitRatioInfoPanel;
/**
* Info Panel to display comulative inserts of the selected cache
*/
private InfoPanel cumulativeInsertsInfoPanel;
/**
* Info Panel to display comulative evictions of the selected cache
*/
private InfoPanel cumulativeEvictionsInfoPanel;
/**
* Boolean value that indicates if the user is displaying hit ratio information
* or specific cache data
*/
private boolean showingSpecificCacheData = false;
/**
* JPanel to hold all the cumulative information
*/
private JPanel cumulativeDataPanel;
/**
* Chart that shows the data
*/
private JFreeChart chart;
/**
* Plot of the above chart
*/
private XYPlot plot;
/**
* Constructor to be injected by Guice
* @param statistic
*/
@Inject
public CacheHistoryPanel(CacheHistoryStatistic statistic) {
super();
this.statistic = statistic;
this.xyDataset = new DefaultXYDataset();
this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
this.add(this.createLeftPanel());
this.add(this.createChartPanel());
this.setShowingSpecificData(false);//Show the hit ratio chart as default
}
/**
* Creates the left panel, with the combo boxes to select wether to see hit ratio
* or specific cache data, and if specific cache data is selected, will also show
* the combo box to select the cache and the cumulative information
* @return
*/
private Component createLeftPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.BOTH;
constraints.insets = new Insets(1,1,1,1);
for(Component component: this.createControlPanel()) {
panel.add(component, constraints);
constraints.gridy++;
}
cumulativeDataPanel = this.createCumulativeDataPanel();
panel.add(cumulativeDataPanel, constraints);
constraints.gridy++;
constraints.weighty = 2.0;
panel.add(Box.createVerticalGlue(), constraints);
panel.setMaximumSize(new Dimension(100, Integer.MAX_VALUE));
return panel;
}
/**
* Creates the cumulative data panel
* @return
*/
private JPanel createCumulativeDataPanel() {
JPanel panel = new RoundedBorderJPanel("Cumulative Data");
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
cumulativeLookupsInfoPanel = new InfoPanel(I18n.get(PREFIX + "Lookups"));
cumulativeHitsInfoPanel = new InfoPanel(I18n.get(PREFIX + "Hits"));
cumulativeHitRatioInfoPanel = new InfoPanel(I18n.get(PREFIX + "HitRatio"));
cumulativeInsertsInfoPanel = new InfoPanel(I18n.get(PREFIX + "Inserts"));
cumulativeEvictionsInfoPanel = new InfoPanel(I18n.get(PREFIX + "Evictions"));
panel.add(cumulativeLookupsInfoPanel);
panel.add(cumulativeHitsInfoPanel);
panel.add(cumulativeHitRatioInfoPanel);
panel.add(cumulativeInsertsInfoPanel);
panel.add(cumulativeEvictionsInfoPanel);
return panel;
}
/**
* Creates the controlers to select hit ratio or caches
* @return
*/
private List<Component> createControlPanel() {
List<Component> components = new LinkedList<Component>();
whatToShowBoxCache = new JComboBox();
whatToShowBoxCache.addItem(I18n.get("statistic.cacheStatistic.showHitRatio"));
whatToShowBoxCache.addItem(I18n.get("statistic.cacheStatistic.showSpecificData"));
whatToShowBoxCache.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
setShowingSpecificData(!whatToShowBoxCache.getSelectedItem().equals(I18n.get("statistic.cacheStatistic.showHitRatio")));
refreshView();
}
});
components.add(whatToShowBoxCache);
components.add(Box.createRigidArea(new Dimension(3, 3)));
comboBoxCache = new JComboBox();
comboBoxCache.addItem("documentCache");
comboBoxCache.addItem("fieldValueCache");
comboBoxCache.addItem("filterCache");
comboBoxCache.addItem("queryResultCache");
comboBoxCache.addActionListener(new ActionListener() {
@SuppressWarnings("unchecked")
@Override
public void actionPerformed(ActionEvent arg0) {
changeChartTitle("title" + comboBoxCache.getSelectedItem().toString());
refreshSeries(Collections.EMPTY_MAP);
refreshView();
}
});
components.add(comboBoxCache);
return components;
}
/**
* Invoke this method when the selection of what to show changes
* @param b
*/
private void setShowingSpecificData(boolean b) {
showingSpecificCacheData = b;
comboBoxCache.setEnabled(showingSpecificCacheData);
cumulativeDataPanel.setVisible(showingSpecificCacheData);
if(b) {
plot.getRangeAxis().setAutoRange(true);
changeChartTitle("title" + comboBoxCache.getSelectedItem().toString());
} else {
plot.getRangeAxis().setRange(new Range(0, 1));
plot.getRangeAxis().setAutoRange(false);
changeChartTitle("titleHitRatio");
}
clearChart();
}
/**
* Removes all series from the chart. To be invoked when the display information
* changes from hit ratio to specific cache data or viceversa, not when the selected
* cache changes
*/
private void clearChart() {
xyDataset.removeSeries(I18n.get(PREFIX + "Lookups"));
xyDataset.removeSeries(I18n.get(PREFIX + "Hits"));
xyDataset.removeSeries(I18n.get(PREFIX + "Size"));
xyDataset.removeSeries(I18n.get(PREFIX + "Inserts"));
xyDataset.removeSeries(I18n.get(PREFIX + "Evictions"));
xyDataset.removeSeries(I18n.get(PREFIX + "hitratio.filterCache"));
xyDataset.removeSeries(I18n.get(PREFIX + "hitratio.documentCache"));
xyDataset.removeSeries(I18n.get(PREFIX + "hitratio.queryResultCache"));
xyDataset.removeSeries(I18n.get(PREFIX + "hitratio.fieldValueCache"));
}
/**
* Refreshes the cumulative cache information
*/
private void refreshCumulativeData() {
CacheData cacheData = (CacheData) ReflectionUtils.getAttribute(statistic, comboBoxCache.getSelectedItem().toString() + "CumulativeData");
if(cacheData != null) {
cumulativeLookupsInfoPanel.setValue(String.valueOf(cacheData.getLookups()));
cumulativeHitsInfoPanel.setValue(String.valueOf(cacheData.getHits()));
cumulativeHitRatioInfoPanel.setValue(String.valueOf(cacheData.getHitratio()));
cumulativeInsertsInfoPanel.setValue(String.valueOf(cacheData.getInserts()));
cumulativeEvictionsInfoPanel.setValue(String.valueOf(cacheData.getEvictions()));
} else {
cumulativeLookupsInfoPanel.setValue("");
cumulativeHitsInfoPanel.setValue("");
cumulativeHitRatioInfoPanel.setValue("");
cumulativeInsertsInfoPanel.setValue("");
cumulativeEvictionsInfoPanel.setValue("");
}
}
/**
* Creates the chart of this statistic
* @return
*/
private Component createChartPanel() {
NumberAxis xaxis = new NumberAxis(I18n.get(PREFIX + "time"));
NumberAxis yaxis = new NumberAxis(I18n.get(PREFIX + "entries"));
plot = new XYPlot(xyDataset, xaxis, yaxis, new XYLineAndShapeRenderer(true, true));
chart = new JFreeChart("notitle",
null, plot, true);
chart.getLegend().setPosition(RectangleEdge.RIGHT);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(CHART_BORDER);
chartPanel.setMinimumDrawHeight(0);
chartPanel.setMinimumDrawWidth(0);
chartPanel.setMaximumDrawHeight(Integer.MAX_VALUE);
chartPanel.setMaximumDrawWidth(Integer.MAX_VALUE);
return chartPanel;
}
/**
* Changes the title of the chart
* @param key is the last part of the i18n key
*/
private void changeChartTitle(String key) {
chart.setTitle(I18n.get(PREFIX + key));
}
@Override
public String getStatisticName() {
return I18n.get("statistic.cacheStatistic.title");
}
@SuppressWarnings("unchecked")
@Override
public synchronized void refreshView() {
logger.debug("Cache History Panel");
if(showingSpecificCacheData) {
refreshSeries((Map<Long, CacheData>)ReflectionUtils.getAttribute(statistic, comboBoxCache.getSelectedItem().toString() + "Data"));
refreshCumulativeData();
} else {
refreshHitRatio();
}
}
/**
* If showing the hit ratio, this method will be invoked to refresh the chart
*/
private void refreshHitRatio() {
// synchronized(statistic) {
addSerie(statistic.getFilterCacheData(), "hitratio", PREFIX + "hitratio.filterCache");
addSerie(statistic.getDocumentCacheData(), "hitratio", PREFIX + "hitratio.documentCache");
addSerie(statistic.getQueryResultCacheData(), "hitratio", PREFIX + "hitratio.queryResultCache");
addSerie(statistic.getFieldValueCacheData(), "hitratio", PREFIX + "hitratio.fieldValueCache");
// }
}
/**
* If showing specific cache data, this method will be invoked to refresh
* @param data
*/
private void refreshSeries(Map<Long, CacheData> data) {
synchronized(data) {
addSerie(data, "Lookups");
addSerie(data, "Hits");
addSerie(data, "Size");
addSerie(data, "Inserts");
addSerie(data, "Evictions");
}
}
private void addSerie(Map<Long, CacheData> data, String element, String label) {
int i = 0;
double[][] seriesData;
seriesData = new double[2][data.size()];
for(Map.Entry<Long, CacheData> xy: data.entrySet()) {
seriesData[0][i] = xy.getKey()/1000.0;
try {
seriesData[1][i] = Double.valueOf(ReflectionUtils.getAttribute(xy.getValue(), element).toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
i++;
}
xyDataset.addSeries(I18n.get(label), seriesData);
}
private void addSerie(Map<Long, CacheData> data, String element) {
this.addSerie(data, element, PREFIX + element);
}
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
refreshView();
}
});
}
}