package com.ibm.nmon.report; import java.io.IOException; import java.util.List; import java.util.Map; import org.slf4j.Logger; import com.ibm.nmon.chart.definition.BaseChartDefinition; import com.ibm.nmon.chart.definition.LineChartDefinition; import com.ibm.nmon.chart.definition.HistogramChartDefinition; import com.ibm.nmon.chart.definition.IntervalChartDefinition; import com.ibm.nmon.chart.definition.BarChartDefinition; import com.ibm.nmon.chart.definition.ChartDefinitionParser; import com.ibm.nmon.data.DataSet; import com.ibm.nmon.data.DataType; import com.ibm.nmon.data.definition.DataDefinition; import com.ibm.nmon.data.definition.DefaultDataDefinition; import com.ibm.nmon.data.definition.ExactDataDefinition; import com.ibm.nmon.data.definition.NamingMode; import com.ibm.nmon.data.matcher.ExactFieldMatcher; import com.ibm.nmon.data.matcher.ExactTypeMatcher; /** * A simple cache for storing 'reports', a list of parsed chart definitions. Reports are stored and retrieved using a * key, which can be any String. Cached reports can be filtered for a list of data sets to avoid creating charts that * are not needed for the application's current set of data. * * @see BaseChartDefinition * @see ChartDefinitionParser */ public final class ReportCache { private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(ReportCache.class); public static final String DEFAULT_SUMMARY_CHARTS_KEY = "summary"; public static final String DEFAULT_INTERVAL_CHARTS_KEY = "interval"; public static final String DEFAULT_DATASET_CHARTS_KEY = "dataset"; public static final String DEFAULT_IOSTAT_CHARTS_KEY = "iostat"; public static final String DEFAULT_IOSTAT_DISKDATA_CHARTS_KEY = "iostat_disk"; private final ChartDefinitionParser parser = new ChartDefinitionParser(); private Map<String, List<BaseChartDefinition>> reports = new java.util.HashMap<String, List<BaseChartDefinition>>(); public ReportCache() { try { reports.put( DEFAULT_SUMMARY_CHARTS_KEY, parser.parseCharts(getClass().getResourceAsStream( "/com/ibm/nmon/report/summary_single_interval.xml"))); reports.put(DEFAULT_INTERVAL_CHARTS_KEY, parser.parseCharts(getClass().getResourceAsStream( "/com/ibm/nmon/report/summary_all_intervals.xml"))); reports.put(DEFAULT_DATASET_CHARTS_KEY, parser.parseCharts(getClass().getResourceAsStream("/com/ibm/nmon/report/dataset_report.xml"))); reports.put(DEFAULT_IOSTAT_CHARTS_KEY, parser.parseCharts(getClass().getResourceAsStream("/com/ibm/nmon/report/iostat_report.xml"))); reports.put(DEFAULT_IOSTAT_DISKDATA_CHARTS_KEY, parser.parseCharts(getClass().getResourceAsStream( "/com/ibm/nmon/report/iostat_diskdata_report.xml"))); } catch (IOException e) { LOGGER.error("cannot parse default report definition xmls", e); } } /** * Parse the given chart definition XML file and store the report with the given key. * * @param file a valid XML file for processing by {@link ChartDefinitionParser} */ public void addReport(String key, String file) throws IOException { if (DEFAULT_SUMMARY_CHARTS_KEY.equals(key) || DEFAULT_INTERVAL_CHARTS_KEY.equals(key) || DEFAULT_DATASET_CHARTS_KEY.equals(key)) { throw new IllegalArgumentException("cannot redefine default charts for key " + key); } reports.put(key, parser.parseCharts(file)); LOGGER.debug("caching chart definitions from '{}' to key '{}'", file, key); } /** * Get the report for the given key. * * @return a list of chart definitions; this list will be empty if the key is not found */ public List<BaseChartDefinition> getReport(String key) { List<BaseChartDefinition> toReturn = reports.get(key); if (toReturn == null) { toReturn = java.util.Collections.emptyList(); } return toReturn; } /** * Get the report for the given key filtering based on a given data set. Charts that are not applicable to a host in * the data set will not be included in the returned list. * * @param dataSets the DataSets to match * @return the filtered list of chart definitions * @see DataDefinition#matchesHost(DataSet) */ public List<BaseChartDefinition> getReport(String key, Iterable<? extends DataSet> dataSets) { List<BaseChartDefinition> report = reports.get(key); if (report == null) { return java.util.Collections.emptyList(); } else { List<BaseChartDefinition> toReturn = new java.util.ArrayList<BaseChartDefinition>(report.size()); // the charts actually used depend on the host // if any DataSet matches a defined host, show the report for (BaseChartDefinition chartDefinition : report) { dataset: for (DataSet data : dataSets) { for (DataDefinition definition : chartDefinition.getData()) { if (definition.matchesHost(data) && (definition.getMatchingTypes(data).size() > 0)) { toReturn.add(chartDefinition); break dataset; } } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("reduced {} charts to {} for data {}: {}", new Object[] { report.size(), toReturn.size(), dataSets, toReturn }); } return toReturn; } } /** * <p> * Get the a custom report for the given key and data. Rather than creating a single chart with a line/bar for each * type, this function creates a chart for <em>each</em> type that matches the given definition. * </p> * <p> * This function uses all charts in the given report so it is possible for this function to create a large number of * charts, especially if multiple DataTypes and/or fields are matched. * </p> * * @param filterByData should the initial reports list be filtered by the given data? See * {@link #getReport(String, Iterable)}. */ public List<BaseChartDefinition> multiplexChartsAcrossTypes(String key, DataSet data, boolean filterByData) { List<BaseChartDefinition> chartDefinitions = null; if (filterByData) { chartDefinitions = getReport(key, java.util.Collections.singletonList(data)); } else { chartDefinitions = getReport(key); } LOGGER.debug("multiplexing charts {} for dataset {} across types", chartDefinitions, data.getHostname()); List<BaseChartDefinition> multiplexedChartDefinitions = new java.util.ArrayList<BaseChartDefinition>( 10 * chartDefinitions.size()); for (BaseChartDefinition chartDefinition : chartDefinitions) { for (DataDefinition dataDefinition : chartDefinition.getData()) { if (dataDefinition.matchesHost(data)) { for (DataType type : dataDefinition.getMatchingTypes(data)) { BaseChartDefinition newChartDefinition = copyChart(chartDefinition); // short name used as filename and/or tabname, so make sure it is unique newChartDefinition.setShortName(chartDefinition.getShortName() + "_" + dataDefinition.renameType(type)); newChartDefinition.setTitle(chartDefinition.getTitle()); newChartDefinition.setSubtitleNamingMode(NamingMode.TYPE); DataDefinition newData = null; if (dataDefinition instanceof DefaultDataDefinition) { DefaultDataDefinition old = (DefaultDataDefinition) dataDefinition; newData = old.withNewTypes(new ExactTypeMatcher(type.toString())); } else { newData = new ExactDataDefinition(data, type, dataDefinition.getMatchingFields(type), dataDefinition.getStatistic(), dataDefinition.usesSecondaryYAxis()); } newChartDefinition.addData(newData); multiplexedChartDefinitions.add(newChartDefinition); } } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("multiplexed charts {} for dataset {} across types to {}", new Object[] { chartDefinitions, data.getHostname(), multiplexedChartDefinitions }); } return multiplexedChartDefinitions; } /** * <p> * Get the a custom report for the given key and data. Rather than creating a single chart with a line/bar for each * field, this function creates a chart for <em>each</em> field that matches the given definition. * </p> * <p> * This function uses all charts in the given report so it is possible for this function to create a large number of * charts, especially if multiple DataTypes and/or fields are matched. * </p> * * @param filterByData should the initial reports list be filtered by the given data? See * {@link #getReport(String, Iterable)}. */ public List<BaseChartDefinition> multiplexChartsAcrossFields(String key, DataSet data, boolean filterByData) { List<BaseChartDefinition> chartDefinitions = null; if (filterByData) { chartDefinitions = getReport(key, java.util.Collections.singletonList(data)); } else { chartDefinitions = getReport(key); } LOGGER.debug("multiplexing charts {} for dataset {} across fields", chartDefinitions, data.getHostname()); Map<String, BaseChartDefinition> multiplexedChartDefinitions = new java.util.HashMap<String, BaseChartDefinition>( chartDefinitions.size() * 10); for (BaseChartDefinition chartDefinition : chartDefinitions) { for (DataDefinition dataDefinition : chartDefinition.getData()) { if (dataDefinition.matchesHost(data)) { for (DataType type : dataDefinition.getMatchingTypes(data)) { for (String field : dataDefinition.getMatchingFields(type)) { // short name used as filename and/or tabname, so make sure it // is unique String name = chartDefinition.getShortName() + "_" + dataDefinition.renameField(field); BaseChartDefinition newChartDefinition = multiplexedChartDefinitions.get(name); if (newChartDefinition == null) { newChartDefinition = copyChart(chartDefinition); newChartDefinition.setShortName(name); newChartDefinition.setTitle(chartDefinition.getTitle()); newChartDefinition.setSubtitleNamingMode(NamingMode.FIELD); multiplexedChartDefinitions.put(name, newChartDefinition); } DataDefinition newData = null; if (dataDefinition instanceof DefaultDataDefinition) { DefaultDataDefinition old = (DefaultDataDefinition) dataDefinition; newData = old.withNewFields(new ExactFieldMatcher(field)); } else { newData = new ExactDataDefinition(data, type, java.util.Collections.singletonList(field), dataDefinition.getStatistic(), dataDefinition.usesSecondaryYAxis()); } newChartDefinition.addData(newData); } } } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("multiplexed charts {} for dataset {} across fields to {}", new Object[] { chartDefinitions, data.getHostname(), multiplexedChartDefinitions }); } return new java.util.ArrayList<BaseChartDefinition>(multiplexedChartDefinitions.values()); } private BaseChartDefinition copyChart(BaseChartDefinition copy) { if (copy.getClass().equals(LineChartDefinition.class)) { return new LineChartDefinition((LineChartDefinition) copy, false); } else if (copy.getClass().equals(BarChartDefinition.class)) { return new BarChartDefinition((BarChartDefinition) copy, false); } else if (copy.getClass().equals(IntervalChartDefinition.class)) { return new IntervalChartDefinition((IntervalChartDefinition) copy, false); } else if (copy.getClass().equals(HistogramChartDefinition.class)) { return new HistogramChartDefinition((HistogramChartDefinition) copy, false); } else { return null; } } }