Copyright header update, 2015 edition
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / views / histogram / HistogramDataModel.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2015 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
11 * Bernd Hufmann - Implementation of new interfaces/listeners and support for
12 * time stamp in any order
13 * Francois Chouinard - Moved from LTTng to TMF
14 * Francois Chouinard - Added support for empty initial buckets
15 * Patrick Tasse - Support selection range
16 * Jean-Christian Kouamé, Simon Delisle - Added support to manage lost events
17 * Xavier Raynaud - Support multi-trace coloring
18 *******************************************************************************/
19
20 package org.eclipse.tracecompass.tmf.ui.views.histogram;
21
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.LinkedHashMap;
25 import java.util.Map;
26
27 import org.eclipse.core.runtime.ListenerList;
28 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
29 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
30 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
31
32 import com.google.common.base.Function;
33 import com.google.common.collect.FluentIterable;
34
35 /**
36 * Histogram-independent data model.
37 *
38 * It has the following characteristics:
39 * <ul>
40 * <li>The <i>basetime</i> is the timestamp of the first event
41 * <li>There is a fixed number (<i>n</i>) of buckets of uniform duration
42 * (<i>d</i>)
43 * <li>The <i>timespan</i> of the model is thus: <i>n</i> * <i>d</i> time units
44 * <li>Bucket <i>i</i> holds the number of events that occurred in time range:
45 * [<i>basetime</i> + <i>i</i> * <i>d</i>, <i>basetime</i> + (<i>i</i> + 1) *
46 * <i>d</i>)
47 * </ul>
48 * Initially, the bucket durations is set to 1ns. As the events are read, they
49 * are tallied (using <i>countEvent()</i>) in the appropriate bucket (relative
50 * to the <i>basetime</i>).
51 * <p>
52 * Eventually, an event will have a timestamp that exceeds the <i>timespan</i>
53 * high end (determined by <i>n</i>, the number of buckets, and <i>d</i>, the
54 * bucket duration). At this point, the histogram needs to be compacted. This is
55 * done by simply merging adjacent buckets by pair, in effect doubling the
56 * <i>timespan</i> (<i>timespan'</i> = <i>n</i> * <i>d'</i>, where <i>d'</i> =
57 * 2<i>d</i>). This compaction happens as needed as the trace is read.
58 * <p>
59 * The model allows for timestamps in not increasing order. The timestamps can
60 * be fed to the model in any order. If an event has a timestamp less than the
61 * <i>basetime</i>, the buckets will be moved to the right to account for the
62 * new smaller timestamp. The new <i>basetime</i> is a multiple of the bucket
63 * duration smaller then the previous <i>basetime</i>. Note that the
64 * <i>basetime</i> might no longer be the timestamp of an event. If necessary,
65 * the buckets will be compacted before moving to the right. This might be
66 * necessary to not lose any event counts at the end of the buckets array.
67 * <p>
68 * The mapping from the model to the UI is performed by the <i>scaleTo()</i>
69 * method. By keeping the number of buckets <i>n</i> relatively large with
70 * respect to to the number of pixels in the actual histogram, we should achieve
71 * a nice result when visualizing the histogram.
72 * <p>
73 *
74 * @version 2.0
75 * @author Francois Chouinard
76 */
77 public class HistogramDataModel implements IHistogramDataModel {
78
79 // ------------------------------------------------------------------------
80 // Constants
81 // ------------------------------------------------------------------------
82
83 /**
84 * The default number of buckets
85 */
86 public static final int DEFAULT_NUMBER_OF_BUCKETS = 16 * 1000;
87
88 /**
89 * Number of events after which listeners will be notified.
90 */
91 public static final int REFRESH_FREQUENCY = DEFAULT_NUMBER_OF_BUCKETS;
92
93 // ------------------------------------------------------------------------
94 // Attributes
95 // ------------------------------------------------------------------------
96
97 // Trace management
98 private ITmfTrace fTrace = null;
99 private final Map<ITmfTrace, Integer> fTraceMap = new LinkedHashMap<>();
100
101 // Bucket management
102 private final int fNbBuckets;
103 private final HistogramBucket[] fBuckets;
104 private final long[] fLostEventsBuckets;
105 private long fBucketDuration;
106 private long fNbEvents;
107 private int fLastBucket;
108
109 // Timestamps
110 private long fFirstBucketTime; // could be negative when analyzing events with descending order!!!
111 private long fFirstEventTime;
112 private long fEndTime;
113 private long fSelectionBegin;
114 private long fSelectionEnd;
115 private long fTimeLimit;
116
117 // Private listener lists
118 private final ListenerList fModelListeners;
119
120 // ------------------------------------------------------------------------
121 // Constructors
122 // ------------------------------------------------------------------------
123
124 /**
125 * Default constructor with default number of buckets.
126 */
127 public HistogramDataModel() {
128 this(0, DEFAULT_NUMBER_OF_BUCKETS);
129 }
130
131 /**
132 * Default constructor with default number of buckets.
133 *
134 * @param startTime
135 * The histogram start time
136 * @since 2.0
137 */
138 public HistogramDataModel(long startTime) {
139 this(startTime, DEFAULT_NUMBER_OF_BUCKETS);
140 }
141
142 /**
143 * Constructor with non-default number of buckets.
144 *
145 * @param nbBuckets
146 * A number of buckets.
147 */
148 public HistogramDataModel(int nbBuckets) {
149 this(0, nbBuckets);
150 }
151
152 /**
153 * Constructor with non-default number of buckets.
154 *
155 * @param startTime
156 * the histogram start time
157 * @param nbBuckets
158 * A number of buckets.
159 * @since 2.0
160 */
161 public HistogramDataModel(long startTime, int nbBuckets) {
162 fFirstBucketTime = fFirstEventTime = fEndTime = startTime;
163 fNbBuckets = nbBuckets;
164 fBuckets = new HistogramBucket[nbBuckets];
165 fLostEventsBuckets = new long[nbBuckets];
166 fModelListeners = new ListenerList();
167 clear();
168 }
169
170 /**
171 * Copy constructor.
172 *
173 * @param other
174 * A model to copy.
175 */
176 public HistogramDataModel(HistogramDataModel other) {
177 fNbBuckets = other.fNbBuckets;
178 fBuckets = new HistogramBucket[fNbBuckets];
179 for (int i = 0; i < fNbBuckets; i++) {
180 fBuckets[i] = new HistogramBucket(other.fBuckets[i]);
181 }
182 fLostEventsBuckets = Arrays.copyOf(other.fLostEventsBuckets, fNbBuckets);
183 fBucketDuration = Math.max(other.fBucketDuration, 1);
184 fNbEvents = other.fNbEvents;
185 fLastBucket = other.fLastBucket;
186 fFirstBucketTime = other.fFirstBucketTime;
187 fFirstEventTime = other.fFirstEventTime;
188 fEndTime = other.fEndTime;
189 fSelectionBegin = other.fSelectionBegin;
190 fSelectionEnd = other.fSelectionEnd;
191 fTimeLimit = other.fTimeLimit;
192 fModelListeners = new ListenerList();
193 Object[] listeners = other.fModelListeners.getListeners();
194 for (Object listener : listeners) {
195 fModelListeners.add(listener);
196 }
197 }
198
199
200 /**
201 * Disposes the data model
202 * @since 3.0
203 */
204 public void dispose() {
205 fTraceMap.clear();
206 fTrace = null;
207 }
208
209 // ------------------------------------------------------------------------
210 // Accessors
211 // ------------------------------------------------------------------------
212
213 /**
214 * Returns the number of events in the data model.
215 *
216 * @return number of events.
217 */
218 public long getNbEvents() {
219 return fNbEvents;
220 }
221
222 /**
223 * Returns the number of buckets in the model.
224 *
225 * @return number of buckets.
226 */
227 public int getNbBuckets() {
228 return fNbBuckets;
229 }
230
231 /**
232 * Returns the current bucket duration.
233 *
234 * @return bucket duration
235 */
236 public long getBucketDuration() {
237 return fBucketDuration;
238 }
239
240 /**
241 * Returns the time value of the first bucket in the model.
242 *
243 * @return time of first bucket.
244 */
245 public long getFirstBucketTime() {
246 return fFirstBucketTime;
247 }
248
249 /**
250 * Returns the time of the first event in the model.
251 *
252 * @return time of first event.
253 */
254 public long getStartTime() {
255 return fFirstEventTime;
256 }
257
258 /**
259 * Sets the trace of this model.
260 * @param trace - a {@link ITmfTrace}
261 * @since 3.0
262 */
263 public void setTrace(ITmfTrace trace) {
264 this.fTrace = trace;
265 fTraceMap.clear();
266 int i = 0;
267 for (ITmfTrace tr : TmfTraceManager.getTraceSet(fTrace)) {
268 fTraceMap.put(tr, i);
269 i++;
270 }
271 }
272
273 /**
274 * Gets the trace of this model.
275 * @return a {@link ITmfTrace}
276 * @since 3.0
277 */
278 public ITmfTrace getTrace() {
279 return this.fTrace;
280 }
281
282 /**
283 * Gets the traces names of this model.
284 * @return an array of trace names
285 * @since 3.0
286 */
287 public String[] getTraceNames() {
288 FluentIterable<ITmfTrace> traces = FluentIterable.from(TmfTraceManager.getTraceSet(fTrace));
289 FluentIterable<String> traceNames = traces.transform(new Function<ITmfTrace, String>() {
290 @Override
291 public String apply(ITmfTrace input) {
292 return input.getName();
293 }
294 });
295 return traceNames.toArray(String.class);
296 }
297
298 /**
299 * Gets the number of traces of this model.
300 * @return the number of traces of this model.
301 * @since 3.0
302 */
303 public int getNbTraces() {
304 Collection<ITmfTrace> traces = TmfTraceManager.getTraceSet(fTrace);
305 if (traces.isEmpty()) {
306 return 1; //
307 }
308 return traces.size();
309 }
310
311 /**
312 * Sets the model start time
313 *
314 * @param startTime
315 * the histogram range start time
316 * @param endTime
317 * the histogram range end time
318 * @since 2.0
319 */
320 public void setTimeRange(long startTime, long endTime) {
321 fFirstBucketTime = fFirstEventTime = fEndTime = startTime;
322 fBucketDuration = 1;
323 updateEndTime();
324 while (endTime >= fTimeLimit) {
325 mergeBuckets();
326 }
327 }
328
329 /**
330 * Set the end time. Setting this ensures that the corresponding bucket is
331 * displayed regardless of the event counts.
332 *
333 * @param endTime
334 * the time of the last used bucket
335 * @since 2.2
336 */
337 public void setEndTime(long endTime) {
338 fEndTime = endTime;
339 fLastBucket = (int) ((endTime - fFirstBucketTime) / fBucketDuration);
340 }
341
342 /**
343 * Returns the end time.
344 *
345 * @return the time of the last used bucket
346 */
347 public long getEndTime() {
348 return fEndTime;
349 }
350
351 /**
352 * Returns the begin time of the current selection in the model.
353 *
354 * @return the begin time of the current selection.
355 * @since 2.1
356 */
357 public long getSelectionBegin() {
358 return fSelectionBegin;
359 }
360
361 /**
362 * Returns the end time of the current selection in the model.
363 *
364 * @return the end time of the current selection.
365 * @since 2.1
366 */
367 public long getSelectionEnd() {
368 return fSelectionEnd;
369 }
370
371 /**
372 * Returns the time limit with is: start time + nbBuckets * bucketDuration
373 *
374 * @return the time limit.
375 */
376 public long getTimeLimit() {
377 return fTimeLimit;
378 }
379
380 // ------------------------------------------------------------------------
381 // Listener handling
382 // ------------------------------------------------------------------------
383
384 /**
385 * Add a listener to the model to be informed about model changes.
386 *
387 * @param listener
388 * A listener to add.
389 */
390 public void addHistogramListener(IHistogramModelListener listener) {
391 fModelListeners.add(listener);
392 }
393
394 /**
395 * Remove a given model listener.
396 *
397 * @param listener
398 * A listener to remove.
399 */
400 public void removeHistogramListener(IHistogramModelListener listener) {
401 fModelListeners.remove(listener);
402 }
403
404 // Notify listeners (always)
405 private void fireModelUpdateNotification() {
406 fireModelUpdateNotification(0);
407 }
408
409 // Notify listener on boundary
410 private void fireModelUpdateNotification(long count) {
411 if ((count % REFRESH_FREQUENCY) == 0) {
412 Object[] listeners = fModelListeners.getListeners();
413 for (Object listener2 : listeners) {
414 IHistogramModelListener listener = (IHistogramModelListener) listener2;
415 listener.modelUpdated();
416 }
417 }
418 }
419
420 // ------------------------------------------------------------------------
421 // Operations
422 // ------------------------------------------------------------------------
423
424 @Override
425 public void complete() {
426 fireModelUpdateNotification();
427 }
428
429 /**
430 * Clear the histogram model.
431 *
432 * @see org.eclipse.tracecompass.tmf.ui.views.distribution.model.IBaseDistributionModel#clear()
433 */
434 @Override
435 public synchronized void clear() {
436 Arrays.fill(fBuckets, null);
437 Arrays.fill(fLostEventsBuckets, 0);
438 fNbEvents = 0;
439 fFirstBucketTime = 0;
440 fEndTime = 0;
441 fSelectionBegin = 0;
442 fSelectionEnd = 0;
443 fLastBucket = 0;
444 fBucketDuration = 1;
445 updateEndTime();
446 fireModelUpdateNotification();
447 }
448
449 /**
450 * Sets the current selection time range (no notification of listeners)
451 *
452 * @param beginTime
453 * The selection begin time.
454 * @param endTime
455 * The selection end time.
456 * @since 2.1
457 */
458 public void setSelection(long beginTime, long endTime) {
459 fSelectionBegin = beginTime;
460 fSelectionEnd = endTime;
461 }
462
463 /**
464 * Sets the current selection time range with notification of listeners
465 *
466 * @param beginTime
467 * The selection begin time.
468 * @param endTime
469 * The selection end time.
470 * @since 2.1
471 */
472 public void setSelectionNotifyListeners(long beginTime, long endTime) {
473 fSelectionBegin = beginTime;
474 fSelectionEnd = endTime;
475 fireModelUpdateNotification();
476 }
477
478 /**
479 * Add event to the correct bucket, compacting the if needed.
480 *
481 * @param eventCount
482 * The current event Count (for notification purposes)
483 * @param timestamp
484 * The timestamp of the event to count
485 * @param trace
486 * The event trace
487 * @since 3.0
488 */
489 @Override
490 public synchronized void countEvent(long eventCount, long timestamp, ITmfTrace trace) {
491
492 // Validate
493 if (timestamp < 0) {
494 return;
495 }
496
497 // Set the start/end time if not already done
498 if ((fFirstBucketTime == 0) && (fLastBucket == 0) && (fBuckets[0] == null) && (timestamp > 0)) {
499 fFirstBucketTime = timestamp;
500 fFirstEventTime = timestamp;
501 updateEndTime();
502 }
503
504 if (timestamp < fFirstEventTime) {
505 fFirstEventTime = timestamp;
506 }
507
508 if (fEndTime < timestamp) {
509 fEndTime = timestamp;
510 }
511
512 if (timestamp >= fFirstBucketTime) {
513
514 // Compact as needed
515 while (timestamp >= fTimeLimit) {
516 mergeBuckets();
517 }
518
519 } else {
520
521 // get offset for adjustment
522 long preMergeOffset = getOffset(timestamp);
523
524 // Compact as needed
525 while ((fLastBucket + preMergeOffset) >= fNbBuckets) {
526 mergeBuckets();
527 preMergeOffset = getOffset(timestamp);
528 }
529
530 // after merging the offset should be less than number of buckets
531 int offset = (int) preMergeOffset;
532 moveBuckets(offset);
533
534 fLastBucket = fLastBucket + offset;
535
536 fFirstBucketTime = fFirstBucketTime - (offset * fBucketDuration);
537 updateEndTime();
538 }
539
540 // Increment the right bucket
541 int index = (int) ((timestamp - fFirstBucketTime) / fBucketDuration);
542 if (fBuckets[index] == null) {
543 fBuckets[index] = new HistogramBucket(getNbTraces());
544 }
545 Integer traceIndex = fTraceMap.get(trace);
546 if (traceIndex == null) {
547 traceIndex = 0;
548 }
549 fBuckets[index].addEvent(traceIndex);
550 fNbEvents++;
551 if (fLastBucket < index) {
552 fLastBucket = index;
553 }
554
555 fireModelUpdateNotification(eventCount);
556 }
557
558 /**
559 * Add lost event to the correct bucket, compacting the if needed.
560 *
561 * @param timeRange
562 * time range of a lost event
563 * @param nbLostEvents
564 * the number of lost events
565 * @param fullRange
566 * Full range or time range for histogram request
567 * @since 2.2
568 */
569 public void countLostEvent(TmfTimeRange timeRange, long nbLostEvents, boolean fullRange) {
570
571 // Validate
572 if (timeRange.getStartTime().getValue() < 0 || timeRange.getEndTime().getValue() < 0) {
573 return;
574 }
575
576 // Compact as needed
577 if (fullRange) {
578 while (timeRange.getEndTime().getValue() >= fTimeLimit) {
579 mergeBuckets();
580 }
581 }
582
583 int indexStart = (int) ((timeRange.getStartTime().getValue() - fFirstBucketTime) / fBucketDuration);
584 int indexEnd = (int) ((timeRange.getEndTime().getValue() - fFirstBucketTime) / fBucketDuration);
585 int nbBucketRange = (indexEnd - indexStart) + 1;
586
587 int lostEventPerBucket = (int) Math.ceil((double) nbLostEvents / nbBucketRange);
588 long lastLostCol = Math.max(1, nbLostEvents - lostEventPerBucket * (nbBucketRange - 1));
589
590 // Increment the right bucket, bear in mind that ranges make it almost certain that some lost events are out of range
591 for (int index = indexStart; index <= indexEnd && index < fLostEventsBuckets.length; index++) {
592 if (index == (indexStart + nbBucketRange - 1)) {
593 fLostEventsBuckets[index] += lastLostCol;
594 } else {
595 fLostEventsBuckets[index] += lostEventPerBucket;
596 }
597 }
598
599 fNbEvents++;
600
601 fireModelUpdateNotification(nbLostEvents);
602 }
603
604 /**
605 * Scale the model data to the width, height and bar width requested.
606 *
607 * @param width
608 * A width of the histogram canvas
609 * @param height
610 * A height of the histogram canvas
611 * @param barWidth
612 * A width (in pixel) of a histogram bar
613 * @return the result array of size [width] and where the highest value
614 * doesn't exceed [height]
615 *
616 * @see org.eclipse.tracecompass.tmf.ui.views.histogram.IHistogramDataModel#scaleTo(int,
617 * int, int)
618 */
619 @Override
620 public HistogramScaledData scaleTo(int width, int height, int barWidth) {
621 // Basic validation
622 if ((width <= 0) || (height <= 0) || (barWidth <= 0))
623 {
624 throw new AssertionError("Invalid histogram dimensions (" + width + "x" + height + ", barWidth=" + barWidth + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
625 }
626
627 // The result structure
628 HistogramScaledData result = new HistogramScaledData(width, height, barWidth);
629
630 // Scale horizontally
631 result.fMaxValue = 0;
632
633 int nbBars = width / barWidth;
634 int bucketsPerBar = (fLastBucket / nbBars) + 1;
635 result.fBucketDuration = Math.max(bucketsPerBar * fBucketDuration, 1);
636 for (int i = 0; i < nbBars; i++) {
637 int count = 0;
638 int countLostEvent = 0;
639 result.fData[i] = new HistogramBucket(getNbTraces());
640 for (int j = i * bucketsPerBar; j < ((i + 1) * bucketsPerBar); j++) {
641 if (fNbBuckets <= j) {
642 break;
643 }
644 if (fBuckets[j] != null) {
645 count += fBuckets[j].getNbEvents();
646 result.fData[i].add(fBuckets[j]);
647 }
648 countLostEvent += fLostEventsBuckets[j];
649 }
650 result.fLostEventsData[i] = countLostEvent;
651 result.fLastBucket = i;
652 if (result.fMaxValue < count) {
653 result.fMaxValue = count;
654 }
655 if (result.fMaxCombinedValue < count + countLostEvent) {
656 result.fMaxCombinedValue = count + countLostEvent;
657 }
658 }
659
660 // Scale vertically
661 if (result.fMaxValue > 0) {
662 result.fScalingFactor = (double) height / result.fMaxValue;
663 }
664 if (result.fMaxCombinedValue > 0) {
665 result.fScalingFactorCombined = (double) height / result.fMaxCombinedValue;
666 }
667
668 fBucketDuration = Math.max(fBucketDuration, 1);
669 // Set selection begin and end index in the scaled histogram
670 result.fSelectionBeginBucket = (int) ((fSelectionBegin - fFirstBucketTime) / fBucketDuration) / bucketsPerBar;
671 result.fSelectionEndBucket = (int) ((fSelectionEnd - fFirstBucketTime) / fBucketDuration) / bucketsPerBar;
672
673 result.fFirstBucketTime = fFirstBucketTime;
674 result.fFirstEventTime = fFirstEventTime;
675 return result;
676 }
677
678 // ------------------------------------------------------------------------
679 // Helper functions
680 // ------------------------------------------------------------------------
681
682 private void updateEndTime() {
683 fTimeLimit = fFirstBucketTime + (fNbBuckets * fBucketDuration);
684 }
685
686 private void mergeBuckets() {
687 for (int i = 0; i < (fNbBuckets / 2); i++) {
688 fBuckets[i] = new HistogramBucket(fBuckets[2 * i], fBuckets[(2 * i) + 1]);
689 fLostEventsBuckets[i] = fLostEventsBuckets[2 * i] + fLostEventsBuckets[(2 * i) + 1];
690 }
691 Arrays.fill(fBuckets, fNbBuckets / 2, fNbBuckets, null);
692 Arrays.fill(fLostEventsBuckets, fNbBuckets / 2, fNbBuckets, 0);
693 fBucketDuration *= 2;
694 updateEndTime();
695 fLastBucket = (fNbBuckets / 2) - 1;
696 }
697
698 private void moveBuckets(int offset) {
699 for (int i = fNbBuckets - 1; i >= offset; i--) {
700 fBuckets[i] = new HistogramBucket(fBuckets[i - offset]);
701 fLostEventsBuckets[i] = fLostEventsBuckets[i - offset];
702 }
703
704 for (int i = 0; i < offset; i++) {
705 fBuckets[i] = null;
706 fLostEventsBuckets[i] = 0;
707 }
708 }
709
710 private long getOffset(long timestamp) {
711 long offset = (fFirstBucketTime - timestamp) / fBucketDuration;
712 if (((fFirstBucketTime - timestamp) % fBucketDuration) != 0) {
713 offset++;
714 }
715 return offset;
716 }
717 }
This page took 0.052572 seconds and 5 git commands to generate.