1 /*******************************************************************************
2 * Copyright (c) 2011, 2015 Ericsson
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
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 *******************************************************************************/
20 package org
.eclipse
.tracecompass
.tmf
.ui
.views
.histogram
;
22 import java
.util
.Arrays
;
23 import java
.util
.Collection
;
24 import java
.util
.LinkedHashMap
;
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
;
32 import com
.google
.common
.base
.Function
;
33 import com
.google
.common
.collect
.FluentIterable
;
36 * Histogram-independent data model.
38 * It has the following characteristics:
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
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) *
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>).
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.
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.
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.
75 * @author Francois Chouinard
77 public class HistogramDataModel
implements IHistogramDataModel
{
79 // ------------------------------------------------------------------------
81 // ------------------------------------------------------------------------
84 * The default number of buckets
86 public static final int DEFAULT_NUMBER_OF_BUCKETS
= 16 * 1000;
89 * Number of events after which listeners will be notified.
91 public static final int REFRESH_FREQUENCY
= DEFAULT_NUMBER_OF_BUCKETS
;
93 // ------------------------------------------------------------------------
95 // ------------------------------------------------------------------------
98 private ITmfTrace fTrace
= null;
99 private final Map
<ITmfTrace
, Integer
> fTraceMap
= new LinkedHashMap
<>();
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
;
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
;
117 // Private listener lists
118 private final ListenerList fModelListeners
;
120 // ------------------------------------------------------------------------
122 // ------------------------------------------------------------------------
125 * Default constructor with default number of buckets.
127 public HistogramDataModel() {
128 this(0, DEFAULT_NUMBER_OF_BUCKETS
);
132 * Default constructor with default number of buckets.
135 * The histogram start time
138 public HistogramDataModel(long startTime
) {
139 this(startTime
, DEFAULT_NUMBER_OF_BUCKETS
);
143 * Constructor with non-default number of buckets.
146 * A number of buckets.
148 public HistogramDataModel(int nbBuckets
) {
153 * Constructor with non-default number of buckets.
156 * the histogram start time
158 * A number of buckets.
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();
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
]);
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
);
201 * Disposes the data model
204 public void dispose() {
209 // ------------------------------------------------------------------------
211 // ------------------------------------------------------------------------
214 * Returns the number of events in the data model.
216 * @return number of events.
218 public long getNbEvents() {
223 * Returns the number of buckets in the model.
225 * @return number of buckets.
227 public int getNbBuckets() {
232 * Returns the current bucket duration.
234 * @return bucket duration
236 public long getBucketDuration() {
237 return fBucketDuration
;
241 * Returns the time value of the first bucket in the model.
243 * @return time of first bucket.
245 public long getFirstBucketTime() {
246 return fFirstBucketTime
;
250 * Returns the time of the first event in the model.
252 * @return time of first event.
254 public long getStartTime() {
255 return fFirstEventTime
;
259 * Sets the trace of this model.
260 * @param trace - a {@link ITmfTrace}
263 public void setTrace(ITmfTrace trace
) {
267 for (ITmfTrace tr
: TmfTraceManager
.getTraceSet(fTrace
)) {
268 fTraceMap
.put(tr
, i
);
274 * Gets the trace of this model.
275 * @return a {@link ITmfTrace}
278 public ITmfTrace
getTrace() {
283 * Gets the traces names of this model.
284 * @return an array of trace names
287 public String
[] getTraceNames() {
288 FluentIterable
<ITmfTrace
> traces
= FluentIterable
.from(TmfTraceManager
.getTraceSet(fTrace
));
289 FluentIterable
<String
> traceNames
= traces
.transform(new Function
<ITmfTrace
, String
>() {
291 public String
apply(ITmfTrace input
) {
292 return input
.getName();
295 return traceNames
.toArray(String
.class);
299 * Gets the number of traces of this model.
300 * @return the number of traces of this model.
303 public int getNbTraces() {
304 Collection
<ITmfTrace
> traces
= TmfTraceManager
.getTraceSet(fTrace
);
305 if (traces
.isEmpty()) {
308 return traces
.size();
312 * Sets the model start time
315 * the histogram range start time
317 * the histogram range end time
320 public void setTimeRange(long startTime
, long endTime
) {
321 fFirstBucketTime
= fFirstEventTime
= fEndTime
= startTime
;
324 while (endTime
>= fTimeLimit
) {
330 * Set the end time. Setting this ensures that the corresponding bucket is
331 * displayed regardless of the event counts.
334 * the time of the last used bucket
337 public void setEndTime(long endTime
) {
339 fLastBucket
= (int) ((endTime
- fFirstBucketTime
) / fBucketDuration
);
343 * Returns the end time.
345 * @return the time of the last used bucket
347 public long getEndTime() {
352 * Returns the begin time of the current selection in the model.
354 * @return the begin time of the current selection.
357 public long getSelectionBegin() {
358 return fSelectionBegin
;
362 * Returns the end time of the current selection in the model.
364 * @return the end time of the current selection.
367 public long getSelectionEnd() {
368 return fSelectionEnd
;
372 * Returns the time limit with is: start time + nbBuckets * bucketDuration
374 * @return the time limit.
376 public long getTimeLimit() {
380 // ------------------------------------------------------------------------
382 // ------------------------------------------------------------------------
385 * Add a listener to the model to be informed about model changes.
390 public void addHistogramListener(IHistogramModelListener listener
) {
391 fModelListeners
.add(listener
);
395 * Remove a given model listener.
398 * A listener to remove.
400 public void removeHistogramListener(IHistogramModelListener listener
) {
401 fModelListeners
.remove(listener
);
404 // Notify listeners (always)
405 private void fireModelUpdateNotification() {
406 fireModelUpdateNotification(0);
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();
420 // ------------------------------------------------------------------------
422 // ------------------------------------------------------------------------
425 public void complete() {
426 fireModelUpdateNotification();
430 * Clear the histogram model.
432 * @see org.eclipse.tracecompass.tmf.ui.views.distribution.model.IBaseDistributionModel#clear()
435 public synchronized void clear() {
436 Arrays
.fill(fBuckets
, null);
437 Arrays
.fill(fLostEventsBuckets
, 0);
439 fFirstBucketTime
= 0;
446 fireModelUpdateNotification();
450 * Sets the current selection time range (no notification of listeners)
453 * The selection begin time.
455 * The selection end time.
458 public void setSelection(long beginTime
, long endTime
) {
459 fSelectionBegin
= beginTime
;
460 fSelectionEnd
= endTime
;
464 * Sets the current selection time range with notification of listeners
467 * The selection begin time.
469 * The selection end time.
472 public void setSelectionNotifyListeners(long beginTime
, long endTime
) {
473 fSelectionBegin
= beginTime
;
474 fSelectionEnd
= endTime
;
475 fireModelUpdateNotification();
479 * Add event to the correct bucket, compacting the if needed.
482 * The current event Count (for notification purposes)
484 * The timestamp of the event to count
490 public synchronized void countEvent(long eventCount
, long timestamp
, ITmfTrace trace
) {
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
;
504 if (timestamp
< fFirstEventTime
) {
505 fFirstEventTime
= timestamp
;
508 if (fEndTime
< timestamp
) {
509 fEndTime
= timestamp
;
512 if (timestamp
>= fFirstBucketTime
) {
515 while (timestamp
>= fTimeLimit
) {
521 // get offset for adjustment
522 long preMergeOffset
= getOffset(timestamp
);
525 while ((fLastBucket
+ preMergeOffset
) >= fNbBuckets
) {
527 preMergeOffset
= getOffset(timestamp
);
530 // after merging the offset should be less than number of buckets
531 int offset
= (int) preMergeOffset
;
534 fLastBucket
= fLastBucket
+ offset
;
536 fFirstBucketTime
= fFirstBucketTime
- (offset
* fBucketDuration
);
540 // Increment the right bucket
541 int index
= (int) ((timestamp
- fFirstBucketTime
) / fBucketDuration
);
542 if (fBuckets
[index
] == null) {
543 fBuckets
[index
] = new HistogramBucket(getNbTraces());
545 Integer traceIndex
= fTraceMap
.get(trace
);
546 if (traceIndex
== null) {
549 fBuckets
[index
].addEvent(traceIndex
);
551 if (fLastBucket
< index
) {
555 fireModelUpdateNotification(eventCount
);
559 * Add lost event to the correct bucket, compacting the if needed.
562 * time range of a lost event
563 * @param nbLostEvents
564 * the number of lost events
566 * Full range or time range for histogram request
569 public void countLostEvent(TmfTimeRange timeRange
, long nbLostEvents
, boolean fullRange
) {
572 if (timeRange
.getStartTime().getValue() < 0 || timeRange
.getEndTime().getValue() < 0) {
578 while (timeRange
.getEndTime().getValue() >= fTimeLimit
) {
583 int indexStart
= (int) ((timeRange
.getStartTime().getValue() - fFirstBucketTime
) / fBucketDuration
);
584 int indexEnd
= (int) ((timeRange
.getEndTime().getValue() - fFirstBucketTime
) / fBucketDuration
);
585 int nbBucketRange
= (indexEnd
- indexStart
) + 1;
587 int lostEventPerBucket
= (int) Math
.ceil((double) nbLostEvents
/ nbBucketRange
);
588 long lastLostCol
= Math
.max(1, nbLostEvents
- lostEventPerBucket
* (nbBucketRange
- 1));
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
;
595 fLostEventsBuckets
[index
] += lostEventPerBucket
;
601 fireModelUpdateNotification(nbLostEvents
);
605 * Scale the model data to the width, height and bar width requested.
608 * A width of the histogram canvas
610 * A height of the histogram canvas
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]
616 * @see org.eclipse.tracecompass.tmf.ui.views.histogram.IHistogramDataModel#scaleTo(int,
620 public HistogramScaledData
scaleTo(int width
, int height
, int barWidth
) {
622 if ((width
<= 0) || (height
<= 0) || (barWidth
<= 0))
624 throw new AssertionError("Invalid histogram dimensions (" + width
+ "x" + height
+ ", barWidth=" + barWidth
+ ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
627 // The result structure
628 HistogramScaledData result
= new HistogramScaledData(width
, height
, barWidth
);
630 // Scale horizontally
631 result
.fMaxValue
= 0;
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
++) {
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
) {
644 if (fBuckets
[j
] != null) {
645 count
+= fBuckets
[j
].getNbEvents();
646 result
.fData
[i
].add(fBuckets
[j
]);
648 countLostEvent
+= fLostEventsBuckets
[j
];
650 result
.fLostEventsData
[i
] = countLostEvent
;
651 result
.fLastBucket
= i
;
652 if (result
.fMaxValue
< count
) {
653 result
.fMaxValue
= count
;
655 if (result
.fMaxCombinedValue
< count
+ countLostEvent
) {
656 result
.fMaxCombinedValue
= count
+ countLostEvent
;
661 if (result
.fMaxValue
> 0) {
662 result
.fScalingFactor
= (double) height
/ result
.fMaxValue
;
664 if (result
.fMaxCombinedValue
> 0) {
665 result
.fScalingFactorCombined
= (double) height
/ result
.fMaxCombinedValue
;
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
;
673 result
.fFirstBucketTime
= fFirstBucketTime
;
674 result
.fFirstEventTime
= fFirstEventTime
;
678 // ------------------------------------------------------------------------
680 // ------------------------------------------------------------------------
682 private void updateEndTime() {
683 fTimeLimit
= fFirstBucketTime
+ (fNbBuckets
* fBucketDuration
);
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];
691 Arrays
.fill(fBuckets
, fNbBuckets
/ 2, fNbBuckets
, null);
692 Arrays
.fill(fLostEventsBuckets
, fNbBuckets
/ 2, fNbBuckets
, 0);
693 fBucketDuration
*= 2;
695 fLastBucket
= (fNbBuckets
/ 2) - 1;
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
];
704 for (int i
= 0; i
< offset
; i
++) {
706 fLostEventsBuckets
[i
] = 0;
710 private long getOffset(long timestamp
) {
711 long offset
= (fFirstBucketTime
- timestamp
) / fBucketDuration
;
712 if (((fFirstBucketTime
- timestamp
) % fBucketDuration
) != 0) {