| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.openide.actions; |
| |
| import java.awt.AWTEvent; |
| import java.awt.BasicStroke; |
| import java.awt.Color; |
| import java.awt.Dimension; |
| import java.awt.Font; |
| import java.awt.FontMetrics; |
| import java.awt.Graphics; |
| import java.awt.Graphics2D; |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.MouseEvent; |
| import java.awt.font.GlyphVector; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Path2D; |
| import java.awt.geom.Rectangle2D; |
| import java.text.MessageFormat; |
| import java.util.Arrays; |
| import java.util.prefs.Preferences; |
| import javax.swing.JCheckBoxMenuItem; |
| import javax.swing.JComponent; |
| import javax.swing.JPopupMenu; |
| import javax.swing.SwingUtilities; |
| import javax.swing.Timer; |
| import javax.swing.UIManager; |
| import org.openide.util.NbBundle; |
| import org.openide.util.NbPreferences; |
| |
| /** |
| * Implements a widget to show the current heap size / max and to trigger a manual GC. |
| * Appearance can be customized using the following properties in the LAF |
| * <ul> |
| * <li> nb.heapview.background - Color of widget background |
| * <li> nb.heapview.foreground - Color of text |
| * <li> nb.heapview.chart - Color of area chart |
| * <li> nb.heapview.background - Color of outline around the text, to provide a contrast against |
| * the chart (may have a non-opaque alpha value) |
| * </ul> |
| * @author sky, radim, peter |
| */ |
| class HeapView extends JComponent { |
| |
| private static final boolean AUTOMATIC_REFRESH = System.getProperty("org.netbeans.log.startup") == null; |
| |
| /* |
| * How often the display is updated. |
| */ |
| private static final int TICK = 1500; |
| |
| /** |
| * Foreground color for the chart. |
| */ |
| private static final Color CHART_COLOR; |
| |
| /** |
| * Color for text. |
| */ |
| private static final Color TEXT_COLOR; |
| |
| /** |
| * Color for an outline around the text. |
| */ |
| private static final Color OUTLINE_COLOR; |
| |
| /** |
| * Color for the background. |
| */ |
| private static final Color BACKGROUND_COLOR; |
| |
| /** |
| * Number of samples to retain in history. |
| */ |
| private static final int GRAPH_COUNT = 100; |
| |
| /** |
| * Key for the Show Text preference. |
| */ |
| private static final String SHOW_TEXT = "showText"; |
| |
| static { |
| //init colors |
| Color c = UIManager.getColor("nb.heapview.chart"); //NOI18N |
| if (null == c) { |
| c = new Color(0x2E90E8); |
| } |
| CHART_COLOR = c; |
| |
| c = UIManager.getColor("nb.heapview.foreground"); //NOI18N |
| if (null == c) { |
| c = Color.DARK_GRAY; |
| } |
| TEXT_COLOR = c; |
| |
| c = UIManager.getColor("nb.heapview.highlight"); //NOI18N |
| if (null == c) { |
| c = new Color(255, 255, 255, 192); |
| } |
| OUTLINE_COLOR = c; |
| |
| c = UIManager.getColor("nb.heapview.background"); //NOI18N |
| if (null == c) { |
| c = new Color(0xCEDBE6); |
| } |
| BACKGROUND_COLOR = c; |
| } |
| |
| /** |
| * MessageFormat used to generate text. |
| */ |
| private final MessageFormat format; |
| |
| /** |
| * Data for the graph as a percentage of the heap used. |
| * It is a circular buffer. |
| */ |
| private final long[] graph = new long[GRAPH_COUNT]; |
| |
| /** |
| * Index into graph for the next tick. |
| */ |
| private int graphIndex; |
| |
| /** |
| * Last total heap size. |
| */ |
| private long lastTotal; |
| |
| /** |
| * Timer used to update data. |
| */ |
| private Timer updateTimer; |
| |
| /** |
| * Max width needed to display 999.9/999.9MB. Used to calculate pref size. |
| */ |
| private int maxTextWidth; |
| |
| /** |
| * Current text being displayed. |
| */ |
| private String heapSizeText; |
| |
| |
| public HeapView() { |
| format = new MessageFormat("{0,choice,0#{0,number,0.0}|999<{0,number,0}}/{1,choice,0#{1,number,0.0}|999<{1,number,0}}MB"); |
| heapSizeText = ""; |
| // Enable mouse events. This is the equivalent to adding a mouse |
| // listener. |
| enableEvents(AWTEvent.MOUSE_EVENT_MASK); |
| setToolTipText(NbBundle.getMessage(GarbageCollectAction.class, "CTL_GC")); |
| updateUI(); |
| } |
| |
| /** |
| * Updates the look and feel for this component. |
| */ |
| @Override |
| public void updateUI() { |
| Font f = UIManager.getFont("Label.font"); |
| setFont(f); |
| /* Setting this true seems to cause some painting artifacts on 150% scaling, as we don't |
| always manage to fill every device pixel with the background color. So leave it off. */ |
| setOpaque(false); |
| } |
| |
| /** |
| * Sets whether the text displaying the heap size should be shown. The |
| * default is true. |
| * |
| * @param showText whether the text displaying the heap size should be |
| * shown. |
| */ |
| public void setShowText(boolean showText) { |
| prefs().putBoolean(SHOW_TEXT, showText); |
| repaint(); |
| } |
| |
| /** |
| * Returns whether the text displaying the heap size should be shown. |
| * |
| * @return whether the text displaying the heap size should be shown |
| */ |
| public boolean getShowText() { |
| return prefs().getBoolean(SHOW_TEXT, true); |
| } |
| |
| /** |
| * Sets the font used to display the heap size. |
| * |
| * @param font the font used to display the heap size |
| */ |
| @Override |
| public void setFont(Font font) { |
| super.setFont(font); |
| updateTextWidth(); |
| } |
| |
| // Called by GarbageCollectAction |
| Dimension heapViewPreferredSize() { |
| Dimension size = new Dimension(maxTextWidth + 8, getFontMetrics( |
| getFont()).getHeight() + 8); |
| return size; |
| } |
| |
| private Preferences prefs() { |
| return NbPreferences.forModule(HeapView.class); |
| } |
| |
| /** |
| * Recalculates the width needed to display the heap size string. |
| */ |
| private void updateTextWidth() { |
| String maxString = format.format(new Object[]{888.8f, 888.8f}); |
| maxTextWidth = getFontMetrics(getFont()).stringWidth(maxString) + 4; |
| } |
| |
| /** |
| * Processes a mouse event. |
| * |
| * @param e the MouseEvent |
| */ |
| @Override |
| protected void processMouseEvent(MouseEvent e) { |
| super.processMouseEvent(e); |
| if (!e.isConsumed()) { |
| if (e.isPopupTrigger()) { |
| // Show a popup allowing to configure the various options |
| showPopup(e.getX(), e.getY()); |
| } |
| } |
| |
| if (e.getID() == MouseEvent.MOUSE_CLICKED |
| && SwingUtilities.isLeftMouseButton(e) |
| && e.getClickCount() == 1) { |
| // Trigger a gc |
| GarbageCollectAction.get(GarbageCollectAction.class).performAction(); |
| } |
| } |
| |
| /** |
| * Shows a popup at the specified location that allows you to configure the |
| * various options. |
| */ |
| private void showPopup(int x, int y) { |
| JPopupMenu popup = new JPopupMenu(); |
| JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(NbBundle.getMessage(HeapView.class, "LBL_ShowText")); |
| cbmi.setSelected(getShowText()); |
| cbmi.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| setShowText(((JCheckBoxMenuItem) e.getSource()). |
| isSelected()); |
| } |
| }); |
| popup.add(cbmi); |
| popup.show(this, x, y); |
| } |
| |
| /** |
| * Paints the component. |
| */ |
| @Override |
| protected void paintComponent(Graphics g) { |
| super.paintComponent(g); |
| int width = getWidth(); |
| int height = getHeight(); |
| |
| if (width > 0 && height > 0) { |
| startTimerIfNecessary(); |
| Graphics2D g2 = (Graphics2D) g.create(); |
| try { |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); |
| g2.clipRect(0, 0, width, height); |
| // Draw background. |
| g2.setColor(BACKGROUND_COLOR); |
| g2.fillRect(0, 0, width, height); |
| // Draw samples |
| g2.setColor(CHART_COLOR); |
| paintSamples(g2, width, height); |
| // Draw text if enabled |
| if (getShowText()) { |
| paintText(g2, width, height); |
| } |
| } finally { |
| g2.dispose(); |
| } |
| } else { |
| stopTimerIfNecessary(); |
| } |
| } |
| |
| /** |
| * Renders the text using an optional drop shadow. |
| */ |
| private void paintText(Graphics2D g, int w, int h) { |
| Font font = getFont(); |
| String text = getHeapSizeText(); |
| GlyphVector gv = font.createGlyphVector(g.getFontRenderContext(), text); |
| FontMetrics fm = g.getFontMetrics(font); |
| Shape outline = gv.getOutline(); |
| Rectangle2D bounds = outline.getBounds2D(); |
| double x = Math.max(0, (w - bounds.getWidth()) / 2.0); |
| double y = h / 2.0 + fm.getAscent() / 2.0 - 2.0; |
| AffineTransform oldTransform = g.getTransform(); |
| g.translate(x, y); |
| g.setColor(OUTLINE_COLOR); |
| g.setStroke(new BasicStroke(2.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| g.draw(outline); |
| g.setColor(TEXT_COLOR); |
| g.fill(outline); |
| g.setTransform(oldTransform); |
| } |
| |
| private String getHeapSizeText() { |
| return heapSizeText; |
| } |
| |
| /** |
| * Invoked when component removed from a heavy weight parent. Stops the |
| * timer. |
| */ |
| @Override |
| public void removeNotify() { |
| super.removeNotify(); |
| stopTimerIfNecessary(); |
| } |
| |
| /** |
| * Restarts the timer. |
| */ |
| private void startTimerIfNecessary() { |
| if (!AUTOMATIC_REFRESH) { |
| return; |
| } |
| if (updateTimer == null) { |
| updateTimer = new Timer(TICK, new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| update(); |
| } |
| }); |
| updateTimer.setRepeats(true); |
| updateTimer.start(); |
| } |
| } |
| |
| /** |
| * Stops the timer. |
| */ |
| private void stopTimerIfNecessary() { |
| if (updateTimer != null) { |
| updateTimer.stop(); |
| updateTimer = null; |
| lastTotal = 0; |
| Arrays.fill(graph, 0); |
| heapSizeText = ""; |
| } |
| } |
| |
| /** |
| * Invoked when the update timer fires. Updates the necessary data |
| * structures and triggers repaints. |
| */ |
| private void update() { |
| if (isShowing()) { |
| Runtime r = Runtime.getRuntime(); |
| long total = r.totalMemory(); |
| long used = total - r.freeMemory(); |
| graph[graphIndex] = used; |
| lastTotal = total; |
| ++graphIndex; |
| if (graphIndex >= GRAPH_COUNT) { |
| graphIndex = 0; |
| } |
| heapSizeText = format.format( |
| new Object[]{(double) used / (1024.0 * 1024.0), (double) total / (1024.0 * 1024.0)}); |
| repaint(); |
| } else { |
| // Either we've become invisible, or one of our ancestors has. |
| // Stop the timer and bale. Next paint will trigger timer to |
| // restart. |
| stopTimerIfNecessary(); |
| } |
| } |
| |
| /** |
| * Draw the graph with the heap samples. It is a simple area chart. |
| * |
| * @param g Where to draw |
| * @param width The width of the chart |
| * @param height The height of the chart |
| */ |
| private void paintSamples(Graphics2D g, int width, int height) { |
| Path2D path = new Path2D.Double(); |
| path.moveTo(0, height); |
| for (int i = 0; i < GRAPH_COUNT; ++i) { |
| int index = (i + graphIndex) % GRAPH_COUNT; |
| double x = (double) i / (double) (GRAPH_COUNT - 1) * (double) width; |
| double y = (double) height * (1.0 - (double) graph[index] / (double) lastTotal); |
| path.lineTo(x, y); |
| } |
| path.lineTo(width, height); |
| path.closePath(); |
| g.fill(path); |
| } |
| } |