/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ddmuilib.log.event;
import com.android.ddmlib.log.EventContainer;
import com.android.ddmlib.log.EventLogParser;
import com.android.ddmlib.log.EventValueDescription;
import com.android.ddmlib.log.InvalidTypeException;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
import org.jfree.chart.renderer.xy.XYAreaRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class DisplayGraph extends EventDisplay {
public DisplayGraph(String name) {
super(name);
}
/**
* Resets the display.
*/
@Override
void resetUI() {
Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values();
for (TimeSeriesCollection dataset : datasets) {
dataset.removeAllSeries();
}
if (mOccurrenceDataSet != null) {
mOccurrenceDataSet.removeAllSeries();
}
mValueDescriptorSeriesMap.clear();
mOcurrenceDescriptorSeriesMap.clear();
}
/**
* Creates the UI for the event display.
* @param parent the parent composite.
* @param logParser the current log parser.
* @return the created control (which may have children).
*/
@Override
public Control createComposite(final Composite parent, EventLogParser logParser,
final ILogColumnListener listener) {
String title = getChartTitle(logParser);
return createCompositeChart(parent, logParser, title);
}
/**
* Adds event to the display.
*/
@Override
void newEvent(EventContainer event, EventLogParser logParser) {
ArrayList<ValueDisplayDescriptor> valueDescriptors =
new ArrayList<ValueDisplayDescriptor>();
ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors =
new ArrayList<OccurrenceDisplayDescriptor>();
if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) {
updateChart(event, logParser, valueDescriptors, occurrenceDescriptors);
}
}
/**
* Updates the chart with the {@link EventContainer} by adding the values/occurrences defined
* by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from
* the two lists.
* <p/>This method is only called when at least one of the descriptor list is non empty.
* @param event
* @param logParser
* @param valueDescriptors
* @param occurrenceDescriptors
*/
private void updateChart(EventContainer event, EventLogParser logParser,
ArrayList<ValueDisplayDescriptor> valueDescriptors,
ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
Map<Integer, String> tagMap = logParser.getTagMap();
Millisecond millisecondTime = null;
long msec = -1;
// If the event container is a cpu container (tag == 2721), and there is no descriptor
// for the total CPU load, then we do accumulate all the values.
boolean accumulateValues = false;
double accumulatedValue = 0;
if (event.mTag == 2721) {
accumulateValues = true;
for (ValueDisplayDescriptor descriptor : valueDescriptors) {
accumulateValues &= (descriptor.valueIndex != 0);
}
}
for (ValueDisplayDescriptor descriptor : valueDescriptors) {
try {
// get the hashmap for this descriptor
HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor);
// if it's not there yet, we create it.
if (map == null) {
map = new HashMap<Integer, TimeSeries>();
mValueDescriptorSeriesMap.put(descriptor, map);
}
// get the TimeSeries for this pid
TimeSeries timeSeries = map.get(event.pid);
// if it doesn't exist yet, we create it
if (timeSeries == null) {
// get the series name
String seriesFullName = null;
String seriesLabel = getSeriesLabel(event, descriptor);
switch (mValueDescriptorCheck) {
case EVENT_CHECK_SAME_TAG:
seriesFullName = String.format("%1$s / %2$s", seriesLabel,
descriptor.valueName);
break;
case EVENT_CHECK_SAME_VALUE:
seriesFullName = String.format("%1$s", seriesLabel);
break;
default:
seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel,
tagMap.get(descriptor.eventTag),
descriptor.valueName);
break;
}
// get the data set for this ValueType
TimeSeriesCollection dataset = getValueDataset(
logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex]
.getValueType(),
accumulateValues);
// create the series
timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
if (mMaximumChartItemAge != -1) {
timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000);
}
dataset.addSeries(timeSeries);
// add it to the map.
map.put(event.pid, timeSeries);
}
// update the timeSeries.
// get the value from the event
double value = event.getValueAsDouble(descriptor.valueIndex);
// accumulate the values if needed.
if (accumulateValues) {
accumulatedValue += value;
value = accumulatedValue;
}
// get the time
if (millisecondTime == null) {
msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
millisecondTime = new Millisecond(new Date(msec));
}
// add the value to the time series
timeSeries.addOrUpdate(millisecondTime, value);
} catch (InvalidTypeException e) {
// just ignore this descriptor if there's a type mismatch
}
}
for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) {
try {
// get the hashmap for this descriptor
HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor);
// if it's not there yet, we create it.
if (map == null) {
map = new HashMap<Integer, TimeSeries>();
mOcurrenceDescriptorSeriesMap.put(descriptor, map);
}
// get the TimeSeries for this pid
TimeSeries timeSeries = map.get(event.pid);
// if it doesn't exist yet, we create it.
if (timeSeries == null) {
String seriesLabel = getSeriesLabel(event, descriptor);
String seriesFullName = String.format("[%1$s:%2$s]",
tagMap.get(descriptor.eventTag), seriesLabel);
timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
if (mMaximumChartItemAge != -1) {
timeSeries.setMaximumItemAge(mMaximumChartItemAge);
}
getOccurrenceDataSet().addSeries(timeSeries);
map.put(event.pid, timeSeries);
}
// update the series
// get the time
if (millisecondTime == null) {
msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
millisecondTime = new Millisecond(new Date(msec));
}
// add the value to the time series
timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused
} catch (InvalidTypeException e) {
// just ignore this descriptor if there's a type mismatch
}
}
// go through all the series and remove old values.
if (msec != -1 && mMaximumChartItemAge != -1) {
Collection<HashMap<Integer, TimeSeries>> pidMapValues =
mValueDescriptorSeriesMap.values();
for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
Collection<TimeSeries> seriesCollection = pidMapValue.values();
for (TimeSeries timeSeries : seriesCollection) {
timeSeries.removeAgedItems(msec, true);
}
}
pidMapValues = mOcurrenceDescriptorSeriesMap.values();
for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
Collection<TimeSeries> seriesCollection = pidMapValue.values();
for (TimeSeries timeSeries : seriesCollection) {
timeSeries.removeAgedItems(msec, true);
}
}
}
}
/**
* Returns a {@link TimeSeriesCollection} for a specific {@link com.android.ddmlib.log.EventValueDescription.ValueType}.
* If the data set is not yet created, it is first allocated and set up into the
* {@link org.jfree.chart.JFreeChart} object.
* @param type the {@link com.android.ddmlib.log.EventValueDescription.ValueType} of the data set.
* @param accumulateValues
*/
private TimeSeriesCollection getValueDataset(EventValueDescription.ValueType type, boolean accumulateValues) {
TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type);
if (dataset == null) {
// create the data set and store it in the map
dataset = new TimeSeriesCollection();
mValueTypeDataSetMap.put(type, dataset);
// create the renderer and configure it depending on the ValueType
AbstractXYItemRenderer renderer;
if (type == EventValueDescription.ValueType.PERCENT && accumulateValues) {
renderer = new XYAreaRenderer();
} else {
XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
r.setBaseShapesVisible(type != EventValueDescription.ValueType.PERCENT);
renderer = r;
}
// set both the dataset and the renderer in the plot object.
XYPlot xyPlot = mChart.getXYPlot();
xyPlot.setDataset(mDataSetCount, dataset);
xyPlot.setRenderer(mDataSetCount, renderer);
// put a new axis label, and configure it.
NumberAxis axis = new NumberAxis(type.toString());
if (type == EventValueDescription.ValueType.PERCENT) {
// force percent range to be (0,100) fixed.
axis.setAutoRange(false);
axis.setRange(0., 100.);
}
// for the index, we ignore the occurrence dataset
int count = mDataSetCount;
if (mOccurrenceDataSet != null) {
count--;
}
xyPlot.setRangeAxis(count, axis);
if ((count % 2) == 0) {
xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT);
} else {
xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT);
}
// now we link the dataset and the axis
xyPlot.mapDatasetToRangeAxis(mDataSetCount, count);
mDataSetCount++;
}
return dataset;
}
/**
* Return the series label for this event. This only contains the pid information.
* @param event the {@link EventContainer}
* @param descriptor the {@link OccurrenceDisplayDescriptor}
* @return the series label.
* @throws InvalidTypeException
*/
private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor)
throws InvalidTypeException {
if (descriptor.seriesValueIndex != -1) {
if (descriptor.includePid == false) {
return event.getValueAsString(descriptor.seriesValueIndex);
} else {
return String.format("%1$s (%2$d)",
event.getValueAsString(descriptor.seriesValueIndex), event.pid);
}
}
return Integer.toString(event.pid);
}
/**
* Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not
* yet created, it is first allocated and set up into the {@link org.jfree.chart.JFreeChart} object.
*/
private TimeSeriesCollection getOccurrenceDataSet() {
if (mOccurrenceDataSet == null) {
mOccurrenceDataSet = new TimeSeriesCollection();
XYPlot xyPlot = mChart.getXYPlot();
xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet);
OccurrenceRenderer renderer = new OccurrenceRenderer();
renderer.setBaseShapesVisible(false);
xyPlot.setRenderer(mDataSetCount, renderer);
mDataSetCount++;
}
return mOccurrenceDataSet;
}
/**
* Gets display type
*
* @return display type as an integer
*/
@Override
int getDisplayType() {
return DISPLAY_TYPE_GRAPH;
}
/**
* Sets the current {@link EventLogParser} object.
*/
@Override
protected void setNewLogParser(EventLogParser logParser) {
if (mChart != null) {
mChart.setTitle(getChartTitle(logParser));
}
}
/**
* Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}.
*
* @param logParser the logParser.
* @return the chart title.
*/
private String getChartTitle(EventLogParser logParser) {
if (mValueDescriptors.size() > 0) {
String chartDesc = null;
switch (mValueDescriptorCheck) {
case EVENT_CHECK_SAME_TAG:
if (logParser != null) {
chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag);
}
break;
case EVENT_CHECK_SAME_VALUE:
if (logParser != null) {
chartDesc = String.format("%1$s / %2$s",
logParser.getTagMap().get(mValueDescriptors.get(0).eventTag),
mValueDescriptors.get(0).valueName);
}
break;
}
if (chartDesc != null) {
return String.format("%1$s - %2$s", mName, chartDesc);
}
}
return mName;
}
}