TMF: Add tracing capabilities for analyses
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / analysis / TmfAbstractAnalysisModule.java
1 /*******************************************************************************
2 * Copyright (c) 2013, 2014 École Polytechnique de Montréal
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 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.core.analysis;
14
15 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.NullProgressMonitor;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.jobs.Job;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.osgi.util.NLS;
34 import org.eclipse.tracecompass.common.core.NonNullUtils;
35 import org.eclipse.tracecompass.internal.tmf.core.Activator;
36 import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer;
37 import org.eclipse.tracecompass.tmf.core.analysis.TmfAnalysisRequirement.ValuePriorityLevel;
38 import org.eclipse.tracecompass.tmf.core.component.TmfComponent;
39 import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
40 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
41 import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
42 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
43 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
44 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
45 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
46
47 /**
48 * Base class that analysis modules main class may extend. It provides default
49 * behavior to some methods of the analysis module
50 *
51 * @author Geneviève Bastien
52 * @since 3.0
53 */
54 @NonNullByDefault
55 public abstract class TmfAbstractAnalysisModule extends TmfComponent implements IAnalysisModule {
56
57 private @Nullable String fId;
58 private boolean fAutomatic = false, fStarted = false;
59 private volatile @Nullable ITmfTrace fTrace;
60 private final Map<String, Object> fParameters = new HashMap<>();
61 private final List<String> fParameterNames = new ArrayList<>();
62 private final List<IAnalysisOutput> fOutputs = new ArrayList<>();
63 private List<IAnalysisParameterProvider> fParameterProviders = new ArrayList<>();
64 private @Nullable Job fJob = null;
65
66 private final Object syncObj = new Object();
67
68 /* Latch tracking if the analysis is completed or not */
69 private CountDownLatch fFinishedLatch = new CountDownLatch(0);
70
71 private boolean fAnalysisCancelled = false;
72
73 @Override
74 public boolean isAutomatic() {
75 return fAutomatic;
76 }
77
78 @Override
79 public String getName() {
80 return super.getName();
81 }
82
83 @Override
84 public void setName(String name) {
85 super.setName(name);
86 }
87
88 @Override
89 public void setId(String id) {
90 fId = id;
91 }
92
93 @Override
94 public String getId() {
95 String id = fId;
96 if (id == null) {
97 id = new String(this.getClass().getCanonicalName());
98 fId = id;
99 }
100 return id;
101 }
102
103 @Override
104 public void setAutomatic(boolean auto) {
105 fAutomatic = auto;
106 }
107
108 @Override
109 public void setTrace(ITmfTrace trace) throws TmfAnalysisException {
110 if (fTrace != null) {
111 throw new TmfAnalysisException(NLS.bind(Messages.TmfAbstractAnalysisModule_TraceSetMoreThanOnce, getName()));
112 }
113
114 TmfCoreTracer.traceAnalysis(getId(), trace, "setting trace for analysis"); //$NON-NLS-1$
115
116 /* Check that analysis can be executed */
117 if (!canExecute(trace)) {
118 throw new TmfAnalysisException(NLS.bind(Messages.TmfAbstractAnalysisModule_AnalysisCannotExecute, getName()));
119 }
120
121 fTrace = trace;
122 /* Get the parameter providers for this trace */
123 fParameterProviders = TmfAnalysisManager.getParameterProviders(this, trace);
124 for (IAnalysisParameterProvider provider : fParameterProviders) {
125 TmfCoreTracer.traceAnalysis(getId(), trace, "registered to parameter provider " + provider.getName()); //$NON-NLS-1$
126 provider.registerModule(this);
127 }
128 resetAnalysis();
129 fStarted = false;
130 }
131
132 /**
133 * Gets the trace
134 *
135 * @return The trace
136 */
137 protected @Nullable ITmfTrace getTrace() {
138 return fTrace;
139 }
140
141 @Override
142 public void addParameter(String name) {
143 fParameterNames.add(name);
144 }
145
146 @Override
147 public synchronized void setParameter(String name, @Nullable Object value) {
148 if (!fParameterNames.contains(name)) {
149 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
150 }
151 Object oldValue = fParameters.get(name);
152 fParameters.put(name, value);
153 if ((value != null) && !(value.equals(oldValue))) {
154 parameterChanged(name);
155 }
156 }
157
158 @Override
159 public synchronized void notifyParameterChanged(String name) {
160 if (!fParameterNames.contains(name)) {
161 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
162 }
163 Object oldValue = fParameters.get(name);
164 Object value = getParameter(name);
165 if ((value != null) && !(value.equals(oldValue))) {
166 parameterChanged(name);
167 }
168 }
169
170 /**
171 * Used to indicate that a parameter value has been changed
172 *
173 * @param name
174 * The name of the modified parameter
175 */
176 protected void parameterChanged(String name) {
177
178 }
179
180 @Override
181 public @Nullable Object getParameter(String name) {
182 Object paramValue = fParameters.get(name);
183 /* The parameter is not set, maybe it can be provided by someone else */
184 if ((paramValue == null) && (fTrace != null)) {
185 for (IAnalysisParameterProvider provider : fParameterProviders) {
186 paramValue = provider.getParameter(name);
187 if (paramValue != null) {
188 break;
189 }
190 }
191 }
192 return paramValue;
193 }
194
195 @Override
196 public boolean canExecute(ITmfTrace trace) {
197 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
198 if (!requirement.isFulfilled(trace)) {
199 return false;
200 }
201 }
202 return true;
203 }
204
205 /**
206 * Set the countdown latch back to 1 so the analysis can be executed again
207 */
208 protected void resetAnalysis() {
209 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "reset: ready for execution"); //$NON-NLS-1$
210 fFinishedLatch.countDown();
211 fFinishedLatch = new CountDownLatch(1);
212 }
213
214 /**
215 * Actually executes the analysis itself
216 *
217 * @param monitor
218 * Progress monitor
219 * @return Whether the analysis was completed successfully or not
220 * @throws TmfAnalysisException
221 * Method may throw an analysis exception
222 */
223 protected abstract boolean executeAnalysis(final IProgressMonitor monitor) throws TmfAnalysisException;
224
225 /**
226 * Indicate the analysis has been canceled. It is abstract to force
227 * implementing class to cleanup what they are running. This is called by
228 * the job's canceling. It does not need to be called directly.
229 */
230 protected abstract void canceling();
231
232 /**
233 * To be called when the analysis is completed, whether normally or because
234 * it was cancelled or for any other reason.
235 *
236 * It has to be called inside a synchronized block
237 */
238 private void setAnalysisCompleted() {
239 synchronized (syncObj) {
240 fStarted = false;
241 fJob = null;
242 fFinishedLatch.countDown();
243 }
244 }
245
246 /**
247 * Cancels the analysis if it is executing
248 */
249 @Override
250 public final void cancel() {
251 synchronized (syncObj) {
252 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "cancelled by application"); //$NON-NLS-1$
253 if (fJob != null) {
254 if (fJob.cancel()) {
255 fAnalysisCancelled = true;
256 setAnalysisCompleted();
257 }
258 }
259 fStarted = false;
260 }
261 }
262
263 @Override
264 public void dispose() {
265 super.dispose();
266 cancel();
267 }
268
269 /**
270 * Get an iterable list of all analyzes this analysis depends on. These
271 * analyzes will be scheduled before this analysis starts and the current
272 * analysis will not be considered completed until all the dependent
273 * analyzes are finished.
274 *
275 * @return An iterable list of analysis this analyzes depends on.
276 */
277 protected Iterable<IAnalysisModule> getDependentAnalyses() {
278 return checkNotNull(Collections.EMPTY_LIST);
279 }
280
281 private void execute(final ITmfTrace trace) {
282 /*
283 * TODO: The analysis in a job should be done at the analysis manager
284 * level instead of depending on this abstract class implementation,
285 * otherwise another analysis implementation may block the main thread
286 */
287
288 /* Do not execute if analysis has already run */
289 if (fFinishedLatch.getCount() == 0) {
290 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already executed"); //$NON-NLS-1$
291 return;
292 }
293
294 /* Do not execute if analysis already running */
295 synchronized (syncObj) {
296 if (fStarted) {
297 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already started, not starting again"); //$NON-NLS-1$
298 return;
299 }
300 fStarted = true;
301 }
302
303 /* Execute dependent analyses before creating the job for this one */
304 final Iterable<IAnalysisModule> dependentAnalyses = getDependentAnalyses();
305 for (IAnalysisModule module : dependentAnalyses) {
306 module.schedule();
307 }
308
309 /*
310 * Actual analysis will be run on a separate thread
311 */
312 String jobName = checkNotNull(NLS.bind(Messages.TmfAbstractAnalysisModule_RunningAnalysis, getName()));
313 fJob = new Job(jobName) {
314 @Override
315 protected @Nullable IStatus run(final @Nullable IProgressMonitor monitor) {
316 IProgressMonitor mon = monitor;
317 if (mon == null) {
318 mon = new NullProgressMonitor();
319 }
320 try {
321 mon.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
322 broadcast(new TmfStartAnalysisSignal(TmfAbstractAnalysisModule.this, TmfAbstractAnalysisModule.this));
323 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "started"); //$NON-NLS-1$
324 fAnalysisCancelled = !executeAnalysis(mon);
325 for (IAnalysisModule module : dependentAnalyses) {
326 module.waitForCompletion(mon);
327 }
328 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "finished"); //$NON-NLS-1$
329 } catch (TmfAnalysisException e) {
330 Activator.logError("Error executing analysis with trace " + trace.getName(), e); //$NON-NLS-1$
331 } finally {
332 synchronized (syncObj) {
333 mon.done();
334 setAnalysisCompleted();
335 }
336 TmfTraceManager.refreshSupplementaryFiles(trace);
337 }
338 if (!fAnalysisCancelled) {
339 return Status.OK_STATUS;
340 }
341 // Reset analysis so that it can be executed again.
342 resetAnalysis();
343 return Status.CANCEL_STATUS;
344 }
345
346 @Override
347 protected void canceling() {
348 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "job cancelled"); //$NON-NLS-1$
349 TmfAbstractAnalysisModule.this.canceling();
350 }
351
352 };
353 fJob.schedule();
354 }
355
356 @Override
357 public IStatus schedule() {
358 synchronized (syncObj) {
359 final ITmfTrace trace = getTrace();
360 if (trace == null) {
361 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, String.format("No trace specified for analysis %s", getName())); //$NON-NLS-1$
362 }
363 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "scheduled"); //$NON-NLS-1$
364 execute(trace);
365 }
366
367 return checkNotNull(Status.OK_STATUS);
368 }
369
370 @Override
371 public Iterable<IAnalysisOutput> getOutputs() {
372 return fOutputs;
373 }
374
375 @Override
376 public void registerOutput(IAnalysisOutput output) {
377 if (!fOutputs.contains(output)) {
378 fOutputs.add(output);
379 }
380 }
381
382 @Override
383 public boolean waitForCompletion() {
384 try {
385 fFinishedLatch.await();
386 } catch (InterruptedException e) {
387 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
388 }
389 return !fAnalysisCancelled;
390 }
391
392 @Override
393 public boolean waitForCompletion(IProgressMonitor monitor) {
394 try {
395 while (!fFinishedLatch.await(500, TimeUnit.MILLISECONDS)) {
396 if (fAnalysisCancelled || monitor.isCanceled()) {
397 fAnalysisCancelled = true;
398 return false;
399 }
400 }
401 } catch (InterruptedException e) {
402 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
403 }
404 return !fAnalysisCancelled;
405 }
406
407 /**
408 * Signal handler for trace closing
409 *
410 * @param signal
411 * Trace closed signal
412 */
413 @TmfSignalHandler
414 public void traceClosed(TmfTraceClosedSignal signal) {
415 /* Is the closing trace the one that was requested? */
416 synchronized (syncObj) {
417 if (signal.getTrace() == fTrace) {
418 cancel();
419 fTrace = null;
420 }
421 }
422 }
423
424 /**
425 * Signal handler for when the trace becomes active
426 *
427 * @param signal
428 * Trace selected signal
429 * @since 3.1
430 */
431 @TmfSignalHandler
432 public void traceSelected(TmfTraceSelectedSignal signal) {
433 /*
434 * Since some parameter providers may handle many traces, we need to
435 * register the current trace to it
436 */
437 if (signal.getTrace() == fTrace) {
438 for (IAnalysisParameterProvider provider : fParameterProviders) {
439 provider.registerModule(this);
440 }
441 }
442 }
443
444 /**
445 * Returns a full help text to display
446 *
447 * @return Full help text for the module
448 */
449 protected String getFullHelpText() {
450 return NonNullUtils.nullToEmptyString(NLS.bind(
451 Messages.TmfAbstractAnalysisModule_AnalysisModule,
452 getName()));
453 }
454
455 /**
456 * Gets a short help text, to display as header to other help text
457 *
458 * @param trace
459 * The trace to show help for
460 *
461 * @return Short help text describing the module
462 */
463 protected String getShortHelpText(ITmfTrace trace) {
464 return NonNullUtils.nullToEmptyString(NLS.bind(
465 Messages.TmfAbstractAnalysisModule_AnalysisForTrace,
466 getName(), trace.getName()));
467 }
468
469 /**
470 * Gets the help text specific for a trace who does not have required
471 * characteristics for module to execute. The default implementation uses
472 * the analysis requirements.
473 *
474 * @param trace
475 * The trace to apply the analysis to
476 * @return Help text
477 */
478 protected String getTraceCannotExecuteHelpText(ITmfTrace trace) {
479 StringBuilder builder = new StringBuilder();
480 builder.append(NLS.bind(Messages.TmfAbstractAnalysisModule_AnalysisCannotExecute, getName()));
481 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
482 if (!requirement.isFulfilled(trace)) {
483 builder.append("\n\n"); //$NON-NLS-1$
484 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementNotFulfilled, requirement.getType()));
485 builder.append("\n"); //$NON-NLS-1$
486 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementMandatoryValues, requirement.getValues(ValuePriorityLevel.MANDATORY)));
487 Set<String> information = requirement.getInformation();
488 if (!information.isEmpty()) {
489 builder.append("\n"); //$NON-NLS-1$
490 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementInformation, information));
491 }
492 }
493 }
494 return checkNotNull(builder.toString());
495 }
496
497 @Override
498 public String getHelpText() {
499 return getFullHelpText();
500 }
501
502 @Override
503 public String getHelpText(ITmfTrace trace) {
504 String text = getShortHelpText(trace);
505 if (!canExecute(trace)) {
506 text = text + "\n\n" + getTraceCannotExecuteHelpText(trace); //$NON-NLS-1$
507 }
508 return text;
509 }
510
511 @Override
512 public Iterable<TmfAnalysisRequirement> getAnalysisRequirements() {
513 return checkNotNull(Collections.EMPTY_SET);
514 }
515 }
This page took 0.051307 seconds and 5 git commands to generate.