Commit | Line | Data |
---|---|---|
12bc2ed7 | 1 | /******************************************************************************* |
c8422608 | 2 | * Copyright (c) 2011, 2013 Ericsson |
12bc2ed7 PT |
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 | * Patrick Tasse - Initial API and implementation | |
11 | ******************************************************************************/ | |
12 | ||
13 | package org.eclipse.linuxtools.tmf.ui.viewers.events; | |
14 | ||
15 | import java.util.ArrayList; | |
6151d86c | 16 | import java.util.Arrays; |
12bc2ed7 PT |
17 | import java.util.List; |
18 | ||
19 | import org.eclipse.core.runtime.IProgressMonitor; | |
20 | import org.eclipse.core.runtime.IStatus; | |
21 | import org.eclipse.core.runtime.Status; | |
22 | import org.eclipse.core.runtime.jobs.Job; | |
23 | import org.eclipse.linuxtools.internal.tmf.ui.Activator; | |
fd3f1eff | 24 | import org.eclipse.linuxtools.tmf.core.component.ITmfEventProvider; |
12bc2ed7 PT |
25 | import org.eclipse.linuxtools.tmf.core.event.ITmfEvent; |
26 | import org.eclipse.linuxtools.tmf.core.filter.ITmfFilter; | |
2740e05c | 27 | import org.eclipse.linuxtools.tmf.core.request.ITmfEventRequest; |
fd3f1eff AM |
28 | import org.eclipse.linuxtools.tmf.core.request.TmfEventRequest; |
29 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimeRange; | |
12bc2ed7 PT |
30 | import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace; |
31 | ||
32 | /** | |
33 | * The generic TMF Events table events cache | |
34 | * | |
35 | * This can help avoid re-reading the trace when the user scrolls a window, | |
36 | * for example. | |
37 | * | |
38 | * @version 1.0 | |
39 | * @author Patrick Tasse | |
40 | */ | |
41 | public class TmfEventsCache { | |
42 | ||
43 | /** | |
44 | * The generic TMF Events table cached event | |
45 | * | |
46 | * @version 1.0 | |
47 | * @author Patrick Tasse | |
48 | */ | |
49 | public static class CachedEvent { | |
50 | ITmfEvent event; | |
51 | long rank; | |
52 | ||
53 | /** | |
54 | * Constructor for new cached events. | |
55 | * | |
56 | * @param iTmfEvent | |
57 | * The original trace event | |
58 | * @param rank | |
59 | * The rank of this event in the trace | |
60 | */ | |
61 | public CachedEvent (ITmfEvent iTmfEvent, long rank) { | |
62 | this.event = iTmfEvent; | |
63 | this.rank = rank; | |
64 | } | |
65 | } | |
66 | ||
67 | private final CachedEvent[] fCache; | |
85438014 | 68 | private final int fCacheSize; |
12bc2ed7 PT |
69 | private int fCacheStartIndex = 0; |
70 | private int fCacheEndIndex = 0; | |
71 | ||
6151d86c | 72 | private ITmfTrace fTrace; |
12bc2ed7 PT |
73 | private final TmfEventsTable fTable; |
74 | private ITmfFilter fFilter; | |
507b1336 | 75 | private final List<Integer> fFilterIndex = new ArrayList<>(); // contains the event rank at each 'cache size' filtered events |
12bc2ed7 PT |
76 | |
77 | /** | |
78 | * Constructor for the event cache | |
79 | * | |
80 | * @param cacheSize | |
81 | * The size of the cache, in number of events | |
82 | * @param table | |
83 | * The Events table this cache will cover | |
84 | */ | |
85 | public TmfEventsCache(int cacheSize, TmfEventsTable table) { | |
85438014 PT |
86 | fCacheSize = cacheSize; |
87 | fCache = new CachedEvent[cacheSize * 2]; // the cache holds two blocks of cache size | |
12bc2ed7 PT |
88 | fTable = table; |
89 | } | |
90 | ||
91 | /** | |
92 | * Assign a new trace to this events cache. This clears the current | |
93 | * contents. | |
94 | * | |
95 | * @param trace | |
96 | * The trace to assign. | |
97 | */ | |
6151d86c | 98 | public void setTrace(ITmfTrace trace) { |
12bc2ed7 PT |
99 | fTrace = trace; |
100 | clear(); | |
101 | } | |
102 | ||
103 | /** | |
104 | * Clear the current contents of this cache. | |
105 | */ | |
106 | public synchronized void clear() { | |
d0e3f356 PT |
107 | if (job != null && job.getState() != Job.NONE) { |
108 | job.cancel(); | |
109 | } | |
6151d86c | 110 | Arrays.fill(fCache, null); |
12bc2ed7 PT |
111 | fCacheStartIndex = 0; |
112 | fCacheEndIndex = 0; | |
113 | fFilterIndex.clear(); | |
114 | } | |
115 | ||
116 | /** | |
117 | * Apply a filter on this event cache. This clears the current cache | |
118 | * contents. | |
119 | * | |
120 | * @param filter | |
121 | * The ITmfFilter to apply. | |
122 | */ | |
123 | public void applyFilter(ITmfFilter filter) { | |
124 | fFilter = filter; | |
125 | clear(); | |
126 | } | |
127 | ||
128 | /** | |
129 | * Clear the current filter on this cache. This also clears the current | |
130 | * cache contents. | |
131 | */ | |
132 | public void clearFilter() { | |
133 | fFilter = null; | |
134 | clear(); | |
135 | } | |
136 | ||
137 | /** | |
85438014 PT |
138 | * Get an event from the cache. If the cache does not contain the event, |
139 | * a cache population request is triggered. | |
12bc2ed7 PT |
140 | * |
141 | * @param index | |
142 | * The index of this event in the cache | |
85438014 | 143 | * @return The cached event, or 'null' if the event is not in the cache |
12bc2ed7 PT |
144 | */ |
145 | public synchronized CachedEvent getEvent(int index) { | |
146 | if ((index >= fCacheStartIndex) && (index < fCacheEndIndex)) { | |
147 | int i = index - fCacheStartIndex; | |
148 | return fCache[i]; | |
149 | } | |
150 | populateCache(index); | |
151 | return null; | |
152 | } | |
153 | ||
154 | /** | |
85438014 | 155 | * Peek an event in the cache. Does not trigger cache population. |
12bc2ed7 PT |
156 | * |
157 | * @param index | |
158 | * Index of the event to peek | |
85438014 | 159 | * @return The cached event, or 'null' if the event is not in the cache |
12bc2ed7 PT |
160 | */ |
161 | public synchronized CachedEvent peekEvent(int index) { | |
162 | if ((index >= fCacheStartIndex) && (index < fCacheEndIndex)) { | |
163 | int i = index - fCacheStartIndex; | |
164 | return fCache[i]; | |
165 | } | |
166 | return null; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Add a trace event to the cache. | |
171 | * | |
172 | * @param event | |
173 | * The original trace event to be cached | |
174 | * @param rank | |
175 | * The rank of this event in the trace | |
176 | * @param index | |
177 | * The index this event will occupy in the cache | |
178 | */ | |
179 | public synchronized void storeEvent(ITmfEvent event, long rank, int index) { | |
180 | if (index == fCacheEndIndex) { | |
181 | int i = index - fCacheStartIndex; | |
182 | if (i < fCache.length) { | |
bd54d363 | 183 | fCache[i] = new CachedEvent(event, rank); |
12bc2ed7 PT |
184 | fCacheEndIndex++; |
185 | } | |
186 | } | |
85438014 PT |
187 | if ((fFilter != null) && ((index % fCacheSize) == 0)) { |
188 | int i = index / fCacheSize; | |
12bc2ed7 PT |
189 | fFilterIndex.add(i, Integer.valueOf((int) rank)); |
190 | } | |
191 | } | |
192 | ||
193 | /** | |
194 | * Get the cache index of an event from his rank in the trace. This will | |
195 | * take in consideration any filter that might be applied. | |
196 | * | |
197 | * @param rank | |
198 | * The rank of the event in the trace | |
199 | * @return The position (index) this event should use once cached | |
200 | */ | |
12bc2ed7 PT |
201 | public int getFilteredEventIndex(final long rank) { |
202 | int current; | |
203 | int startRank; | |
fd3f1eff | 204 | TmfEventRequest request; |
12bc2ed7 PT |
205 | final ITmfFilter filter = fFilter; |
206 | synchronized (this) { | |
207 | int start = 0; | |
208 | int end = fFilterIndex.size(); | |
209 | ||
210 | if ((fCacheEndIndex - fCacheStartIndex) > 1) { | |
211 | if (rank < fCache[0].rank) { | |
85438014 | 212 | end = (fCacheStartIndex / fCacheSize) + 1; |
12bc2ed7 | 213 | } else if (rank > fCache[fCacheEndIndex - fCacheStartIndex - 1].rank) { |
85438014 | 214 | start = fCacheEndIndex / fCacheSize; |
12bc2ed7 PT |
215 | } else { |
216 | for (int i = 0; i < (fCacheEndIndex - fCacheStartIndex); i++) { | |
217 | if (fCache[i].rank >= rank) { | |
218 | return fCacheStartIndex + i; | |
219 | } | |
220 | } | |
221 | return fCacheEndIndex; | |
222 | } | |
223 | } | |
224 | ||
225 | current = (start + end) / 2; | |
226 | while (current != start) { | |
227 | if (rank < fFilterIndex.get(current)) { | |
228 | end = current; | |
229 | current = (start + end) / 2; | |
230 | } else { | |
231 | start = current; | |
232 | current = (start + end) / 2; | |
233 | } | |
234 | } | |
235 | startRank = fFilterIndex.size() > 0 ? fFilterIndex.get(current) : 0; | |
236 | } | |
237 | ||
85438014 | 238 | final int index = current * fCacheSize; |
12bc2ed7 | 239 | |
fd3f1eff | 240 | class DataRequest extends TmfEventRequest { |
3dca7aa5 AM |
241 | ITmfFilter requestFilter; |
242 | int requestRank; | |
243 | int requestIndex; | |
12bc2ed7 | 244 | |
3dca7aa5 | 245 | DataRequest(Class<? extends ITmfEvent> dataType, ITmfFilter reqFilter, int start, int nbRequested) { |
fd3f1eff AM |
246 | super(dataType, TmfTimeRange.ETERNITY, start, nbRequested, |
247 | TmfEventRequest.ExecutionType.FOREGROUND); | |
3dca7aa5 AM |
248 | requestFilter = reqFilter; |
249 | requestRank = start; | |
250 | requestIndex = index; | |
12bc2ed7 PT |
251 | } |
252 | ||
253 | @Override | |
6151d86c | 254 | public void handleData(ITmfEvent event) { |
12bc2ed7 PT |
255 | super.handleData(event); |
256 | if (isCancelled()) { | |
257 | return; | |
258 | } | |
3dca7aa5 | 259 | if (requestRank >= rank) { |
12bc2ed7 PT |
260 | cancel(); |
261 | return; | |
262 | } | |
3dca7aa5 AM |
263 | requestRank++; |
264 | if (requestFilter.matches(event)) { | |
265 | requestIndex++; | |
12bc2ed7 PT |
266 | } |
267 | } | |
268 | ||
269 | public int getFilteredIndex() { | |
3dca7aa5 | 270 | return requestIndex; |
12bc2ed7 PT |
271 | } |
272 | } | |
273 | ||
2740e05c | 274 | request = new DataRequest(ITmfEvent.class, filter, startRank, ITmfEventRequest.ALL_DATA); |
fd3f1eff | 275 | ((ITmfEventProvider) fTrace).sendRequest(request); |
12bc2ed7 PT |
276 | try { |
277 | request.waitForCompletion(); | |
6151d86c | 278 | return ((DataRequest) request).getFilteredIndex(); |
12bc2ed7 PT |
279 | } catch (InterruptedException e) { |
280 | Activator.getDefault().logError("Filter request interrupted!", e); //$NON-NLS-1$ | |
281 | } | |
282 | return 0; | |
283 | } | |
284 | ||
285 | // ------------------------------------------------------------------------ | |
286 | // Event cache population | |
287 | // ------------------------------------------------------------------------ | |
288 | ||
289 | // The event fetching job | |
290 | private Job job; | |
291 | private synchronized void populateCache(final int index) { | |
292 | ||
293 | /* Check if the current job will fetch the requested event: | |
294 | * 1. The job must exist | |
295 | * 2. It must be running (i.e. not completed) | |
296 | * 3. The requested index must be within the cache range | |
297 | * | |
298 | * If the job meets these conditions, we simply exit. | |
299 | * Otherwise, we create a new job but we might have to cancel | |
300 | * an existing job for an obsolete range. | |
301 | */ | |
302 | if (job != null) { | |
303 | if (job.getState() != Job.NONE) { | |
304 | if ((index >= fCacheStartIndex) && (index < (fCacheStartIndex + fCache.length))) { | |
305 | return; | |
306 | } | |
307 | // The new index is out of the requested range | |
308 | // Kill the job and start a new one | |
309 | job.cancel(); | |
310 | } | |
311 | } | |
312 | ||
85438014 PT |
313 | // Populate the cache starting at the index that is one block less |
314 | // of cache size than the requested index. The cache will hold two | |
315 | // consecutive blocks of cache size, centered on the requested index. | |
316 | fCacheStartIndex = Math.max(0, index - fCacheSize); | |
317 | fCacheEndIndex = fCacheStartIndex; | |
12bc2ed7 PT |
318 | |
319 | job = new Job("Fetching Events") { //$NON-NLS-1$ | |
85438014 | 320 | private int startIndex = fCacheStartIndex; |
12bc2ed7 PT |
321 | private int skipCount = 0; |
322 | @Override | |
12bc2ed7 PT |
323 | protected IStatus run(final IProgressMonitor monitor) { |
324 | ||
325 | int nbRequested; | |
326 | if (fFilter == null) { | |
327 | nbRequested = fCache.length; | |
328 | } else { | |
2740e05c | 329 | nbRequested = ITmfEventRequest.ALL_DATA; |
85438014 | 330 | int i = startIndex / fCacheSize; |
12bc2ed7 | 331 | if (i < fFilterIndex.size()) { |
85438014 | 332 | skipCount = startIndex - (i * fCacheSize); |
12bc2ed7 | 333 | startIndex = fFilterIndex.get(i); |
12bc2ed7 PT |
334 | } |
335 | } | |
336 | ||
fd3f1eff AM |
337 | TmfEventRequest request = new TmfEventRequest(ITmfEvent.class, |
338 | TmfTimeRange.ETERNITY, | |
9765b9f1 AM |
339 | startIndex, |
340 | nbRequested, | |
fd3f1eff | 341 | TmfEventRequest.ExecutionType.FOREGROUND) { |
12bc2ed7 PT |
342 | private int count = 0; |
343 | private long rank = startIndex; | |
344 | @Override | |
345 | public void handleData(ITmfEvent event) { | |
346 | // If the job is canceled, cancel the request so waitForCompletion() will unlock | |
347 | if (monitor.isCanceled()) { | |
348 | cancel(); | |
349 | return; | |
350 | } | |
351 | super.handleData(event); | |
352 | if (event != null) { | |
353 | if (((fFilter == null) || fFilter.matches(event)) && (skipCount-- <= 0)) { | |
354 | synchronized (TmfEventsCache.this) { | |
355 | if (monitor.isCanceled()) { | |
356 | return; | |
357 | } | |
bd54d363 | 358 | fCache[count] = new CachedEvent(event, rank); |
12bc2ed7 PT |
359 | count++; |
360 | fCacheEndIndex++; | |
361 | } | |
362 | if (fFilter != null) { | |
363 | fTable.cacheUpdated(false); | |
364 | } | |
365 | } | |
366 | } | |
367 | if (count >= fCache.length) { | |
368 | cancel(); | |
369 | } else if ((fFilter != null) && (count >= (fTable.getTable().getItemCount() - 3))) { // -1 for header row, -2 for top and bottom filter status rows | |
370 | cancel(); | |
371 | } | |
372 | rank++; | |
373 | } | |
374 | }; | |
375 | ||
fd3f1eff | 376 | ((ITmfEventProvider) fTrace).sendRequest(request); |
12bc2ed7 PT |
377 | try { |
378 | request.waitForCompletion(); | |
379 | } catch (InterruptedException e) { | |
380 | Activator.getDefault().logError("Wait for completion interrupted for populateCache ", e); //$NON-NLS-1$ | |
381 | } | |
382 | ||
383 | fTable.cacheUpdated(true); | |
384 | ||
385 | // Flag the UI thread that the cache is ready | |
386 | if (monitor.isCanceled()) { | |
387 | return Status.CANCEL_STATUS; | |
388 | } | |
389 | return Status.OK_STATUS; | |
390 | } | |
391 | }; | |
392 | //job.setSystem(true); | |
393 | job.setPriority(Job.SHORT); | |
394 | job.schedule(); | |
395 | } | |
396 | ||
397 | } |