001: /*
002: * NbStatisticalDataset.java
003: *
004: * Created on October 16, 2002, 8:39 PM
005: */
006:
007: package org.netbeans.performance.impl.chart;
008:
009: import com.jrefinery.chart.*;
010: import com.jrefinery.data.*;
011: import org.netbeans.performance.spi.*;
012: import java.util.*;
013:
014: /**An implementation of a JFreeChart statistical data set that understands
015: * DataAggregations and queries on them to find its data. Contains null
016: * implementations of the required listener interfaces; it is presumed that
017: * the data has already been put in the DataAggregation, and cannot change.
018: * <P>Note that this class assumes any objects to be graphed will contain
019: * values that are subclasses of Number, which implement the Named interface
020: * (it is difficult to graph something if it doesn't have a numeric value
021: * and you don't know what to call it!). ClassCastExceptions will occur if
022: * the query + keys return a LogElement that does not implement both of
023: * these interfaces.
024: *
025: * @author Tim Boudreau
026: */
027: public class NbStatisticalDataset implements Dataset, CategoryDataset,
028: StatisticalCategoryDataset, SeriesDataset, SeriesChangeListener {
029: String query;
030: String[] fields;
031: Comparator comparator = null;
032: ElementFilter filter = null;
033: DataAggregation source = null;
034:
035: /** Creates a new instance of NbStatisticalDataset. The query arguments defines
036: * a query (see DataAggregation.query()) such as <code>/mytest*javaconfig</code> which
037: * will match all of the elements of a DataAggregation whose paths match this
038: * query. The fields argument determines what child elements of this aggregation
039: * (for example, the <code>Average time in GC<code>Seconds spent in GC</code>
040: * child of all elements matching the query). The DataAggregation is the aggregation
041: * that contains data parsed from logs of a series of runs.
042: * @param query A query string, as defined in DataAggregation, which will
043: * match some of the elements in a DataAggregation containing data to be graphed.
044: * For example, if you have a set of log wrappers that store the value of something
045: * like the frequency of garbage collection, you will want a query string that will
046: * match all of the wrappers in question.
047: * @param fields The particular children of the wrapper (or other DataAggregation) which
048: * should be graphed. For each field there will be generated one bar on the
049: * graph per query result.
050: * @param data The DataAggregation containing the data to be queried.
051: * Generally this data is generated by running NetBeans inside
052: * the testing infrastructure, with some logging options turned
053: * on, and then parsing the resulting log files from several
054: * runs into a DataAggregation.
055: * @see org.netbeans.performance.spi.DataAggregation
056: * @see org.netbeans.performance.spi.AbstractLogFile
057: */
058: public NbStatisticalDataset(String query, String fields[],
059: DataAggregation data) {
060: if ((query == null) || (fields.length == 0) || (data == null))
061: throw new IllegalArgumentException(
062: "Cannot construct a dataset from null");
063: this .query = "/run/GC Tuning*javaconfig";
064: this .fields = fields;
065: this .source = data;
066: }
067:
068: /** Creates a new instance of NbStatisticalDataset. The query arguments defines
069: * a query (see DataAggregation.query()) such as <code>/mytest*javaconfig</code> which
070: * will match all of the elements of a DataAggregation whose paths match this
071: * query. The fields argument determines what child elements of this aggregation
072: * (for example, the <code>Average time in GC<code>Seconds spent in GC</code>
073: * child of all elements matching the query). The DataAggregation is the aggregation
074: * that contains data parsed from logs of a series of runs.
075: * @param fields The particular children of the wrapper (or other DataAggregation) which
076: * should be graphed. For each field there will be generated one bar on the
077: * graph per query result.
078: * @param data The DataAggregation containing the data to be queried.
079: * Generally this data is generated by running NetBeans inside
080: * the testing infrastructure, with some logging options turned
081: * on, and then parsing the resulting log files from several
082: * runs into a DataAggregation.
083: * @param filter An object implementing the ElementFilter interface, which can be used
084: * to weed out uninteresting results, so they will not be graphed.
085: * @param comparator A comparator used to sort the data. Data will appear in the
086: * resulting chart(s) using the sort order determined by this
087: * comparator.
088: * @see org.netbeans.performance.spi.ElementFilter
089: * @see org.netbeans.performance.spi.DataAggregation
090: * @see org.netbeans.performance.spi.AbstractLogFile
091: */
092: public NbStatisticalDataset(String query, String fields[],
093: DataAggregation data, ElementFilter filter,
094: Comparator comparator) {
095: this (query, fields, data);
096: this .filter = filter;
097: this .comparator = comparator;
098: }
099:
100: private LogElement[] queryElements = null;
101:
102: /**Gets the LogElement objects that were part of the query.
103: */
104: private synchronized LogElement[] getQueryElements() {
105: if (queryElements == null) {
106: if (filter == null) {
107: queryElements = source.query(query);
108: } else {
109: queryElements = source.query(query, filter);
110: }
111: if (comparator != null)
112: Arrays.sort(queryElements, comparator);
113: }
114: return queryElements;
115: }
116:
117: private List categories = null;
118:
119: public synchronized List getCategories() {
120: if (categories == null) {
121: categories = new LinkedList();
122: LogElement[] els = getQueryElements();
123: for (int i = 0; i < els.length; i++) {
124: if (filter == null) {
125: categories.add(els[i].getParent().getName());
126: } else {
127: LogElement el = els[i];
128: if (filter.accept(els[i])) {
129: categories.add(els[i].getParent().getName());
130: } else {
131: discarded.add(els[i].getParent());
132: }
133: }
134: }
135: }
136: return categories;
137: }
138:
139: //XXX may want to list the elements that were not included in the
140: //dataset somewhere, so store this.
141: private List discarded = new LinkedList();
142:
143: public List getDiscarded() {
144: //make sure it's initialized
145: getCategories();
146: return Collections.unmodifiableList(discarded);
147: }
148:
149: /** Returns the number of categories in the dataset.
150: *
151: * @return The category count.
152: *
153: */
154: public int getCategoryCount() {
155: return getQueryElements().length;
156: }
157:
158: /** Returns the value for a series and category.
159: *
160: * @param series The series (zero-based index).
161: * @param category The category.
162: *
163: * @return the value for a series and category.
164: *
165: */
166: public Number getValue(int series, Object category) {
167: return fetchNumberValue(getAggregationForCategory(category),
168: fields[series]);
169: }
170:
171: /** Returns the mean value for the specified series
172: * (zero-based index) and category.
173: *
174: * @param series The series index (zero-based).
175: * @param category The category.
176: * @return the mean value
177: */
178: public Number getMeanValue(int series, Object category) {
179: return (Number) getElementForSeries(series, category)
180: .getValue();
181: }
182:
183: private ValueLogElement getElementForSeries(int series,
184: Object category) {
185: DataAggregation ag = getAggregationForCategory(category);
186: ValueLogElement el = (ValueLogElement) ag
187: .findChild(fields[series]);
188: return el;
189: }
190:
191: private DataAggregation getAggregationForCategory(Object category) {
192: LogElement[] els = getQueryElements();
193: int idx = 0;
194: DataAggregation result = null;
195: while ((idx < els.length) && (result == null)) {
196: try {
197: if (category.toString().equals(
198: ((Named) els[idx]).getName())) {
199: try {
200: result = (DataAggregation) els[idx];
201: } catch (ClassCastException cce0) {
202: System.out
203: .println("Tried to cast "
204: + els[idx].getPath()
205: + " as an instance of DataAggregation. It is "
206: + els[idx].getClass());
207: throw cce0;
208: }
209: }
210: } catch (ClassCastException cce) {
211: System.out.println("Tried to cast "
212: + els[idx].getPath()
213: + " as an instance of Named. It is "
214: + els[idx].getClass());
215: throw cce;
216: }
217: }
218: return result;
219: }
220:
221: /** Returns the standard deviation value for the specified series
222: * (zero-based index) and category. Returns 0 if the object requested
223: * does not implement the Average interface.
224: *
225: * @param series The series index (zero-based).
226: * @param category The category.
227: * @return the standard deviation
228: */
229: public Number getStdDevValue(int series, Object category) {
230: return fetchStdDev(getAggregationForCategory(category),
231: fields[series]);
232: }
233:
234: /**
235: * Returns the number of series in the dataset.
236: *
237: * @return The number of series in the dataset.
238: */
239: public int getSeriesCount() {
240: return fields.length;
241: }
242:
243: /**Makes a reasonable guess at how big a png of the
244: * chart should be.
245: */
246: public int getOptimalWidth() {
247: return getSeriesCount() * getCategoryCount() * 40;
248: }
249:
250: /**
251: * Returns the name of a series.
252: *
253: * @param series The series (zero-based index).
254: *
255: * @return The series name.
256: */
257: public String getSeriesName(int series) {
258: return fields[series];
259: }
260:
261: /**Convenience method to retrieve an element from an aggregation.
262: */
263: private static LogElement fetchElement(DataAggregation da,
264: String key) {
265: return da.findChild(key);
266: }
267:
268: /**Convenience method to retrieve the standard deviation of a value
269: * from an aggregation. If the LogElement requested does not implement
270: * the Average interface, this method will fail silently returning 0.
271: */
272: private static Number fetchStdDev(DataAggregation da, String key) {
273: LogElement el = fetchElement(da, key);
274: if (!(el instanceof ValueLogElement))
275: throw new IllegalArgumentException(
276: el.getPath()
277: + " is not an instance of ValueLogElement. Cannot determine its value in order to chart it.");
278:
279: Object val = ((ValueLogElement) el).getValue();
280: if (val instanceof Average) {
281: return ((Average) el).getStandardDeviation();
282: }
283: return new Double(0);
284: }
285:
286: /**Convenience method to retrieve a the value of a ValueLogElement
287: * from an aggregation. If the ValueLogElement's value class is
288: * not a subclass of Number, an exception will be thrown.
289: */
290: private static Number fetchNumberValue(DataAggregation da,
291: String key) {
292: LogElement el = fetchElement(da, key);
293: if (!(el instanceof ValueLogElement))
294: throw new IllegalArgumentException(
295: el.getPath()
296: + " is not an instance of ValueLogElement. Cannot determine its value in order to chart it.");
297: Object result = ((ValueLogElement) el).getValue();
298: if (!(result instanceof Number))
299: throw new IllegalArgumentException(
300: el.getPath()
301: + " has a value property that is not an instance of Number - it is an instance of "
302: + result.getClass()
303: + " I cannot determine how to evalue it it in order to graph it.");
304: return (Number) result;
305: }
306:
307: /**
308: */
309: private static String findStringValue(DataAggregation da, String key) {
310: return ((ValueLogElement) fetchElement(da, key)).getValue()
311: .toString();
312: }
313:
314: public void seriesChanged(SeriesChangeEvent event) {
315: //null implementation - data will not change
316: }
317:
318: public void addChangeListener(DatasetChangeListener listener) {
319: //empty impl - this data will not change dynamically
320: }
321:
322: public void removeChangeListener(DatasetChangeListener listener) {
323: //empty impl - this data will not change dynamically
324: }
325:
326: }
|