1 /*******************************************************************************
2 * Copyright (c) 2011, 2012 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 *******************************************************************************/
16 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.histogram
;
18 import java
.util
.Arrays
;
20 import org
.eclipse
.core
.runtime
.ListenerList
;
23 * Histogram-independent data model.
25 * It has the following characteristics:
27 * <li>The <i>basetime</i> is the timestamp of the first event
28 * <li>There is a fixed number (<i>n</i>) of buckets of uniform duration
30 * <li>The <i>timespan</i> of the model is thus: <i>n</i> * <i>d</i> time units
31 * <li>Bucket <i>i</i> holds the number of events that occurred in time range:
32 * [<i>basetime</i> + <i>i</i> * <i>d</i>, <i>basetime</i> + (<i>i</i> + 1) *
35 * Initially, the bucket durations is set to 1ns. As the events are read, they
36 * are tallied (using <i>countEvent()</i>) in the appropriate bucket (relative
37 * to the <i>basetime</i>).
39 * Eventually, an event will have a timestamp that exceeds the <i>timespan</i>
40 * high end (determined by <i>n</i>, the number of buckets, and <i>d</i>, the
41 * bucket duration). At this point, the histogram needs to be compacted. This is
42 * done by simply merging adjacent buckets by pair, in effect doubling the
43 * <i>timespan</i> (<i>timespan'</i> = <i>n</i> * <i>d'</i>, where <i>d'</i> =
44 * 2<i>d</i>). This compaction happens as needed as the trace is read.
46 * The model allows for timestamps in not increasing order. The timestamps can
47 * be fed to the model in any order. If an event has a timestamp less than the
48 * <i>basetime</i>, the buckets will be moved to the right to account for the
49 * new smaller timestamp. The new <i>basetime</i> is a multiple of the bucket
50 * duration smaller then the previous <i>basetime</i>. Note that the <i>basetime</i>
51 * might not be anymore a timestamp of an event. If necessary, the buckets will
52 * be compacted before moving to the right. This might be necessary to not
53 * loose any event counts at the end of the buckets array.
55 * The mapping from the model to the UI is performed by the <i>scaleTo()</i>
56 * method. By keeping the number of buckets <i>n</i> relatively large with
57 * respect to to the number of pixels in the actual histogram, we should achieve
58 * a nice result when visualizing the histogram.
62 * @author Francois Chouinard
64 public class HistogramDataModel
implements IHistogramDataModel
{
66 // ------------------------------------------------------------------------
68 // ------------------------------------------------------------------------
71 * The default number of buckets
73 public static final int DEFAULT_NUMBER_OF_BUCKETS
= 16 * 1000;
76 * Number of events after which listeners will be notified.
78 public static final int REFRESH_FREQUENCY
= DEFAULT_NUMBER_OF_BUCKETS
;
80 // ------------------------------------------------------------------------
82 // ------------------------------------------------------------------------
85 private final int fNbBuckets
;
86 private final long[] fBuckets
;
87 private long fBucketDuration
;
88 private long fNbEvents
;
89 private int fLastBucket
;
92 private long fFirstBucketTime
; // could be negative when analyzing events with descending order!!!
93 private long fFirstEventTime
;
94 private long fLastEventTime
;
95 private long fCurrentEventTime
;
96 private long fTimeLimit
;
98 // Private listener lists
99 private final ListenerList fModelListeners
;
101 // ------------------------------------------------------------------------
103 // ------------------------------------------------------------------------
106 * Default constructor with default number of buckets.
108 public HistogramDataModel() {
109 this(DEFAULT_NUMBER_OF_BUCKETS
);
113 * Constructor with non-default number of buckets.
114 * @param nbBuckets A number of buckets.
116 public HistogramDataModel(int nbBuckets
) {
117 fNbBuckets
= nbBuckets
;
118 fBuckets
= new long[nbBuckets
];
119 fModelListeners
= new ListenerList();
125 * @param other A model to copy.
127 public HistogramDataModel(HistogramDataModel other
) {
128 fNbBuckets
= other
.fNbBuckets
;
129 fBuckets
= Arrays
.copyOf(other
.fBuckets
, fNbBuckets
);
130 fBucketDuration
= Math
.max(other
.fBucketDuration
,1);
131 fNbEvents
= other
.fNbEvents
;
132 fLastBucket
= other
.fLastBucket
;
133 fFirstBucketTime
= other
.fFirstBucketTime
;
134 fFirstEventTime
= other
.fFirstEventTime
;
135 fLastEventTime
= other
.fLastEventTime
;
136 fCurrentEventTime
= other
.fCurrentEventTime
;
137 fTimeLimit
= other
.fTimeLimit
;
138 fModelListeners
= new ListenerList();
139 Object
[] listeners
= other
.fModelListeners
.getListeners();
140 for (Object listener
: listeners
) {
141 fModelListeners
.add(listener
);
145 // ------------------------------------------------------------------------
147 // ------------------------------------------------------------------------
150 * Returns the number of events in the data model.
151 * @return number of events.
153 public long getNbEvents() {
158 * Returns the number of buckets in the model.
159 * @return number of buckets.
161 public int getNbBuckets() {
166 * Returns the current bucket duration.
167 * @return bucket duration
169 public long getBucketDuration() {
170 return fBucketDuration
;
174 * Returns the time value of the first bucket in the model.
175 * @return time of first bucket.
177 public long getFirstBucketTime() {
178 return fFirstBucketTime
;
182 * Returns the time of the first event in the model.
183 * @return time of first event.
185 public long getStartTime() {
186 return fFirstEventTime
;
190 * Returns the time of the last event in the model.
191 * @return the time of last event.
193 public long getEndTime() {
194 return fLastEventTime
;
198 * Returns the time of the current event in the model.
199 * @return the time of the current event.
201 public long getCurrentEventTime() {
202 return fCurrentEventTime
;
206 * Returns the time limit with is: start time + nbBuckets * bucketDuration
207 * @return the time limit.
209 public long getTimeLimit() {
213 // ------------------------------------------------------------------------
215 // ------------------------------------------------------------------------
218 * Add a listener to the model to be informed about model changes.
219 * @param listener A listener to add.
221 public void addHistogramListener(IHistogramModelListener listener
) {
222 fModelListeners
.add(listener
);
226 * Remove a given model listener.
227 * @param listener A listener to remove.
229 public void removeHistogramListener(IHistogramModelListener listener
) {
230 fModelListeners
.remove(listener
);
233 // Notify listeners (always)
234 private void fireModelUpdateNotification() {
235 fireModelUpdateNotification(0);
238 // Notify listener on boundary
239 private void fireModelUpdateNotification(long count
) {
240 if ((count
% REFRESH_FREQUENCY
) == 0) {
241 Object
[] listeners
= fModelListeners
.getListeners();
242 for (Object listener2
: listeners
) {
243 IHistogramModelListener listener
= (IHistogramModelListener
) listener2
;
244 listener
.modelUpdated();
249 // ------------------------------------------------------------------------
251 // ------------------------------------------------------------------------
255 * @see org.eclipse.linuxtools.tmf.ui.views.distribution.model.IBaseDistributionModel#complete()
258 public void complete() {
259 fireModelUpdateNotification();
263 * Clear the histogram model.
264 * @see org.eclipse.linuxtools.tmf.ui.views.distribution.model.IBaseDistributionModel#clear()
267 public void clear() {
268 Arrays
.fill(fBuckets
, 0);
270 fFirstBucketTime
= 0;
272 fCurrentEventTime
= 0;
276 fireModelUpdateNotification();
280 * Sets the current event time (no notification of listeners)
282 * @param timestamp A time stamp to set.
284 public void setCurrentEvent(long timestamp
) {
285 fCurrentEventTime
= timestamp
;
289 * Sets the current event time with notification of listeners
291 * @param timestamp A time stamp to set.
293 public void setCurrentEventNotifyListeners(long timestamp
) {
294 fCurrentEventTime
= timestamp
;
295 fireModelUpdateNotification();
299 * Add event to the correct bucket, compacting the if needed.
301 * @param eventCount The current event Count (for notification purposes)
302 * @param timestamp The timestamp of the event to count
306 public void countEvent(long eventCount
, long timestamp
) {
313 // Set the start/end time if not already done
314 if ((fLastBucket
== 0) && (fBuckets
[0] == 0) && (timestamp
> 0)) {
315 fFirstBucketTime
= timestamp
;
316 fFirstEventTime
= timestamp
;
320 if (timestamp
< fFirstEventTime
) {
321 fFirstEventTime
= timestamp
;
324 if (fLastEventTime
< timestamp
) {
325 fLastEventTime
= timestamp
;
328 if (timestamp
>= fFirstBucketTime
) {
331 while (timestamp
>= fTimeLimit
) {
337 // get offset for adjustment
338 int offset
= getOffset(timestamp
);
341 while((fLastBucket
+ offset
) >= fNbBuckets
) {
343 offset
= getOffset(timestamp
);
348 fLastBucket
= fLastBucket
+ offset
;
350 fFirstBucketTime
= fFirstBucketTime
- (offset
*fBucketDuration
);
354 // Increment the right bucket
355 int index
= (int) ((timestamp
- fFirstBucketTime
) / fBucketDuration
);
358 if (fLastBucket
< index
) {
362 fireModelUpdateNotification(eventCount
);
366 * Scale the model data to the width, height and bar width requested.
368 * @param width A width of the histogram canvas
369 * @param height A height of the histogram canvas
370 * @param barWidth A width (in pixel) of a histogram bar
371 * @return the result array of size [width] and where the highest value doesn't exceed [height]
373 * @see org.eclipse.linuxtools.tmf.ui.views.histogram.IHistogramDataModel#scaleTo(int, int, int)
376 public HistogramScaledData
scaleTo(int width
, int height
, int barWidth
) {
378 if ((width
<= 0) || (height
<= 0) || (barWidth
<= 0))
380 throw new AssertionError("Invalid histogram dimensions (" + width
+ "x" + height
+ ", barWidth=" + barWidth
+ ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
383 // The result structure
384 HistogramScaledData result
= new HistogramScaledData(width
, height
, barWidth
);
386 // Scale horizontally
387 result
.fMaxValue
= 0;
389 int nbBars
= width
/ barWidth
;
390 int bucketsPerBar
= (fLastBucket
/ nbBars
) + 1;
391 result
.fBucketDuration
= Math
.max(bucketsPerBar
* fBucketDuration
,1);
392 for (int i
= 0; i
< nbBars
; i
++) {
394 for (int j
= i
* bucketsPerBar
; j
< ((i
+ 1) * bucketsPerBar
); j
++) {
395 if (fNbBuckets
<= j
) {
398 count
+= fBuckets
[j
];
400 result
.fData
[i
] = count
;
401 result
.fLastBucket
= i
;
402 if (result
.fMaxValue
< count
) {
403 result
.fMaxValue
= count
;
408 if (result
.fMaxValue
> 0) {
409 result
.fScalingFactor
= (double) height
/ result
.fMaxValue
;
412 fBucketDuration
= Math
.max(fBucketDuration
, 1);
413 // Set the current event index in the scaled histogram
414 if ((fCurrentEventTime
>= fFirstBucketTime
) && (fCurrentEventTime
<= fLastEventTime
)) {
415 result
.fCurrentBucket
= (int) ((fCurrentEventTime
- fFirstBucketTime
) / fBucketDuration
) / bucketsPerBar
;
417 result
.fCurrentBucket
= HistogramScaledData
.OUT_OF_RANGE_BUCKET
;
420 result
.fFirstBucketTime
= fFirstBucketTime
;
421 result
.fFirstEventTime
= fFirstEventTime
;
425 // ------------------------------------------------------------------------
427 // ------------------------------------------------------------------------
429 private void updateEndTime() {
430 fTimeLimit
= fFirstBucketTime
+ (fNbBuckets
* fBucketDuration
);
433 private void mergeBuckets() {
434 for (int i
= 0; i
< (fNbBuckets
/ 2); i
++) {
435 fBuckets
[i
] = fBuckets
[2 * i
] + fBuckets
[(2 * i
) + 1];
437 Arrays
.fill(fBuckets
, fNbBuckets
/ 2, fNbBuckets
, 0);
438 fBucketDuration
*= 2;
440 fLastBucket
= (fNbBuckets
/ 2) - 1;
443 private void moveBuckets(int offset
) {
444 for(int i
= fNbBuckets
- 1; i
>= offset
; i
--) {
445 fBuckets
[i
] = fBuckets
[i
-offset
];
448 for (int i
= 0; i
< offset
; i
++) {
453 private int getOffset(long timestamp
) {
454 int offset
= (int) ((fFirstBucketTime
- timestamp
) / fBucketDuration
);
455 if (((fFirstBucketTime
- timestamp
) % fBucketDuration
) != 0) {