/******************************************************************************* | |
* Copyright (c) 2008, 2023 SAP AG, IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which accompanies this distribution, and is available at | |
* https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* SPDX-License-Identifier: EPL-2.0 | |
* | |
* Contributors: | |
* SAP AG - initial API and implementation | |
* Andrew Johnson/IBM Corporation - com.ibm.icu fixes, notes | |
*******************************************************************************/ | |
package org.eclipse.mat.ui.snapshot.views; | |
import java.io.File; | |
import java.io.Serializable; | |
import java.io.UncheckedIOException; | |
import java.util.ArrayList; | |
import java.util.Date; | |
import java.util.List; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.jface.action.Action; | |
import org.eclipse.jface.viewers.ISelection; | |
import org.eclipse.jface.viewers.ISelectionChangedListener; | |
import org.eclipse.jface.viewers.ITableLabelProvider; | |
import org.eclipse.jface.viewers.ITreeContentProvider; | |
import org.eclipse.jface.viewers.LabelProvider; | |
import org.eclipse.jface.viewers.TreeViewer; | |
import org.eclipse.jface.viewers.Viewer; | |
import org.eclipse.mat.snapshot.ISnapshot; | |
import org.eclipse.mat.snapshot.SnapshotInfo; | |
import org.eclipse.mat.snapshot.UnreachableObjectsHistogram; | |
import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; | |
import org.eclipse.mat.ui.internal.views.NotesView; | |
import org.eclipse.mat.ui.snapshot.editor.ISnapshotEditorInput; | |
import org.eclipse.mat.ui.util.Copy; | |
import org.eclipse.mat.util.MessageUtil; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.swt.widgets.Composite; | |
import org.eclipse.swt.widgets.Control; | |
import org.eclipse.swt.widgets.TreeColumn; | |
import org.eclipse.ui.actions.ActionFactory; | |
import org.eclipse.ui.part.IPageSite; | |
import org.eclipse.ui.part.Page; | |
import org.eclipse.ui.views.contentoutline.IContentOutlinePage; | |
import com.ibm.icu.text.DateFormat; | |
import com.ibm.icu.text.DecimalFormat; | |
import com.ibm.icu.text.NumberFormat; | |
public abstract class SnapshotOutlinePage extends Page implements IContentOutlinePage | |
{ | |
public static class HeapEditorOutlinePage extends SnapshotOutlinePage implements | |
ISnapshotEditorInput.IChangeListener | |
{ | |
private ISnapshotEditorInput snapshotInput; | |
public HeapEditorOutlinePage(ISnapshotEditorInput input) | |
{ | |
this.snapshotInput = input; | |
this.snapshotInput.addChangeListener(this); | |
} | |
@Override | |
protected void createColumns() | |
{ | |
super.createColumns(); | |
TreeColumn column = new TreeColumn(treeViewer.getTree(), SWT.LEFT); | |
column.setText(Messages.baseline); | |
column.setWidth(80); | |
} | |
public void onSnapshotLoaded(ISnapshot snapshot) | |
{ | |
updateSnapshotInput(); | |
} | |
public void onBaselineLoaded(ISnapshot snapshot) | |
{ | |
updateSnapshotInput(); | |
} | |
@Override | |
protected SnapshotInfo getBaseline() | |
{ | |
return this.snapshotInput.hasBaseline() ? this.snapshotInput.getBaseline().getSnapshotInfo() : null; | |
} | |
@Override | |
protected SnapshotInfo getSnapshot() | |
{ | |
if (snapshotInput == null) | |
return null; | |
else | |
return snapshotInput.hasSnapshot() ? snapshotInput.getSnapshot().getSnapshotInfo() : null; | |
} | |
@Override | |
protected IPath getSnapshotPath() | |
{ | |
if (snapshotInput == null) | |
return null; | |
return this.snapshotInput.getPath(); | |
} | |
@Override | |
public void dispose() | |
{ | |
if (snapshotInput != null) | |
{ | |
snapshotInput.removeChangeListener(this); | |
snapshotInput = null; | |
} | |
super.dispose(); | |
} | |
} | |
static class Label | |
{ | |
String text; | |
Object snapshotValue, baselineValue; | |
public Label(String typeId, Object snapshotValue, Object baselineValue) | |
{ | |
this.text = typeId; | |
this.snapshotValue = snapshotValue; | |
this.baselineValue = baselineValue; | |
} | |
public String getText() | |
{ | |
return text; | |
} | |
public Object getBaselineValue() | |
{ | |
return baselineValue; | |
} | |
public Object getSnapshotValue() | |
{ | |
return snapshotValue; | |
} | |
} | |
static class Category | |
{ | |
String categoryId; | |
protected List<Label> children = new ArrayList<Label>(); | |
public Category(String categoryId) | |
{ | |
this.categoryId = categoryId; | |
} | |
public String getCategoryId() | |
{ | |
return categoryId; | |
} | |
public void addChild(Label label) | |
{ | |
children.add(label); | |
} | |
} | |
static class OutlineContentProvider implements ITreeContentProvider | |
{ | |
List<Object> elements; | |
@SuppressWarnings("unchecked") | |
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) | |
{ | |
elements = (List<Object>) newInput; | |
} | |
public Object[] getElements(Object inputElement) | |
{ | |
return elements.toArray(); | |
} | |
public Object[] getChildren(Object parentElement) | |
{ | |
return parentElement instanceof Category ? ((Category) parentElement).children.toArray() : null; | |
} | |
public Object getParent(Object element) | |
{ | |
return null; | |
} | |
public boolean hasChildren(Object element) | |
{ | |
return element instanceof Category; | |
} | |
public void dispose() | |
{} | |
} | |
static class OutlineLabelProvider extends LabelProvider implements ITableLabelProvider | |
{ | |
public Image getColumnImage(Object element, int columnIndex) | |
{ | |
return null; | |
} | |
public String getColumnText(Object element, int columnIndex) | |
{ | |
if (element instanceof Category) | |
{ | |
return columnIndex == 0 ? ((Category) element).getCategoryId() : null; | |
} | |
else if (element instanceof Label) | |
{ | |
Label label = (Label) element; | |
if (columnIndex == 0) | |
{ | |
return label.getText(); | |
} | |
else | |
{ | |
Object obj = columnIndex == 1 ? label.getSnapshotValue() : label.getBaselineValue(); | |
if (obj == null) | |
{ | |
return null; | |
} | |
else if (Messages.identifier_size.equals(label.getText())) | |
{ | |
int identifierSize = ((Integer) obj).intValue(); | |
return MessageUtil.format(Messages.identifier_format, identifierSize); | |
} | |
else if ((obj instanceof Long) || (obj instanceof Integer)) | |
{ | |
return ((Number) obj).longValue() == 0 ? null : NumberFormat.getInstance().format(obj); | |
} | |
else if (obj instanceof Double) | |
{ | |
DecimalFormat format = new DecimalFormat("#,##0.0"); //$NON-NLS-1$ | |
NumberFormat nf = NumberFormat.getNumberInstance(); | |
if (nf instanceof DecimalFormat) | |
{ | |
// Use the locale formatter | |
DecimalFormat df = (DecimalFormat)nf; | |
df.setMinimumFractionDigits(1); | |
df.setMaximumFractionDigits(1); | |
format = df; | |
} | |
return MessageUtil.format(MessageUtil.format(Messages.size_format, format.format(obj))); | |
} | |
else if (obj instanceof Date) | |
{ | |
return Messages.date.equals(label.getText()) ? DateFormat.getDateInstance().format(obj) | |
: DateFormat.getTimeInstance().format(obj); | |
} | |
else | |
{ | |
return String.valueOf(obj); | |
} | |
} | |
} | |
return null; | |
} | |
} | |
protected TreeViewer treeViewer; | |
@Override | |
public void createControl(Composite parent) | |
{ | |
treeViewer = new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI); | |
createColumns(); | |
AccessibleCompositeAdapter.access(treeViewer.getTree()); | |
treeViewer.getTree().setLinesVisible(true); | |
treeViewer.getTree().setHeaderVisible(true); | |
treeViewer.setContentProvider(new OutlineContentProvider()); | |
treeViewer.setLabelProvider(new OutlineLabelProvider()); | |
Action copyAction = new Action() | |
{ | |
@Override | |
public void run() | |
{ | |
Copy.copyToClipboard(treeViewer.getTree()); | |
} | |
}; | |
getSite().getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction); | |
getSite().getActionBars().updateActionBars(); | |
updateSnapshotInput(); | |
} | |
protected void createColumns() | |
{ | |
TreeColumn column = new TreeColumn(treeViewer.getTree(), SWT.LEFT); | |
column.setText(Messages.col_property); | |
column.setWidth(160); | |
column = new TreeColumn(treeViewer.getTree(), SWT.LEFT); | |
column.setText(Messages.col_file); | |
column.setWidth(80); | |
} | |
protected void updateSnapshotInput() | |
{ | |
List<Object> elements = new ArrayList<Object>(); | |
SnapshotInfo info = getSnapshot(); | |
if (info != null) | |
{ | |
SnapshotInfo bInfo = getBaseline(); | |
if (bInfo == null) | |
bInfo = new SnapshotInfo(null, null, null, 0, null, 0, 0, 0, 0, 0); | |
// resource property (filename) | |
int p = info.getPath().lastIndexOf(File.separatorChar); | |
String filename = p >= 0 ? info.getPath().substring(p + 1) : info.getPath(); | |
String bFilename = null; | |
if (bInfo.getPath() != null) | |
{ | |
p = bInfo.getPath().lastIndexOf(File.separatorChar); | |
bFilename = p >= 0 ? bInfo.getPath().substring(p + 1) : bInfo.getPath(); | |
} | |
elements.add(new Label(Messages.resource, filename, bFilename)); | |
Category category = new Category(Messages.general_info); | |
elements.add(category); | |
category.addChild(new Label(Messages.format, info.getProperty("$heapFormat"), bInfo //$NON-NLS-1$ | |
.getProperty("$heapFormat"))); //$NON-NLS-1$ | |
category.addChild(new Label(Messages.jvm_version, info.getJvmInfo(), bInfo.getJvmInfo())); | |
category.addChild(new Label(Messages.time, info.getCreationDate(), bInfo.getCreationDate())); | |
category.addChild(new Label(Messages.date, info.getCreationDate(), bInfo.getCreationDate())); | |
category.addChild(new Label(Messages.identifier_size, info.getIdentifierSize(), bInfo.getIdentifierSize())); | |
if (info.getIdentifierSize() == 8 || bInfo.getIdentifierSize() == 8) | |
{ | |
Boolean useCompressedOops = (Boolean) info.getProperty("$useCompressedOops"); //$NON-NLS-1$ | |
Boolean buseCompressedOops = (Boolean) bInfo.getProperty("$useCompressedOops"); //$NON-NLS-1$ | |
if (useCompressedOops != null || buseCompressedOops != null) | |
{ | |
category.addChild(new Label(Messages.use_compressed_oops, useCompressedOops != null ? useCompressedOops.toString() : "", //$NON-NLS-1$ | |
buseCompressedOops != null ? buseCompressedOops.toString() : "")); //$NON-NLS-1$ | |
} | |
} | |
category.addChild(new Label(Messages.file_path, info.getPath(), bInfo.getPath())); | |
Double fileLength = Double.valueOf((double) new File(info.getPath()).length() / (1024 * 1024)); | |
Double bFileLength = bInfo.getPath() != null ? Double.valueOf((double) new File(bInfo.getPath()).length() | |
/ (1024 * 1024)) : null; | |
category.addChild(new Label(Messages.file_length, fileLength, bFileLength)); | |
if (info.getProperty("$runtimeId") != null || bInfo.getProperty("$runtimeId") != null) //$NON-NLS-1$//$NON-NLS-2$ | |
{ | |
category.addChild(new Label(Messages.identifier, info.getProperty("$runtimeId"), bInfo //$NON-NLS-1$ | |
.getProperty("$runtimeId"))); //$NON-NLS-1$ | |
} | |
Serializable discard_ratio = info.getProperty("discard_ratio"); //$NON-NLS-1$ | |
Serializable bDiscard_ratio = bInfo.getProperty("discard_ratio"); //$NON-NLS-1$ | |
if (discard_ratio instanceof Integer && (Integer)discard_ratio != 0 || | |
bDiscard_ratio instanceof Integer && (Integer)bDiscard_ratio != 0) | |
{ | |
category.addChild(new Label(org.eclipse.mat.ui.Messages.UIPreferencePage_DiscardPercentage, discard_ratio, bDiscard_ratio)); | |
Serializable discard_pattern = info.getProperty("discard_pattern"); //$NON-NLS-1$ | |
Serializable bDiscard_pattern = bInfo.getProperty("discard_pattern"); //$NON-NLS-1$ | |
category.addChild(new Label(org.eclipse.mat.ui.Messages.UIPreferencePage_DiscardPattern, discard_pattern, bDiscard_pattern)); | |
Serializable discard_offset = info.getProperty("discard_offset"); //$NON-NLS-1$ | |
Serializable bDiscard_offset = bInfo.getProperty("discard_offset"); //$NON-NLS-1$ | |
category.addChild(new Label(org.eclipse.mat.ui.Messages.UIPreferencePage_DiscardOffset, discard_offset, bDiscard_offset)); | |
Serializable discard_seed = info.getProperty("discard_seed"); //$NON-NLS-1$ | |
Serializable bDiscard_seed = bInfo.getProperty("discard_seed"); //$NON-NLS-1$ | |
category.addChild(new Label(org.eclipse.mat.ui.Messages.UIPreferencePage_DiscardSeed, discard_seed, bDiscard_seed)); | |
} | |
String note = getNotes(info); | |
String noteb = getNotes(bInfo); | |
if (note != null || noteb != null) | |
category.addChild(new Label(Messages.notes, note, noteb)); | |
category = new Category(Messages.statistic_info); | |
elements.add(category); | |
category.addChild(new Label(Messages.heap, info.getUsedHeapSize(), bInfo.getUsedHeapSize())); | |
category.addChild(new Label(Messages.number_of_objects, info.getNumberOfObjects(), bInfo | |
.getNumberOfObjects())); | |
category.addChild(new Label(Messages.number_of_classes, info.getNumberOfClasses(), bInfo | |
.getNumberOfClasses())); | |
category.addChild(new Label(Messages.number_of_classloaders, info.getNumberOfClassLoaders(), bInfo | |
.getNumberOfClassLoaders())); | |
category.addChild(new Label(Messages.number_of_gc_roots, info.getNumberOfGCRoots(), bInfo | |
.getNumberOfGCRoots())); | |
long u[] = unreachableObjects(info); | |
long bU[] = unreachableObjects(bInfo); | |
long unreachableObjects = u[0]; | |
long bUnreachableObjects = bU[0]; | |
if (unreachableObjects > 0 || bUnreachableObjects > 0) | |
{ | |
category.addChild(new Label(Messages.unreachable_heap, u[1], bU[1])); | |
category.addChild(new Label(Messages.number_of_unreachable_objects, unreachableObjects, bUnreachableObjects)); | |
} | |
} | |
else if (getSnapshotPath() != null) | |
{ | |
// create tree from file information | |
IPath path = getSnapshotPath(); | |
File osFile = path.toFile(); | |
elements.add(new Label(Messages.resource, path.lastSegment(), null)); | |
Category category = new Category(Messages.general_info); | |
elements.add(category); | |
category.addChild(new Label(Messages.format, null, null)); | |
category.addChild(new Label(Messages.jvm_version, null, null)); | |
category.addChild(new Label(Messages.time, new Date(osFile.lastModified()), null)); | |
category.addChild(new Label(Messages.date, new Date(osFile.lastModified()), null)); | |
category.addChild(new Label(Messages.identifier_size, null, null)); | |
category.addChild(new Label(Messages.file_path, path.toOSString(), null)); | |
final Double fileLength = Double.valueOf((double) osFile.length() / (1024 * 1024)); | |
category.addChild(new Label(Messages.file_length, fileLength, null)); | |
String note = getNotes(osFile); | |
if (note != null) | |
category.addChild(new Label(Messages.notes, note, null)); | |
} | |
treeViewer.getTree().setRedraw(false); | |
treeViewer.setInput(elements); | |
treeViewer.expandAll(); | |
treeViewer.getTree().setRedraw(true); | |
} | |
private String getNotes(SnapshotInfo info) | |
{ | |
if (info != null) | |
{ | |
String path = info.getPath(); | |
String pfx = info.getPrefix(); | |
if (path == null) | |
return null; | |
// See SnapshotHistoryFile#openFile | |
if (pfx != null && info.getProperty("$runtimeId") != null) //$NON-NLS-1$ | |
{ | |
return getNotes(new File(pfx + "index")); //$NON-NLS-1$ | |
} | |
else | |
{ | |
// New location | |
String notes = getNotes(new File(pfx)); | |
if (notes != null) | |
return notes; | |
// Old location (might be the same) | |
return getNotes(new File(path)); | |
} | |
} | |
return null; | |
} | |
private String getNotes(File snapshotFile) | |
{ | |
String notes; | |
try | |
{ | |
notes = NotesView.readNotes(snapshotFile); | |
} | |
catch (UncheckedIOException e) | |
{ | |
return null; | |
} | |
// Just use the first line | |
if (notes != null) | |
return notes.split("\n", 2)[0]; //$NON-NLS-1$ | |
return null; | |
} | |
private long[] unreachableObjects(SnapshotInfo info) | |
{ | |
long discardedObjects = 0; | |
long discardedHeap = 0; | |
Serializable unreachable = info.getProperty(UnreachableObjectsHistogram.class.getName()); | |
if (unreachable instanceof UnreachableObjectsHistogram) | |
{ | |
UnreachableObjectsHistogram ur = (UnreachableObjectsHistogram)unreachable; | |
for(UnreachableObjectsHistogram.Record r : ur.getRecords()) { | |
discardedObjects += r.getObjectCount(); | |
discardedHeap += r.getShallowHeapSize(); | |
} | |
} | |
return new long[] {discardedObjects,discardedHeap}; | |
} | |
protected abstract IPath getSnapshotPath(); | |
protected abstract SnapshotInfo getSnapshot(); | |
protected abstract SnapshotInfo getBaseline(); | |
@Override | |
public Control getControl() | |
{ | |
return treeViewer.getControl(); | |
} | |
@Override | |
public void setFocus() | |
{ | |
treeViewer.getControl().setFocus(); | |
} | |
public void addSelectionChangedListener(ISelectionChangedListener listener) | |
{ | |
treeViewer.addSelectionChangedListener(listener); | |
} | |
public ISelection getSelection() | |
{ | |
return treeViewer.getSelection(); | |
} | |
public void removeSelectionChangedListener(ISelectionChangedListener listener) | |
{ | |
treeViewer.removeSelectionChangedListener(listener); | |
} | |
public void setSelection(ISelection selection) | |
{ | |
treeViewer.setSelection(selection); | |
} | |
/* | |
* (non-Javadoc) | |
* @see | |
* org.eclipse.ui.part.IPageBookViewPage#init(org.eclipse.ui.part.IPageSite) | |
*/ | |
@Override | |
public void init(IPageSite pageSite) | |
{ | |
super.init(pageSite); | |
pageSite.setSelectionProvider(this); | |
} | |
} |