Move alltests plugin to the Trace Compass namespace
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / histogram / HistogramDataModel.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2014 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.linuxtools.tmf.ui.views.histogram;
21
22 import java.util.Arrays;
23 import java.util.LinkedHashMap;
24 import java.util.Map;
25
26 import org.eclipse.core.runtime.ListenerList;
27 import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimeRange;
28 import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
29 import org.eclipse.linuxtools.tmf.core.trace.TmfTraceManager;
30
31 /**
32 * Histogram-independent data model.
33 *
34 * It has the following characteristics:
35 * <ul>
36 * <li>The <i>basetime</i> is the timestamp of the first event
37 * <li>There is a fixed number (<i>n</i>) of buckets of uniform duration
38 * (<i>d</i>)
39 * <li>The <i>timespan</i> of the model is thus: <i>n</i> * <i>d</i> time units
40 * <li>Bucket <i>i</i> holds the number of events that occurred in time range:
41 * [<i>basetime</i> + <i>i</i> * <i>d</i>, <i>basetime</i> + (<i>i</i> + 1) *
42 * <i>d</i>)
43 * </ul>
44 * Initially, the bucket durations is set to 1ns. As the events are read, they
45 * are tallied (using <i>countEvent()</i>) in the appropriate bucket (relative
46 * to the <i>basetime</i>).
47 * <p>
48 * Eventually, an event will have a timestamp that exceeds the <i>timespan</i>
49 * high end (determined by <i>n</i>, the number of buckets, and <i>d</i>, the
50 * bucket duration). At this point, the histogram needs to be compacted. This is
51 * done by simply merging adjacent buckets by pair, in effect doubling the
52 * <i>timespan</i> (<i>timespan'</i> = <i>n</i> * <i>d'</i>, where <i>d'</i> =
53 * 2<i>d</i>). This compaction happens as needed as the trace is read.
54 * <p>
55 * The model allows for timestamps in not increasing order. The timestamps can
56 * be fed to the model in any order. If an event has a timestamp less than the
57 * <i>basetime</i>, the buckets will be moved to the right to account for the
58 * new smaller timestamp. The new <i>basetime</i> is a multiple of the bucket
59 * duration smaller then the previous <i>basetime</i>. Note that the
60 * <i>basetime</i> might no longer be the timestamp of an event. If necessary,
61 * the buckets will be compacted before moving to the right. This might be
62 * necessary to not lose any event counts at the end of the buckets array.
63 * <p>
64 * The mapping from the model to the UI is performed by the <i>scaleTo()</i>
65 * method. By keeping the number of buckets <i>n</i> relatively large with
66 * respect to to the number of pixels in the actual histogram, we should achieve
67 * a nice result when visualizing the histogram.
68 * <p>
69 *
70 * @version 2.0
71 * @author Francois Chouinard
72 */
73 public class HistogramDataModel implements IHistogramDataModel {
74
75 // ------------------------------------------------------------------------
76 // Constants
77 // ------------------------------------------------------------------------
78
79 /**
80 * The default number of buckets
81 */
82 public static final int DEFAULT_NUMBER_OF_BUCKETS = 16 * 1000;
83
84 /**
85 * Number of events after which listeners will be notified.
86 */
87 public static final int REFRESH_FREQUENCY = DEFAULT_NUMBER_OF_BUCKETS;
88
89 // ------------------------------------------------------------------------
90 // Attributes
91 // ------------------------------------------------------------------------
92
93 // Trace management
94 private ITmfTrace fTrace = null;
95 private final Map<ITmfTrace, Integer> fTraceMap = new LinkedHashMap<>();
96
97 // Bucket management
98 private final int fNbBuckets;
99 private final HistogramBucket[] fBuckets;
100 private final long[] fLostEventsBuckets;
101 private long fBucketDuration;
102 private long fNbEvents;
103 private int fLastBucket;
104
105 // Timestamps
106 private long fFirstBucketTime; // could be negative when analyzing events with descending order!!!
107 private long fFirstEventTime;
108 private long fEndTime;
109 private long fSelectionBegin;
110 private long fSelectionEnd;
111 private long fTimeLimit;
112
113 // Private listener lists
114 private final ListenerList fModelListeners;
115
116 // ------------------------------------------------------------------------
117 // Constructors
118 // ------------------------------------------------------------------------
119
120 /**
121 * Default constructor with default number of buckets.
122 */
123 public HistogramDataModel() {
124 this(0, DEFAULT_NUMBER_OF_BUCKETS);
125 }
126
127 /**
128 * Default constructor with default number of buckets.
129 *
130 * @param startTime
131 * The histogram start time
132 * @since 2.0
133 */
134 public HistogramDataModel(long startTime) {
135 this(startTime, DEFAULT_NUMBER_OF_BUCKETS);
136 }
137
138 /**
139 * Constructor with non-default number of buckets.
140 *
141 * @param nbBuckets
142 * A number of buckets.
143 */
144 public HistogramDataModel(int nbBuckets) {
145 this(0, nbBuckets);
146 }
147
148 /**
149 * Constructor with non-default number of buckets.
150 *
151 * @param startTime
152 * the histogram start time
153 * @param nbBuckets
154 * A number of buckets.
155 * @since 2.0
156 */
157 public HistogramDataModel(long startTime, int nbBuckets) {
158 fFirstBucketTime = fFirstEventTime = fEndTime = startTime;
159 fNbBuckets = nbBuckets;
160 fBuckets = new HistogramBucket[nbBuckets];
161 fLostEventsBuckets = new long[nbBuckets];
162 fModelListeners = new ListenerList();
163 clear();
164 }
165
166 /**
167 * Copy constructor.
168 *
169 * @param other
170 * A model to copy.
171 */
172 public HistogramDataModel(HistogramDataModel other) {
173 fNbBuckets = other.fNbBuckets;
174 fBuckets = new HistogramBucket[fNbBuckets];
175 for (int i = 0; i < fNbBuckets; i++) {
176 fBuckets[i] = new HistogramBucket(other.fBuckets[i]);
177 }
178 fLostEventsBuckets = Arrays.copyOf(other.fLostEventsBuckets, fNbBuckets);
179 fBucketDuration = Math.max(other.fBucketDuration, 1);
180 fNbEvents = other.fNbEvents;
181 fLastBucket = other.fLastBucket;
182 fFirstBucketTime = other.fFirstBucketTime;
183 fFirstEventTime = other.fFirstEventTime;
184 fEndTime = other.fEndTime;
185 fSelectionBegin = other.fSelectionBegin;
186 fSelectionEnd = other.fSelectionEnd;
187 fTimeLimit = other.fTimeLimit;
188 fModelListeners = new ListenerList();
189 Object[] listeners = other.fModelListeners.getListeners();
190 for (Object listener : listeners) {
191 fModelListeners.add(listener);
192 }
193 }
194
195
196 /**
197 * Disposes the data model
198 * @since 3.0
199 */
200 public void dispose() {
201 fTraceMap.clear();
202 fTrace = null;
203 }
204
205 // ------------------------------------------------------------------------
206 // Accessors
207 // ------------------------------------------------------------------------
208
209 /**
210 * Returns the number of events in the data model.
211 *
212 * @return number of events.
213 */
214 public long getNbEvents() {
215 return fNbEvents;
216 }
217
218 /**
219 * Returns the number of buckets in the model.
220 *
221 * @return number of buckets.
222 */
223 public int getNbBuckets() {
224 return fNbBuckets;
225 }
226
227 /**
228 * Returns the current bucket duration.
229 *
230 * @return bucket duration
231 */
232 public long getBucketDuration() {
233 return fBucketDuration;
234 }
235
236 /**
237 * Returns the time value of the first bucket in the model.
238 *
239 * @return time of first bucket.
240 */
241 public long getFirstBucketTime() {
242 return fFirstBucketTime;
243 }
244
245 /**
246 * Returns the time of the first event in the model.
247 *
248 * @return time of first event.
249 */
250 public long getStartTime() {
251 return fFirstEventTime;
252 }
253
254 /**
255 * Sets the trace of this model.
256 * @param trace - a {@link ITmfTrace}
257 * @since 3.0
258 */
259 public void setTrace(ITmfTrace trace) {
260 this.fTrace = trace;
261 fTraceMap.clear();
262 ITmfTrace[] traces = TmfTraceManager.getTraceSet(fTrace);
263 if (traces != null) {
264 int i = 0;
265 for (ITmfTrace tr : traces) {
266 fTraceMap.put(tr, i);
267 i++;
268 }
269 }
270 }
271
272 /**
273 * Gets the trace of this model.
274 * @return a {@link ITmfTrace}
275 * @since 3.0
276 */
277 public ITmfTrace getTrace() {
278 return this.fTrace;
279 }
280
281 /**
282 * Gets the traces names of this model.
283 * @return an array of trace names
284 * @since 3.0
285 */
286 public String[] getTraceNames() {
287 ITmfTrace[] traces = TmfTraceManager.getTraceSet(fTrace);
288 if (traces == null) {
289 return new String[0];
290 }
291 String[] traceNames = new String[traces.length];
292 int i = 0;
293 for (ITmfTrace tr : traces) {
294 traceNames[i] = tr.getName();
295 i++;
296 }
297 return traceNames;
298 }
299
300 /**
301 * Gets the number of traces of this model.
302 * @return the number of traces of this model.
303 * @since 3.0
304 */
305 public int getNbTraces() {
306 ITmfTrace[] traces = TmfTraceManager.getTraceSet(fTrace);
307 if (traces == null) {
308 return 1; //
309 }
310 return traces.length;
311 }
312
313 /**
314 * Sets the model start time
315 *
316 * @param startTime
317 * the histogram range start time
318 * @param endTime
319 * the histogram range end time
320 * @since 2.0
321 */
322 public void setTimeRange(long startTime, long endTime) {
323 fFirstBucketTime = fFirstEventTime = fEndTime = startTime;
324 fBucketDuration = 1;
325 updateEndTime();
326 while (endTime >= fTimeLimit) {
327 mergeBuckets();
328 }
329 }
330
331 /**
332 * Set the end time. Setting this ensures that the corresponding bucket is
333 * displayed regardless of the event counts.
334 *
335 * @param endTime
336 * the time of the last used bucket
337 * @since 2.2
338 */
339 public void setEndTime(long endTime) {
340 fEndTime = endTime;
341 fLastBucket = (int) ((endTime - fFirstBucketTime) / fBucketDuration);
342 }
343
344 /**
345 * Returns the end time.
346 *
347 * @return the time of the last used bucket
348 */
349 public long getEndTime() {
350 return fEndTime;
351 }
352
353 /**
354 * Returns the begin time of the current selection in the model.
355 *
356 * @return the begin time of the current selection.
357 * @since 2.1
358 */
359 public long getSelectionBegin() {
360 return fSelectionBegin;
361 }
362
363 /**
364 * Returns the end time of the current selection in the model.
365 *
366 * @return the end time of the current selection.
367 * @since 2.1
368 */
369 public long getSelectionEnd() {
370 return fSelectionEnd;
371 }
372
373 /**
374 * Returns the time limit with is: start time + nbBuckets * bucketDuration
375 *
376 * @return the time limit.
377 */
378 public long getTimeLimit() {
379 return fTimeLimit;
380 }
381
382 // ------------------------------------------------------------------------
383 // Listener handling
384 // ------------------------------------------------------------------------
385
386 /**
387 * Add a listener to the model to be informed about model changes.
388 *
389 * @param listener
390 * A listener to add.
391 */
392 public void addHistogramListener(IHistogramModelListener listener) {
393 fModelListeners.add(listener);
394 }
395
396 /**
397 * Remove a given model listener.
398 *
399 * @param listener
400 * A listener to remove.
401 */
402 public void removeHistogramListener(IHistogramModelListener listener) {
403 fModelListeners.remove(listener);
404 }
405
406 // Notify listeners (always)
407 private void fireModelUpdateNotification() {
408 fireModelUpdateNotification(0);
409 }
410
411 // Notify listener on boundary
412 private void fireModelUpdateNotification(long count) {
413 if ((count % REFRESH_FREQUENCY) == 0) {
414 Object[] listeners = fModelListeners.getListeners();
415 for (Object listener2 : listeners) {
416 IHistogramModelListener listener = (IHistogramModelListener) listener2;
417 listener.modelUpdated();
418 }
419 }
420 }
421
422 // ------------------------------------------------------------------------
423 // Operations
424 // ------------------------------------------------------------------------
425
426 @Override
427 public void complete() {
428 fireModelUpdateNotification();
429 }
430
431 /**
432 * Clear the histogram model.
433 *
434 * @see org.eclipse.linuxtools.tmf.ui.views.distribution.model.IBaseDistributionModel#clear()
435 */
436 @Override
437 public void clear() {
438 Arrays.fill(fBuckets, null);
439 Arrays.fill(fLostEventsBuckets, 0);
440 fNbEvents = 0;
441 fFirstBucketTime = 0;
442 fEndTime = 0;
443 fSelectionBegin = 0;
444 fSelectionEnd = 0;
445 fLastBucket = 0;
446 fBucketDuration = 1;
447 updateEndTime();
448 fireModelUpdateNotification();
449 }
450
451 /**
452 * Sets the current selection time range (no notification of listeners)
453 *
454 * @param beginTime
455 * The selection begin time.
456 * @param endTime
457 * The selection end time.
458 * @since 2.1
459 */
460 public void setSelection(long beginTime, long endTime) {
461 fSelectionBegin = beginTime;
462 fSelectionEnd = endTime;
463 }
464
465 /**
466 * Sets the current selection time range with notification of listeners
467 *
468 * @param beginTime
469 * The selection begin time.
470 * @param endTime
471 * The selection end time.
472 * @since 2.1
473 */
474 public void setSelectionNotifyListeners(long beginTime, long endTime) {
475 fSelectionBegin = beginTime;
476 fSelectionEnd = endTime;
477 fireModelUpdateNotification();
478 }
479
480 /**
481 * Add event to the correct bucket, compacting the if needed.
482 *
483 * @param eventCount
484 * The current event Count (for notification purposes)
485 * @param timestamp
486 * The timestamp of the event to count
487 * @param trace
488 * The event trace
489 * @since 3.0
490 */
491 @Override
492 public void countEvent(long eventCount, long timestamp, ITmfTrace trace) {
493
494 // Validate
495 if (timestamp < 0) {
496 return;
497 }
498
499 // Set the start/end time if not already done
500 if ((fFirstBucketTime == 0) && (fLastBucket == 0) && (fBuckets[0] == null) && (timestamp > 0)) {
501 fFirstBucketTime = timestamp;
502 fFirstEventTime = timestamp;
503 updateEndTime();
504 }
505
506 if (timestamp < fFirstEventTime) {
507 fFirstEventTime = timestamp;
508 }
509
510 if (fEndTime < timestamp) {
511 fEndTime = timestamp;
512 }
513
514 if (timestamp >= fFirstBucketTime) {
515
516 // Compact as needed
517 while (timestamp >= fTimeLimit) {
518 mergeBuckets();
519 }
520
521 } else {
522
523 // get offset for adjustment
524 int offset = getOffset(timestamp);
525
526 // Compact as needed
527 while ((fLastBucket + offset) >= fNbBuckets) {
528 mergeBuckets();
529 offset = getOffset(timestamp);
530 }
531
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.linuxtools.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 int getOffset(long timestamp) {
711 int offset = (int) ((fFirstBucketTime - timestamp) / fBucketDuration);
712 if (((fFirstBucketTime - timestamp) % fBucketDuration) != 0) {
713 offset++;
714 }
715 return offset;
716 }
717 }
This page took 0.052964 seconds and 5 git commands to generate.