1 /*******************************************************************************
2 * Copyright (c) 2011 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 *******************************************************************************/
15 package org
.eclipse
.linuxtools
.lttng
.ui
.views
.histogram
;
17 import java
.util
.Arrays
;
19 import org
.eclipse
.core
.runtime
.ListenerList
;
20 import org
.eclipse
.linuxtools
.lttng
.core
.exceptions
.EventOutOfSequenceException
;
21 import org
.eclipse
.linuxtools
.lttng
.ui
.LTTngUILogger
;
24 * <b><u>HistogramDataModel</u></b>
26 * Histogram-independent data model with the following characteristics:
28 * <li>The <i>basetime</i> is the timestamp of the first event
29 * <li>There is a fixed number (<i>n</i>) of buckets of uniform duration
31 * <li>The <i>timespan</i> of the model is thus: <i>n</i> * <i>d</i> time units
32 * <li>Bucket <i>i</i> holds the number of events that occurred in time range:
33 * [<i>basetime</i> + <i>i</i> * <i>d</i>, <i>basetime</i> + (<i>i</i> + 1) *
36 * Initially, the bucket durations is set to 1ns. As the events are read, they
37 * are tallied (using <i>countEvent()</i>) in the appropriate bucket (relative
38 * to the <i>basetime</i>).
40 * Eventually, an event will have a timestamp that exceeds the <i>timespan</i>
41 * high end (determined by <i>n</i>, the number of buckets, and <i>d</i>, the
42 * bucket duration). At this point, the histogram needs to be compacted. This is
43 * done by simply merging adjacent buckets by pair, in effect doubling the
44 * <i>timespan</i> (<i>timespan'</i> = <i>n</i> * <i>d'</i>, where <i>d'</i> =
45 * 2<i>d</i>). This compaction happens as needed as the trace is read.
47 * The model allows for timestamps in not increasing order. The timestamps can
48 * be fed to the model in any order. If an event has a timestamp less than the
49 * <i>basetime</i>, the buckets will be moved to the right to account for the
50 * new smaller timestamp. The new <i>basetime</i> is a multiple of the bucket
51 * duration smaller then the previous <i>basetime</i>. Note that the <i>basetime</i>
52 * might not be anymore a timestamp of an event. If necessary, the buckets will
53 * be compacted before moving to the right. This might be necessary to not
54 * loose any event counts at the end of the buckets array.
56 * The mapping from the model to the UI is performed by the <i>scaleTo()</i>
57 * method. By keeping the number of buckets <i>n</i> relatively large with
58 * respect to to the number of pixels in the actual histogram, we should achieve
59 * a nice result when visualizing the histogram.
61 * TODO: Add filter support for more refined event counting (e.g. by trace,
64 * TODO: Cut-off eccentric values? TODO: Support for going back in time?
66 public class HistogramDataModel
implements IHistogramDataModel
{
68 // ------------------------------------------------------------------------
70 // ------------------------------------------------------------------------
72 // The default number of buckets
73 public static final int DEFAULT_NUMBER_OF_BUCKETS
= 16 * 1000;
75 public static final int REFRESH_FREQUENCY
= DEFAULT_NUMBER_OF_BUCKETS
;
77 // // The ratio where an eccentric value will be truncated
78 // private static final int MAX_TO_AVERAGE_CUTOFF_RATIO = 5;
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 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
= other
.fBucketDuration
;
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 (int i
= 0; i
< listeners
.length
; i
++) {
186 IHistogramModelListener listener
= (IHistogramModelListener
) listeners
[i
];
187 listener
.modelUpdated();
192 // ------------------------------------------------------------------------
194 // ------------------------------------------------------------------------
196 public void complete() {
197 fireModelUpdateNotification();
201 * Clear the histogram model.
204 public void clear() {
205 Arrays
.fill(fBuckets
, 0);
207 fFirstBucketTime
= 0;
209 fCurrentEventTime
= 0;
211 fBucketDuration
= 1; // 1ns
213 fireModelUpdateNotification();
217 * Sets the current event time
221 public void setCurrentEvent(long timestamp
) {
222 fCurrentEventTime
= timestamp
;
226 * Sets the current event time
230 public void setCurrentEventNotifyListeners(long timestamp
) {
231 fCurrentEventTime
= timestamp
;
232 fireModelUpdateNotification();
236 * Add event to the correct bucket, compacting the if needed.
238 * @param timestamp the timestamp of the event to count
241 public void countEvent(long eventCount
, long timestamp
) {
245 String message
= "Negative time value"; //$NON-NLS-1$
246 EventOutOfSequenceException exception
= new EventOutOfSequenceException(message
);
247 LTTngUILogger
.logError(message
, exception
);
251 // Set the start/end time if not already done
252 if (fLastBucket
== 0 && fBuckets
[0] == 0 && timestamp
> 0) {
253 fFirstBucketTime
= timestamp
;
254 fFirstEventTime
= timestamp
;
258 if (timestamp
< fFirstEventTime
) {
259 fFirstEventTime
= timestamp
;
262 if (fLastEventTime
< timestamp
) {
263 fLastEventTime
= timestamp
;
266 if (timestamp
>= fFirstBucketTime
) {
269 while (timestamp
>= fTimeLimit
) {
275 // get offset for adjustment
276 int offset
= getOffset(timestamp
);
279 while(fLastBucket
+ offset
>= fNbBuckets
) {
281 offset
= getOffset(timestamp
);
286 fLastBucket
= fLastBucket
+ offset
;
288 fFirstBucketTime
= fFirstBucketTime
- offset
*fBucketDuration
;
292 // Increment the right bucket
293 int index
= (int) ((timestamp
- fFirstBucketTime
) / fBucketDuration
);
296 if (fLastBucket
< index
)
299 fireModelUpdateNotification(eventCount
);
303 * Scale the model data to the width, height and bar width requested.
308 * @return the result array of size [width] and where the highest value
309 * doesn't exceed [height]
312 public HistogramScaledData
scaleTo(int width
, int height
, int barWidth
) {
314 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$
317 // The result structure
318 HistogramScaledData result
= new HistogramScaledData(width
, height
, barWidth
);
320 // Scale horizontally
321 result
.fMaxValue
= 0;
323 int nbBars
= width
/ barWidth
;
324 int bucketsPerBar
= fLastBucket
/ nbBars
+ 1;
325 result
.fBucketDuration
= bucketsPerBar
* fBucketDuration
;
326 for (int i
= 0; i
< nbBars
; i
++) {
328 for (int j
= i
* bucketsPerBar
; j
< (i
+ 1) * bucketsPerBar
; j
++) {
331 count
+= fBuckets
[j
];
333 result
.fData
[i
] = count
;
334 result
.fLastBucket
= i
;
335 if (result
.fMaxValue
< count
)
336 result
.fMaxValue
= count
;
340 if (result
.fMaxValue
> 0) {
341 result
.fScalingFactor
= (double) height
/ result
.fMaxValue
;
344 // Set the current event index in the scaled histogram
345 if (fCurrentEventTime
>= fFirstBucketTime
&& fCurrentEventTime
<= fLastEventTime
)
346 result
.fCurrentBucket
= (int) ((fCurrentEventTime
- fFirstBucketTime
) / fBucketDuration
) / bucketsPerBar
;
348 result
.fCurrentBucket
= HistogramScaledData
.OUT_OF_RANGE_BUCKET
;
350 result
.fFirstBucketTime
= fFirstBucketTime
;
351 result
.fFirstEventTime
= fFirstEventTime
;
355 // ------------------------------------------------------------------------
357 // ------------------------------------------------------------------------
359 private void updateEndTime() {
360 fTimeLimit
= fFirstBucketTime
+ fNbBuckets
* fBucketDuration
;
363 private void mergeBuckets() {
364 for (int i
= 0; i
< fNbBuckets
/ 2; i
++) {
365 fBuckets
[i
] = fBuckets
[2 * i
] + fBuckets
[2 * i
+ 1];
367 Arrays
.fill(fBuckets
, fNbBuckets
/ 2, fNbBuckets
, 0);
368 fBucketDuration
*= 2;
370 fLastBucket
= fNbBuckets
/ 2 - 1;
373 private void moveBuckets(int offset
) {
374 for(int i
= fNbBuckets
- 1; i
>= offset
; i
--) {
375 fBuckets
[i
] = fBuckets
[i
-offset
];
378 for (int i
= 0; i
< offset
; i
++) {
383 private int getOffset(long timestamp
) {
384 int offset
= (int) ((fFirstBucketTime
- timestamp
) / fBucketDuration
);
385 if ((fFirstBucketTime
- timestamp
) % fBucketDuration
!= 0) {