| /******************************************************************************* |
| * Copyright (c) 2005, 2007 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 |
| * - Fix for Bug 192996 [Workbench] Reduce amount of garbage created by HeapStatus |
| *******************************************************************************/ |
| |
| 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 boolean updateTooltip = false; |
| |
| private final Runnable timer = new Runnable() { |
| public void run() { |
| if (!isDisposed()) { |
| updateStats(); |
| if (hasChanged) { |
| if (updateTooltip) { |
| 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.MouseEnter: |
| HeapStatus.this.updateTooltip = true; |
| updateToolTip(); |
| break; |
| case SWT.MouseExit: |
| if (event.widget == HeapStatus.this) { |
| HeapStatus.this.updateTooltip = false; |
| } else if (event.widget == button) { |
| arm(false); |
| } |
| break; |
| } |
| } |
| |
| }; |
| addListener(SWT.Dispose, listener); |
| addListener(SWT.MouseDown, listener); |
| addListener(SWT.Paint, listener); |
| addListener(SWT.Resize, listener); |
| addListener(SWT.MouseEnter, listener); |
| addListener(SWT.MouseExit, 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); |
| // } |
| // |
| // } |
| // } |
| |
| } |
| |