package com.ibm.nmon.gui.main; import java.util.List; import java.io.File; import java.util.prefs.Preferences; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JOptionPane; import java.awt.BorderLayout; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.SwingUtilities; import com.ibm.nmon.NMONVisualizerApp; import com.ibm.nmon.data.DataSet; import com.ibm.nmon.data.transform.name.HostRenamer; import com.ibm.nmon.interval.Interval; import com.ibm.nmon.file.CombinedFileFilter; import com.ibm.nmon.gui.file.ParserRunner; import com.ibm.nmon.gui.interval.IntervalPicker; import com.ibm.nmon.gui.report.ReportFrame; import com.ibm.nmon.gui.tree.TreePanel; import com.ibm.nmon.gui.util.LogViewerDialog; import com.ibm.nmon.parser.HATJParser; import com.ibm.nmon.parser.IOStatParser; import com.ibm.nmon.gui.parse.*; import com.ibm.nmon.report.ReportCache; import com.ibm.nmon.gui.Styles; import com.ibm.nmon.util.FileHelper; import com.ibm.nmon.util.GranularityHelper; import com.ibm.nmon.util.TimeFormatCache; public final class NMONVisualizerGui extends NMONVisualizerApp { public static void main(final String[] args) throws Exception { javax.swing.UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); String temp = System.getProperty("fontSize"); if (temp != null) { try { int fontSize = Integer.parseInt(temp); javax.swing.UIManager.getLookAndFeelDefaults().put("defaultFont", new javax.swing.plaf.FontUIResource( new java.awt.Font("Dialog", java.awt.Font.PLAIN, fontSize))); } catch (NumberFormatException nfe) { System.err.println("ignoring -DfontSize=" + temp + "; it must be an integer"); } } SwingUtilities.invokeLater(new Runnable() { public void run() { try { NMONVisualizerGui gui = new NMONVisualizerGui(); gui.getMainFrame().setVisible(true); if (args.length > 0) { gui.logger.info("starting with files {}", java.util.Arrays.toString(args)); File[] files = new File[args.length]; for (int i = 0; i < args.length; i++) { files[i] = new File(args[i]); } List<String> toParse = new java.util.ArrayList<String>(); gui.logger.debug("parsing files {}", toParse); FileHelper.recurseDirectories(files, CombinedFileFilter.getInstance(false), toParse); new Thread(new ParserRunner(gui, toParse, gui.getDisplayTimeZone()), getClass().getName() + " Parser").start(); } } catch (Exception e) { e.printStackTrace(); } } }); } private static final String DEFAULT_WINDOW_TITLE = "NMON Visualizer"; private final Preferences preferences; private final JFrame mainFrame; private final MainMenu menu; private final LogViewerDialog logViewer; private VerboseGCPreParser gcPreParser; private IOStatPostParser ioStatPostParser; private ZPoolIOStatPostParser zpoolPostParser; private HATJPostParser hatJPostParser; private final GranularityHelper granularityHelper; private final ReportCache reportCache; public NMONVisualizerGui() throws Exception { super(); granularityHelper = new GranularityHelper(this); setGranularity(-1); // automatic reportCache = new ReportCache(); preferences = Preferences.userNodeForPackage(NMONVisualizerGui.class); setProperty("chartsDisplayed", true); String systemsNamedBy = preferences.get("systemsNamedBy", null); if (systemsNamedBy != null) { if ("host".equals(systemsNamedBy)) { setHostRenamer(HostRenamer.BY_HOST); } else if ("lpar".equals(systemsNamedBy)) { setHostRenamer(HostRenamer.BY_LPAR); } else if ("run".equals(systemsNamedBy)) { setHostRenamer(HostRenamer.BY_RUN); } else if ("custom".equals(systemsNamedBy)) { // reset back to host if custom since the JSON file to load is not known systemsNamedBy = "host"; } setProperty("systemsNamedBy", systemsNamedBy); } // else NMONVisualizerApp already set HostRenamer to BY_HOST and systemsNamedBy property // NMONVisuzlizerApp already set default value for scaleProcessesByCPUs property setProperty("scaleProcessesByCPUs", preferences.get("scaleProcessesByCPUs", getProperty("scaleProcessesByCPUs"))); setProperty("showStatusBar", preferences.get("showStatusBar", "false")); mainFrame = new JFrame(DEFAULT_WINDOW_TITLE); mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); mainFrame.addWindowListener(windowManager); mainFrame.setIconImage(Styles.IBM_ICON.getImage()); menu = new MainMenu(this); mainFrame.setJMenuBar(menu); logViewer = new LogViewerDialog(this); // tree of parsed files on the left, content on the left JSplitPane lrSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); // never resize tree automatically lrSplitPane.setResizeWeight(0); mainFrame.setContentPane(lrSplitPane); // tree of parsed files on the left, content on the right TreePanel treePanel = new TreePanel(this); JPanel right = new JPanel(new BorderLayout()); lrSplitPane.setLeftComponent(treePanel); lrSplitPane.setRightComponent(right); ChartTableToggle toggle = new ChartTableToggle(this); JPanel top = new JPanel(new BorderLayout()); top.add(new IntervalPicker(this), BorderLayout.CENTER); top.add(toggle, BorderLayout.LINE_END); right.add(top, BorderLayout.PAGE_START); // ViewManager's SummaryView adds the checkbox to the top panel // so top must already be created and added to the gui before the data panel is initialized ViewManager dataPanel = new ViewManager(this); StatusBar statusBar = new StatusBar(this); treePanel.addTreeSelectionListener(dataPanel); treePanel.addTreeSelectionListener(statusBar); right.add(dataPanel, BorderLayout.CENTER); right.add(statusBar, BorderLayout.PAGE_END); mainFrame.pack(); positionMainFrame(); } /** * Gets the Preferences for this application. * * @return the Preferences, which will never be <code>null</code> */ public final Preferences getPreferences() { return preferences; } /** * Gets the main JFrame used by this class. This frame should be considered the "main window" for the application. * * @return the application's main window */ public JFrame getMainFrame() { return mainFrame; } public ViewManager getViewManager() { return (ViewManager) ((JPanel) ((JSplitPane) mainFrame.getContentPane()).getRightComponent()).getComponent(1); } public void showReportFrame() { ReportFrame report = new ReportFrame(this); report.setVisible(true); } public LogViewerDialog getLogViewer() { return logViewer; } public int getGranularity() { return granularityHelper.getGranularity(); } /** * Defines how granular charts will be, i.e. how many seconds will pass between data points. This method causes * either a <code>automaticGranularity</code> or <code>granularity</code> property change event to be fired. * * @param granularity the new granularity, in seconds. A zero or negative value implies that granularity will be * automatically calculated based on the current interval. */ public void setGranularity(int granularity) { int oldGranularity = getGranularity(); if (granularity <= 0) { if (getBooleanProperty("automaticGranularity")) { granularityHelper.recalculate(); } else { granularityHelper.setAutomatic(true); setProperty("automaticGranularity", true); } } else { granularityHelper.setGranularity(granularity); if (getBooleanProperty("automaticGranularity")) { setProperty("automaticGranularity", false); } } if (getGranularity() != oldGranularity) { for (DataSet data : getDataSets()) { getAnalysis(data).setGranularity(getGranularity()); } setProperty("granularity", getGranularity()); } } public ReportCache getReportCache() { return reportCache; } @Override public void currentIntervalChanged(Interval interval) { super.currentIntervalChanged(interval); if (getBooleanProperty("automaticGranularity")) { setGranularity(-1); } updateWindowTitle(interval); } @Override public void intervalRenamed(Interval interval) { super.intervalRenamed(interval); if (getIntervalManager().getCurrentInterval().equals(interval)) { updateWindowTitle(interval); } } private void updateWindowTitle(Interval interval) { if ((getMinSystemTime() > 0) && (getMaxSystemTime() < Long.MAX_VALUE)) { mainFrame.setTitle(DEFAULT_WINDOW_TITLE + " - " + TimeFormatCache.formatInterval(interval)); } else { mainFrame.setTitle(DEFAULT_WINDOW_TITLE); } } /** * Gracefully exits the application. This method asks the user for confirmation through a JOptionPane before * continuing. If the user selects "Yes", the application saves all preferences and calls dispose() on the main * frame. */ void exit() { int confirm = JOptionPane.showConfirmDialog(mainFrame, "Are you sure you want to Exit?", "Exit?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (confirm == 0) { // save the window sizes to the Preferences // if the window is maximized, do not save the sizes -- keep // the old ones so the user can un-maximize later if ((mainFrame.getExtendedState() & JFrame.MAXIMIZED_BOTH) != 0) { getPreferences().putBoolean("WindowMaximized", true); } else { getPreferences().putBoolean("WindowMaximized", false); getPreferences().putInt("WindowPosX", mainFrame.getX()); getPreferences().putInt("WindowPosY", mainFrame.getY()); getPreferences().putInt("WindowSizeX", (int) mainFrame.getSize().getWidth()); getPreferences().putInt("WindowSizeY", (int) mainFrame.getSize().getHeight()); } // close any open custom report windows for (java.awt.Frame frame : java.awt.Frame.getFrames()) { if (frame.getClass() == com.ibm.nmon.gui.report.ReportFrame.class) { ((com.ibm.nmon.gui.report.ReportFrame) frame).setVisible(false); ((com.ibm.nmon.gui.report.ReportFrame) frame).dispose(); } } getPreferences().put("systemsNamedBy", getProperty("systemsNamedBy")); getPreferences().put("scaleProcessesByCPUs", getProperty("scaleProcessesByCPUs")); getPreferences().put("showStatusBar", getProperty("showStatusBar")); logViewer.dispose(); mainFrame.dispose(); try { preferences.sync(); } catch (java.util.prefs.BackingStoreException e) { logger.warn("could not save preferences", e); } } } @Override protected String[] getDataForGCParse(final String fileToParse) { if (gcPreParser == null) { gcPreParser = new VerboseGCPreParser(this); } try { // wait here so parsing does not continue in the background, possibly throwing up more // preparser dialogs SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { java.io.File file = new java.io.File(fileToParse); java.io.File parent = file.getParentFile(); if (parent != null) { gcPreParser.setJVMName(parent.getName()); } parent = parent.getParentFile(); if (parent != null) { gcPreParser.setHostname(parent.getName()); } gcPreParser.parseDataSet(fileToParse); } }); } catch (Exception e) { logger.error("cannot get hostname and JVM name for file '{}'", fileToParse, e); } if (gcPreParser.isSkipped()) { return null; } else { return new String[] { gcPreParser.getHostname(), gcPreParser.getJVMName() }; } } @Override protected Object[] getDataForIOStatParse(final String fileToParse, final String hostname) { if (ioStatPostParser == null) { ioStatPostParser = new IOStatPostParser(this); } try { // wait here so parsing does not continue in the background, possibly throwing up more // postparser dialogs SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { if (!hostname.equals(IOStatParser.DEFAULT_HOSTNAME)) { ioStatPostParser.setHostname(hostname); } ioStatPostParser.parseDataSet(fileToParse); } }); } catch (Exception e) { logger.error("cannot get hostname and time zone for file '{}'", fileToParse, e); } if (ioStatPostParser.isSkipped()) { return null; } else { return new Object[] { ioStatPostParser.getHostname(), ioStatPostParser.getDate() }; } } @Override protected String getDataForZPoolIOStatParse(final String fileToParse) { if (zpoolPostParser == null) { zpoolPostParser = new ZPoolIOStatPostParser(this); } try { // wait here so parsing does not continue in the background, possibly throwing up more // postparser dialogs SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { zpoolPostParser.parseDataSet(fileToParse); } }); } catch (Exception e) { logger.error("cannot get hostname for file '{}'", fileToParse, e); } if (zpoolPostParser.isSkipped()) { return null; } else { return zpoolPostParser.getHostname(); } } @Override protected Object[] getDataForHATJParse(final String fileToParse, final String hostname) { if (hatJPostParser == null) { hatJPostParser = new HATJPostParser(this); } try { // wait here so parsing does not continue in the background, possibly throwing up more // postparser dialogs SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { if (!hostname.equals(HATJParser.DEFAULT_HOSTNAME)) { hatJPostParser.setHostname(hostname); } hatJPostParser.parseDataSet(fileToParse); } }); } catch (Exception e) { logger.error("cannot get hostname for file '{}'", fileToParse, e); } if (hatJPostParser.isSkipped()) { return null; } else { return new Object[] { hatJPostParser.getHostname() }; } } private void positionMainFrame() { // load the window position and sizes from the Preferences // use max value here so that first time users / empty prefs create a default sized window // centered on the primary display int x = getPreferences().getInt("WindowPosX", 0); int y = this.getPreferences().getInt("WindowPosY", 0); int xSize = getPreferences().getInt("WindowSizeX", Integer.MAX_VALUE); int ySize = this.getPreferences().getInt("WindowSizeY", Integer.MAX_VALUE); // check to see if the preferred window will be visible with the current screen config // (user could have removed / reconfigured multiple monitors or resolutions) boolean willBeVisible = false; java.awt.Rectangle preferred = new java.awt.Rectangle(x, y, xSize, ySize); GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); outer: for (GraphicsDevice device : devices) { GraphicsConfiguration[] configs = device.getConfigurations(); for (GraphicsConfiguration config : configs) { if (SwingUtilities.isRectangleContainingRectangle(config.getBounds(), preferred)) { willBeVisible = true; break outer; } } } // if not visible put the window in the middle of the primary monitor if (!willBeVisible) { java.awt.Rectangle defaultScreen = GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration().getBounds(); // resize if too big if (xSize > defaultScreen.width) { xSize = 800; } if (ySize > defaultScreen.height) { ySize = 600; } // center the window on the screen x = defaultScreen.x + (defaultScreen.width / 2) - (xSize / 2); y = defaultScreen.y + (defaultScreen.height / 2) - (ySize / 2); } mainFrame.setLocation(new java.awt.Point(x, y)); // set the size even if the window will not be maximized to ensure that // the window will be the right size if the user does un-maximize it mainFrame.setSize(xSize, ySize); if (getPreferences().getBoolean("WindowMaximized", false)) { mainFrame.setExtendedState(mainFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH); } } @Override protected void fireDataAdded(final DataSet data) { // fire in the Swing event dispatcher thread if not already running there if (SwingUtilities.isEventDispatchThread()) { super.fireDataAdded(data); if (getBooleanProperty("automaticGranularity")) { setGranularity(-1); } } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { NMONVisualizerGui.super.fireDataAdded(data); if (getBooleanProperty("automaticGranularity")) { setGranularity(-1); } } }); } } private WindowAdapter windowManager = new WindowAdapter() { @Override public void windowOpened(WindowEvent e) { // can only update the divider location when the window is visible // charts get 80% JSplitPane lrSplitPane = ((JSplitPane) mainFrame.getContentPane()); lrSplitPane.setDividerLocation(0.2); }; @Override public void windowClosing(WindowEvent e) { NMONVisualizerGui.this.exit(); } }; }