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