001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: * Brock Janicyak - brockj@tpg.com.au - Fix for Bug 11142
011: * [HeapStatus] Heap status is updated too frequently
012: *******************************************************************************/package org.eclipse.ui.internal;
013:
014: import java.lang.reflect.Method;
015:
016: import org.eclipse.jface.action.Action;
017: import org.eclipse.jface.action.IAction;
018: import org.eclipse.jface.action.IMenuListener;
019: import org.eclipse.jface.action.IMenuManager;
020: import org.eclipse.jface.action.MenuManager;
021: import org.eclipse.jface.preference.IPreferenceStore;
022: import org.eclipse.jface.resource.ImageDescriptor;
023: import org.eclipse.jface.util.IPropertyChangeListener;
024: import org.eclipse.jface.util.PropertyChangeEvent;
025: import org.eclipse.osgi.util.NLS;
026: import org.eclipse.swt.SWT;
027: import org.eclipse.swt.custom.BusyIndicator;
028: import org.eclipse.swt.graphics.Color;
029: import org.eclipse.swt.graphics.GC;
030: import org.eclipse.swt.graphics.Image;
031: import org.eclipse.swt.graphics.Point;
032: import org.eclipse.swt.graphics.Rectangle;
033: import org.eclipse.swt.widgets.Canvas;
034: import org.eclipse.swt.widgets.Composite;
035: import org.eclipse.swt.widgets.Display;
036: import org.eclipse.swt.widgets.Event;
037: import org.eclipse.swt.widgets.Listener;
038: import org.eclipse.swt.widgets.Menu;
039:
040: /**
041: * The Heap Status control, which shows the heap usage statistics in the window trim.
042: *
043: * @since 3.1
044: */
045: public class HeapStatus extends Composite {
046:
047: private boolean armed;
048: private Image gcImage;
049: private Color bgCol, usedMemCol, lowMemCol, freeMemCol, topLeftCol,
050: bottomRightCol, sepCol, textCol, markCol, armCol;
051: private Canvas button;
052: private IPreferenceStore prefStore;
053: private int updateInterval;
054: private boolean showMax;
055: private long totalMem;
056: private long prevTotalMem = -1L;
057: private long prevUsedMem = -1L;
058: private boolean hasChanged;
059: private long usedMem;
060: private long mark = -1;
061: // start with 12x12
062: private Rectangle imgBounds = new Rectangle(0, 0, 12, 12);
063: private long maxMem = Long.MAX_VALUE;
064: private boolean maxMemKnown;
065: private float lowMemThreshold = 0.05f;
066: private boolean showLowMemThreshold = true;
067:
068: private final Runnable timer = new Runnable() {
069: public void run() {
070: if (!isDisposed()) {
071: updateStats();
072: if (hasChanged) {
073: updateToolTip();
074: redraw();
075: hasChanged = false;
076: }
077: getDisplay().timerExec(updateInterval, this );
078: }
079: }
080: };
081:
082: private final IPropertyChangeListener prefListener = new IPropertyChangeListener() {
083: public void propertyChange(PropertyChangeEvent event) {
084: if (IHeapStatusConstants.PREF_UPDATE_INTERVAL.equals(event
085: .getProperty())) {
086: setUpdateIntervalInMS(prefStore
087: .getInt(IHeapStatusConstants.PREF_UPDATE_INTERVAL));
088: } else if (IHeapStatusConstants.PREF_SHOW_MAX.equals(event
089: .getProperty())) {
090: showMax = prefStore
091: .getBoolean(IHeapStatusConstants.PREF_SHOW_MAX);
092: }
093: }
094: };
095:
096: /**
097: * Creates a new heap status control with the given parent, and using
098: * the given preference store to obtain settings such as the refresh
099: * interval.
100: *
101: * @param parent the parent composite
102: * @param prefStore the preference store
103: */
104: public HeapStatus(Composite parent, IPreferenceStore prefStore) {
105: super (parent, SWT.NONE);
106:
107: maxMem = getMaxMem();
108: maxMemKnown = maxMem != Long.MAX_VALUE;
109:
110: this .prefStore = prefStore;
111: prefStore.addPropertyChangeListener(prefListener);
112:
113: setUpdateIntervalInMS(prefStore
114: .getInt(IHeapStatusConstants.PREF_UPDATE_INTERVAL));
115: showMax = prefStore
116: .getBoolean(IHeapStatusConstants.PREF_SHOW_MAX);
117:
118: button = new Canvas(this , SWT.NONE);
119: button
120: .setToolTipText(WorkbenchMessages.HeapStatus_buttonToolTip);
121:
122: ImageDescriptor imageDesc = WorkbenchImages
123: .getWorkbenchImageDescriptor("elcl16/trash.gif"); //$NON-NLS-1$
124: gcImage = imageDesc.createImage();
125: if (gcImage != null) {
126: imgBounds = gcImage.getBounds();
127: }
128: Display display = getDisplay();
129: usedMemCol = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
130: lowMemCol = new Color(display, 255, 70, 70); // medium red
131: freeMemCol = new Color(display, 255, 190, 125); // light orange
132: bgCol = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
133: sepCol = topLeftCol = armCol = display
134: .getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
135: bottomRightCol = display
136: .getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
137: markCol = textCol = display
138: .getSystemColor(SWT.COLOR_INFO_FOREGROUND);
139:
140: createContextMenu();
141:
142: Listener listener = new Listener() {
143:
144: public void handleEvent(Event event) {
145: switch (event.type) {
146: case SWT.Dispose:
147: doDispose();
148: break;
149: case SWT.Resize:
150: Rectangle rect = getClientArea();
151: button.setBounds(rect.width - imgBounds.width - 1,
152: 1, imgBounds.width, rect.height - 2);
153: break;
154: case SWT.Paint:
155: if (event.widget == HeapStatus.this ) {
156: paintComposite(event.gc);
157: } else if (event.widget == button) {
158: paintButton(event.gc);
159: }
160: break;
161: case SWT.MouseUp:
162: if (event.button == 1) {
163: gc();
164: arm(false);
165: }
166: break;
167: case SWT.MouseDown:
168: if (event.button == 1) {
169: if (event.widget == HeapStatus.this ) {
170: setMark();
171: } else if (event.widget == button) {
172: arm(true);
173: }
174: }
175: break;
176: case SWT.MouseExit:
177: arm(false);
178: break;
179: }
180: }
181:
182: };
183: addListener(SWT.Dispose, listener);
184: addListener(SWT.MouseDown, listener);
185: addListener(SWT.Paint, listener);
186: addListener(SWT.Resize, listener);
187: button.addListener(SWT.MouseDown, listener);
188: button.addListener(SWT.MouseExit, listener);
189: button.addListener(SWT.MouseUp, listener);
190: button.addListener(SWT.Paint, listener);
191:
192: // make sure stats are updated before first paint
193: updateStats();
194:
195: getDisplay().asyncExec(new Runnable() {
196: public void run() {
197: if (!isDisposed()) {
198: getDisplay().timerExec(updateInterval, timer);
199: }
200: }
201: });
202: }
203:
204: /**
205: * Returns the maximum memory limit, or Long.MAX_VALUE if the max is not known.
206: */
207: private long getMaxMem() {
208: long max = Long.MAX_VALUE;
209: try {
210: // Must use reflect to allow compilation against JCL/Foundation
211: Method maxMemMethod = Runtime.class.getMethod(
212: "maxMemory", new Class[0]); //$NON-NLS-1$
213: Object o = maxMemMethod.invoke(Runtime.getRuntime(),
214: new Object[0]);
215: if (o instanceof Long) {
216: max = ((Long) o).longValue();
217: }
218: } catch (Exception e) {
219: // ignore if method missing or if there are other failures trying to determine the max
220: }
221: return max;
222: }
223:
224: private void setUpdateIntervalInMS(int interval) {
225: updateInterval = Math.max(100, interval);
226: }
227:
228: private void doDispose() {
229: prefStore.removePropertyChangeListener(prefListener);
230: if (gcImage != null) {
231: gcImage.dispose();
232: }
233:
234: if (lowMemCol != null) {
235: lowMemCol.dispose();
236: }
237: if (freeMemCol != null) {
238: freeMemCol.dispose();
239: }
240: }
241:
242: /* (non-Javadoc)
243: * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
244: */
245: public Point computeSize(int wHint, int hHint, boolean changed) {
246: GC gc = new GC(this );
247: Point p = gc.textExtent(WorkbenchMessages.HeapStatus_widthStr);
248: int height = imgBounds.height;
249: // choose the largest of
250: // - Text height + margins
251: // - Image height + margins
252: // - Default Trim heightin
253: height = Math.max(height, p.y) + 4;
254: height = Math.max(TrimUtil.TRIM_DEFAULT_HEIGHT, height);
255: gc.dispose();
256: return new Point(p.x + 15, height);
257: }
258:
259: private void arm(boolean armed) {
260: if (this .armed == armed) {
261: return;
262: }
263: this .armed = armed;
264: button.redraw();
265: button.update();
266: }
267:
268: /**
269: * Creates the context menu
270: */
271: private void createContextMenu() {
272: MenuManager menuMgr = new MenuManager();
273: menuMgr.setRemoveAllWhenShown(true);
274: menuMgr.addMenuListener(new IMenuListener() {
275: public void menuAboutToShow(IMenuManager menuMgr) {
276: fillMenu(menuMgr);
277: }
278: });
279: Menu menu = menuMgr.createContextMenu(this );
280: setMenu(menu);
281: }
282:
283: private void fillMenu(IMenuManager menuMgr) {
284: menuMgr.add(new SetMarkAction());
285: menuMgr.add(new ClearMarkAction());
286: menuMgr.add(new ShowMaxAction());
287: menuMgr.add(new CloseHeapStatusAction());
288: // if (isKyrsoftViewAvailable()) {
289: // menuMgr.add(new ShowKyrsoftViewAction());
290: // }
291: }
292:
293: /**
294: * Sets the mark to the current usedMem level.
295: */
296: private void setMark() {
297: updateStats(); // get up-to-date stats before taking the mark
298: mark = usedMem;
299: hasChanged = true;
300: redraw();
301: }
302:
303: /**
304: * Clears the mark.
305: */
306: private void clearMark() {
307: mark = -1;
308: hasChanged = true;
309: redraw();
310: }
311:
312: private void gc() {
313: BusyIndicator.showWhile(getDisplay(), new Runnable() {
314: public void run() {
315: Thread t = new Thread() {
316: public void run() {
317: busyGC();
318: }
319: };
320: t.start();
321: while (t.isAlive()) {
322: try {
323: Display d = getDisplay();
324: while (d != null && !d.isDisposed()
325: && d.readAndDispatch()) {
326: // loop
327: }
328: t.join(10);
329: } catch (InterruptedException e) {
330: Thread.currentThread().interrupt();
331: }
332: }
333: }
334: });
335: }
336:
337: private void busyGC() {
338: for (int i = 0; i < 2; ++i) {
339: System.gc();
340: System.runFinalization();
341: }
342: }
343:
344: private void paintButton(GC gc) {
345: Rectangle rect = button.getClientArea();
346:
347: if (armed) {
348: gc.setBackground(armCol);
349: gc.fillRectangle(rect.x, rect.y, rect.width, rect.height);
350: }
351: if (gcImage != null) {
352: int by = (rect.height - imgBounds.height) / 2 + rect.y; // button y
353: gc.drawImage(gcImage, rect.x, by);
354: }
355: }
356:
357: private void paintComposite(GC gc) {
358: if (showMax && maxMemKnown) {
359: paintCompositeMaxKnown(gc);
360: } else {
361: paintCompositeMaxUnknown(gc);
362: }
363: }
364:
365: private void paintCompositeMaxUnknown(GC gc) {
366: Rectangle rect = getClientArea();
367: int x = rect.x;
368: int y = rect.y;
369: int w = rect.width;
370: int h = rect.height;
371: int bw = imgBounds.width; // button width
372: int dx = x + w - bw - 2; // divider x
373: int sw = w - bw - 3; // status width
374: int uw = (int) (sw * usedMem / totalMem); // used mem width
375: int ux = x + 1 + uw; // used mem right edge
376:
377: gc.setBackground(bgCol);
378: gc.fillRectangle(rect);
379: gc.setForeground(sepCol);
380: gc.drawLine(dx, y, dx, y + h);
381: gc.drawLine(ux, y, ux, y + h);
382: gc.setForeground(topLeftCol);
383: gc.drawLine(x, y, x + w, y);
384: gc.drawLine(x, y, x, y + h);
385: gc.setForeground(bottomRightCol);
386: gc.drawLine(x + w - 1, y, x + w - 1, y + h);
387: gc.drawLine(x, y + h - 1, x + w, y + h - 1);
388:
389: gc.setBackground(usedMemCol);
390: gc.fillRectangle(x + 1, y + 1, uw, h - 2);
391:
392: String s = NLS.bind(WorkbenchMessages.HeapStatus_status,
393: convertToMegString(usedMem),
394: convertToMegString(totalMem));
395: Point p = gc.textExtent(s);
396: int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1;
397: int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1;
398: gc.setForeground(textCol);
399: gc.drawString(s, sx, sy, true);
400:
401: // draw an I-shaped bar in the foreground colour for the mark (if present)
402: if (mark != -1) {
403: int ssx = (int) (sw * mark / totalMem) + x + 1;
404: paintMark(gc, ssx, y, h);
405: }
406: }
407:
408: private void paintCompositeMaxKnown(GC gc) {
409: Rectangle rect = getClientArea();
410: int x = rect.x;
411: int y = rect.y;
412: int w = rect.width;
413: int h = rect.height;
414: int bw = imgBounds.width; // button width
415: int dx = x + w - bw - 2; // divider x
416: int sw = w - bw - 3; // status width
417: int uw = (int) (sw * usedMem / maxMem); // used mem width
418: int ux = x + 1 + uw; // used mem right edge
419: int tw = (int) (sw * totalMem / maxMem); // current total mem width
420: int tx = x + 1 + tw; // current total mem right edge
421:
422: gc.setBackground(bgCol);
423: gc.fillRectangle(rect);
424: gc.setForeground(sepCol);
425: gc.drawLine(dx, y, dx, y + h);
426: gc.drawLine(ux, y, ux, y + h);
427: gc.drawLine(tx, y, tx, y + h);
428: gc.setForeground(topLeftCol);
429: gc.drawLine(x, y, x + w, y);
430: gc.drawLine(x, y, x, y + h);
431: gc.setForeground(bottomRightCol);
432: gc.drawLine(x + w - 1, y, x + w - 1, y + h);
433: gc.drawLine(x, y + h - 1, x + w, y + h - 1);
434:
435: if (lowMemThreshold != 0
436: && ((double) (maxMem - usedMem) / (double) maxMem < lowMemThreshold)) {
437: gc.setBackground(lowMemCol);
438: } else {
439: gc.setBackground(usedMemCol);
440: }
441: gc.fillRectangle(x + 1, y + 1, uw, h - 2);
442:
443: gc.setBackground(freeMemCol);
444: gc.fillRectangle(ux + 1, y + 1, tx - (ux + 1), h - 2);
445:
446: // paint line for low memory threshold
447: if (showLowMemThreshold && lowMemThreshold != 0) {
448: gc.setForeground(lowMemCol);
449: int thresholdX = x + 1
450: + (int) (sw * (1.0 - lowMemThreshold));
451: gc.drawLine(thresholdX, y + 1, thresholdX, y + h - 2);
452: }
453:
454: String s = NLS.bind(WorkbenchMessages.HeapStatus_status,
455: convertToMegString(usedMem),
456: convertToMegString(totalMem));
457: Point p = gc.textExtent(s);
458: int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1;
459: int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1;
460: gc.setForeground(textCol);
461: gc.drawString(s, sx, sy, true);
462:
463: // draw an I-shaped bar in the foreground colour for the mark (if present)
464: if (mark != -1) {
465: int ssx = (int) (sw * mark / maxMem) + x + 1;
466: paintMark(gc, ssx, y, h);
467: }
468: }
469:
470: private void paintMark(GC gc, int x, int y, int h) {
471: gc.setForeground(markCol);
472: gc.drawLine(x, y + 1, x, y + h - 2);
473: gc.drawLine(x - 1, y + 1, x + 1, y + 1);
474: gc.drawLine(x - 1, y + h - 2, x + 1, y + h - 2);
475: }
476:
477: private void updateStats() {
478: Runtime runtime = Runtime.getRuntime();
479: totalMem = runtime.totalMemory();
480: long freeMem = runtime.freeMemory();
481: usedMem = totalMem - freeMem;
482:
483: if (convertToMeg(prevUsedMem) != convertToMeg(usedMem)) {
484: prevUsedMem = usedMem;
485: this .hasChanged = true;
486: }
487:
488: if (prevTotalMem != totalMem) {
489: prevTotalMem = totalMem;
490: this .hasChanged = true;
491: }
492: }
493:
494: private void updateToolTip() {
495: String usedStr = convertToMegString(usedMem);
496: String totalStr = convertToMegString(totalMem);
497: String maxStr = maxMemKnown ? convertToMegString(maxMem)
498: : WorkbenchMessages.HeapStatus_maxUnknown;
499: String markStr = mark == -1 ? WorkbenchMessages.HeapStatus_noMark
500: : convertToMegString(mark);
501: String toolTip = NLS.bind(
502: WorkbenchMessages.HeapStatus_memoryToolTip,
503: new Object[] { usedStr, totalStr, maxStr, markStr });
504: if (!toolTip.equals(getToolTipText())) {
505: setToolTipText(toolTip);
506: }
507: }
508:
509: /**
510: * Converts the given number of bytes to a printable number of megabytes (rounded up).
511: */
512: private String convertToMegString(long numBytes) {
513: return NLS.bind(WorkbenchMessages.HeapStatus_meg, new Long(
514: convertToMeg(numBytes)));
515: }
516:
517: /**
518: * Converts the given number of bytes to the corresponding number of megabytes (rounded up).
519: */
520: private long convertToMeg(long numBytes) {
521: return (numBytes + (512 * 1024)) / (1024 * 1024);
522: }
523:
524: class SetMarkAction extends Action {
525: SetMarkAction() {
526: super (WorkbenchMessages.SetMarkAction_text);
527: }
528:
529: public void run() {
530: setMark();
531: }
532: }
533:
534: class ClearMarkAction extends Action {
535: ClearMarkAction() {
536: super (WorkbenchMessages.ClearMarkAction_text);
537: }
538:
539: public void run() {
540: clearMark();
541: }
542: }
543:
544: class ShowMaxAction extends Action {
545: ShowMaxAction() {
546: super (WorkbenchMessages.ShowMaxAction_text,
547: IAction.AS_CHECK_BOX);
548: setEnabled(maxMemKnown);
549: setChecked(showMax);
550: }
551:
552: public void run() {
553: prefStore.setValue(IHeapStatusConstants.PREF_SHOW_MAX,
554: isChecked());
555: redraw();
556: }
557: }
558:
559: class CloseHeapStatusAction extends Action {
560:
561: CloseHeapStatusAction() {
562: super (WorkbenchMessages.WorkbenchWindow_close);
563: }
564:
565: /* (non-Javadoc)
566: * @see org.eclipse.jface.action.IAction#run()
567: */
568: public void run() {
569: dispose();
570: }
571: }
572:
573: // /**
574: // * Returns whether the Kyrsoft memory monitor view is available.
575: // *
576: // * @return <code>true</code> if available, <code>false</code> otherwise
577: // */
578: // private boolean isKyrsoftViewAvailable() {
579: // return (Platform.getBundle(IHeapStatusConstants.KYRSOFT_PLUGIN_ID) != null) && PlatformUI.getWorkbench().getViewRegistry().find(IHeapStatusConstants.KYRSOFT_VIEW_ID) != null;
580: // }
581: //
582: // class ShowKyrsoftViewAction extends Action {
583: // ShowKyrsoftViewAction() {
584: // super(WorkbenchMessages.ShowKyrsoftViewAction_text);
585: // }
586: // public void run() {
587: // if (!isKyrsoftViewAvailable()) {
588: // MessageDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, WorkbenchMessages.ShowKyrsoftViewAction_KyrsoftNotInstalled);
589: // return;
590: // }
591: // IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
592: // IWorkbenchPage page = window == null ? null : window.getActivePage();
593: // if (page == null) {
594: // MessageDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, WorkbenchMessages.ShowKyrsoftViewAction_OpenPerspectiveFirst);
595: // return;
596: // }
597: // try {
598: // page.showView(IHeapStatusConstants.KYRSOFT_VIEW_ID);
599: // }
600: // catch (PartInitException e) {
601: // String msg = WorkbenchMessages.ShowKyrsoftViewAction_ErrorShowingKyrsoftView;
602: // IStatus status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0, msg, e);
603: // ErrorDialog.openError(getShell(), WorkbenchMessages.HeapStatus_Error, msg, status);
604: // }
605: //
606: // }
607: // }
608:
609: }
|