2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.ddmuilib.log.event;
19 import com.android.ddmlib.log.EventContainer;
20 import com.android.ddmlib.log.EventLogParser;
21 import com.android.ddmlib.log.EventValueDescription;
22 import com.android.ddmlib.log.InvalidTypeException;
23 import org.eclipse.swt.widgets.Composite;
24 import org.eclipse.swt.widgets.Control;
25 import org.jfree.chart.axis.AxisLocation;
26 import org.jfree.chart.axis.NumberAxis;
27 import org.jfree.chart.plot.XYPlot;
28 import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
29 import org.jfree.chart.renderer.xy.XYAreaRenderer;
30 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
31 import org.jfree.data.time.Millisecond;
32 import org.jfree.data.time.TimeSeries;
33 import org.jfree.data.time.TimeSeriesCollection;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Date;
38 import java.util.HashMap;
41 public class DisplayGraph extends EventDisplay {
43 public DisplayGraph(String name) {
52 Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values();
53 for (TimeSeriesCollection dataset : datasets) {
54 dataset.removeAllSeries();
56 if (mOccurrenceDataSet != null) {
57 mOccurrenceDataSet.removeAllSeries();
59 mValueDescriptorSeriesMap.clear();
60 mOcurrenceDescriptorSeriesMap.clear();
64 * Creates the UI for the event display.
65 * @param parent the parent composite.
66 * @param logParser the current log parser.
67 * @return the created control (which may have children).
70 public Control createComposite(final Composite parent, EventLogParser logParser,
71 final ILogColumnListener listener) {
72 String title = getChartTitle(logParser);
73 return createCompositeChart(parent, logParser, title);
77 * Adds event to the display.
80 void newEvent(EventContainer event, EventLogParser logParser) {
81 ArrayList<ValueDisplayDescriptor> valueDescriptors =
82 new ArrayList<ValueDisplayDescriptor>();
84 ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors =
85 new ArrayList<OccurrenceDisplayDescriptor>();
87 if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) {
88 updateChart(event, logParser, valueDescriptors, occurrenceDescriptors);
93 * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined
94 * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from
96 * <p/>This method is only called when at least one of the descriptor list is non empty.
99 * @param valueDescriptors
100 * @param occurrenceDescriptors
102 private void updateChart(EventContainer event, EventLogParser logParser,
103 ArrayList<ValueDisplayDescriptor> valueDescriptors,
104 ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
105 Map<Integer, String> tagMap = logParser.getTagMap();
107 Millisecond millisecondTime = null;
110 // If the event container is a cpu container (tag == 2721), and there is no descriptor
111 // for the total CPU load, then we do accumulate all the values.
112 boolean accumulateValues = false;
113 double accumulatedValue = 0;
115 if (event.mTag == 2721) {
116 accumulateValues = true;
117 for (ValueDisplayDescriptor descriptor : valueDescriptors) {
118 accumulateValues &= (descriptor.valueIndex != 0);
122 for (ValueDisplayDescriptor descriptor : valueDescriptors) {
124 // get the hashmap for this descriptor
125 HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor);
127 // if it's not there yet, we create it.
129 map = new HashMap<Integer, TimeSeries>();
130 mValueDescriptorSeriesMap.put(descriptor, map);
133 // get the TimeSeries for this pid
134 TimeSeries timeSeries = map.get(event.pid);
136 // if it doesn't exist yet, we create it
137 if (timeSeries == null) {
138 // get the series name
139 String seriesFullName = null;
140 String seriesLabel = getSeriesLabel(event, descriptor);
142 switch (mValueDescriptorCheck) {
143 case EVENT_CHECK_SAME_TAG:
144 seriesFullName = String.format("%1$s / %2$s", seriesLabel,
145 descriptor.valueName);
147 case EVENT_CHECK_SAME_VALUE:
148 seriesFullName = String.format("%1$s", seriesLabel);
151 seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel,
152 tagMap.get(descriptor.eventTag),
153 descriptor.valueName);
157 // get the data set for this ValueType
158 TimeSeriesCollection dataset = getValueDataset(
159 logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex]
164 timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
165 if (mMaximumChartItemAge != -1) {
166 timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000);
169 dataset.addSeries(timeSeries);
171 // add it to the map.
172 map.put(event.pid, timeSeries);
175 // update the timeSeries.
177 // get the value from the event
178 double value = event.getValueAsDouble(descriptor.valueIndex);
180 // accumulate the values if needed.
181 if (accumulateValues) {
182 accumulatedValue += value;
183 value = accumulatedValue;
187 if (millisecondTime == null) {
188 msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
189 millisecondTime = new Millisecond(new Date(msec));
192 // add the value to the time series
193 timeSeries.addOrUpdate(millisecondTime, value);
194 } catch (InvalidTypeException e) {
195 // just ignore this descriptor if there's a type mismatch
199 for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) {
201 // get the hashmap for this descriptor
202 HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor);
204 // if it's not there yet, we create it.
206 map = new HashMap<Integer, TimeSeries>();
207 mOcurrenceDescriptorSeriesMap.put(descriptor, map);
210 // get the TimeSeries for this pid
211 TimeSeries timeSeries = map.get(event.pid);
213 // if it doesn't exist yet, we create it.
214 if (timeSeries == null) {
215 String seriesLabel = getSeriesLabel(event, descriptor);
217 String seriesFullName = String.format("[%1$s:%2$s]",
218 tagMap.get(descriptor.eventTag), seriesLabel);
220 timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
221 if (mMaximumChartItemAge != -1) {
222 timeSeries.setMaximumItemAge(mMaximumChartItemAge);
225 getOccurrenceDataSet().addSeries(timeSeries);
227 map.put(event.pid, timeSeries);
233 if (millisecondTime == null) {
234 msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
235 millisecondTime = new Millisecond(new Date(msec));
238 // add the value to the time series
239 timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused
240 } catch (InvalidTypeException e) {
241 // just ignore this descriptor if there's a type mismatch
245 // go through all the series and remove old values.
246 if (msec != -1 && mMaximumChartItemAge != -1) {
247 Collection<HashMap<Integer, TimeSeries>> pidMapValues =
248 mValueDescriptorSeriesMap.values();
250 for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
251 Collection<TimeSeries> seriesCollection = pidMapValue.values();
253 for (TimeSeries timeSeries : seriesCollection) {
254 timeSeries.removeAgedItems(msec, true);
258 pidMapValues = mOcurrenceDescriptorSeriesMap.values();
259 for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
260 Collection<TimeSeries> seriesCollection = pidMapValue.values();
262 for (TimeSeries timeSeries : seriesCollection) {
263 timeSeries.removeAgedItems(msec, true);
270 * Returns a {@link TimeSeriesCollection} for a specific {@link com.android.ddmlib.log.EventValueDescription.ValueType}.
271 * If the data set is not yet created, it is first allocated and set up into the
272 * {@link org.jfree.chart.JFreeChart} object.
273 * @param type the {@link com.android.ddmlib.log.EventValueDescription.ValueType} of the data set.
274 * @param accumulateValues
276 private TimeSeriesCollection getValueDataset(EventValueDescription.ValueType type, boolean accumulateValues) {
277 TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type);
278 if (dataset == null) {
279 // create the data set and store it in the map
280 dataset = new TimeSeriesCollection();
281 mValueTypeDataSetMap.put(type, dataset);
283 // create the renderer and configure it depending on the ValueType
284 AbstractXYItemRenderer renderer;
285 if (type == EventValueDescription.ValueType.PERCENT && accumulateValues) {
286 renderer = new XYAreaRenderer();
288 XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
289 r.setBaseShapesVisible(type != EventValueDescription.ValueType.PERCENT);
294 // set both the dataset and the renderer in the plot object.
295 XYPlot xyPlot = mChart.getXYPlot();
296 xyPlot.setDataset(mDataSetCount, dataset);
297 xyPlot.setRenderer(mDataSetCount, renderer);
299 // put a new axis label, and configure it.
300 NumberAxis axis = new NumberAxis(type.toString());
302 if (type == EventValueDescription.ValueType.PERCENT) {
303 // force percent range to be (0,100) fixed.
304 axis.setAutoRange(false);
305 axis.setRange(0., 100.);
308 // for the index, we ignore the occurrence dataset
309 int count = mDataSetCount;
310 if (mOccurrenceDataSet != null) {
314 xyPlot.setRangeAxis(count, axis);
315 if ((count % 2) == 0) {
316 xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT);
318 xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT);
321 // now we link the dataset and the axis
322 xyPlot.mapDatasetToRangeAxis(mDataSetCount, count);
331 * Return the series label for this event. This only contains the pid information.
332 * @param event the {@link EventContainer}
333 * @param descriptor the {@link OccurrenceDisplayDescriptor}
334 * @return the series label.
335 * @throws InvalidTypeException
337 private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor)
338 throws InvalidTypeException {
339 if (descriptor.seriesValueIndex != -1) {
340 if (descriptor.includePid == false) {
341 return event.getValueAsString(descriptor.seriesValueIndex);
343 return String.format("%1$s (%2$d)",
344 event.getValueAsString(descriptor.seriesValueIndex), event.pid);
348 return Integer.toString(event.pid);
352 * Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not
353 * yet created, it is first allocated and set up into the {@link org.jfree.chart.JFreeChart} object.
355 private TimeSeriesCollection getOccurrenceDataSet() {
356 if (mOccurrenceDataSet == null) {
357 mOccurrenceDataSet = new TimeSeriesCollection();
359 XYPlot xyPlot = mChart.getXYPlot();
360 xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet);
362 OccurrenceRenderer renderer = new OccurrenceRenderer();
363 renderer.setBaseShapesVisible(false);
364 xyPlot.setRenderer(mDataSetCount, renderer);
369 return mOccurrenceDataSet;
375 * @return display type as an integer
378 int getDisplayType() {
379 return DISPLAY_TYPE_GRAPH;
383 * Sets the current {@link EventLogParser} object.
386 protected void setNewLogParser(EventLogParser logParser) {
387 if (mChart != null) {
388 mChart.setTitle(getChartTitle(logParser));
392 * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}.
394 * @param logParser the logParser.
395 * @return the chart title.
397 private String getChartTitle(EventLogParser logParser) {
398 if (mValueDescriptors.size() > 0) {
399 String chartDesc = null;
400 switch (mValueDescriptorCheck) {
401 case EVENT_CHECK_SAME_TAG:
402 if (logParser != null) {
403 chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag);
406 case EVENT_CHECK_SAME_VALUE:
407 if (logParser != null) {
408 chartDesc = String.format("%1$s / %2$s",
409 logParser.getTagMap().get(mValueDescriptors.get(0).eventTag),
410 mValueDescriptors.get(0).valueName);
415 if (chartDesc != null) {
416 return String.format("%1$s - %2$s", mName, chartDesc);