/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program 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 3 of the License, or (at your option)
* any later version.
* This program 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 this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.rcp.developer;
import com.sun.media.jai.util.CacheDiagnostics;
import com.sun.media.jai.util.SunTileCache;
import org.esa.snap.core.image.RasterDataNodeOpImage;
import org.esa.snap.core.image.SingleBandedOpImage;
import org.esa.snap.core.image.VirtualBandOpImage;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.UnitType;
import javax.media.jai.CachedTile;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.TileCache;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.table.AbstractTableModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
public class TileCacheMonitor {
private JTabbedPane tabbedPane;
private static final String CACHE_INFO_TAB = "Cache Info";
private static final String CACHE_CHART_TAB = "Cache Chart";
private static final String IMAGES_TAB = "Images in Cache";
private static class CachedTileInfo {
Object uid;
String imageName;
int level;
int numTiles;
long size;
String comment;
}
private static class TileCacheTableModel extends AbstractTableModel {
private final static String[] COLUM_NAMES = {"Image", "#Tiles", "Size (kB)", "Level", "Comment"};
private final static Class[] COLUM_CLASSES = {String.class, Integer.class, Long.class,
Integer.class, String.class};
List<CachedTileInfo> data = new ArrayList<CachedTileInfo>(50);
@Override
public int getRowCount() {
return data.size();
}
@Override
public int getColumnCount() {
return COLUM_NAMES.length;
}
@Override
public String getColumnName(int columnIndex) {
return COLUM_NAMES[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUM_CLASSES[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int row, int column) {
CachedTileInfo cachedTileInfo = data.get(row);
switch (column) {
case 0:
return cachedTileInfo.imageName;
case 1:
return cachedTileInfo.numTiles;
case 2:
return cachedTileInfo.size / 1024;
case 3:
return cachedTileInfo.level;
case 4:
return cachedTileInfo.comment;
}
return null;
}
public void reset() {
for (CachedTileInfo tileInfo : data) {
tileInfo.numTiles = 0;
tileInfo.size = 0;
}
}
public void cleanUp() {
Iterator<CachedTileInfo> iterator = data.iterator();
while (iterator.hasNext()) {
CachedTileInfo tileInfo = iterator.next();
if (tileInfo.numTiles == 0) {
iterator.remove();
}
}
}
public void addRow(CachedTileInfo tileInfo) {
data.add(tileInfo);
}
}
/**
* The datasets.
*/
private TimeSeriesCollection[] datasets;
private TileCacheTableModel tableModel;
private JTextArea textarea;
/**
* Creates a new monitor panel.
*
* @return the monitor panel
*/
public JPanel createPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new DateAxis("Time"));
this.datasets = new TimeSeriesCollection[4];
this.datasets[0] = addSubPlot(plot, "#Tiles");
this.datasets[1] = addSubPlot(plot, "#Hits");
this.datasets[2] = addSubPlot(plot, "#Misses");
this.datasets[3] = addSubPlot(plot, "Mem (kB)");
JFreeChart chart = new JFreeChart(plot);
LegendTitle legend = (LegendTitle) chart.getSubtitle(0);
legend.setPosition(RectangleEdge.RIGHT);
legend.setMargin(new RectangleInsets(UnitType.ABSOLUTE, 0, 4, 0, 4));
chart.setBorderPaint(Color.black);
chart.setBorderVisible(true);
chart.setBackgroundPaint(Color.white);
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
plot.setAxisOffset(new RectangleInsets(4, 4, 4, 4));
ValueAxis axis = plot.getDomainAxis();
axis.setAutoRange(true);
axis.setFixedAutoRange(60000.0); // 60 seconds
textarea = new JTextArea();
tableModel = new TileCacheTableModel();
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 470));
chartPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
tabbedPane = new JTabbedPane();
tabbedPane.add(CACHE_INFO_TAB, new JScrollPane(textarea));
tabbedPane.add(CACHE_CHART_TAB, chartPanel);
tabbedPane.add(IMAGES_TAB, new JScrollPane(new JTable(tableModel)));
tabbedPane.setSelectedIndex(0);
mainPanel.add(tabbedPane);
return mainPanel;
}
public synchronized void updateState() {
final TileCache tileCache = JAI.getDefaultInstance().getTileCache();
int selectedIndex = tabbedPane.getSelectedIndex();
//System.out.println("selectedIndex = " + selectedIndex);
if (selectedIndex == 1) {
// (1) if graph view visible
if (tileCache instanceof CacheDiagnostics) {
CacheDiagnostics cacheDiagnostics = (CacheDiagnostics) tileCache;
cacheDiagnostics.enableDiagnostics();
final Millisecond t = new Millisecond();
update(0, t, cacheDiagnostics.getCacheTileCount());
update(1, t, cacheDiagnostics.getCacheHitCount());
update(2, t, cacheDiagnostics.getCacheMissCount());
update(3, t, cacheDiagnostics.getCacheMemoryUsed() / 1024);
}
} else if (selectedIndex == 2) {
// (2) if table view visible
if (tileCache instanceof SunTileCache) {
SunTileCache stc = (SunTileCache) tileCache;
synchronized (stc) {
Hashtable cachedObject = (Hashtable) stc.getCachedObject();
Collection<CachedTile> tiles = cachedObject.values();
// dumpImageTree(tiles);
updateTableModel(tiles);
}
}
} else if (selectedIndex == 0) {
// (3) if info view visible
StringBuilder sb = new StringBuilder();
if (tileCache != null) {
sb.append("tileCache.memoryCapacity: \t");
sb.append(tileCache.getMemoryCapacity() / (1024 * 1024));
sb.append(" MB\n");
sb.append("tileCache.memoryThreshold: \t");
sb.append(tileCache.getMemoryThreshold());
sb.append("\n");
sb.append("tileCache.tileComparator: \t");
sb.append(tileCache.getTileComparator());
sb.append("\n");
sb.append("tileCache.class: \t");
sb.append(tileCache.getClass().getName());
sb.append("\n");
}
if (tileCache instanceof SunTileCache) {
SunTileCache sunTileCache = (SunTileCache) tileCache;
sb.append("sunTileCache.cacheMemoryUsed: \t");
sb.append(sunTileCache.getCacheMemoryUsed() / (1024 * 1024));
sb.append(" MB\n");
sb.append("sunTileCache.cacheHitCount: \t");
sb.append(sunTileCache.getCacheHitCount());
sb.append("\n");
sb.append("sunTileCache.cacheMissCount: \t");
sb.append(sunTileCache.getCacheMissCount());
sb.append("\n");
sb.append("sunTileCache.cacheTileCount: \t");
sb.append(sunTileCache.getCacheTileCount());
sb.append("\n");
}
textarea.setText(sb.toString());
}
}
private void updateTableModel(Collection<CachedTile> tiles) {
tableModel.reset();
for (CachedTile sct : tiles) {
RenderedImage owner = sct.getOwner();
if (owner == null || !(owner instanceof PlanarImage)) {
continue;
}
PlanarImage image = (PlanarImage) owner;
Object imageID = image.getImageID();
CachedTileInfo cachedTileInfo = null;
for (CachedTileInfo info : tableModel.data) {
if (info.uid.equals(imageID)) {
cachedTileInfo = info;
break;
}
}
if (cachedTileInfo == null) {
cachedTileInfo = new CachedTileInfo();
cachedTileInfo.uid = imageID;
cachedTileInfo.imageName = getImageName(image);
cachedTileInfo.level = getImageLevel(image);
cachedTileInfo.comment = getImageComment(image);
tableModel.data.add(cachedTileInfo);
}
cachedTileInfo.numTiles++;
cachedTileInfo.size += sct.getTileSize();
}
tableModel.cleanUp();
tableModel.fireTableDataChanged();
}
private String getImageName(RenderedImage image) {
return image.getClass().getSimpleName();
}
private int getImageLevel(RenderedImage image) {
if (image instanceof SingleBandedOpImage) {
SingleBandedOpImage sboi = (SingleBandedOpImage) image;
return sboi.getLevel();
}
return -1;
}
private String getImageComment(RenderedImage image) {
if (image instanceof RasterDataNodeOpImage) {
RasterDataNodeOpImage rdnoi = (RasterDataNodeOpImage) image;
return rdnoi.getRasterDataNode().getName();
} else if (image instanceof VirtualBandOpImage) {
VirtualBandOpImage vboi = (VirtualBandOpImage) image;
return vboi.getExpression();
} else {
final String s = image.toString();
final int p1 = s.indexOf('[');
final int p2 = s.indexOf(']', p1 + 1);
if (p1 > 0 && p2 > p1) {
return s.substring(p1 + 1, p2 - 1);
}
return s;
}
}
private void dumpImageTree(Collection<CachedTile> tiles) {
final Map<RenderedImage, Integer> ownerMap = new HashMap<RenderedImage, Integer>(100);
final Map<RenderedImage, Long> sizeMap = new HashMap<RenderedImage, Long>(100);
for (CachedTile sct : tiles) {
RenderedImage owner = sct.getOwner();
if (owner == null) {
continue;
}
Integer count = ownerMap.get(owner);
if (count == null) {
ownerMap.put(owner, Integer.valueOf(1));
} else {
ownerMap.put(owner, Integer.valueOf(count.intValue() + 1));
}
Long size = sizeMap.get(owner);
if (size == null) {
sizeMap.put(owner, Long.valueOf(sct.getTileSize()));
} else {
sizeMap.put(owner, Long.valueOf(size + sct.getTileSize()));
}
}
Set<RenderedImage> rootEntries = new HashSet<RenderedImage>(ownerMap.keySet());
for (RenderedImage image : ownerMap.keySet()) {
Vector<RenderedImage> sources = image.getSources();
eleminateSources(sources, rootEntries);
}
for (RenderedImage image : rootEntries) {
printImage(image, ownerMap.get(image), sizeMap.get(image));
printSources("", image.getSources(), ownerMap, sizeMap);
}
System.out.println("======================================");
}
private void eleminateSources(Vector<RenderedImage> sources,
Set<RenderedImage> rootEntries) {
if (sources != null) {
for (RenderedImage renderedImage : sources) {
if (rootEntries.contains(renderedImage)) {
rootEntries.remove(renderedImage);
}
eleminateSources(renderedImage.getSources(), rootEntries);
}
}
}
private void printSources(String prefix,
Vector<RenderedImage> sources,
Map<RenderedImage, Integer> ownerMap,
Map<RenderedImage, Long> sizeMap) {
if (sources != null) {
for (RenderedImage image : sources) {
System.out.print(prefix + "->");
if (ownerMap.containsKey(image)) {
printImage(image, ownerMap.get(image), sizeMap.get(image));
} else {
printImage(image, 0, 0L);
}
printSources("--" + prefix, image.getSources(), ownerMap, sizeMap);
}
}
}
private void printImage(RenderedImage image, int numTiles, Long size) {
System.out.print("(" + getImageName(image) + ") ");
if (numTiles > 0) {
System.out.print("#tiles=" + numTiles + " ");
System.out.printf("size=%8.2fMB ", (size / (1024.0 * 1024.0)));
}
final int level = getImageLevel(image);
if (level >= 0) {
System.out.print("level=" + level + " ");
}
System.out.print(getImageComment(image));
System.out.println();
}
private static TimeSeriesCollection addSubPlot(CombinedDomainXYPlot plot, String label) {
final TimeSeriesCollection seriesCollection = new TimeSeriesCollection(new TimeSeries(label, Millisecond.class));
NumberAxis rangeAxis = new NumberAxis();
rangeAxis.setAutoRangeIncludesZero(false);
XYPlot subplot = new XYPlot(seriesCollection, null, rangeAxis, new StandardXYItemRenderer());
subplot.setBackgroundPaint(Color.lightGray);
subplot.setDomainGridlinePaint(Color.white);
subplot.setRangeGridlinePaint(Color.white);
plot.add(subplot);
return seriesCollection;
}
private void update(int i, Millisecond t, double value) {
this.datasets[i].getSeries(0).add(t, value);
}
}