blob: ce9462b6ea439d6162ae2035636431a76d1fc1f3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brock Janicyak - brockj@tpg.com.au - Fix for Bug 11142
* [HeapStatus] Heap status is updated too frequently
*******************************************************************************/
package org.eclipse.ui.internal;
import java.lang.reflect.Method;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
/**
* The Heap Status control, which shows the heap usage statistics in the window trim.
*
* @since 3.1
*/
public class HeapStatus extends Composite {
private boolean armed;
private Image gcImage;
private Color bgCol, usedMemCol, lowMemCol, freeMemCol, topLeftCol, bottomRightCol, sepCol, textCol, markCol, armCol;
private Canvas button;
private IPreferenceStore prefStore;
private int updateInterval;
private boolean showMax;
private long totalMem;
private long prevTotalMem = -1L;
private long prevUsedMem = -1L;
private boolean hasChanged;
private long usedMem;
private long mark = -1;
// start with 12x12
private Rectangle imgBounds = new Rectangle(0,0,12,12);
private long maxMem = Long.MAX_VALUE;
private boolean maxMemKnown;
private float lowMemThreshold = 0.05f;
private boolean showLowMemThreshold = true;
private final Runnable timer = new Runnable() {
public void run() {
if (!isDisposed()) {
updateStats();
if (hasChanged) {
updateToolTip();
redraw();
hasChanged = false;
}
getDisplay().timerExec(updateInterval, this);
}
}
};
private final IPropertyChangeListener prefListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (IHeapStatusConstants.PREF_UPDATE_INTERVAL.equals(event.getProperty())) {
setUpdateIntervalInMS(prefStore.getInt(IHeapStatusConstants.PREF_UPDATE_INTERVAL));
}
else if (IHeapStatusConstants.PREF_SHOW_MAX.equals(event.getProperty())) {
showMax = prefStore.getBoolean(IHeapStatusConstants.PREF_SHOW_MAX);
}
}
};
/**
* Creates a new heap status control with the given parent, and using
* the given preference store to obtain settings such as the refresh
* interval.
*
* @param parent the parent composite
* @param prefStore the preference store
*/
public HeapStatus(Composite parent, IPreferenceStore prefStore) {
super(parent, SWT.NONE);
maxMem = getMaxMem();
maxMemKnown = maxMem != Long.MAX_VALUE;
this.prefStore = prefStore;
prefStore.addPropertyChangeListener(prefListener);
setUpdateIntervalInMS(prefStore.getInt(IHeapStatusConstants.PREF_UPDATE_INTERVAL));
showMax = prefStore.getBoolean(IHeapStatusConstants.PREF_SHOW_MAX);
button = new Canvas(this, SWT.NONE);
button.setToolTipText(WorkbenchMessages.HeapStatus_buttonToolTip);
ImageDescriptor imageDesc = WorkbenchImages.getWorkbenchImageDescriptor("elcl16/trash.gif"); //$NON-NLS-1$
gcImage = imageDesc.createImage();
if (gcImage != null) {
imgBounds = gcImage.getBounds();
}
Display display = getDisplay();
usedMemCol = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
lowMemCol = new Color(display, 255, 70, 70); // medium red
freeMemCol = new Color(display, 255, 190, 125); // light orange
bgCol = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
sepCol = topLeftCol = armCol = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
bottomRightCol = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
markCol = textCol = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
createContextMenu();
Listener listener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose:
doDispose();
break;
case SWT.Resize:
Rectangle rect = getClientArea();
button.setBounds(rect.width - imgBounds.width - 1, 1, imgBounds.width, rect.height - 2);
break;
case SWT.Paint:
if (event.widget == HeapStatus.this) {
paintComposite(event.gc);
}
else if (event.widget == button) {
paintButton(event.gc);
}
break;
case SWT.MouseUp:
if (event.button == 1) {
gc();
arm(false);
}
break;
case SWT.MouseDown:
if (event.button == 1) {
if (event.widget == HeapStatus.this) {
setMark();
} else if (event.widget == button) {
arm(true);
}
}
break;
case SWT.MouseExit:
arm(false);
break;
}
}
};
addListener(SWT.Dispose, listener);
addListener(SWT.MouseDown, listener);
addListener(SWT.Paint, listener);
addListener(SWT.Resize, listener);
button.addListener(SWT.MouseDown, listener);
button.addListener(SWT.MouseExit, listener);
button.addListener(SWT.MouseUp, listener);
button.addListener(SWT.Paint, listener);
// make sure stats are updated before first paint
updateStats();
getDisplay().asyncExec(new Runnable() {
public void run() {
if (!isDisposed()) {
getDisplay().timerExec(updateInterval, timer);
}
}
});
}
/**
* Returns the maximum memory limit, or Long.MAX_VALUE if the max is not known.
*/
private long getMaxMem() {
long max = Long.MAX_VALUE;
try {
// Must use reflect to allow compilation against JCL/Foundation
Method maxMemMethod = Runtime.class.getMethod("maxMemory", new Class[0]); //$NON-NLS-1$
Object o = maxMemMethod.invoke(Runtime.getRuntime(), new Object[0]);
if (o instanceof Long) {
max = ((Long) o).longValue();
}
}
catch (Exception e) {
// ignore if method missing or if there are other failures trying to determine the max
}
return max;
}
private void setUpdateIntervalInMS(int interval) {
updateInterval = Math.max(100, interval);
}
private void doDispose() {
prefStore.removePropertyChangeListener(prefListener);
if (gcImage != null) {
gcImage.dispose();
}
if (lowMemCol != null) {
lowMemCol.dispose();
}
if (freeMemCol != null) {
freeMemCol.dispose();
}
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
*/
public Point computeSize(int wHint, int hHint, boolean changed) {
GC gc = new GC(this);
Point p = gc.textExtent(WorkbenchMessages.HeapStatus_widthStr);
int height = imgBounds.height;
// choose the largest of
// - Text height + margins
// - Image height + margins
// - Default Trim heightin
height = Math.max(height, p.y) + 4;
height = Math.max(TrimUtil.TRIM_DEFAULT_HEIGHT, height);
gc.dispose();
return new Point(p.x + 15, height);
}
private void arm(boolean armed) {
if (this.armed == armed) {
return;
}
this.armed = armed;
button.redraw();
button.update();
}
/**
* Creates the context menu
*/
private void createContextMenu() {
MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager menuMgr) {
fillMenu(menuMgr);
}
});
Menu menu = menuMgr.createContextMenu(this);
setMenu(menu);
}
private void fillMenu(IMenuManager menuMgr) {
menuMgr.add(new SetMarkAction());
menuMgr.add(new ClearMarkAction());
menuMgr.add(new ShowMaxAction());
menuMgr.add(new CloseHeapStatusAction());
// if (isKyrsoftViewAvailable()) {
// menuMgr.add(new ShowKyrsoftViewAction());
// }
}
/**
* Sets the mark to the current usedMem level.
*/
private void setMark() {
updateStats(); // get up-to-date stats before taking the mark
mark = usedMem;
hasChanged = true;
redraw();
}
/**
* Clears the mark.
*/
private void clearMark() {
mark = -1;
hasChanged = true;
redraw();
}
private void gc() {
BusyIndicator.showWhile(getDisplay(), new Runnable() {
public void run() {
Thread t = new Thread() {
public void run() {
busyGC();
}};
t.start();
while(t.isAlive()) {
try {
Display d = getDisplay();
while(d != null && !d.isDisposed() && d.readAndDispatch()) {
// loop
}
t.join(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
});
}
private void busyGC() {
for (int i = 0; i < 2; ++i) {
System.gc();
System.runFinalization();
}
}
private void paintButton(GC gc) {
Rectangle rect = button.getClientArea();
if (armed) {
gc.setBackground(armCol);
gc.fillRectangle(rect.x, rect.y, rect.width, rect.height);
}
if (gcImage != null) {
int by = (rect.height - imgBounds.height) / 2 + rect.y; // button y
gc.drawImage(gcImage, rect.x, by);
}
}
private void paintComposite(GC gc) {
if (showMax && maxMemKnown) {
paintCompositeMaxKnown(gc);
} else {
paintCompositeMaxUnknown(gc);
}
}
private void paintCompositeMaxUnknown(GC gc) {
Rectangle rect = getClientArea();
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
int bw = imgBounds.width; // button width
int dx = x + w - bw - 2; // divider x
int sw = w - bw - 3; // status width
int uw = (int) (sw * usedMem / totalMem); // used mem width
int ux = x + 1 + uw; // used mem right edge
gc.setBackground(bgCol);
gc.fillRectangle(rect);
gc.setForeground(sepCol);
gc.drawLine(dx, y, dx, y + h);
gc.drawLine(ux, y, ux, y + h);
gc.setForeground(topLeftCol);
gc.drawLine(x, y, x+w, y);
gc.drawLine(x, y, x, y+h);
gc.setForeground(bottomRightCol);
gc.drawLine(x+w-1, y, x+w-1, y+h);
gc.drawLine(x, y+h-1, x+w, y+h-1);
gc.setBackground(usedMemCol);
gc.fillRectangle(x + 1, y + 1, uw, h - 2);
String s = NLS.bind(WorkbenchMessages.HeapStatus_status, convertToMegString(usedMem), convertToMegString(totalMem));
Point p = gc.textExtent(s);
int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1;
int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1;
gc.setForeground(textCol);
gc.drawString(s, sx, sy, true);
// draw an I-shaped bar in the foreground colour for the mark (if present)
if (mark != -1) {
int ssx = (int) (sw * mark / totalMem) + x + 1;
paintMark(gc, ssx, y, h);
}
}
private void paintCompositeMaxKnown(GC gc) {
Rectangle rect = getClientArea();
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
int bw = imgBounds.width; // button width
int dx = x + w - bw - 2; // divider x
int sw = w - bw - 3; // status width
int uw = (int) (sw * usedMem / maxMem); // used mem width
int ux = x + 1 + uw; // used mem right edge
int tw = (int) (sw * totalMem / maxMem); // current total mem width
int tx = x + 1 + tw; // current total mem right edge
gc.setBackground(bgCol);
gc.fillRectangle(rect);
gc.setForeground(sepCol);
gc.drawLine(dx, y, dx, y + h);
gc.drawLine(ux, y, ux, y + h);
gc.drawLine(tx, y, tx, y + h);
gc.setForeground(topLeftCol);
gc.drawLine(x, y, x+w, y);
gc.drawLine(x, y, x, y+h);
gc.setForeground(bottomRightCol);
gc.drawLine(x+w-1, y, x+w-1, y+h);
gc.drawLine(x, y+h-1, x+w, y+h-1);
if (lowMemThreshold != 0 && ((double)(maxMem - usedMem) / (double)maxMem < lowMemThreshold)) {
gc.setBackground(lowMemCol);
} else {
gc.setBackground(usedMemCol);
}
gc.fillRectangle(x + 1, y + 1, uw, h - 2);
gc.setBackground(freeMemCol);
gc.fillRectangle(ux + 1, y + 1, tx - (ux + 1), h - 2);
// paint line for low memory threshold
if (showLowMemThreshold && lowMemThreshold != 0) {
gc.setForeground(lowMemCol);
int thresholdX = x + 1 + (int) (sw * (1.0 - lowMemThreshold));
gc.drawLine(thresholdX, y + 1, thresholdX, y + h - 2);
}
String s = NLS.bind(WorkbenchMessages.HeapStatus_status,
convertToMegString(usedMem), convertToMegString(totalMem));
Point p = gc.textExtent(s);
int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1;
int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1;
gc.setForeground(textCol);
gc.drawString(s, sx, sy, true);
// draw an I-shaped bar in the foreground colour for the mark (if present)
if (mark != -1) {
int ssx = (int) (sw * mark / maxMem) + x + 1;
paintMark(gc, ssx, y, h);
}
}
private void paintMark(GC gc, int x, int y, int h) {
gc.setForeground(markCol);
gc.drawLine(x, y+1, x, y+h-2);
gc.drawLine(x-1, y+1, x+1, y+1);
gc.drawLine(x-1, y+h-2, x+1, y+h-2);
}
private void updateStats() {
Runtime runtime = Runtime.getRuntime();
totalMem = runtime.totalMemory();
long freeMem = runtime.freeMemory();
usedMem = totalMem - freeMem;
if (convertToMeg(prevUsedMem) != convertToMeg(usedMem)) {
prevUsedMem = usedMem;
this.hasChanged = true;
}
if (prevTotalMem != totalMem) {
prevTotalMem = totalMem;
this.hasChanged = true;
}
}
private void updateToolTip() {
String usedStr = convertToMegString(usedMem);
String totalStr = convertToMegString(totalMem);
String maxStr = maxMemKnown ? convertToMegString(maxMem) : WorkbenchMessages.HeapStatus_maxUnknown;
String markStr = mark == -1 ? WorkbenchMessages.HeapStatus_noMark : convertToMegString(mark);
String toolTip = NLS.bind(WorkbenchMessages.HeapStatus_memoryToolTip, new Object[] { usedStr, totalStr, maxStr, markStr });
if (!toolTip.equals(getToolTipText())) {
setToolTipText(toolTip);
}
}
/**
* Converts the given number of bytes to a printable number of megabytes (rounded up).
*/
private String convertToMegString(long numBytes) {
return NLS.bind(WorkbenchMessages.HeapStatus_meg, new Long(convertToMeg(numBytes)));
}
/**
* Converts the given number of bytes to the corresponding number of megabytes (rounded up).
*/
private long convertToMeg(long numBytes) {
return (numBytes + (512 * 1024)) / (1024 * 1024);
}
class SetMarkAction extends Action {
SetMarkAction() {
super(WorkbenchMessages.SetMarkAction_text);
}
public void run() {
setMark();
}
}
class ClearMarkAction extends Action {
ClearMarkAction() {
super(WorkbenchMessages.ClearMarkAction_text);
}
public void run() {
clearMark();
}
}
class ShowMaxAction extends Action {
ShowMaxAction() {
super(WorkbenchMessages.ShowMaxAction_text, IAction.AS_CHECK_BOX);
setEnabled(maxMemKnown);
setChecked(showMax);
}
public void run() {
prefStore.setValue(IHeapStatusConstants.PREF_SHOW_MAX, isChecked());
redraw();
}
}
class CloseHeapStatusAction extends Action{
CloseHeapStatusAction(){
super(WorkbenchMessages.WorkbenchWindow_close );
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.IAction#run()
*/
public void run(){
dispose();
}
}
// /**
// * Returns whether the Kyrsoft memory monitor view is available.
// *
// * @return <code>true</code> if available, <code>false</code> otherwise
// */
// private boolean isKyrsoftViewAvailable() {
// return (Platform.getBundle(IHeapStatusConstants.KYRSOFT_PLUGIN_ID) != null) && PlatformUI.getWorkbench().getViewRegistry().find(IHeapStatusConstants.KYRSOFT_VIEW_ID) != null;
// }
//
// class ShowKyrsoftViewAction extends Action {
// ShowKyrsoftViewAction() {
// super(WorkbenchMessages.ShowKyrsoftViewAction_text);
// }
// public void run() {
// if (!isKyrsoftViewAvailable()) {
// MessageDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, WorkbenchMessages.ShowKyrsoftViewAction_KyrsoftNotInstalled);
// return;
// }
// IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
// IWorkbenchPage page = window == null ? null : window.getActivePage();
// if (page == null) {
// MessageDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, WorkbenchMessages.ShowKyrsoftViewAction_OpenPerspectiveFirst);
// return;
// }
// try {
// page.showView(IHeapStatusConstants.KYRSOFT_VIEW_ID);
// }
// catch (PartInitException e) {
// String msg = WorkbenchMessages.ShowKyrsoftViewAction_ErrorShowingKyrsoftView;
// IStatus status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0, msg, e);
// ErrorDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, msg, status);
// }
//
// }
// }
}