package com.ibm.nmon; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Set; import java.util.Map; import java.util.TimeZone; import java.util.Properties; import org.slf4j.Logger; import com.ibm.nmon.data.DataSetListener; import com.ibm.nmon.data.DataSet; import com.ibm.nmon.data.SystemDataSet; import com.ibm.nmon.data.transform.name.HostRenamer; import com.ibm.nmon.parser.*; import com.ibm.nmon.parser.gc.VerboseGCParser; import com.ibm.nmon.interval.*; import com.ibm.nmon.analysis.AnalysisRecord; import com.ibm.nmon.util.ParserLog; import com.ibm.nmon.util.TimeFormatCache; import com.ibm.nmon.util.TimeZoneFactory; import com.ibm.nmon.file.CombinedFileFilter; /** * Main application base class responsible for parsing and managing {@link DataSet DataSets} and {@link AnalysisRecord * AnalysisRecords}; managing intervals, times and time zones; and managing application level properties. * * @see IntervalManager * @see NMONParser * @see VerboseGCParser */ public abstract class NMONVisualizerApp implements IntervalListener { protected final Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); private final NMONParser nmonParser; private final VerboseGCParser gcParser; private final IOStatParser iostatParser; private final JSONParser jsonParser; private final HATJParser hatJParser; private final PerfmonParser perfmonParser; private final TopasOutParser topasoutParser; private final FIOParser fioParser; private final ZPoolIOStatParser zpoolParser; private HostRenamer hostRenamer; // assume event order does not matter private final Set<DataSetListener> listeners; private final IntervalManager intervalManager = new IntervalManager(); private TimeZone displayTimeZone; private final Map<SystemDataSet, AnalysisRecord> analysisRecords = new java.util.TreeMap<SystemDataSet, AnalysisRecord>(); private long minSystemTime = 0; private long maxSystemTime = Long.MAX_VALUE; private final Properties properties = new Properties(); protected final PropertyChangeSupport propertyChangeSupport; protected NMONVisualizerApp() { // ensure ParserLogHandler is instantiated before any parsers // since it creates the parent logger ParserLog.getInstance(); nmonParser = new NMONParser(); gcParser = new VerboseGCParser(); iostatParser = new IOStatParser(); jsonParser = new JSONParser(); hatJParser = new HATJParser(); perfmonParser = new PerfmonParser(); topasoutParser = new TopasOutParser(nmonParser); fioParser = new FIOParser(); zpoolParser = new ZPoolIOStatParser(); TimeZone defaultTz = TimeZone.getDefault(); // use the timezone names from TimeZoneFactory, if possible for (TimeZone timeZone : TimeZoneFactory.TIMEZONES) { long defaultOffset = defaultTz.getOffset(System.currentTimeMillis()); if (timeZone.getOffset(System.currentTimeMillis()) == defaultOffset) { displayTimeZone = timeZone; break; } } if (displayTimeZone == null) { displayTimeZone = defaultTz; } TimeFormatCache.setTimeZone(displayTimeZone); listeners = new java.util.HashSet<DataSetListener>(); propertyChangeSupport = new PropertyChangeSupport(this); intervalManager.addListener(this); setProperty("systemsNamedBy", "host"); hostRenamer = HostRenamer.BY_HOST; setProperty("scaleProcessesByCPUs", "true"); } /** * <p> * Parse the given file, if possible. * </p> * <p> * This method uses {@link SystemDataSet SystemDataSets} to store the parsed data. If multiple files from the same * host are parsed, the data is merged into a single data set. There will never be multiple data sets stored by this * class from the same host. * </p> * <p> * This time zone passed to this file should be the time zone where the data was <em>collected</em> not where the * application is running. All parsed files should line up to same absolute epoch time. The * {@link #setDisplayTimeZone(TimeZone) displayed time zone} will control how this time is presented to end users. */ public final void parse(String fileToParse, TimeZone timeZone) throws Exception { fileToParse = fileToParse.replace('\\', '/'); // skipped already parsed files for (SystemDataSet systemData : analysisRecords.keySet()) { if (systemData.containsSourceFile(fileToParse)) { return; } } DataSet data = null; CombinedFileFilter filter = CombinedFileFilter.getInstance(false); if (filter.getNMONFileFilter().accept(fileToParse)) { data = nmonParser.parse(fileToParse, timeZone, getBooleanProperty("scaleProcessesByCPUs")); } else if (filter.getGCFileFilter().accept(fileToParse)) { // GC data does not have a hostname or JVM name so get it before parsing String[] values = getDataForGCParse(fileToParse); if (values == null) { logger.info("skipping file '{}'", fileToParse); return; } else if (values.length < 2) { logger.error("need both hostname and JVM name to parse GC data, only {} provided", java.util.Arrays.toString(values)); return; } else { data = gcParser.parse(fileToParse, timeZone, values[0], values[1]); } } else if (filter.getIOStatFileFilter().accept(fileToParse)) { // IOStat data may have a hostname and time zone so get it after parsing data = iostatParser.parse(fileToParse, getDisplayTimeZone()); String hostname = data.getHostname(); boolean verifyData = "AIX".equals(((com.ibm.nmon.data.BasicDataSet) data).getMetadata("OS")); // assume AIX, which also needs a parsed date if (hostname.equals(IOStatParser.DEFAULT_HOSTNAME) || verifyData) { Object[] values = getDataForIOStatParse(fileToParse, hostname); if (values == null) { logger.info("skipping file '{}'", fileToParse); return; } hostname = (String) values[0]; data.setHostname(hostname); long newDate = (Long) values[1]; long defaultDate = IOStatParser.getDefaultDate(); if (defaultDate != newDate) { long start = System.nanoTime(); data.adjustTimes(newDate - defaultDate); if (logger.isDebugEnabled()) { logger.debug("data for '{}' times adjusted by {} hours in {} ms", new Object[] { fileToParse, (newDate - defaultDate) / 3600.0 / 1000.0, System.nanoTime() - start }); } } } } else if (filter.getJSONFileFilter().accept(fileToParse)) { data = jsonParser.parse(fileToParse); } else if (filter.getHATJFileFilter().accept(fileToParse)) { data = hatJParser.parse(fileToParse); String hostname = data.getHostname(); if (hostname.equals(HATJParser.DEFAULT_HOSTNAME)) { Object[] values = getDataForHATJParse(fileToParse, hostname); if (values == null) { logger.info("skipping file '{}'", fileToParse); return; } hostname = (String) values[0]; data.setHostname(hostname); } } else if (filter.getPerfmonFileFilter().accept(fileToParse)) { data = perfmonParser.parse(fileToParse, getBooleanProperty("scaleProcessesByCPUs")); } else if (filter.getZPoolIOStatOutFileFilter().accept(fileToParse)) { data = zpoolParser.parse(fileToParse); data.setHostname(getDataForZPoolIOStatParse(fileToParse)); } else if (filter.getTopasOutFileFilter().accept(fileToParse)) { data = topasoutParser.parse(fileToParse, timeZone, getBooleanProperty("scaleProcessesByCPUs")); } else if (filter.getFIOFileFilter().accept(fileToParse)) { data = fioParser.parse(fileToParse, timeZone); } else { throw new IllegalArgumentException("cannot parse " + fileToParse + ": unknown file type"); } if (data.getRecordCount() == 0) { throw new IllegalArgumentException(fileToParse + " does not appear to contain any data"); } // rename the host hostRenamer.rename(data); // find an existing data set for the host SystemDataSet systemData = null; for (SystemDataSet toSearch : analysisRecords.keySet()) { if (toSearch.getHostname().equals(data.getHostname())) { systemData = toSearch; break; } } // create the data set if none exists if (systemData == null) { systemData = new SystemDataSet(data.getHostname()); AnalysisRecord record = new AnalysisRecord(systemData); record.setInterval(intervalManager.getCurrentInterval()); analysisRecords.put(systemData, record); } // add the parsed data to the system data set systemData.addData(fileToParse, data); recalculateMinAndMaxSystemTime(); fireDataAdded(systemData); } protected String[] getDataForGCParse(String fileToParse) { // hostname and JVM name default to the file name int idx = fileToParse.lastIndexOf('/'); if (idx != -1) { fileToParse = fileToParse.substring(idx + 1); } return new String[] { fileToParse, fileToParse }; } protected Object[] getDataForIOStatParse(String fileToParse, String hostname) { // hostname, date return new Object[] { IOStatParser.DEFAULT_HOSTNAME, IOStatParser.getDefaultDate() }; } protected String getDataForZPoolIOStatParse(String fileToParse) { // default to file name int idx = fileToParse.lastIndexOf('/'); if (idx != -1) { fileToParse = fileToParse.substring(idx + 1); } return fileToParse; } protected Object[] getDataForHATJParse(String fileToParse, String hostname) { // hostname return new Object[] { HATJParser.DEFAULT_HOSTNAME }; } // this is a separate function in order to allow subclasses (i.e. the gui) to run parsing in // another thread yet still fire the data added event in the main thread protected void fireDataAdded(DataSet data) { for (DataSetListener listener : listeners) { listener.dataAdded(data); } } public final void setHostRenamer(HostRenamer hostRenamer) { if (hostRenamer != null) { this.hostRenamer = hostRenamer; } } public final TimeZone getDisplayTimeZone() { return displayTimeZone; } public final void setDisplayTimeZone(TimeZone displayTimeZone) { if (displayTimeZone == null) { return; } if (!this.displayTimeZone.equals(displayTimeZone)) { TimeZone old = this.displayTimeZone; this.displayTimeZone = displayTimeZone; TimeFormatCache.setTimeZone(displayTimeZone); propertyChangeSupport.firePropertyChange("timeZone", old, this.displayTimeZone); } } public final void removeDataSet(DataSet data) { if (analysisRecords.remove(data) != null) { recalculateMinAndMaxSystemTime(); if (analysisRecords.isEmpty()) { for (DataSetListener listener : listeners) { listener.dataCleared(); } } else { for (DataSetListener listener : listeners) { listener.dataRemoved(data); } } } } public final void updateDataSet(SystemDataSet data) { if (analysisRecords.remove(data) != null) { AnalysisRecord record = new AnalysisRecord(data); record.setInterval(intervalManager.getCurrentInterval()); analysisRecords.put(data, record); recalculateMinAndMaxSystemTime(); for (DataSetListener listener : listeners) { listener.dataChanged(data); } } } public final void clearDataSets() { minSystemTime = 0; maxSystemTime = Long.MAX_VALUE; TimeFormatCache.setDefaultIntervalRange(minSystemTime, maxSystemTime); intervalManager.setCurrentInterval(Interval.DEFAULT); analysisRecords.clear(); for (DataSetListener listener : listeners) { listener.dataCleared(); } } public final Iterable<SystemDataSet> getDataSets() { return java.util.Collections.unmodifiableSet(analysisRecords.keySet()); } public final int getDataSetCount() { return analysisRecords.size(); } /** * @return the minimum time defined by any parsed DataSet or 0 if nothing has been parsed */ public final long getMinSystemTime() { return minSystemTime; } /** * @return the maximum time defined by any parsed DataSet or <code>Long.MAX_VALUE</code> if nothing has been parsed */ public final long getMaxSystemTime() { return maxSystemTime; } public final IntervalManager getIntervalManager() { return intervalManager; } public final AnalysisRecord getAnalysis(DataSet data) { return analysisRecords.get(data); } public final String getProperty(String name) { return properties.getProperty(name); } public final boolean getBooleanProperty(String name) { return Boolean.parseBoolean(properties.getProperty(name)); } /** * Set an application level property and fire a {@link PropertyChangeEvent} for the associated property. No attempt * is made to see if the property actually changed. */ public final void setProperty(String name, String value) { String old = properties.getProperty(name); properties.setProperty(name, value); propertyChangeSupport.firePropertyChange(name, old, value); } public final void setProperty(String name, int value) { String temp = properties.getProperty(name); int old = -1; if (temp != null) { old = Integer.parseInt(temp); } properties.setProperty(name, Integer.toString(value)); propertyChangeSupport.firePropertyChange(name, old, value); } public final void setProperty(String name, boolean value) { boolean old = getBooleanProperty(name); properties.setProperty(name, Boolean.toString(value)); propertyChangeSupport.firePropertyChange(name, old, value); } // update the start and end times when new DataSets are added // this may change the meaning of Interval.DEFAULT so update that if necessary private void recalculateMinAndMaxSystemTime() { if (analysisRecords.size() > 0) { long minStart = Long.MAX_VALUE; long maxEnd = Long.MIN_VALUE; for (DataSet data : analysisRecords.keySet()) { if (data.getStartTime() < minStart) { minStart = data.getStartTime(); } if (data.getEndTime() > maxEnd) { maxEnd = data.getEndTime(); } } boolean update = false; if (minStart != minSystemTime) { minSystemTime = minStart; update = true; } if (maxEnd != maxSystemTime) { maxSystemTime = maxEnd; update = true; } if (update && intervalManager.getCurrentInterval().equals(Interval.DEFAULT)) { TimeFormatCache.setDefaultIntervalRange(minSystemTime, maxSystemTime); intervalManager.setCurrentInterval(Interval.DEFAULT); } } } @Override public void intervalAdded(Interval interval) {} @Override public void intervalRemoved(Interval interval) {} @Override public void intervalsCleared() { // assume clearing also sets current interval back to DEFAULT } @Override public void currentIntervalChanged(Interval interval) { for (AnalysisRecord record : analysisRecords.values()) { record.setInterval(interval); } } @Override public void intervalRenamed(Interval interval) {} public void addDataSetListener(DataSetListener listener) { if (listener != null) { synchronized (listeners) { listeners.add(listener); } } } public void removeDataSetListener(DataSetListener listener) { synchronized (listeners) { listeners.remove(listener); } } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } }