1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package com.android.ddmuilib.log.event; 18 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; 34 35 import java.util.ArrayList; 36 import java.util.Collection; 37 import java.util.Date; 38 import java.util.HashMap; 39 import java.util.Map; 40 41 public class DisplayGraph extends EventDisplay { 42 DisplayGraph(String name)43 public DisplayGraph(String name) { 44 super(name); 45 } 46 47 /** 48 * Resets the display. 49 */ 50 @Override resetUI()51 void resetUI() { 52 Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values(); 53 for (TimeSeriesCollection dataset : datasets) { 54 dataset.removeAllSeries(); 55 } 56 if (mOccurrenceDataSet != null) { 57 mOccurrenceDataSet.removeAllSeries(); 58 } 59 mValueDescriptorSeriesMap.clear(); 60 mOcurrenceDescriptorSeriesMap.clear(); 61 } 62 63 /** 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). 68 */ 69 @Override createComposite(final Composite parent, EventLogParser logParser, final ILogColumnListener listener)70 public Control createComposite(final Composite parent, EventLogParser logParser, 71 final ILogColumnListener listener) { 72 String title = getChartTitle(logParser); 73 return createCompositeChart(parent, logParser, title); 74 } 75 76 /** 77 * Adds event to the display. 78 */ 79 @Override newEvent(EventContainer event, EventLogParser logParser)80 void newEvent(EventContainer event, EventLogParser logParser) { 81 ArrayList<ValueDisplayDescriptor> valueDescriptors = 82 new ArrayList<ValueDisplayDescriptor>(); 83 84 ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors = 85 new ArrayList<OccurrenceDisplayDescriptor>(); 86 87 if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) { 88 updateChart(event, logParser, valueDescriptors, occurrenceDescriptors); 89 } 90 } 91 92 /** 93 * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined 94 * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from 95 * the two lists. 96 * <p/>This method is only called when at least one of the descriptor list is non empty. 97 * @param event 98 * @param logParser 99 * @param valueDescriptors 100 * @param occurrenceDescriptors 101 */ updateChart(EventContainer event, EventLogParser logParser, ArrayList<ValueDisplayDescriptor> valueDescriptors, ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors)102 private void updateChart(EventContainer event, EventLogParser logParser, 103 ArrayList<ValueDisplayDescriptor> valueDescriptors, 104 ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { 105 Map<Integer, String> tagMap = logParser.getTagMap(); 106 107 Millisecond millisecondTime = null; 108 long msec = -1; 109 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; 114 115 if (event.mTag == 2721) { 116 accumulateValues = true; 117 for (ValueDisplayDescriptor descriptor : valueDescriptors) { 118 accumulateValues &= (descriptor.valueIndex != 0); 119 } 120 } 121 122 for (ValueDisplayDescriptor descriptor : valueDescriptors) { 123 try { 124 // get the hashmap for this descriptor 125 HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor); 126 127 // if it's not there yet, we create it. 128 if (map == null) { 129 map = new HashMap<Integer, TimeSeries>(); 130 mValueDescriptorSeriesMap.put(descriptor, map); 131 } 132 133 // get the TimeSeries for this pid 134 TimeSeries timeSeries = map.get(event.pid); 135 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); 141 142 switch (mValueDescriptorCheck) { 143 case EVENT_CHECK_SAME_TAG: 144 seriesFullName = String.format("%1$s / %2$s", seriesLabel, 145 descriptor.valueName); 146 break; 147 case EVENT_CHECK_SAME_VALUE: 148 seriesFullName = String.format("%1$s", seriesLabel); 149 break; 150 default: 151 seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel, 152 tagMap.get(descriptor.eventTag), 153 descriptor.valueName); 154 break; 155 } 156 157 // get the data set for this ValueType 158 TimeSeriesCollection dataset = getValueDataset( 159 logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex] 160 .getValueType(), 161 accumulateValues); 162 163 // create the series 164 timeSeries = new TimeSeries(seriesFullName, Millisecond.class); 165 if (mMaximumChartItemAge != -1) { 166 timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000); 167 } 168 169 dataset.addSeries(timeSeries); 170 171 // add it to the map. 172 map.put(event.pid, timeSeries); 173 } 174 175 // update the timeSeries. 176 177 // get the value from the event 178 double value = event.getValueAsDouble(descriptor.valueIndex); 179 180 // accumulate the values if needed. 181 if (accumulateValues) { 182 accumulatedValue += value; 183 value = accumulatedValue; 184 } 185 186 // get the time 187 if (millisecondTime == null) { 188 msec = (long)event.sec * 1000L + (event.nsec / 1000000L); 189 millisecondTime = new Millisecond(new Date(msec)); 190 } 191 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 196 } 197 } 198 199 for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) { 200 try { 201 // get the hashmap for this descriptor 202 HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor); 203 204 // if it's not there yet, we create it. 205 if (map == null) { 206 map = new HashMap<Integer, TimeSeries>(); 207 mOcurrenceDescriptorSeriesMap.put(descriptor, map); 208 } 209 210 // get the TimeSeries for this pid 211 TimeSeries timeSeries = map.get(event.pid); 212 213 // if it doesn't exist yet, we create it. 214 if (timeSeries == null) { 215 String seriesLabel = getSeriesLabel(event, descriptor); 216 217 String seriesFullName = String.format("[%1$s:%2$s]", 218 tagMap.get(descriptor.eventTag), seriesLabel); 219 220 timeSeries = new TimeSeries(seriesFullName, Millisecond.class); 221 if (mMaximumChartItemAge != -1) { 222 timeSeries.setMaximumItemAge(mMaximumChartItemAge); 223 } 224 225 getOccurrenceDataSet().addSeries(timeSeries); 226 227 map.put(event.pid, timeSeries); 228 } 229 230 // update the series 231 232 // get the time 233 if (millisecondTime == null) { 234 msec = (long)event.sec * 1000L + (event.nsec / 1000000L); 235 millisecondTime = new Millisecond(new Date(msec)); 236 } 237 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 242 } 243 } 244 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(); 249 250 for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { 251 Collection<TimeSeries> seriesCollection = pidMapValue.values(); 252 253 for (TimeSeries timeSeries : seriesCollection) { 254 timeSeries.removeAgedItems(msec, true); 255 } 256 } 257 258 pidMapValues = mOcurrenceDescriptorSeriesMap.values(); 259 for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { 260 Collection<TimeSeries> seriesCollection = pidMapValue.values(); 261 262 for (TimeSeries timeSeries : seriesCollection) { 263 timeSeries.removeAgedItems(msec, true); 264 } 265 } 266 } 267 } 268 269 /** 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 275 */ getValueDataset(EventValueDescription.ValueType type, boolean 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); 282 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(); 287 } else { 288 XYLineAndShapeRenderer r = new XYLineAndShapeRenderer(); 289 r.setBaseShapesVisible(type != EventValueDescription.ValueType.PERCENT); 290 291 renderer = r; 292 } 293 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); 298 299 // put a new axis label, and configure it. 300 NumberAxis axis = new NumberAxis(type.toString()); 301 302 if (type == EventValueDescription.ValueType.PERCENT) { 303 // force percent range to be (0,100) fixed. 304 axis.setAutoRange(false); 305 axis.setRange(0., 100.); 306 } 307 308 // for the index, we ignore the occurrence dataset 309 int count = mDataSetCount; 310 if (mOccurrenceDataSet != null) { 311 count--; 312 } 313 314 xyPlot.setRangeAxis(count, axis); 315 if ((count % 2) == 0) { 316 xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT); 317 } else { 318 xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT); 319 } 320 321 // now we link the dataset and the axis 322 xyPlot.mapDatasetToRangeAxis(mDataSetCount, count); 323 324 mDataSetCount++; 325 } 326 327 return dataset; 328 } 329 330 /** 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 336 */ getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor)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); 342 } else { 343 return String.format("%1$s (%2$d)", 344 event.getValueAsString(descriptor.seriesValueIndex), event.pid); 345 } 346 } 347 348 return Integer.toString(event.pid); 349 } 350 351 /** 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. 354 */ getOccurrenceDataSet()355 private TimeSeriesCollection getOccurrenceDataSet() { 356 if (mOccurrenceDataSet == null) { 357 mOccurrenceDataSet = new TimeSeriesCollection(); 358 359 XYPlot xyPlot = mChart.getXYPlot(); 360 xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet); 361 362 OccurrenceRenderer renderer = new OccurrenceRenderer(); 363 renderer.setBaseShapesVisible(false); 364 xyPlot.setRenderer(mDataSetCount, renderer); 365 366 mDataSetCount++; 367 } 368 369 return mOccurrenceDataSet; 370 } 371 372 /** 373 * Gets display type 374 * 375 * @return display type as an integer 376 */ 377 @Override getDisplayType()378 int getDisplayType() { 379 return DISPLAY_TYPE_GRAPH; 380 } 381 382 /** 383 * Sets the current {@link EventLogParser} object. 384 */ 385 @Override setNewLogParser(EventLogParser logParser)386 protected void setNewLogParser(EventLogParser logParser) { 387 if (mChart != null) { 388 mChart.setTitle(getChartTitle(logParser)); 389 } 390 } 391 /** 392 * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}. 393 * 394 * @param logParser the logParser. 395 * @return the chart title. 396 */ getChartTitle(EventLogParser logParser)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); 404 } 405 break; 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); 411 } 412 break; 413 } 414 415 if (chartDesc != null) { 416 return String.format("%1$s - %2$s", mName, chartDesc); 417 } 418 } 419 420 return mName; 421 } 422 }