From 6ae6c5bd0970f2c1a4d41527fa22681976f5c70b Mon Sep 17 00:00:00 2001 From: Patrick Tasse Date: Thu, 16 Jul 2015 17:38:11 -0400 Subject: [PATCH] tmf: Add an abstract state system time graph view This subclass of the abstract time graph view can be used when the time events are built using a state system. The full states of the state system are first queried chronologically for the whole time range using a time resolution, and the list of full states is kept in memory and reused by every time graph entry to build its time event list. This avoids repeatedly loading the state system nodes from disk for each time graph entry, which can be very slow if the full time range queries require more nodes than is available in the state system cache. Change-Id: I7dcac5d546a84462dba0e5a0cf320ba18d3617f5 Signed-off-by: Patrick Tasse Reviewed-on: https://git.eclipse.org/r/52965 Reviewed-by: Hudson CI Reviewed-by: Bernd Hufmann Tested-by: Bernd Hufmann --- .../AbstractStateSystemTimeGraphView.java | 460 ++++++++++++++++++ .../timegraph/AbstractTimeGraphView.java | 126 ++++- .../timegraph/widgets/TimeGraphControl.java | 7 +- 3 files changed, 570 insertions(+), 23 deletions(-) create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractStateSystemTimeGraphView.java diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractStateSystemTimeGraphView.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractStateSystemTimeGraphView.java new file mode 100644 index 0000000000..f1daa92043 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractStateSystemTimeGraphView.java @@ -0,0 +1,460 @@ +/******************************************************************************* + * Copyright (c) 2015 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 org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +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.signal.TmfSignalHandler; +import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; +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.ITimeEvent; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; +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> fSSEntryListMap = new HashMap<>(); + + /** The trace to state system multi map */ + private final Multimap fTraceSSMap = HashMultimap.create(); + + // ------------------------------------------------------------------------ + // 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> fullStates, @Nullable List prevFullState); + } + + private class ZoomThreadByTime extends ZoomThread { + private final @NonNull List fZoomSSList; + private boolean fClearZoomedLists; + + public ZoomThreadByTime(@NonNull List ssList, long startTime, long endTime, long resolution, boolean restart) { + super(startTime, endTime, resolution); + fZoomSSList = ssList; + fClearZoomedLists = !restart; + } + + @Override + public void run() { + final List links = new ArrayList<>(); + if (fClearZoomedLists) { + clearZoomedLists(); + } + for (ITmfStateSystem ss : fZoomSSList) { + List entryList = null; + synchronized (fSSEntryListMap) { + entryList = fSSEntryListMap.get(ss); + } + if (entryList != null) { + zoomByTime(ss, entryList, links, getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor()); + } + } + if (!getMonitor().isCanceled()) { + getTimeGraphViewer().setLinks(links); + } + } + + @Override + public void cancel() { + super.cancel(); + if (fClearZoomedLists) { + clearZoomedLists(); + } + } + + private void zoomByTime(final ITmfStateSystem ss, final List entryList, final List links, + 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> fullStates, @Nullable List prevFullState) { + if (!fullRange) { + for (TimeGraphEntry entry : entryList) { + zoom(checkNotNull(entry), ss, fullStates, prevFullState, monitor); + } + } + /* Refresh the arrows when zooming */ + links.addAll(getLinkList(ss, fullStates, monitor)); + } + }); + refresh(); + } + + private void zoom(@NonNull TimeGraphEntry entry, ITmfStateSystem ss, @NonNull List> fullStates, @Nullable List prevFullState, @NonNull IProgressMonitor monitor) { + List eventList = getEventList(entry, ss, fullStates, prevFullState, monitor); + if (eventList != null) { + for (ITimeEvent event : eventList) { + entry.addZoomedEvent(event); + } + } + for (ITimeGraphEntry child : entry.getChildren()) { + if (monitor.isCanceled()) { + return; + } + if (child instanceof TimeGraphEntry) { + zoom((TimeGraphEntry) child, ss, fullStates, prevFullState, monitor); + } + } + } + + private void clearZoomedLists() { + for (ITmfStateSystem ss : fZoomSSList) { + List 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 (ITimeGraphEntry child : entry.getChildren()) { + if (child instanceof TimeGraphEntry) { + clearZoomedList((TimeGraphEntry) 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 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 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 list) { + super.addToEntryList(trace, list); + synchronized (fSSEntryListMap) { + List 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 list) { + super.removeFromEntryList(trace, list); + synchronized (fSSEntryListMap) { + List 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 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) { + List> fullStates = new ArrayList<>(); + List prevFullState = null; + try { + long time = start; + while (true) { + if (monitor.isCanceled()) { + break; + } + List 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. + * + * @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 + * @since 1.1 + */ + protected abstract @Nullable List getEventList(@NonNull TimeGraphEntry tgentry, ITmfStateSystem ss, + @NonNull List> fullStates, @Nullable List 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 monitor + * A progress monitor + * @return The list of link events + */ + protected @NonNull List getLinkList(ITmfStateSystem ss, + @NonNull List> fullStates, @NonNull IProgressMonitor monitor) { + return new ArrayList<>(); + } + + /** + * @deprecated The subclass should call {@link #getEntryList(ITmfStateSystem)} instead. + */ + @Deprecated + @Override + protected final List 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 list) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated The subclass should call {@link #putEntryList(ITmfTrace, ITmfStateSystem, List)} instead. + */ + @Deprecated + @Override + protected final void putEntryList(ITmfTrace trace, List list) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated The subclass should call {@link #removeFromEntryList(ITmfTrace, ITmfStateSystem, List)} instead. + */ + @Deprecated + @Override + protected final void removeFromEntryList(ITmfTrace trace, List list) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated The subclass should implement {@link #getEventList(TimeGraphEntry, ITmfStateSystem, List, List, IProgressMonitor)} instead. + */ + @Deprecated + @Override + protected final List getEventList(TimeGraphEntry entry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated The subclass should implement {@link #getLinkList(ITmfStateSystem, List, IProgressMonitor)} instead. + */ + @Deprecated + @Override + protected final List getLinkList(long startTime, long endTime, long resolution, IProgressMonitor monitor) { + throw new UnsupportedOperationException(); + } + + // ------------------------------------------------------------------------ + // Signal handlers + // ------------------------------------------------------------------------ + + @TmfSignalHandler + @Override + public void traceClosed(final TmfTraceClosedSignal signal) { + super.traceClosed(signal); + synchronized (fSSEntryListMap) { + for (ITmfStateSystem ss : fTraceSSMap.removeAll(signal.getTrace())) { + fSSEntryListMap.remove(ss); + } + } + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractTimeGraphView.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractTimeGraphView.java index 9e5dfdaf79..7358c9f891 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractTimeGraphView.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/timegraph/AbstractTimeGraphView.java @@ -460,35 +460,91 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } } - private class ZoomThread extends Thread { - private final @NonNull List fZoomEntryList; + /** + * Zoom thread + * @since 1.1 + */ + protected abstract class ZoomThread extends Thread { private final long fZoomStartTime; private final long fZoomEndTime; private final long fResolution; private final @NonNull IProgressMonitor fMonitor; - public ZoomThread(@NonNull List entryList, long startTime, long endTime, String name) { - super(name + " zoom"); //$NON-NLS-1$ - fZoomEntryList = entryList; + /** + * Constructor + * + * @param startTime + * the start time + * @param endTime + * the end time + * @param resolution + * the resolution + */ + public ZoomThread(long startTime, long endTime, long resolution) { + super(AbstractTimeGraphView.this.getName() + " zoom"); //$NON-NLS-1$ fZoomStartTime = startTime; fZoomEndTime = endTime; - fResolution = Math.max(1, (fZoomEndTime - fZoomStartTime) / fDisplayWidth); + fResolution = resolution; fMonitor = new NullProgressMonitor(); } + /** + * @return the zoom start time + */ + public long getZoomStartTime() { + return fZoomStartTime; + } + + /** + * @return the zoom end time + */ + public long getZoomEndTime() { + return fZoomEndTime; + } + + /** + * @return the resolution + */ + public long getResolution() { + return fResolution; + } + + /** + * @return the monitor + */ + public @NonNull IProgressMonitor getMonitor() { + return fMonitor; + } + + /** + * Cancel the zoom thread + */ + public void cancel() { + fMonitor.setCanceled(true); + } + } + + private class ZoomThreadByEntry extends ZoomThread { + private final @NonNull List fZoomEntryList; + + public ZoomThreadByEntry(@NonNull List entryList, long startTime, long endTime, long resolution) { + super(startTime, endTime, resolution); + fZoomEntryList = entryList; + } + @Override public void run() { for (TimeGraphEntry entry : fZoomEntryList) { - if (fMonitor.isCanceled()) { + if (getMonitor().isCanceled()) { return; } if (entry == null) { break; } - zoom(entry, fMonitor); + zoom(entry, getMonitor()); } /* Refresh the arrows when zooming */ - List events = getLinkList(fZoomStartTime, fZoomEndTime, fResolution, fMonitor); + List events = getLinkList(getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor()); if (events != null) { fTimeGraphWrapper.getTimeGraphViewer().setLinks(events); redraw(); @@ -496,17 +552,17 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } private void zoom(@NonNull TimeGraphEntry entry, @NonNull IProgressMonitor monitor) { - if (fZoomStartTime <= fStartTime && fZoomEndTime >= fEndTime) { + if (getZoomStartTime() <= fStartTime && getZoomEndTime() >= fEndTime) { entry.setZoomedEventList(null); } else { - List zoomedEventList = getEventList(entry, fZoomStartTime, fZoomEndTime, fResolution, monitor); + List zoomedEventList = getEventList(entry, getZoomStartTime(), getZoomEndTime(), getResolution(), monitor); if (zoomedEventList != null) { entry.setZoomedEventList(zoomedEventList); } } redraw(); for (ITimeGraphEntry child : entry.getChildren()) { - if (fMonitor.isCanceled()) { + if (monitor.isCanceled()) { return; } if (child instanceof TimeGraphEntry) { @@ -515,9 +571,6 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } } - public void cancel() { - fMonitor.setCanceled(true); - } } // ------------------------------------------------------------------------ @@ -939,7 +992,6 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA return; } fTrace = signal.getTrace(); - loadTrace(); } @@ -968,6 +1020,7 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA fEndTime = 0; if (fZoomThread != null) { fZoomThread.cancel(); + fZoomThread = null; } refresh(); } @@ -1048,6 +1101,10 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA // ------------------------------------------------------------------------ private void loadTrace() { + if (fZoomThread != null) { + fZoomThread.cancel(); + fZoomThread = null; + } synchronized (fEntryListMap) { fEntryList = fEntryListMap.get(fTrace); if (fEntryList == null) { @@ -1167,6 +1224,7 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA * Refresh the display */ protected void refresh() { + final boolean zoomThread = Thread.currentThread() instanceof ZoomThread; TmfUiRefreshHandler.getInstance().queueUpdate(this, new Runnable() { @Override public void run() { @@ -1188,6 +1246,7 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } if (fEntryList != fTimeGraphWrapper.getInput()) { fTimeGraphWrapper.setInput(fEntryList); + fTimeGraphWrapper.getTimeGraphViewer().setLinks(null); } else { fTimeGraphWrapper.refresh(); } @@ -1212,7 +1271,9 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } } - startZoomThread(startTime, endTime); + if (!zoomThread) { + startZoomThread(startTime, endTime); + } } }); } @@ -1250,15 +1311,40 @@ public abstract class AbstractTimeGraphView extends TmfView implements ITmfTimeA } private void startZoomThread(long startTime, long endTime) { + boolean restart = false; if (fZoomThread != null) { fZoomThread.cancel(); + if (fZoomThread.fZoomStartTime == startTime && fZoomThread.fZoomEndTime == endTime) { + restart = true; + } + } + long resolution = Math.max(1, (endTime - startTime) / fDisplayWidth); + fZoomThread = createZoomThread(startTime, endTime, resolution, restart); + if (fZoomThread != null) { + fZoomThread.start(); } + } + + /** + * Create a zoom thread. + * + * @param startTime + * the zoom start time + * @param endTime + * the zoom end time + * @param resolution + * the resolution + * @param restart + * true if restarting zoom for the same time range + * @return a zoom thread + * @since 1.1 + */ + protected @Nullable ZoomThread createZoomThread(long startTime, long endTime, long resolution, boolean restart) { final List entryList = fEntryList; if (entryList == null) { - return; + return null; } - fZoomThread = new ZoomThread(entryList, startTime, endTime, getName()); - fZoomThread.start(); + return new ZoomThreadByEntry(entryList, startTime, endTime, resolution); } private void makeActions() { diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java index af63f1da43..925540e373 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java @@ -1505,7 +1505,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * @param timeProvider * The time provider * @param links - * The array items to draw + * The list of link events * @param nameSpace * The width reserved for the names * @param gc @@ -1517,8 +1517,9 @@ public class TimeGraphControl extends TimeGraphBaseControl return; } gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); - for (ILinkEvent event : links) { - drawLink(event, bounds, timeProvider, nameSpace, gc); + /* the list can grow concurrently but cannot shrink */ + for (int i = 0; i < links.size(); i++) { + drawLink(links.get(i), bounds, timeProvider, nameSpace, gc); } gc.setClipping((Rectangle) null); } -- 2.34.1