diff --git a/lttng/org.lttng.scope.lttng.kernel.core/src/org/lttng/scope/lttng/kernel/core/views/controlflow2/ControlFlowTreeElement.java b/lttng/org.lttng.scope.lttng.kernel.core/src/org/lttng/scope/lttng/kernel/core/views/controlflow2/ControlFlowTreeElement.java index fbcb4e20..06e986e8 100644 --- a/lttng/org.lttng.scope.lttng.kernel.core/src/org/lttng/scope/lttng/kernel/core/views/controlflow2/ControlFlowTreeElement.java +++ b/lttng/org.lttng.scope.lttng.kernel.core/src/org/lttng/scope/lttng/kernel/core/views/controlflow2/ControlFlowTreeElement.java @@ -1,9 +1,12 @@ package org.lttng.scope.lttng.kernel.core.views.controlflow2; import java.util.List; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import org.lttng.scope.lttng.kernel.core.analysis.os.Attributes; +import org.lttng.scope.lttng.kernel.core.event.aspect.KernelTidAspect; import org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem.StateSystemTimeGraphTreeElement; import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement; @@ -48,4 +51,16 @@ public String getThreadName() { return fThreadName; } + @Override + public @Nullable Predicate getEventMatching() { + /* + * This tree element represents a thread ID. Return true for events + * whose TID aspect is the same as the TID of this element. + */ + return event -> { + Integer eventTid = KernelTidAspect.INSTANCE.resolve(event); + return (eventTid != null && eventTid.intValue() == fTid); + }; + } + } diff --git a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/control/TimeGraphModelControl.java b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/control/TimeGraphModelControl.java index d4a79213..dbc569a9 100644 --- a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/control/TimeGraphModelControl.java +++ b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/control/TimeGraphModelControl.java @@ -55,7 +55,12 @@ public void dispose() { // Accessors // ------------------------------------------------------------------------ - @Nullable ITmfTrace getCurrentTrace() { + /** + * Get the trace currently being tracked by this control. + * + * @return The trace, may be null if there is no trace + */ + public @Nullable ITmfTrace getCurrentTrace() { return fCurrentTrace; } diff --git a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/states/TimeGraphStateInterval.java b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/states/TimeGraphStateInterval.java index ab96cf13..17787cea 100644 --- a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/states/TimeGraphStateInterval.java +++ b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/states/TimeGraphStateInterval.java @@ -75,6 +75,10 @@ public LineThickness getLineThickness() { return fLineThickness; } + public TimeGraphTreeElement getTreeElement() { + return getStartEvent().getTreeElement(); + } + @Override public int hashCode() { return Objects.hash(fStartEvent, fEndEvent, fStateName, fLabel, fColor, fLineThickness); diff --git a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/tree/TimeGraphTreeElement.java b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/tree/TimeGraphTreeElement.java index 3dcb0250..72babad8 100644 --- a/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/tree/TimeGraphTreeElement.java +++ b/tmf2/org.lttng.scope.tmf2.views.core/src/org/lttng/scope/tmf2/views/core/timegraph/model/render/tree/TimeGraphTreeElement.java @@ -11,8 +11,10 @@ import java.util.List; import java.util.Objects; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; @@ -35,12 +37,21 @@ public List getChildElements() { return fChildElements; } - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("fName", fName) //$NON-NLS-1$ - .add("fChildElements", fChildElements.toString()) //$NON-NLS-1$ - .toString(); + /** + * Determine if and how this tree element corresponds to a component of a + * trace event. + * + * For example, if this tree element represents "CPU #2", then the predicate + * should return true for all trace events belonging to CPU #2. + * + * The method returns null if this tree element does not correspond to a + * particular aspect of trace events. + * + * @return The event matching predicate, if there is one + */ + public @Nullable Predicate getEventMatching() { + /* Sub-classes can override */ + return null; } @Override @@ -64,6 +75,12 @@ public boolean equals(@Nullable Object obj) { && Objects.equals(fChildElements, other.fChildElements); } - + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("fName", fName) //$NON-NLS-1$ + .add("fChildElements", fChildElements.toString()) //$NON-NLS-1$ + .toString(); + } } diff --git a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/Messages.java b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/Messages.java index 34e3d6ec..193602e4 100644 --- a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/Messages.java +++ b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/Messages.java @@ -28,6 +28,9 @@ public class Messages extends NLS { public static String sfFollowArrowsNavModeName; public static String sfFollowBookmarksNavModeName; + public static String sfNextEventJobName; + public static String sfPreviousEventJobName; + static { NLS.initializeMessages(BUNDLE_NAME, Messages.class); } diff --git a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavUtils.java b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavUtils.java new file mode 100644 index 00000000..6f1bd8a5 --- /dev/null +++ b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.toolbar.nav; + +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; +import org.lttng.scope.tmf2.views.core.TimeRange; +import org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.SwtJfxTimeGraphViewer; + +/** + * Common utilities for navigation actions. + * + * @author Alexandre Montplaisir + */ +final class NavUtils { + + private NavUtils() {} + + /** + * Move the selection to the target timestamp. Also update the visible range + * to be centered on that timestamp, but only if it is outside of the + * current visible range. + * + * This should only be called when reaching the new timestamp is caused by a + * user action (ie, not simply because another view sent a signal). + * + * @param viewer + * The viewer on which to work + * @param timestamp + * The timestamp to select, and potentially move to + */ + public static void selectNewTimestamp(SwtJfxTimeGraphViewer viewer, long timestamp) { + /* Update the selection to the new timestamp. */ + viewer.getControl().updateTimeRangeSelection(TimeRange.of(timestamp, timestamp)); + + TimeRange fullTimeGraphRange = viewer.getControl().getFullTimeGraphRange(); + TmfTimeRange windowRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange(); + long windowStart = windowRange.getStartTime().toNanos(); + long windowEnd = windowRange.getEndTime().toNanos(); + if (windowStart <= timestamp && timestamp <= windowEnd) { + /* Timestamp is still in the visible range, don't touch anything. */ + return; + } + /* Update the visible range to the requested timestamp. */ + /* The "span" of the window (aka zoom level) will remain constant. */ + long windowSpan = windowEnd - windowStart; + if (windowSpan > fullTimeGraphRange.getDuration()) { + /* Should never happen, but just to be mathematically safe. */ + windowSpan = fullTimeGraphRange.getDuration(); + } + + long newStart = timestamp - (windowSpan / 2); + long newEnd = newStart + windowSpan; + + /* Clamp the range to the borders of the pane/trace. */ + if (newStart < fullTimeGraphRange.getStart()) { + newStart = fullTimeGraphRange.getStart(); + newEnd = newStart + windowSpan; + } else if (newEnd > fullTimeGraphRange.getEnd()) { + newEnd = fullTimeGraphRange.getEnd(); + newStart = newEnd - windowSpan; + } + + viewer.getControl().updateVisibleTimeRange(TimeRange.of(newStart, newEnd), true); + } +} diff --git a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowEvents.java b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowEvents.java index 92be4d3b..6aa98978 100644 --- a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowEvents.java +++ b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowEvents.java @@ -11,6 +11,21 @@ import static java.util.Objects.requireNonNull; +import java.util.function.Predicate; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; +import org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.StateRectangle; import org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.SwtJfxTimeGraphViewer; /** @@ -26,6 +41,21 @@ public class NavigationModeFollowEvents extends NavigationMode { private static final String BACK_ICON_PATH = "/icons/toolbar/nav_event_back.gif"; //$NON-NLS-1$ private static final String FWD_ICON_PATH = "/icons/toolbar/nav_event_fwd.gif"; //$NON-NLS-1$ + /** + * Mutex rule for search action jobs, making sure they execute sequentially + */ + private final ISchedulingRule fSearchActionMutexRule = new ISchedulingRule() { + @Override + public boolean isConflicting(@Nullable ISchedulingRule rule) { + return (rule == this); + } + + @Override + public boolean contains(@Nullable ISchedulingRule rule) { + return (rule == this); + } + }; + /** * Constructor */ @@ -37,14 +67,51 @@ public NavigationModeFollowEvents() { @Override public void navigateBackwards(SwtJfxTimeGraphViewer viewer) { - // TODO NYI - System.out.println("Follow events backwards"); + navigate(viewer, false); } @Override public void navigateForwards(SwtJfxTimeGraphViewer viewer) { - // TODO NYI - System.out.println("Follow events forwards"); + navigate(viewer, true); } + private void navigate(SwtJfxTimeGraphViewer viewer, boolean forward) { + StateRectangle state = viewer.getSelectedState(); + ITmfTrace trace = viewer.getControl().getCurrentTrace(); + if (state == null || trace == null) { + return; + } + Predicate predicate = state.getStateInterval().getTreeElement().getEventMatching(); + if (predicate == null) { + /* The tree element does not support navigating by events. */ + return; + } + + String jobName = (forward ? Messages.sfNextEventJobName : Messages.sfPreviousEventJobName); + + Job job = new Job(jobName) { + @Override + protected IStatus run(@Nullable IProgressMonitor monitor) { + long currentTime = TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange().getStartTime().toNanos(); + ITmfContext ctx = trace.seekEvent(TmfTimestamp.fromNanos(currentTime)); + long rank = ctx.getRank(); + ctx.dispose(); + + ITmfEvent event = (forward ? + TmfTraceUtils.getNextEventMatching(trace, rank, predicate, monitor) : + TmfTraceUtils.getPreviousEventMatching(trace, rank, predicate, monitor)); + if (event != null) { + NavUtils.selectNewTimestamp(viewer, event.getTimestamp().toNanos()); + } + return Status.OK_STATUS; + } + }; + + /* + * Make subsequent jobs not run concurrently, but wait after one + * another. + */ + job.setRule(fSearchActionMutexRule); + job.schedule(); + } } diff --git a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowStateChanges.java b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowStateChanges.java index 752a4e53..db4ce68c 100644 --- a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowStateChanges.java +++ b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/NavigationModeFollowStateChanges.java @@ -18,9 +18,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; -import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; -import org.lttng.scope.tmf2.views.core.TimeRange; import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement; import org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.MultiStateInterval; import org.lttng.scope.tmf2.views.ui.timegraph.swtjfx.StateRectangle; @@ -82,8 +79,7 @@ private static void navigate(SwtJfxTimeGraphViewer viewer, boolean forward) { * Go to the end/start of the current state. */ long bound = (forward ? stateEndTime : stateStartTime); - viewer.getControl().updateTimeRangeSelection(TimeRange.of(bound, bound)); - updateVisibleRange(viewer, bound); + NavUtils.selectNewTimestamp(viewer, bound); return; } @@ -123,9 +119,7 @@ private static void navigate(SwtJfxTimeGraphViewer viewer, boolean forward) { } viewer.setSelectedState(newState); - - viewer.getControl().updateTimeRangeSelection(TimeRange.of(targetTimestamp, targetTimestamp)); - updateVisibleRange(viewer, targetTimestamp); + NavUtils.selectNewTimestamp(viewer, targetTimestamp); } /** @@ -214,40 +208,4 @@ private static Optional getBestPotentialState(List fullTimeGraphRange.getDuration()) { - /* Should never happen, but just to be mathematically safe. */ - windowSpan = fullTimeGraphRange.getDuration(); - } - - long newStart = timestamp - (windowSpan / 2); - long newEnd = newStart + windowSpan; - - /* Clamp the range to the borders of the pane/trace. */ - if (newStart < fullTimeGraphRange.getStart()) { - newStart = fullTimeGraphRange.getStart(); - newEnd = newStart + windowSpan; - } else if (newEnd > fullTimeGraphRange.getEnd()) { - newEnd = fullTimeGraphRange.getEnd(); - newStart = newEnd - windowSpan; - } - - viewer.getControl().updateVisibleTimeRange(TimeRange.of(newStart, newEnd), true); - } - } diff --git a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/messages.properties b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/messages.properties index 1181b356..46321d05 100644 --- a/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/messages.properties +++ b/tmf2/org.lttng.scope.tmf2.views.ui/src/org/lttng/scope/tmf2/views/ui/timegraph/swtjfx/toolbar/nav/messages.properties @@ -11,3 +11,6 @@ sfFollowStateChangesNavModeName = Follow State Changes sfFollowEventsNavModeName = Follow Events sfFollowArrowsNavModeName = Follow Arrows sfFollowBookmarksNavModeName = Follow Bookmarks + +sfNextEventJobName = Searching for next matching event +sfPreviousEventJobName = Searching for previous matching event