/******************************************************************************* * Copyright (c) 2015, 2016 Ericsson * * 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 * * Contributors: * Patrick Tasse - Initial API and implementation *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.views.timegraph; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.common.core.log.TraceCompassLog; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * An abstract time graph view where each entry's time event list is populated * from a state system. The state system full state is queried in chronological * order before creating the time event lists as this is optimal for state * system queries. * * @since 1.1 */ public abstract class AbstractStateSystemTimeGraphView extends AbstractTimeGraphView { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ private static final long MAX_INTERVALS = 1000000; // ------------------------------------------------------------------------ // Fields // ------------------------------------------------------------------------ /** The state system to entry list hash map */ private final Map<ITmfStateSystem, List<@NonNull TimeGraphEntry>> fSSEntryListMap = new HashMap<>(); /** The trace to state system multi map */ private final Multimap<ITmfTrace, ITmfStateSystem> fTraceSSMap = HashMultimap.create(); private static final Logger LOGGER = TraceCompassLog.getLogger(AbstractStateSystemTimeGraphView.class); // ------------------------------------------------------------------------ // Classes // ------------------------------------------------------------------------ /** * Handler for state system queries */ public interface IQueryHandler { /** * Handle a full or partial list of full states. This can be called many * times for the same query if the query result is split, in which case * the previous full state is null only the first time it is called, and * set to the last full state of the previous call from then on. * * @param fullStates * the list of full states * @param prevFullState * the previous full state, or null */ void handle(@NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState); } private class ZoomThreadByTime extends ZoomThread { private final @NonNull List<ITmfStateSystem> fZoomSSList; private boolean fClearZoomedLists; public ZoomThreadByTime(@NonNull List<ITmfStateSystem> ssList, long startTime, long endTime, long resolution, boolean restart) { super(startTime, endTime, resolution); fZoomSSList = ssList; fClearZoomedLists = !restart; } @Override public void doRun() { final List<ILinkEvent> links = new ArrayList<>(); final List<IMarkerEvent> markers = new ArrayList<>(); if (fClearZoomedLists) { clearZoomedLists(); } for (ITmfStateSystem ss : fZoomSSList) { List<TimeGraphEntry> entryList = null; synchronized (fSSEntryListMap) { entryList = fSSEntryListMap.get(ss); } if (entryList != null) { zoomByTime(ss, entryList, links, markers, getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor()); } } if (!getMonitor().isCanceled()) { /* Refresh the trace-specific markers when zooming */ markers.addAll(getTraceMarkerList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor())); applyResults(() -> { getTimeGraphViewer().setLinks(links); getTimeGraphViewer().setMarkerCategories(getMarkerCategories()); getTimeGraphViewer().setMarkers(markers); }); } else { LOGGER.info(() -> "[TimeGraphView:ZoomThreadCanceled]"); //$NON-NLS-1$ } } @Override public void cancel() { super.cancel(); if (fClearZoomedLists) { clearZoomedLists(); } } private void zoomByTime(final ITmfStateSystem ss, final List<TimeGraphEntry> entryList, final List<ILinkEvent> links, final List<IMarkerEvent> markers, long startTime, long endTime, long resolution, final @NonNull IProgressMonitor monitor) { final long start = Math.max(startTime, ss.getStartTime()); final long end = Math.min(endTime, ss.getCurrentEndTime()); final boolean fullRange = getZoomStartTime() <= getStartTime() && getZoomEndTime() >= getEndTime(); if (end < start) { return; } if (fullRange) { redraw(); } queryFullStates(ss, start, end, resolution, monitor, new IQueryHandler() { @Override public void handle(@NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState) { LOGGER.config(() -> "[TimeGraphView:ZoomThreadGettingStates]"); //$NON-NLS-1$ if (!fullRange) { for (TimeGraphEntry entry : entryList) { zoom(checkNotNull(entry), ss, fullStates, prevFullState, monitor); } } /* Refresh the arrows when zooming */ LOGGER.config(() -> "[TimeGraphView:ZoomThreadGettingLinks]"); //$NON-NLS-1$ links.addAll(getLinkList(ss, fullStates, prevFullState, monitor)); /* Refresh the view-specific markers when zooming */ LOGGER.config(() -> "[TimeGraphView:ZoomThreadGettingMarkers]"); //$NON-NLS-1$ markers.addAll(getViewMarkerList(ss, fullStates, prevFullState, monitor)); LOGGER.config(() -> "[TimeGraphView:ZoomThreadDone]"); //$NON-NLS-1$ } }); refresh(); } private void zoom(@NonNull TimeGraphEntry entry, ITmfStateSystem ss, @NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState, @NonNull IProgressMonitor monitor) { List<ITimeEvent> eventList = getEventList(entry, ss, fullStates, prevFullState, monitor); if (eventList != null) { applyResults(() -> { for (ITimeEvent event : eventList) { if (monitor.isCanceled()) { return; } entry.addZoomedEvent(event); } }); } for (TimeGraphEntry child : entry.getChildren()) { if (monitor.isCanceled()) { LOGGER.info(() -> "[TimeGraphView:ZoomThreadCanceled]"); //$NON-NLS-1$ return; } zoom(child, ss, fullStates, prevFullState, monitor); } } private void clearZoomedLists() { for (ITmfStateSystem ss : fZoomSSList) { List<TimeGraphEntry> entryList = null; synchronized (fSSEntryListMap) { entryList = fSSEntryListMap.get(ss); } if (entryList != null) { for (TimeGraphEntry entry : entryList) { clearZoomedList(entry); } } } fClearZoomedLists = false; } private void clearZoomedList(TimeGraphEntry entry) { entry.setZoomedEventList(null); for (TimeGraphEntry child : entry.getChildren()) { clearZoomedList(child); } } } // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Constructs a time graph view that contains either a time graph viewer or * a time graph combo. * * By default, the view uses a time graph viewer. To use a time graph combo, * the subclass constructor must call {@link #setTreeColumns(String[])} and * {@link #setTreeLabelProvider(TreeLabelProvider)}. * * @param id * The id of the view * @param pres * The presentation provider */ public AbstractStateSystemTimeGraphView(String id, TimeGraphPresentationProvider pres) { super(id, pres); } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ /** * Gets the entry list for a state system * * @param ss * the state system * * @return the entry list map */ protected List<@NonNull TimeGraphEntry> getEntryList(ITmfStateSystem ss) { synchronized (fSSEntryListMap) { return fSSEntryListMap.get(ss); } } /** * Adds a trace entry list to the entry list map * * @param trace * the trace * @param ss * the state system * @param list * the list of time graph entries */ protected void putEntryList(ITmfTrace trace, ITmfStateSystem ss, List<@NonNull TimeGraphEntry> list) { super.putEntryList(trace, list); synchronized (fSSEntryListMap) { fSSEntryListMap.put(ss, new CopyOnWriteArrayList<>(list)); fTraceSSMap.put(trace, ss); } } /** * Adds a list of entries to a trace's entry list * * @param trace * the trace * @param ss * the state system * @param list * the list of time graph entries to add */ protected void addToEntryList(ITmfTrace trace, ITmfStateSystem ss, List<@NonNull TimeGraphEntry> list) { super.addToEntryList(trace, list); synchronized (fSSEntryListMap) { List<@NonNull TimeGraphEntry> entryList = fSSEntryListMap.get(ss); if (entryList == null) { fSSEntryListMap.put(ss, new CopyOnWriteArrayList<>(list)); } else { entryList.addAll(list); } fTraceSSMap.put(trace, ss); } } /** * Removes a list of entries from a trace's entry list * * @param trace * the trace * @param ss * the state system * @param list * the list of time graph entries to remove */ protected void removeFromEntryList(ITmfTrace trace, ITmfStateSystem ss, List<TimeGraphEntry> list) { super.removeFromEntryList(trace, list); synchronized (fSSEntryListMap) { List<TimeGraphEntry> entryList = fSSEntryListMap.get(ss); if (entryList != null) { entryList.removeAll(list); if (entryList.isEmpty()) { fTraceSSMap.remove(trace, ss); } } } } @Override protected @Nullable ZoomThread createZoomThread(long startTime, long endTime, long resolution, boolean restart) { List<ITmfStateSystem> ssList = null; synchronized (fSSEntryListMap) { ssList = new ArrayList<>(fTraceSSMap.get(getTrace())); } if (ssList.isEmpty()) { return null; } return new ZoomThreadByTime(ssList, startTime, endTime, resolution, restart); } /** * Query the state system full state for the given time range. * * @param ss * The state system * @param start * The start time * @param end * The end time * @param resolution * The resolution * @param monitor * The progress monitor * @param handler * The query handler */ protected void queryFullStates(ITmfStateSystem ss, long start, long end, long resolution, @NonNull IProgressMonitor monitor, @NonNull IQueryHandler handler) { if (end < start) { /* We have an empty trace, the state system will be empty: nothing to do here. */ return; } List<List<ITmfStateInterval>> fullStates = new ArrayList<>(); List<ITmfStateInterval> prevFullState = null; try { long time = start; while (true) { if (monitor.isCanceled()) { break; } List<ITmfStateInterval> fullState = ss.queryFullState(time); fullStates.add(fullState); if (fullStates.size() * fullState.size() > MAX_INTERVALS) { handler.handle(fullStates, prevFullState); prevFullState = fullStates.get(fullStates.size() - 1); fullStates.clear(); } if (time >= end) { break; } time = Math.min(end, time + resolution); } if (fullStates.size() > 0) { handler.handle(fullStates, prevFullState); } } catch (StateSystemDisposedException e) { /* Ignored */ } } /** * Gets the list of events for an entry for a given list of full states. * <p> * Called from the ZoomThread for every entry to update the zoomed event * list. Can be an empty implementation if the view does not support zoomed * event lists. Can also be used to compute the full event list. * * @param tgentry * The time graph entry * @param ss * The state system * @param fullStates * A list of full states * @param prevFullState * The previous full state, or null * @param monitor * A progress monitor * @return The list of time graph events */ protected abstract @Nullable List<ITimeEvent> getEventList(@NonNull TimeGraphEntry tgentry, ITmfStateSystem ss, @NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState, @NonNull IProgressMonitor monitor); /** * Gets the list of links (displayed as arrows) for a given list of full * states. The default implementation returns an empty list. * * @param ss * The state system * @param fullStates * A list of full states * @param prevFullState * The previous full state, or null * @param monitor * A progress monitor * @return The list of link events * @since 2.0 */ protected @NonNull List<ILinkEvent> getLinkList(ITmfStateSystem ss, @NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState, @NonNull IProgressMonitor monitor) { return new ArrayList<>(); } /** * Gets the list of markers for a given list of full * states. The default implementation returns an empty list. * * @param ss * The state system * @param fullStates * A list of full states * @param prevFullState * The previous full state, or null * @param monitor * A progress monitor * @return The list of marker events * @since 2.0 */ protected @NonNull List<IMarkerEvent> getViewMarkerList(ITmfStateSystem ss, @NonNull List<List<ITmfStateInterval>> fullStates, @Nullable List<ITmfStateInterval> prevFullState, @NonNull IProgressMonitor monitor) { return new ArrayList<>(); } /** * @deprecated The subclass should call {@link #getEntryList(ITmfStateSystem)} instead. */ @Deprecated @Override protected final List<TimeGraphEntry> getEntryList(ITmfTrace trace) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should call {@link #addToEntryList(ITmfTrace, ITmfStateSystem, List)} instead. */ @Deprecated @Override protected final void addToEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should call {@link #putEntryList(ITmfTrace, ITmfStateSystem, List)} instead. */ @Deprecated @Override protected final void putEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should call {@link #removeFromEntryList(ITmfTrace, ITmfStateSystem, List)} instead. */ @Deprecated @Override protected final void removeFromEntryList(ITmfTrace trace, List<TimeGraphEntry> list) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should implement {@link #getEventList(TimeGraphEntry, ITmfStateSystem, List, List, IProgressMonitor)} instead. */ @Deprecated @Override protected final List<ITimeEvent> getEventList(TimeGraphEntry entry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should implement {@link #getLinkList(ITmfStateSystem, List, List, IProgressMonitor)} instead. */ @Deprecated @Override protected final List<ILinkEvent> getLinkList(long startTime, long endTime, long resolution, IProgressMonitor monitor) { throw new UnsupportedOperationException(); } /** * @deprecated The subclass should implement {@link #getViewMarkerList(ITmfStateSystem, List, List, IProgressMonitor)} instead. */ @Deprecated @Override protected final List<IMarkerEvent> getViewMarkerList(long startTime, long endTime, long resolution, IProgressMonitor monitor) { throw new UnsupportedOperationException(); } @Override protected void resetView(ITmfTrace viewTrace) { // Don't remove super call super.resetView(viewTrace); synchronized (fSSEntryListMap) { for (ITmfStateSystem ss : fTraceSSMap.removeAll(viewTrace)) { fSSEntryListMap.remove(ss); } } } }