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 * <b><u>HistogramDataModel</u></b>
25 * Histogram-independent data model with 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.
60 * TODO: Add filter support for more refined event counting (e.g. by trace,
63 * TODO: Cut-off eccentric values? TODO: Support for going back in time?
65 public class HistogramDataModel
implements IHistogramDataModel
{
67 // ------------------------------------------------------------------------
69 // ------------------------------------------------------------------------
71 // The default number of buckets
72 public static final int DEFAULT_NUMBER_OF_BUCKETS
= 16 * 1000;
74 public static final int REFRESH_FREQUENCY
= DEFAULT_NUMBER_OF_BUCKETS
;
76 // ------------------------------------------------------------------------
78 // ------------------------------------------------------------------------
81 private final int fNbBuckets
;
82 private final long[] fBuckets
;
83 private long fBucketDuration
;
84 private long fNbEvents
;
85 private int fLastBucket
;
88 private long fFirstBucketTime
; // could be negative when analyzing events with descending order!!!
89 private long fFirstEventTime
;
90 private long fLastEventTime
;
91 private long fCurrentEventTime
;
92 private long fTimeLimit
;
94 // Private listener lists
95 private final ListenerList fModelListeners
;
97 // ------------------------------------------------------------------------
99 // ------------------------------------------------------------------------
101 public HistogramDataModel() {
102 this(DEFAULT_NUMBER_OF_BUCKETS
);
105 public HistogramDataModel(int nbBuckets
) {
106 fNbBuckets
= nbBuckets
;
107 fBuckets
= new long[nbBuckets
];
108 fModelListeners
= new ListenerList();
112 public HistogramDataModel(HistogramDataModel other
) {
113 fNbBuckets
= other
.fNbBuckets
;
114 fBuckets
= Arrays
.copyOf(other
.fBuckets
, fNbBuckets
);
115 fBucketDuration
= Math
.max(other
.fBucketDuration
,1);
116 fNbEvents
= other
.fNbEvents
;
117 fLastBucket
= other
.fLastBucket
;
118 fFirstBucketTime
= other
.fFirstBucketTime
;
119 fFirstEventTime
= other
.fFirstEventTime
;
120 fLastEventTime
= other
.fLastEventTime
;
121 fCurrentEventTime
= other
.fCurrentEventTime
;
122 fTimeLimit
= other
.fTimeLimit
;
123 fModelListeners
= new ListenerList();
124 Object
[] listeners
= other
.fModelListeners
.getListeners();
125 for (Object listener
: listeners
) {
126 fModelListeners
.add(listener
);
130 // ------------------------------------------------------------------------
132 // ------------------------------------------------------------------------
134 public long getNbEvents() {
138 public int getNbBuckets() {
142 public long getBucketDuration() {
143 return fBucketDuration
;
146 public long getFirstBucketTime() {
147 return fFirstBucketTime
;
150 public long getStartTime() {
151 return fFirstEventTime
;
154 public long getEndTime() {
155 return fLastEventTime
;
158 public long getCurrentEventTime() {
159 return fCurrentEventTime
;
162 public long getTimeLimit() {
166 // ------------------------------------------------------------------------
168 // ------------------------------------------------------------------------
170 public void addHistogramListener(IHistogramModelListener listener
) {
171 fModelListeners
.add(listener
);
174 public void removeHistogramListener(IHistogramModelListener listener
) {
175 fModelListeners
.remove(listener
);
178 private void fireModelUpdateNotification() {
179 fireModelUpdateNotification(0);
182 private void fireModelUpdateNotification(long count
) {
183 if ((count
% REFRESH_FREQUENCY
) == 0) {
184 Object
[] listeners
= fModelListeners
.getListeners();
185 for (Object listener2
: listeners
) {
186 IHistogramModelListener listener
= (IHistogramModelListener
) listener2
;
187 listener
.modelUpdated();
192 // ------------------------------------------------------------------------
194 // ------------------------------------------------------------------------
197 public void complete() {
198 fireModelUpdateNotification();
202 * Clear the histogram model.
205 public void clear() {
206 Arrays
.fill(fBuckets
, 0);
208 fFirstBucketTime
= 0;
210 fCurrentEventTime
= 0;
212 fBucketDuration
= 1; // 1ns
214 fireModelUpdateNotification();
218 * Sets the current event time
222 public void setCurrentEvent(long timestamp
) {
223 fCurrentEventTime
= timestamp
;
227 * Sets the current event time
231 public void setCurrentEventNotifyListeners(long timestamp
) {
232 fCurrentEventTime
= timestamp
;
233 fireModelUpdateNotification();
237 * Add event to the correct bucket, compacting the if needed.
239 * @param timestamp the timestamp of the event to count
242 public void countEvent(long eventCount
, long timestamp
) {
249 // Set the start/end time if not already done
250 if ((fLastBucket
== 0) && (fBuckets
[0] == 0) && (timestamp
> 0)) {
251 fFirstBucketTime
= timestamp
;
252 fFirstEventTime
= timestamp
;
256 if (timestamp
< fFirstEventTime
) {
257 fFirstEventTime
= timestamp
;
260 if (fLastEventTime
< timestamp
) {
261 fLastEventTime
= timestamp
;
264 if (timestamp
>= fFirstBucketTime
) {
267 while (timestamp
>= fTimeLimit
) {
273 // get offset for adjustment
274 int offset
= getOffset(timestamp
);
277 while((fLastBucket
+ offset
) >= fNbBuckets
) {
279 offset
= getOffset(timestamp
);
284 fLastBucket
= fLastBucket
+ offset
;
286 fFirstBucketTime
= fFirstBucketTime
- (offset
*fBucketDuration
);
290 // Increment the right bucket
291 int index
= (int) ((timestamp
- fFirstBucketTime
) / fBucketDuration
);
294 if (fLastBucket
< index
) {
298 fireModelUpdateNotification(eventCount
);
302 * Scale the model data to the width, height and bar width requested.
307 * @return the result array of size [width] and where the highest value
308 * doesn't exceed [height]
311 public HistogramScaledData
scaleTo(int width
, int height
, int barWidth
) {
313 if ((width
<= 0) || (height
<= 0) || (barWidth
<= 0))
315 throw new AssertionError("Invalid histogram dimensions (" + width
+ "x" + height
+ ", barWidth=" + barWidth
+ ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
318 // The result structure
319 HistogramScaledData result
= new HistogramScaledData(width
, height
, barWidth
);
321 // Scale horizontally
322 result
.fMaxValue
= 0;
324 int nbBars
= width
/ barWidth
;
325 int bucketsPerBar
= (fLastBucket
/ nbBars
) + 1;
326 result
.fBucketDuration
= Math
.max(bucketsPerBar
* fBucketDuration
,1);
327 for (int i
= 0; i
< nbBars
; i
++) {
329 for (int j
= i
* bucketsPerBar
; j
< ((i
+ 1) * bucketsPerBar
); j
++) {
330 if (fNbBuckets
<= j
) {
333 count
+= fBuckets
[j
];
335 result
.fData
[i
] = count
;
336 result
.fLastBucket
= i
;
337 if (result
.fMaxValue
< count
) {
338 result
.fMaxValue
= count
;
343 if (result
.fMaxValue
> 0) {
344 result
.fScalingFactor
= (double) height
/ result
.fMaxValue
;
347 fBucketDuration
= Math
.max(fBucketDuration
, 1);
348 // Set the current event index in the scaled histogram
349 if ((fCurrentEventTime
>= fFirstBucketTime
) && (fCurrentEventTime
<= fLastEventTime
)) {
350 result
.fCurrentBucket
= (int) ((fCurrentEventTime
- fFirstBucketTime
) / fBucketDuration
) / bucketsPerBar
;
352 result
.fCurrentBucket
= HistogramScaledData
.OUT_OF_RANGE_BUCKET
;
355 result
.fFirstBucketTime
= fFirstBucketTime
;
356 result
.fFirstEventTime
= fFirstEventTime
;
360 // ------------------------------------------------------------------------
362 // ------------------------------------------------------------------------
364 private void updateEndTime() {
365 fTimeLimit
= fFirstBucketTime
+ (fNbBuckets
* fBucketDuration
);
368 private void mergeBuckets() {
369 for (int i
= 0; i
< (fNbBuckets
/ 2); i
++) {
370 fBuckets
[i
] = fBuckets
[2 * i
] + fBuckets
[(2 * i
) + 1];
372 Arrays
.fill(fBuckets
, fNbBuckets
/ 2, fNbBuckets
, 0);
373 fBucketDuration
*= 2;
375 fLastBucket
= (fNbBuckets
/ 2) - 1;
378 private void moveBuckets(int offset
) {
379 for(int i
= fNbBuckets
- 1; i
>= offset
; i
--) {
380 fBuckets
[i
] = fBuckets
[i
-offset
];
383 for (int i
= 0; i
< offset
; i
++) {
388 private int getOffset(long timestamp
) {
389 int offset
= (int) ((fFirstBucketTime
- timestamp
) / fBucketDuration
);
390 if (((fFirstBucketTime
- timestamp
) % fBucketDuration
) != 0) {