1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal and others.
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
10 * Patrick Tasse - Initial API and implementation
11 * Geneviève Bastien - Move code to provide base classes for time graph view
12 * Christian Mansky - Add check active / uncheck inactive buttons
13 *******************************************************************************/
15 package org
.eclipse
.tracecompass
.analysis
.os
.linux
.ui
.views
.controlflow
;
17 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Comparator
;
21 import java
.util
.HashMap
;
22 import java
.util
.List
;
25 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
26 import org
.eclipse
.jdt
.annotation
.NonNull
;
27 import org
.eclipse
.jdt
.annotation
.Nullable
;
28 import org
.eclipse
.jface
.action
.IAction
;
29 import org
.eclipse
.jface
.action
.IToolBarManager
;
30 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
31 import org
.eclipse
.swt
.widgets
.Composite
;
32 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.kernelanalysis
.Attributes
;
33 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.kernelanalysis
.KernelAnalysisModule
;
34 import org
.eclipse
.tracecompass
.analysis
.os
.linux
.core
.kernelanalysis
.KernelThreadInformationProvider
;
35 import org
.eclipse
.tracecompass
.internal
.analysis
.os
.linux
.ui
.Activator
;
36 import org
.eclipse
.tracecompass
.internal
.analysis
.os
.linux
.ui
.Messages
;
37 import org
.eclipse
.tracecompass
.statesystem
.core
.ITmfStateSystem
;
38 import org
.eclipse
.tracecompass
.statesystem
.core
.StateSystemUtils
;
39 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.AttributeNotFoundException
;
40 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateSystemDisposedException
;
41 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.StateValueTypeException
;
42 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.TimeRangeException
;
43 import org
.eclipse
.tracecompass
.statesystem
.core
.interval
.ITmfStateInterval
;
44 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.ITmfStateValue
;
45 import org
.eclipse
.tracecompass
.tmf
.core
.statesystem
.TmfStateSystemAnalysisModule
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfTrace
;
47 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceManager
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceUtils
;
49 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.timegraph
.AbstractTimeGraphView
;
50 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ILinkEvent
;
51 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeEvent
;
52 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
53 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.TimeEvent
;
54 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.TimeGraphEntry
;
55 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.TimeLinkEvent
;
56 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
.Utils
;
57 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
.Utils
.Resolution
;
58 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
.Utils
.TimeFormat
;
61 * The Control Flow view main object
64 public class ControlFlowView
extends AbstractTimeGraphView
{
66 // ------------------------------------------------------------------------
68 // ------------------------------------------------------------------------
73 public static final String ID
= "org.eclipse.tracecompass.analysis.os.linux.views.controlflow"; //$NON-NLS-1$
75 private static final String PROCESS_COLUMN
= Messages
.ControlFlowView_processColumn
;
76 private static final String TID_COLUMN
= Messages
.ControlFlowView_tidColumn
;
77 private static final String PTID_COLUMN
= Messages
.ControlFlowView_ptidColumn
;
78 private static final String BIRTH_TIME_COLUMN
= Messages
.ControlFlowView_birthTimeColumn
;
79 private static final String TRACE_COLUMN
= Messages
.ControlFlowView_traceColumn
;
81 private static final String
[] COLUMN_NAMES
= new String
[] {
89 private static final String
[] FILTER_COLUMN_NAMES
= new String
[] {
94 // Timeout between updates in the build thread in ms
95 private static final long BUILD_UPDATE_TIMEOUT
= 500;
97 // ------------------------------------------------------------------------
99 // ------------------------------------------------------------------------
104 public ControlFlowView() {
105 super(ID
, new ControlFlowPresentationProvider());
106 setTreeColumns(COLUMN_NAMES
);
107 setTreeLabelProvider(new ControlFlowTreeLabelProvider());
108 setFilterColumns(FILTER_COLUMN_NAMES
);
109 setFilterLabelProvider(new ControlFlowFilterLabelProvider());
110 setEntryComparator(new ControlFlowEntryComparator());
114 public void createPartControl(Composite parent
) {
115 super.createPartControl(parent
);
116 // add "Check active" Button to TimeGraphFilterDialog
117 super.getTimeGraphCombo().addTimeGraphFilterCheckActiveButton(
118 new ControlFlowCheckActiveProvider(Messages
.ControlFlowView_checkActiveLabel
, Messages
.ControlFlowView_checkActiveToolTip
));
119 // add "Uncheck inactive" Button to TimeGraphFilterDialog
120 super.getTimeGraphCombo().addTimeGraphFilterUncheckInactiveButton(
121 new ControlFlowCheckActiveProvider(Messages
.ControlFlowView_uncheckInactiveLabel
, Messages
.ControlFlowView_uncheckInactiveToolTip
));
125 protected void fillLocalToolBar(IToolBarManager manager
) {
126 super.fillLocalToolBar(manager
);
127 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
128 IDialogSettings section
= settings
.getSection(getClass().getName());
129 if (section
== null) {
130 section
= settings
.addNewSection(getClass().getName());
133 IAction hideArrowsAction
= getTimeGraphCombo().getTimeGraphViewer().getHideArrowsAction(section
);
134 manager
.add(hideArrowsAction
);
136 IAction followArrowBwdAction
= getTimeGraphCombo().getTimeGraphViewer().getFollowArrowBwdAction();
137 followArrowBwdAction
.setText(Messages
.ControlFlowView_followCPUBwdText
);
138 followArrowBwdAction
.setToolTipText(Messages
.ControlFlowView_followCPUBwdText
);
139 manager
.add(followArrowBwdAction
);
141 IAction followArrowFwdAction
= getTimeGraphCombo().getTimeGraphViewer().getFollowArrowFwdAction();
142 followArrowFwdAction
.setText(Messages
.ControlFlowView_followCPUFwdText
);
143 followArrowFwdAction
.setToolTipText(Messages
.ControlFlowView_followCPUFwdText
);
144 manager
.add(followArrowFwdAction
);
148 protected String
getNextText() {
149 return Messages
.ControlFlowView_nextProcessActionNameText
;
153 protected String
getNextTooltip() {
154 return Messages
.ControlFlowView_nextProcessActionToolTipText
;
158 protected String
getPrevText() {
159 return Messages
.ControlFlowView_previousProcessActionNameText
;
163 protected String
getPrevTooltip() {
164 return Messages
.ControlFlowView_previousProcessActionToolTipText
;
167 private static class ControlFlowEntryComparator
implements Comparator
<ITimeGraphEntry
> {
170 public int compare(ITimeGraphEntry o1
, ITimeGraphEntry o2
) {
174 if ((o1
instanceof ControlFlowEntry
) && (o2
instanceof ControlFlowEntry
)) {
175 ControlFlowEntry entry1
= (ControlFlowEntry
) o1
;
176 ControlFlowEntry entry2
= (ControlFlowEntry
) o2
;
177 result
= entry1
.getTrace().getStartTime().compareTo(entry2
.getTrace().getStartTime());
179 result
= entry1
.getTrace().getName().compareTo(entry2
.getTrace().getName());
182 result
= entry1
.getThreadId() < entry2
.getThreadId() ?
-1 : entry1
.getThreadId() > entry2
.getThreadId() ?
1 : 0;
187 result
= o1
.getStartTime() < o2
.getStartTime() ?
-1 : o1
.getStartTime() > o2
.getStartTime() ?
1 : 0;
198 protected static class ControlFlowTreeLabelProvider
extends TreeLabelProvider
{
201 public String
getColumnText(Object element
, int columnIndex
) {
202 ControlFlowEntry entry
= (ControlFlowEntry
) element
;
204 if (COLUMN_NAMES
[columnIndex
].equals(Messages
.ControlFlowView_processColumn
)) {
205 return entry
.getName();
206 } else if (COLUMN_NAMES
[columnIndex
].equals(Messages
.ControlFlowView_tidColumn
)) {
207 return Integer
.toString(entry
.getThreadId());
208 } else if (COLUMN_NAMES
[columnIndex
].equals(Messages
.ControlFlowView_ptidColumn
)) {
209 if (entry
.getParentThreadId() > 0) {
210 return Integer
.toString(entry
.getParentThreadId());
212 } else if (COLUMN_NAMES
[columnIndex
].equals(Messages
.ControlFlowView_birthTimeColumn
)) {
213 return Utils
.formatTime(entry
.getStartTime(), TimeFormat
.CALENDAR
, Resolution
.NANOSEC
);
214 } else if (COLUMN_NAMES
[columnIndex
].equals(Messages
.ControlFlowView_traceColumn
)) {
215 return entry
.getTrace().getName();
217 return ""; //$NON-NLS-1$
222 private static class ControlFlowFilterLabelProvider
extends TreeLabelProvider
{
225 public String
getColumnText(Object element
, int columnIndex
) {
226 ControlFlowEntry entry
= (ControlFlowEntry
) element
;
228 if (columnIndex
== 0) {
229 return entry
.getName();
230 } else if (columnIndex
== 1) {
231 return Integer
.toString(entry
.getThreadId());
233 return ""; //$NON-NLS-1$
238 // ------------------------------------------------------------------------
240 // ------------------------------------------------------------------------
243 protected void buildEventList(final ITmfTrace trace
, ITmfTrace parentTrace
, IProgressMonitor monitor
) {
244 ITmfStateSystem ssq
= TmfStateSystemAnalysisModule
.getStateSystem(trace
, KernelAnalysisModule
.ID
);
249 List
<ControlFlowEntry
> entryList
= new ArrayList
<>();
250 Map
<Integer
, ControlFlowEntry
> entryMap
= new HashMap
<>();
252 long start
= ssq
.getStartTime();
253 setStartTime(Math
.min(getStartTime(), start
));
255 boolean complete
= false;
257 if (monitor
.isCanceled()) {
260 complete
= ssq
.waitUntilBuilt(BUILD_UPDATE_TIMEOUT
);
261 if (ssq
.isCancelled()) {
264 long end
= ssq
.getCurrentEndTime();
265 if (start
== end
&& !complete
) { // when complete execute one last time regardless of end time
268 setEndTime(Math
.max(getEndTime(), end
+ 1));
269 List
<Integer
> threadQuarks
= ssq
.getQuarks(Attributes
.THREADS
, "*"); //$NON-NLS-1$
270 for (int threadQuark
: threadQuarks
) {
271 if (monitor
.isCanceled()) {
274 String threadName
= ssq
.getAttributeName(threadQuark
);
277 threadId
= Integer
.parseInt(threadName
);
278 } catch (NumberFormatException e1
) {
281 if (threadId
<= 0) { // ignore the 'unknown' (-1) and swapper (0) threads
286 List
<ITmfStateInterval
> execNameIntervals
;
288 execNameQuark
= ssq
.getQuarkRelative(threadQuark
, Attributes
.EXEC_NAME
);
289 execNameIntervals
= StateSystemUtils
.queryHistoryRange(ssq
, execNameQuark
, start
, end
);
290 } catch (AttributeNotFoundException e
) {
291 /* No information on this thread (yet?), skip it for now */
293 } catch (StateSystemDisposedException e
) {
294 /* State system is closing down, no point continuing */
298 for (ITmfStateInterval execNameInterval
: execNameIntervals
) {
299 if (monitor
.isCanceled()) {
302 ControlFlowEntry entry
= entryMap
.get(threadId
);
303 if (!execNameInterval
.getStateValue().isNull() &&
304 execNameInterval
.getStateValue().getType() == ITmfStateValue
.Type
.STRING
) {
305 String execName
= execNameInterval
.getStateValue().unboxStr();
306 long startTime
= execNameInterval
.getStartTime();
307 long endTime
= execNameInterval
.getEndTime() + 1;
310 ITmfStateInterval ppidInterval
= null;
312 int ppidQuark
= ssq
.getQuarkRelative(threadQuark
, Attributes
.PPID
);
313 ppidInterval
= StateSystemUtils
.queryUntilNonNullValue(ssq
, ppidQuark
, startTime
, endTime
);
314 } catch (AttributeNotFoundException e
) {
315 /* No info, keep PPID at -1 */
319 if (ppidInterval
!= null) {
320 ppid
= ppidInterval
.getStateValue().unboxInt();
322 entry
= new ControlFlowEntry(threadQuark
, trace
, execName
, threadId
, ppid
, startTime
, endTime
);
323 entryList
.add(entry
);
324 entryMap
.put(threadId
, entry
);
326 // update the name of the entry to the latest
328 entry
.setName(execName
);
329 entry
.updateEndTime(endTime
);
332 entryMap
.remove(threadId
);
337 updateTree(entryList
, parentTrace
);
339 if (parentTrace
.equals(getTrace())) {
343 for (ControlFlowEntry entry
: entryList
) {
344 if (monitor
.isCanceled()) {
347 buildStatusEvents(entry
.getTrace(), entry
, monitor
, start
, end
);
354 private void updateTree(List
<ControlFlowEntry
> entryList
, ITmfTrace parentTrace
) {
355 List
<TimeGraphEntry
> rootListToAdd
= new ArrayList
<>();
356 List
<TimeGraphEntry
> rootListToRemove
= new ArrayList
<>();
357 List
<TimeGraphEntry
> rootList
= getEntryList(parentTrace
);
359 for (ControlFlowEntry entry
: entryList
) {
360 boolean root
= (entry
.getParent() == null);
361 if (root
&& entry
.getParentThreadId() > 0) {
362 for (ControlFlowEntry parent
: entryList
) {
364 * Associate the parent entry only if their time overlap. A
365 * child entry may start before its parent, for example at
366 * the beginning of the trace if a parent has not yet
367 * appeared in the state system. We just want to make sure
368 * that the entry didn't start after the parent ended or
369 * ended before the parent started.
371 if (parent
.getThreadId() == entry
.getParentThreadId() &&
372 !(entry
.getStartTime() > parent
.getEndTime() ||
373 entry
.getEndTime() < parent
.getStartTime())) {
374 parent
.addChild(entry
);
376 if (rootList
!= null && rootList
.contains(entry
)) {
377 rootListToRemove
.add(entry
);
383 if (root
&& (rootList
== null || !rootList
.contains(entry
))) {
384 rootListToAdd
.add(entry
);
388 addToEntryList(parentTrace
, rootListToAdd
);
389 removeFromEntryList(parentTrace
, rootListToRemove
);
392 private void buildStatusEvents(ITmfTrace trace
, ControlFlowEntry entry
, @NonNull IProgressMonitor monitor
, long start
, long end
) {
393 if (start
< entry
.getEndTime() && end
> entry
.getStartTime()) {
394 ITmfStateSystem ssq
= TmfStateSystemAnalysisModule
.getStateSystem(entry
.getTrace(), KernelAnalysisModule
.ID
);
399 long startTime
= Math
.max(start
, entry
.getStartTime());
400 long endTime
= Math
.min(end
+ 1, entry
.getEndTime());
401 long resolution
= Math
.max(1, (end
- ssq
.getStartTime()) / getDisplayWidth());
402 List
<ITimeEvent
> eventList
= getEventList(entry
, startTime
, endTime
, resolution
, monitor
);
403 if (eventList
== null) {
406 for (ITimeEvent event
: eventList
) {
407 entry
.addEvent(event
);
409 if (trace
.equals(getTrace())) {
413 for (ITimeGraphEntry child
: entry
.getChildren()) {
414 if (monitor
.isCanceled()) {
417 buildStatusEvents(trace
, (ControlFlowEntry
) child
, monitor
, start
, end
);
422 protected @Nullable List
<ITimeEvent
> getEventList(TimeGraphEntry tgentry
, long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
423 List
<ITimeEvent
> eventList
= null;
424 if (!(tgentry
instanceof ControlFlowEntry
)) {
427 ControlFlowEntry entry
= (ControlFlowEntry
) tgentry
;
428 final long realStart
= Math
.max(startTime
, entry
.getStartTime());
429 final long realEnd
= Math
.min(endTime
, entry
.getEndTime());
430 if (realEnd
<= realStart
) {
433 KernelAnalysisModule kernelAnalysis
= TmfTraceUtils
.getAnalysisModuleOfClass(entry
.getTrace(), KernelAnalysisModule
.class, KernelAnalysisModule
.ID
);
434 if (kernelAnalysis
== null) {
435 // ITmfStateSystem ssq = TmfStateSystemAnalysisModule.getStateSystem(entry.getTrace(), KernelAnalysis.ID);
436 // if (ssq == null) {
440 List
<ITmfStateInterval
> statusIntervals
= KernelThreadInformationProvider
.getStatusIntervalsForThread(
441 kernelAnalysis
, checkNotNull(entry
.getThreadId()), realStart
, realEnd
, resolution
, monitor
);
442 eventList
= new ArrayList
<>(statusIntervals
.size());
443 long lastEndTime
= -1;
444 for (ITmfStateInterval statusInterval
: statusIntervals
) {
445 if (monitor
.isCanceled()) {
448 long time
= statusInterval
.getStartTime();
449 long duration
= statusInterval
.getEndTime() - time
+ 1;
452 status
= statusInterval
.getStateValue().unboxInt();
453 } catch (StateValueTypeException e
) {
456 if (lastEndTime
!= time
&& lastEndTime
!= -1) {
457 eventList
.add(new TimeEvent(entry
, lastEndTime
, time
- lastEndTime
));
459 eventList
.add(new TimeEvent(entry
, time
, duration
, status
));
460 lastEndTime
= time
+ duration
;
462 } catch (TimeRangeException e
) {
463 Activator
.getDefault().logError(e
.getMessage());
469 * Returns a value corresponding to the selected entry.
471 * Used in conjunction with synchingToTime to change the selected entry. If
472 * one of these methods is overridden in child class, then both should be.
475 * The currently selected time
476 * @return a value identifying the entry
478 private int getSelectionValue(long time
) {
480 for (ITmfTrace trace
: TmfTraceManager
.getTraceSet(getTrace())) {
487 ITmfStateSystem ssq
= TmfStateSystemAnalysisModule
.getStateSystem(trace
, KernelAnalysisModule
.ID
);
491 if (time
>= ssq
.getStartTime() && time
<= ssq
.getCurrentEndTime()) {
492 List
<Integer
> currentThreadQuarks
= ssq
.getQuarks(Attributes
.CPUS
, "*", Attributes
.CURRENT_THREAD
); //$NON-NLS-1$
493 for (int currentThreadQuark
: currentThreadQuarks
) {
495 ITmfStateInterval currentThreadInterval
= ssq
.querySingleState(time
, currentThreadQuark
);
496 int currentThread
= currentThreadInterval
.getStateValue().unboxInt();
497 if (currentThread
> 0) {
498 int statusQuark
= ssq
.getQuarkAbsolute(Attributes
.THREADS
, Integer
.toString(currentThread
), Attributes
.STATUS
);
499 ITmfStateInterval statusInterval
= ssq
.querySingleState(time
, statusQuark
);
500 if (statusInterval
.getStartTime() == time
) {
501 thread
= currentThread
;
505 } catch (AttributeNotFoundException
| TimeRangeException
| StateValueTypeException e
) {
507 } catch (StateSystemDisposedException e
) {
517 protected void synchingToTime(long time
) {
518 int selected
= getSelectionValue(time
);
520 for (Object element
: getTimeGraphViewer().getExpandedElements()) {
521 if (element
instanceof ControlFlowEntry
) {
522 ControlFlowEntry entry
= (ControlFlowEntry
) element
;
523 if (entry
.getThreadId() == selected
) {
524 getTimeGraphCombo().setSelection(entry
);
533 protected List
<ILinkEvent
> getLinkList(long startTime
, long endTime
, long resolution
, IProgressMonitor monitor
) {
534 List
<ILinkEvent
> list
= new ArrayList
<>();
535 List
<TimeGraphEntry
> entryList
= getEntryList(getTrace());
536 if (entryList
== null) {
539 for (ITmfTrace trace
: TmfTraceManager
.getTraceSet(getTrace())) {
543 ITmfStateSystem ssq
= TmfStateSystemAnalysisModule
.getStateSystem(trace
, KernelAnalysisModule
.ID
);
548 long start
= Math
.max(startTime
, ssq
.getStartTime());
549 long end
= Math
.min(endTime
, ssq
.getCurrentEndTime());
553 List
<Integer
> currentThreadQuarks
= ssq
.getQuarks(Attributes
.CPUS
, "*", Attributes
.CURRENT_THREAD
); //$NON-NLS-1$
554 for (int currentThreadQuark
: currentThreadQuarks
) {
555 // adjust the query range to include the previous and following intervals
556 long qstart
= Math
.max(ssq
.querySingleState(start
, currentThreadQuark
).getStartTime() - 1, ssq
.getStartTime());
557 long qend
= Math
.min(ssq
.querySingleState(end
, currentThreadQuark
).getEndTime() + 1, ssq
.getCurrentEndTime());
558 List
<ITmfStateInterval
> currentThreadIntervals
= StateSystemUtils
.queryHistoryRange(ssq
, currentThreadQuark
, qstart
, qend
, resolution
, monitor
);
562 for (ITmfStateInterval currentThreadInterval
: currentThreadIntervals
) {
563 if (monitor
.isCanceled()) {
566 long time
= currentThreadInterval
.getStartTime();
567 if (time
!= lastEnd
) {
568 // don't create links where there are gaps in intervals due to the resolution
572 int thread
= currentThreadInterval
.getStateValue().unboxInt();
573 if (thread
> 0 && prevThread
> 0) {
574 ITimeGraphEntry prevEntry
= findEntry(entryList
, trace
, prevThread
);
575 ITimeGraphEntry nextEntry
= findEntry(entryList
, trace
, thread
);
576 list
.add(new TimeLinkEvent(prevEntry
, nextEntry
, prevEnd
, time
- prevEnd
, 0));
578 lastEnd
= currentThreadInterval
.getEndTime() + 1;
585 } catch (TimeRangeException
| AttributeNotFoundException
| StateValueTypeException e
) {
587 } catch (StateSystemDisposedException e
) {
594 private ControlFlowEntry
findEntry(List
<?
extends ITimeGraphEntry
> entryList
, ITmfTrace trace
, int threadId
) {
595 for (ITimeGraphEntry entry
: entryList
) {
596 if (entry
instanceof ControlFlowEntry
) {
597 ControlFlowEntry controlFlowEntry
= (ControlFlowEntry
) entry
;
598 if (controlFlowEntry
.getThreadId() == threadId
&& controlFlowEntry
.getTrace() == trace
) {
599 return controlFlowEntry
;
600 } else if (entry
.hasChildren()) {
601 controlFlowEntry
= findEntry(entry
.getChildren(), trace
, threadId
);
602 if (controlFlowEntry
!= null) {
603 return controlFlowEntry
;