Commit | Line | Data |
---|---|---|
7692e512 AM |
1 | /******************************************************************************* |
2 | * Copyright (c) 2013 École Polytechnique de Montréal, 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 | * Florian Wininger - Initial API and implementation | |
11 | * Alexandre Montplaisir - Refactoring, performance tweaks | |
fec1ac0b | 12 | * Bernd Hufmann - Updated signal handling |
7692e512 AM |
13 | *******************************************************************************/ |
14 | ||
15 | package org.eclipse.linuxtools.tmf.ui.views.statesystem; | |
16 | ||
17 | import java.util.LinkedHashMap; | |
18 | import java.util.List; | |
19 | import java.util.Map; | |
20 | ||
21 | import org.eclipse.linuxtools.tmf.core.exceptions.AttributeNotFoundException; | |
22 | import org.eclipse.linuxtools.tmf.core.exceptions.StateSystemDisposedException; | |
23 | import org.eclipse.linuxtools.tmf.core.exceptions.StateValueTypeException; | |
24 | import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException; | |
25 | import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval; | |
26 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler; | |
27 | import org.eclipse.linuxtools.tmf.core.signal.TmfTimeSynchSignal; | |
28 | import org.eclipse.linuxtools.tmf.core.signal.TmfTraceClosedSignal; | |
fec1ac0b | 29 | import org.eclipse.linuxtools.tmf.core.signal.TmfTraceOpenedSignal; |
7692e512 AM |
30 | import org.eclipse.linuxtools.tmf.core.signal.TmfTraceSelectedSignal; |
31 | import org.eclipse.linuxtools.tmf.core.statesystem.ITmfStateSystem; | |
32 | import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue; | |
33 | import org.eclipse.linuxtools.tmf.core.timestamp.ITmfTimestamp; | |
34 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestamp; | |
35 | import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace; | |
b9a5bf8f | 36 | import org.eclipse.linuxtools.tmf.core.trace.TmfTraceManager; |
7692e512 AM |
37 | import org.eclipse.linuxtools.tmf.ui.views.TmfView; |
38 | import org.eclipse.swt.SWT; | |
39 | import org.eclipse.swt.widgets.Composite; | |
40 | import org.eclipse.swt.widgets.Event; | |
41 | import org.eclipse.swt.widgets.Listener; | |
42 | import org.eclipse.swt.widgets.Tree; | |
43 | import org.eclipse.swt.widgets.TreeColumn; | |
44 | import org.eclipse.swt.widgets.TreeItem; | |
45 | ||
46 | /** | |
47 | * Displays the State System at a current time. | |
48 | * | |
49 | * @author Florian Wininger | |
50 | * @author Alexandre Montplaisir | |
51 | * @since 2.0 | |
52 | */ | |
53 | public class TmfStateSystemExplorer extends TmfView { | |
54 | ||
55 | /** The Environment View's ID */ | |
56 | public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.ssview"; //$NON-NLS-1$ | |
57 | ||
58 | private static final String emptyString = ""; //$NON-NLS-1$ | |
59 | ||
60 | /* Order of columns */ | |
61 | private static final int ATTRIBUTE_NAME_COL = 0; | |
62 | private static final int QUARK_COL = 1; | |
63 | private static final int VALUE_COL = 2; | |
64 | private static final int START_TIME_COL = 3; | |
65 | private static final int END_TIME_COL = 4; | |
66 | private static final int ATTRIBUTE_FULLPATH_COL = 5; | |
67 | ||
68 | private ITmfTrace fTrace; | |
69 | private Tree fTree; | |
70 | private volatile long fCurrentTimestamp = -1L; | |
71 | ||
72 | /** | |
73 | * Default constructor | |
74 | */ | |
75 | public TmfStateSystemExplorer() { | |
76 | super(ID); | |
77 | } | |
78 | ||
79 | // ------------------------------------------------------------------------ | |
80 | // ViewPart | |
81 | // ------------------------------------------------------------------------ | |
82 | ||
83 | @Override | |
84 | public void createPartControl(Composite parent) { | |
85 | fTree = new Tree(parent, SWT.NONE); | |
86 | TreeColumn nameCol = new TreeColumn(fTree, SWT.NONE, ATTRIBUTE_NAME_COL); | |
87 | TreeColumn quarkCol = new TreeColumn(fTree, SWT.NONE, QUARK_COL); | |
88 | TreeColumn valueCol = new TreeColumn(fTree, SWT.NONE, VALUE_COL); | |
89 | TreeColumn startCol = new TreeColumn(fTree, SWT.NONE, START_TIME_COL); | |
90 | TreeColumn endCol = new TreeColumn(fTree, SWT.NONE, END_TIME_COL); | |
91 | TreeColumn pathCol = new TreeColumn(fTree, SWT.NONE, ATTRIBUTE_FULLPATH_COL); | |
92 | ||
93 | nameCol.setText(Messages.TreeNodeColumnLabel); | |
94 | quarkCol.setText(Messages.QuarkColumnLabel); | |
95 | valueCol.setText(Messages.ValueColumnLabel); | |
96 | startCol.setText(Messages.StartTimeColumLabel); | |
97 | endCol.setText(Messages.EndTimeColumLabel); | |
98 | pathCol.setText(Messages.AttributePathColumnLabel); | |
99 | ||
100 | fTree.setItemCount(0); | |
101 | ||
102 | fTree.setHeaderVisible(true); | |
103 | nameCol.pack(); | |
104 | valueCol.pack(); | |
105 | ||
106 | fTree.addListener(SWT.Expand, new Listener() { | |
107 | @Override | |
108 | public void handleEvent(Event e) { | |
109 | TreeItem item = (TreeItem) e.item; | |
110 | item.setExpanded(true); | |
111 | updateTable(); | |
112 | } | |
113 | }); | |
114 | ||
115 | ITmfTrace trace = getActiveTrace(); | |
116 | if (trace != null) { | |
117 | traceSelected(new TmfTraceSelectedSignal(this, trace)); | |
118 | } | |
119 | } | |
120 | ||
121 | // ------------------------------------------------------------------------ | |
122 | // Operations | |
123 | // ------------------------------------------------------------------------ | |
124 | ||
125 | /** | |
126 | * Create the initial tree from a trace. | |
127 | */ | |
128 | private synchronized void createTable() { | |
129 | if (fTrace == null) { | |
130 | return; | |
131 | } | |
132 | ||
133 | /* Clear the table, in case a trace was previously using it */ | |
134 | fTree.getDisplay().asyncExec(new Runnable() { | |
135 | @Override | |
136 | public void run() { | |
137 | fTree.setItemCount(0); | |
138 | } | |
139 | }); | |
140 | ||
b9a5bf8f | 141 | for (final ITmfTrace currentTrace : TmfTraceManager.getTraceSet(fTrace)) { |
7692e512 AM |
142 | /* |
143 | * We will first do all the queries for this trace, then update that | |
144 | * sub-tree in the UI thread. | |
145 | */ | |
146 | final Map<String, ITmfStateSystem> sss = currentTrace.getStateSystems(); | |
147 | final Map<String, List<ITmfStateInterval>> fullStates = | |
148 | new LinkedHashMap<String, List<ITmfStateInterval>>(); | |
149 | for (Map.Entry<String, ITmfStateSystem> entry : sss.entrySet()) { | |
150 | String ssName = entry.getKey(); | |
151 | ITmfStateSystem ss = entry.getValue(); | |
152 | ss.waitUntilBuilt(); | |
153 | long startTime = ss.getStartTime(); | |
154 | try { | |
155 | fullStates.put(ssName, ss.queryFullState(startTime)); | |
156 | } catch (TimeRangeException e) { | |
157 | /* Should not happen since we're querying at start time */ | |
158 | throw new RuntimeException(); | |
159 | } catch (StateSystemDisposedException e) { | |
160 | /* Probably shutting down, cancel and return */ | |
161 | return; | |
162 | } | |
163 | } | |
164 | ||
165 | /* Update the table (in the UI thread) */ | |
166 | fTree.getDisplay().asyncExec(new Runnable() { | |
167 | @Override | |
168 | public void run() { | |
169 | TreeItem traceRoot = new TreeItem(fTree, SWT.NONE); | |
170 | traceRoot.setText(ATTRIBUTE_NAME_COL, currentTrace.getName()); | |
171 | ||
172 | for (Map.Entry<String, ITmfStateSystem> entry : sss.entrySet()) { | |
173 | String ssName = entry.getKey(); | |
174 | ITmfStateSystem ss = entry.getValue(); | |
175 | List<ITmfStateInterval> fullState = fullStates.get(ssName); | |
176 | ||
177 | /* Root item of the current state system */ | |
178 | TreeItem item = new TreeItem(traceRoot, SWT.NONE); | |
179 | ||
180 | /* Name of the SS goes in the first column */ | |
181 | item.setText(ATTRIBUTE_NAME_COL, ssName); | |
182 | ||
183 | /* | |
184 | * Calling with quark '-1' here to start with the root | |
185 | * attribute, then it will be called recursively. | |
186 | */ | |
187 | addChildren(ss, fullState, -1, item); | |
188 | } | |
189 | ||
190 | /* Expand the first-level tree items */ | |
191 | for (TreeItem item : fTree.getItems()) { | |
192 | item.setExpanded(true); | |
193 | } | |
194 | packColumns(); | |
195 | } | |
196 | }); | |
197 | } | |
198 | } | |
199 | ||
200 | /** | |
201 | * Add children node to a newly-created tree. Should only be called by the | |
202 | * UI thread. | |
203 | */ | |
204 | private void addChildren(ITmfStateSystem ss, | |
205 | List<ITmfStateInterval> fullState, int rootQuark, TreeItem root) { | |
206 | try { | |
207 | for (int quark : ss.getSubAttributes(rootQuark, false)) { | |
208 | TreeItem subItem = new TreeItem(root, SWT.NONE); | |
209 | ||
210 | /* Write the info we already know */ | |
211 | subItem.setText(ATTRIBUTE_NAME_COL, ss.getAttributeName(quark)); | |
212 | subItem.setText(QUARK_COL, String.valueOf(quark)); | |
213 | subItem.setText(ATTRIBUTE_FULLPATH_COL, ss.getFullAttributePath(quark)); | |
214 | ||
215 | /* Populate the other columns */ | |
216 | ITmfStateInterval interval = fullState.get(quark); | |
217 | populateColumns(subItem, interval); | |
218 | ||
219 | /* Update this node's children recursively */ | |
220 | addChildren(ss, fullState, quark, subItem); | |
221 | } | |
222 | ||
223 | } catch (AttributeNotFoundException e) { | |
224 | /* Should not happen, we're iterating on known attributes */ | |
225 | throw new RuntimeException(); | |
226 | } | |
227 | } | |
228 | ||
229 | /** | |
230 | * Update the tree, which means keep the tree of attributes in the first | |
231 | * column as-is, but update the values to the ones at a new timestamp. | |
232 | */ | |
233 | private synchronized void updateTable() { | |
b9a5bf8f | 234 | ITmfTrace[] traces = TmfTraceManager.getTraceSet(fTrace); |
7692e512 AM |
235 | long ts = fCurrentTimestamp; |
236 | ||
237 | /* For each trace... */ | |
238 | for (int traceNb = 0; traceNb < traces.length; traceNb++) { | |
239 | Map<String, ITmfStateSystem> sss = traces[traceNb].getStateSystems(); | |
240 | ||
241 | /* For each state system associated with this trace... */ | |
242 | int ssNb = 0; | |
243 | for (Map.Entry<String, ITmfStateSystem> entry : sss.entrySet()) { | |
244 | /* | |
245 | * Even though we only use the value, it just feels safer to | |
246 | * iterate the same way as before to keep the order the same. | |
247 | */ | |
248 | final ITmfStateSystem ss = entry.getValue(); | |
249 | final int traceNb1 = traceNb; | |
250 | final int ssNb1 = ssNb; | |
251 | ts = (ts == -1 ? ss.getStartTime() : ts); | |
252 | try { | |
253 | final List<ITmfStateInterval> fullState = ss.queryFullState(ts); | |
254 | fTree.getDisplay().asyncExec(new Runnable() { | |
255 | @Override | |
256 | public void run() { | |
257 | /* Get the tree item of the relevant state system */ | |
258 | TreeItem traceItem = fTree.getItem(traceNb1); | |
259 | TreeItem item = traceItem.getItem(ssNb1); | |
260 | /* Update it, then its children, recursively */ | |
261 | item.setText(VALUE_COL, emptyString); | |
262 | updateChildren(ss, fullState, -1, item); | |
263 | } | |
264 | }); | |
265 | ||
266 | } catch (TimeRangeException e) { | |
267 | /* | |
268 | * This can happen in an experiment, if the user selects a | |
269 | * range valid in the experiment, but this specific does not | |
270 | * exist. Print "out-of-range" into all the values. | |
271 | */ | |
272 | fTree.getDisplay().asyncExec(new Runnable() { | |
273 | @Override | |
274 | public void run() { | |
275 | TreeItem traceItem = fTree.getItem(traceNb1); | |
276 | TreeItem item = traceItem.getItem(ssNb1); | |
277 | markOutOfRange(item); | |
278 | } | |
279 | }); | |
280 | } catch (StateSystemDisposedException e) { | |
281 | return; | |
282 | } | |
283 | ||
284 | ssNb++; | |
285 | } | |
286 | } | |
287 | } | |
288 | ||
289 | /** | |
290 | * Update the values shown by a child row when doing an update. Should only | |
291 | * be called by the UI thread. | |
292 | */ | |
293 | private void updateChildren(ITmfStateSystem ss, | |
294 | List<ITmfStateInterval> state, int root_quark, TreeItem root) { | |
295 | try { | |
296 | for (TreeItem item : root.getItems()) { | |
297 | int quark = ss.getQuarkRelative(root_quark, item.getText(0)); | |
298 | ITmfStateInterval interval = state.get(quark); | |
299 | populateColumns(item, interval); | |
300 | ||
301 | /* Update children recursively */ | |
302 | updateChildren(ss, state, quark, item); | |
303 | } | |
304 | ||
305 | } catch (AttributeNotFoundException e) { | |
306 | /* We're iterating on known attributes, should not happen */ | |
307 | throw new RuntimeException(); | |
308 | } | |
309 | } | |
310 | ||
311 | /** | |
312 | * Populate an 'item' (a row in the tree) with the information found in the | |
313 | * interval. This method should only be called by the UI thread. | |
314 | */ | |
315 | private static void populateColumns(TreeItem item, ITmfStateInterval interval) { | |
316 | try { | |
317 | ITmfStateValue state = interval.getStateValue(); | |
318 | ||
319 | // add the value in the 2nd column | |
320 | switch (state.getType()) { | |
321 | case INTEGER: | |
322 | item.setText(VALUE_COL, String.valueOf(state.unboxInt())); | |
323 | break; | |
324 | case LONG: | |
325 | item.setText(VALUE_COL, String.valueOf(state.unboxLong())); | |
326 | break; | |
327 | case STRING: | |
328 | item.setText(VALUE_COL, state.unboxStr()); | |
329 | break; | |
330 | case NULL: | |
331 | default: | |
332 | item.setText(VALUE_COL, emptyString); | |
333 | break; | |
334 | } | |
335 | ||
336 | TmfTimestamp startTime = new TmfTimestamp(interval.getStartTime(), ITmfTimestamp.NANOSECOND_SCALE); | |
337 | item.setText(START_TIME_COL, startTime.toString()); | |
338 | ||
339 | TmfTimestamp endTime = new TmfTimestamp(interval.getEndTime(), ITmfTimestamp.NANOSECOND_SCALE); | |
340 | item.setText(END_TIME_COL, endTime.toString()); | |
341 | ||
342 | } catch (StateValueTypeException e) { | |
343 | /* Should not happen, we're case-switching on the specific types */ | |
344 | throw new RuntimeException(); | |
345 | } | |
346 | } | |
347 | ||
348 | /** | |
349 | * Same concept as {@link updateChildren}, but instead of printing actual | |
350 | * values coming from the state system, we print "Out of range" into all | |
351 | * values. This is to indicate that this specific state system is not | |
352 | * currently defined at the selected timestamp. | |
353 | * | |
354 | * Guess by which thread this should be called? Hint: starts with a U, ends | |
355 | * with an I. | |
356 | */ | |
357 | private void markOutOfRange(TreeItem root) { | |
358 | root.setText(VALUE_COL, Messages.OutOfRangeMsg); | |
359 | root.setText(START_TIME_COL, emptyString); | |
360 | root.setText(END_TIME_COL, emptyString); | |
361 | for (TreeItem item : root.getItems()) { | |
362 | markOutOfRange(item); | |
363 | } | |
364 | } | |
365 | ||
366 | /** | |
367 | * Auto-pack all the columns in the display. Should only be called by the UI | |
368 | * thread. | |
369 | */ | |
370 | private void packColumns() { | |
371 | //FIXME should add a bit of padding | |
372 | for (TreeColumn column : fTree.getColumns()) { | |
373 | column.pack(); | |
374 | } | |
375 | } | |
376 | ||
377 | @Override | |
378 | public void setFocus() { | |
379 | fTree.setFocus(); | |
380 | } | |
381 | ||
382 | // ------------------------------------------------------------------------ | |
383 | // Signal handlers | |
384 | // ------------------------------------------------------------------------ | |
fec1ac0b BH |
385 | /** |
386 | * Handler for the trace opened signal. | |
387 | * @param signal | |
388 | * The incoming signal | |
389 | * @since 2.0 | |
390 | */ | |
391 | @TmfSignalHandler | |
392 | public void traceOpened(TmfTraceOpenedSignal signal) { | |
393 | fTrace = signal.getTrace(); | |
394 | loadTrace(); | |
395 | } | |
7692e512 AM |
396 | |
397 | /** | |
398 | * Handler for the trace selected signal. This will make the view display | |
399 | * the information for the newly-selected trace. | |
400 | * | |
401 | * @param signal | |
402 | * The incoming signal | |
403 | */ | |
404 | @TmfSignalHandler | |
405 | public void traceSelected(TmfTraceSelectedSignal signal) { | |
406 | ITmfTrace trace = signal.getTrace(); | |
407 | if (trace != fTrace) { | |
408 | fTrace = trace; | |
fec1ac0b | 409 | loadTrace(); |
7692e512 AM |
410 | } |
411 | } | |
412 | ||
413 | /** | |
414 | * Handler for the trace closed signal. This will clear the view. | |
415 | * | |
416 | * @param signal | |
417 | * the incoming signal | |
418 | */ | |
419 | @TmfSignalHandler | |
420 | public void traceClosed(TmfTraceClosedSignal signal) { | |
421 | // delete the tree at the trace closed | |
422 | if (signal.getTrace() == fTrace) { | |
423 | fTrace = null; | |
424 | fTree.setItemCount(0); | |
425 | } | |
426 | } | |
427 | ||
428 | /** | |
429 | * Handles the current time updated signal. This will update the view's | |
430 | * values to the newly-selected timestamp. | |
431 | * | |
432 | * @param signal | |
433 | * the signal to process | |
434 | */ | |
435 | @TmfSignalHandler | |
436 | public void currentTimeUpdated(final TmfTimeSynchSignal signal) { | |
437 | Thread thread = new Thread("State system visualizer update") { //$NON-NLS-1$ | |
438 | @Override | |
439 | public void run() { | |
440 | ITmfTimestamp currentTime = signal.getCurrentTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE); | |
441 | fCurrentTimestamp = currentTime.getValue(); | |
442 | updateTable(); | |
443 | } | |
444 | }; | |
445 | thread.start(); | |
446 | } | |
fec1ac0b BH |
447 | |
448 | private void loadTrace() { | |
449 | Thread thread = new Thread("State system visualizer construction") { //$NON-NLS-1$ | |
450 | @Override | |
451 | public void run() { | |
452 | createTable(); | |
453 | } | |
454 | }; | |
455 | thread.start(); | |
456 | } | |
457 | ||
7692e512 | 458 | } |