/******************************************************************************* * Copyright (c) 2013, 2015 École Polytechnique de Montréal * * 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: * Geneviève Bastien - Initial implementation and API *******************************************************************************/ package org.eclipse.tracecompass.tmf.core.event.matching; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.osgi.util.NLS; import org.eclipse.tracecompass.internal.tmf.core.Activator; import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest; import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.Table; /** * Abstract class to extend to match certain type of events in a trace * * @author Geneviève Bastien */ public class TmfEventMatching implements ITmfEventMatching { private static final Set<ITmfMatchEventDefinition> MATCH_DEFINITIONS = new HashSet<>(); /** * The array of traces to match */ private final @NonNull Collection<@NonNull ITmfTrace> fTraces; /** * The class to call once a match is found */ private final IMatchProcessingUnit fMatches; private final Multimap<ITmfTrace, ITmfMatchEventDefinition> fMatchMap = HashMultimap.create(); /** * Hashtables for unmatches incoming events */ private final Table<ITmfTrace, IEventMatchingKey, ITmfEvent> fUnmatchedIn = HashBasedTable.create(); /** * Hashtables for unmatches outgoing events */ private final Table<ITmfTrace, IEventMatchingKey, ITmfEvent> fUnmatchedOut = HashBasedTable.create(); /** * Enum for cause and effect types of event * @since 1.0 */ public enum Direction { /** * The event is the first event of the match */ CAUSE, /** * The event is the second event, the one that matches with the cause */ EFFECT, } /** * Constructor with multiple traces * * @param traces * The set of traces for which to match events * @since 1.0 */ public TmfEventMatching(Collection<@NonNull ITmfTrace> traces) { this(traces, new TmfEventMatches()); } /** * Constructor with multiple traces and a match processing object * * @param traces * The set of traces for which to match events * @param tmfEventMatches * The match processing class */ public TmfEventMatching(Collection<@NonNull ITmfTrace> traces, IMatchProcessingUnit tmfEventMatches) { if (tmfEventMatches == null) { throw new IllegalArgumentException(); } fTraces = new HashSet<>(traces); fMatches = tmfEventMatches; } /** * Returns the traces to synchronize. These are the traces that were * specified in the constructor, they may contain either traces or * experiment. * * @return The traces to synchronize */ protected Collection<ITmfTrace> getTraces() { return new HashSet<>(fTraces); } /** * Returns the individual traces to process. If some of the traces specified * to synchronize in the constructor were experiments, only the traces * contained in this experiment will be returned. No {@link TmfExperiment} * are returned by this method. * * @return The individual traces to synchronize, no experiments */ protected Collection<ITmfTrace> getIndividualTraces() { Set<ITmfTrace> traces = new HashSet<>(); for (ITmfTrace trace : fTraces) { traces.addAll(TmfTraceManager.getTraceSet(trace)); } return traces; } /** * Returns the match processing unit * * @return The match processing unit */ protected IMatchProcessingUnit getProcessingUnit() { return fMatches; } /** * Returns the match event definitions corresponding to the trace * * @param trace * The trace * @return The match event definition object */ protected Collection<ITmfMatchEventDefinition> getEventDefinitions(ITmfTrace trace) { return ImmutableList.copyOf(fMatchMap.get(trace)); } /** * Method that initializes any data structure for the event matching. It * also assigns to each trace an event matching definition instance that * applies to the trace * * @since 1.0 */ public void initMatching() { // Initialize the matching infrastructure (unmatched event lists) fUnmatchedIn.clear(); fUnmatchedOut.clear(); fMatches.init(fTraces); for (ITmfTrace trace : getIndividualTraces()) { for (ITmfMatchEventDefinition def : MATCH_DEFINITIONS) { if (def.canMatchTrace(trace)) { fMatchMap.put(trace, def); } } } } /** * Calls any post matching methods of the processing class */ protected void finalizeMatching() { fMatches.matchingEnded(); } /** * Prints stats from the matching * * @return string of statistics */ @Override public String toString() { final String cr = System.getProperty("line.separator"); //$NON-NLS-1$ StringBuilder b = new StringBuilder(); b.append(getProcessingUnit()); int i = 0; for (ITmfTrace trace : getIndividualTraces()) { b.append("Trace " + i++ + ":" + cr + //$NON-NLS-1$ //$NON-NLS-2$ " " + fUnmatchedIn.row(trace).size() + " unmatched incoming events" + cr + //$NON-NLS-1$ //$NON-NLS-2$ " " + fUnmatchedOut.row(trace).size() + " unmatched outgoing events" + cr); //$NON-NLS-1$ //$NON-NLS-2$ } return b.toString(); } /** * Matches one event * * @param event * The event to match * @param trace * The trace to which this event belongs * @param monitor * The monitor for the synchronization job * @since 1.0 */ public void matchEvent(ITmfEvent event, ITmfTrace trace, @NonNull IProgressMonitor monitor) { ITmfMatchEventDefinition def = null; Direction evType = null; for (ITmfMatchEventDefinition oneDef : getEventDefinitions(event.getTrace())) { def = oneDef; evType = def.getDirection(event); if (evType != null) { break; } } if (def == null || evType == null) { return; } /* Get the event's unique fields */ IEventMatchingKey eventKey = def.getEventKey(event); if (eventKey == null) { return; } Table<ITmfTrace, IEventMatchingKey, ITmfEvent> unmatchedTbl, companionTbl; /* Point to the appropriate table */ switch (evType) { case EFFECT: unmatchedTbl = fUnmatchedIn; companionTbl = fUnmatchedOut; break; case CAUSE: unmatchedTbl = fUnmatchedOut; companionTbl = fUnmatchedIn; break; default: return; } boolean found = false; TmfEventDependency dep = null; /* Search for the event in the companion table */ for (ITmfTrace mTrace : getIndividualTraces()) { if (companionTbl.contains(mTrace, eventKey)) { found = true; ITmfEvent companionEvent = companionTbl.get(mTrace, eventKey); /* Remove the element from the companion table */ companionTbl.remove(mTrace, eventKey); /* Create the dependency object */ switch (evType) { case EFFECT: dep = new TmfEventDependency(companionEvent, event); break; case CAUSE: dep = new TmfEventDependency(event, companionEvent); break; default: break; } } } /* * If no companion was found, add the event to the appropriate unMatched * lists */ if (found) { getProcessingUnit().addMatch(checkNotNull(dep)); monitor.subTask(NLS.bind(Messages.TmfEventMatching_MatchesFound, getProcessingUnit().countMatches())); } else { /* * If an event is already associated with this key, do not add it * again, we keep the first event chronologically, so if its match * is eventually found, it is associated with the first send or * receive event. At best, it is a good guess, at worst, the match * will be too far off to be accurate. Too bad! * * TODO: maybe instead of just one event, we could have a list of * events as value for the unmatched table. Not necessary right now * though */ if (!unmatchedTbl.contains(event.getTrace(), eventKey)) { unmatchedTbl.put(event.getTrace(), eventKey, event); } } } /** * Method that start the process of matching events * * @return Whether the match was completed correctly or not */ @Override public boolean matchEvents() { /* Are there traces to match? If no, return false */ if (!(fTraces.size() > 0)) { return false; } initMatching(); /* * Actual analysis will be run on a separate thread */ Job job = new Job(Messages.TmfEventMatching_MatchingEvents) { @Override protected IStatus run(final IProgressMonitor monitor) { /** * FIXME For now, we use the experiment strategy: the trace that * is asked to be matched is actually an experiment and the * experiment does the request. But depending on how divergent * the traces' times are and how long it takes to get the first * match, it can use a lot of memory. * * Some strategies can help limit the memory usage of this * algorithm: * * <pre> * Other possible matching strategy: * * start with the shortest trace * * take a few events at the beginning and at the end and try * to match them * </pre> */ for (ITmfTrace trace : fTraces) { monitor.beginTask(NLS.bind(Messages.TmfEventMatching_LookingEventsFrom, trace.getName()), IProgressMonitor.UNKNOWN); setName(NLS.bind(Messages.TmfEventMatching_RequestingEventsFrom, trace.getName())); /* Send the request to the trace */ EventMatchingBuildRequest request = new EventMatchingBuildRequest(TmfEventMatching.this, trace, monitor); trace.sendRequest(request); try { request.waitForCompletion(); } catch (InterruptedException e) { Activator.logInfo(e.getMessage()); } if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } } return Status.OK_STATUS; } }; job.schedule(); try { job.join(); } catch (InterruptedException e) { } finalizeMatching(); return true; } /** * Registers an event match definition * * @param match * The event matching definition */ public static void registerMatchObject(ITmfMatchEventDefinition match) { MATCH_DEFINITIONS.add(match); } } class EventMatchingBuildRequest extends TmfEventRequest { private final TmfEventMatching matching; private final ITmfTrace trace; private final @NonNull IProgressMonitor fMonitor; EventMatchingBuildRequest(TmfEventMatching matching, ITmfTrace trace, IProgressMonitor monitor) { super(ITmfEvent.class, TmfTimeRange.ETERNITY, 0, ITmfEventRequest.ALL_DATA, ITmfEventRequest.ExecutionType.FOREGROUND); this.matching = matching; this.trace = trace; if (monitor == null) { fMonitor = new NullProgressMonitor(); } else { fMonitor = monitor; } } @Override public void handleData(final ITmfEvent event) { super.handleData(event); if (fMonitor.isCanceled()) { this.cancel(); } matching.matchEvent(event, trace, fMonitor); } }