Generalize and move the Histogram view from LTTng to TMF
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / histogram / HistogramDataModel.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2012 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 *******************************************************************************/
15
16 package org.eclipse.linuxtools.tmf.ui.views.histogram;
17
18 import java.util.Arrays;
19
20 import org.eclipse.core.runtime.ListenerList;
21
22 /**
23 * <b><u>HistogramDataModel</u></b>
24 * <p>
25 * Histogram-independent data model with the following characteristics:
26 * <ul>
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
29 * (<i>d</i>)
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) *
33 * <i>d</i>)
34 * </ul>
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>).
38 * <p>
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.
45 * <p>
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.
54 * <p>
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.
59 * <p>
60 * TODO: Add filter support for more refined event counting (e.g. by trace,
61 * event type, etc.)
62 * <p>
63 * TODO: Cut-off eccentric values? TODO: Support for going back in time?
64 */
65 public class HistogramDataModel implements IHistogramDataModel {
66
67 // ------------------------------------------------------------------------
68 // Constants
69 // ------------------------------------------------------------------------
70
71 // The default number of buckets
72 public static final int DEFAULT_NUMBER_OF_BUCKETS = 16 * 1000;
73
74 public static final int REFRESH_FREQUENCY = DEFAULT_NUMBER_OF_BUCKETS;
75
76 // ------------------------------------------------------------------------
77 // Attributes
78 // ------------------------------------------------------------------------
79
80 // Bucket management
81 private final int fNbBuckets;
82 private final long[] fBuckets;
83 private long fBucketDuration;
84 private long fNbEvents;
85 private int fLastBucket;
86
87 // Timestamps
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;
93
94 // Private listener lists
95 private final ListenerList fModelListeners;
96
97 // ------------------------------------------------------------------------
98 // Constructors
99 // ------------------------------------------------------------------------
100
101 public HistogramDataModel() {
102 this(DEFAULT_NUMBER_OF_BUCKETS);
103 }
104
105 public HistogramDataModel(int nbBuckets) {
106 fNbBuckets = nbBuckets;
107 fBuckets = new long[nbBuckets];
108 fModelListeners = new ListenerList();
109 clear();
110 }
111
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);
127 }
128 }
129
130 // ------------------------------------------------------------------------
131 // Accessors
132 // ------------------------------------------------------------------------
133
134 public long getNbEvents() {
135 return fNbEvents;
136 }
137
138 public int getNbBuckets() {
139 return fNbBuckets;
140 }
141
142 public long getBucketDuration() {
143 return fBucketDuration;
144 }
145
146 public long getFirstBucketTime() {
147 return fFirstBucketTime;
148 }
149
150 public long getStartTime() {
151 return fFirstEventTime;
152 }
153
154 public long getEndTime() {
155 return fLastEventTime;
156 }
157
158 public long getCurrentEventTime() {
159 return fCurrentEventTime;
160 }
161
162 public long getTimeLimit() {
163 return fTimeLimit;
164 }
165
166 // ------------------------------------------------------------------------
167 // Listener handling
168 // ------------------------------------------------------------------------
169
170 public void addHistogramListener(IHistogramModelListener listener) {
171 fModelListeners.add(listener);
172 }
173
174 public void removeHistogramListener(IHistogramModelListener listener) {
175 fModelListeners.remove(listener);
176 }
177
178 private void fireModelUpdateNotification() {
179 fireModelUpdateNotification(0);
180 }
181
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();
188 }
189 }
190 }
191
192 // ------------------------------------------------------------------------
193 // Operations
194 // ------------------------------------------------------------------------
195
196 @Override
197 public void complete() {
198 fireModelUpdateNotification();
199 }
200
201 /**
202 * Clear the histogram model.
203 */
204 @Override
205 public void clear() {
206 Arrays.fill(fBuckets, 0);
207 fNbEvents = 0;
208 fFirstBucketTime = 0;
209 fLastEventTime = 0;
210 fCurrentEventTime = 0;
211 fLastBucket = 0;
212 fBucketDuration = 1; // 1ns
213 updateEndTime();
214 fireModelUpdateNotification();
215 }
216
217 /**
218 * Sets the current event time
219 *
220 * @param timestamp
221 */
222 public void setCurrentEvent(long timestamp) {
223 fCurrentEventTime = timestamp;
224 }
225
226 /**
227 * Sets the current event time
228 *
229 * @param timestamp
230 */
231 public void setCurrentEventNotifyListeners(long timestamp) {
232 fCurrentEventTime = timestamp;
233 fireModelUpdateNotification();
234 }
235
236 /**
237 * Add event to the correct bucket, compacting the if needed.
238 *
239 * @param timestamp the timestamp of the event to count
240 */
241 @Override
242 public void countEvent(long eventCount, long timestamp) {
243
244 // Validate
245 if (timestamp < 0) {
246 return;
247 }
248
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;
253 updateEndTime();
254 }
255
256 if (timestamp < fFirstEventTime) {
257 fFirstEventTime = timestamp;
258 }
259
260 if (fLastEventTime < timestamp) {
261 fLastEventTime = timestamp;
262 }
263
264 if (timestamp >= fFirstBucketTime) {
265
266 // Compact as needed
267 while (timestamp >= fTimeLimit) {
268 mergeBuckets();
269 }
270
271 } else {
272
273 // get offset for adjustment
274 int offset = getOffset(timestamp);
275
276 // Compact as needed
277 while(fLastBucket + offset >= fNbBuckets) {
278 mergeBuckets();
279 offset = getOffset(timestamp);
280 }
281
282 moveBuckets(offset);
283
284 fLastBucket = fLastBucket + offset;
285
286 fFirstBucketTime = fFirstBucketTime - offset*fBucketDuration;
287 updateEndTime();
288 }
289
290 // Increment the right bucket
291 int index = (int) ((timestamp - fFirstBucketTime) / fBucketDuration);
292 fBuckets[index]++;
293 fNbEvents++;
294 if (fLastBucket < index)
295 fLastBucket = index;
296
297 fireModelUpdateNotification(eventCount);
298 }
299
300 /**
301 * Scale the model data to the width, height and bar width requested.
302 *
303 * @param width
304 * @param height
305 * @param bar width
306 * @return the result array of size [width] and where the highest value
307 * doesn't exceed [height]
308 */
309 @Override
310 public HistogramScaledData scaleTo(int width, int height, int barWidth) {
311 // Basic validation
312 if (width <= 0 || height <= 0 || barWidth <= 0)
313 throw new AssertionError("Invalid histogram dimensions (" + width + "x" + height + ", barWidth=" + barWidth + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
314
315 // The result structure
316 HistogramScaledData result = new HistogramScaledData(width, height, barWidth);
317
318 // Scale horizontally
319 result.fMaxValue = 0;
320
321 int nbBars = width / barWidth;
322 int bucketsPerBar = fLastBucket / nbBars + 1;
323 result.fBucketDuration = bucketsPerBar * fBucketDuration;
324 for (int i = 0; i < nbBars; i++) {
325 int count = 0;
326 for (int j = i * bucketsPerBar; j < (i + 1) * bucketsPerBar; j++) {
327 if (fNbBuckets <= j)
328 break;
329 count += fBuckets[j];
330 }
331 result.fData[i] = count;
332 result.fLastBucket = i;
333 if (result.fMaxValue < count)
334 result.fMaxValue = count;
335 }
336
337 // Scale vertically
338 if (result.fMaxValue > 0) {
339 result.fScalingFactor = (double) height / result.fMaxValue;
340 }
341
342 // Set the current event index in the scaled histogram
343 if (fCurrentEventTime >= fFirstBucketTime && fCurrentEventTime <= fLastEventTime)
344 result.fCurrentBucket = (int) ((fCurrentEventTime - fFirstBucketTime) / fBucketDuration) / bucketsPerBar;
345 else
346 result.fCurrentBucket = HistogramScaledData.OUT_OF_RANGE_BUCKET;
347
348 result.fFirstBucketTime = fFirstBucketTime;
349 result.fFirstEventTime = fFirstEventTime;
350 return result;
351 }
352
353 // ------------------------------------------------------------------------
354 // Helper functions
355 // ------------------------------------------------------------------------
356
357 private void updateEndTime() {
358 fTimeLimit = fFirstBucketTime + fNbBuckets * fBucketDuration;
359 }
360
361 private void mergeBuckets() {
362 for (int i = 0; i < fNbBuckets / 2; i++) {
363 fBuckets[i] = fBuckets[2 * i] + fBuckets[2 * i + 1];
364 }
365 Arrays.fill(fBuckets, fNbBuckets / 2, fNbBuckets, 0);
366 fBucketDuration *= 2;
367 updateEndTime();
368 fLastBucket = fNbBuckets / 2 - 1;
369 }
370
371 private void moveBuckets(int offset) {
372 for(int i = fNbBuckets - 1; i >= offset; i--) {
373 fBuckets[i] = fBuckets[i-offset];
374 }
375
376 for (int i = 0; i < offset; i++) {
377 fBuckets[i] = 0;
378 }
379 }
380
381 private int getOffset(long timestamp) {
382 int offset = (int) ((fFirstBucketTime - timestamp) / fBucketDuration);
383 if ((fFirstBucketTime - timestamp) % fBucketDuration != 0) {
384 offset++;
385 }
386 return offset;
387 }
388
389 }
This page took 0.039193 seconds and 6 git commands to generate.