/******************************************************************************* * Copyright (c) 2012, 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: * Alexandre Montplaisir - Initial API and implementation *******************************************************************************/ package org.eclipse.tracecompass.tmf.core.statesystem; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.common.core.collect.BufferedBlockingQueue; import org.eclipse.tracecompass.internal.tmf.core.Activator; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import org.eclipse.tracecompass.tmf.core.event.TmfEvent; import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; /** * Instead of using IStateChangeInput directly, one can extend this class, which * defines a lot of the common functions of the state change input plugin. * * It will handle the state-system-processing in a separate thread, which is * normally not a bad idea for traces of some size. * * processEvent() is replaced with eventHandle(), so that all the multi-thread * logic is abstracted away. * * @author Alexandre Montplaisir */ public abstract class AbstractTmfStateProvider implements ITmfStateProvider { private static final int DEFAULT_EVENTS_QUEUE_SIZE = 127; private static final int DEFAULT_EVENTS_CHUNK_SIZE = 127; private final ITmfTrace fTrace; private final BufferedBlockingQueue<ITmfEvent> fEventsQueue; private final Thread fEventHandlerThread; private boolean fStateSystemAssigned; /** State system in which to insert the state changes */ private @Nullable ITmfStateSystemBuilder fSS = null; /* The last safe time at which this state provider can be queried */ private volatile long fSafeTime; /** * Instantiate a new state provider plugin. * * @param trace * The LTTng 2.0 kernel trace directory * @param id * Name given to this state change input. Only used internally. */ public AbstractTmfStateProvider(ITmfTrace trace, String id) { fTrace = trace; fEventsQueue = new BufferedBlockingQueue<>(DEFAULT_EVENTS_QUEUE_SIZE, DEFAULT_EVENTS_CHUNK_SIZE); fStateSystemAssigned = false; // set the safe time to before the trace start, the analysis has not yet started fSafeTime = trace.getStartTime().toNanos() - 1; fEventHandlerThread = new Thread(new EventProcessor(), id + " Event Handler"); //$NON-NLS-1$ } /** * Get the state system builder of this provider (to insert states in). * * @return The state system object to be filled */ protected @Nullable ITmfStateSystemBuilder getStateSystemBuilder() { return fSS; } @Override public ITmfTrace getTrace() { return fTrace; } @Override public long getStartTime() { return fTrace.getStartTime().toNanos(); } /** * @since 2.0 */ @Override public long getLatestSafeTime() { return fSafeTime; } @Override public void assignTargetStateSystem(ITmfStateSystemBuilder ssb) { fSS = ssb; fStateSystemAssigned = true; fEventHandlerThread.start(); } @Override public @Nullable ITmfStateSystem getAssignedStateSystem() { return fSS; } @Override public void dispose() { /* Insert a null event in the queue to stop the event handler's thread. */ try { fEventsQueue.put(END_EVENT); fEventsQueue.flushInputBuffer(); fEventHandlerThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } fStateSystemAssigned = false; fSS = null; } @Override public void processEvent(ITmfEvent event) { /* Make sure the target state system has been assigned */ if (!fStateSystemAssigned) { throw new IllegalStateException("Cannot process event without a target state system. ID: " + getClass().getSimpleName()); //$NON-NLS-1$ } /* Insert the event we're received into the events queue */ ITmfEvent curEvent = event; fEventsQueue.put(curEvent); } /** * Block the caller until the events queue is empty. */ public void waitForEmptyQueue() { /* * We will first insert a dummy event that is guaranteed to not modify * the state. That way, when that event leaves the queue, we will know * for sure that the state system processed the preceding real event. */ try { fEventsQueue.put(EMPTY_QUEUE_EVENT); fEventsQueue.flushInputBuffer(); while (!fEventsQueue.isEmpty()) { Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } // ------------------------------------------------------------------------ // Special event types // ------------------------------------------------------------------------ /** Fake event indicating the build is over, and the provider should close */ private static class EndEvent extends TmfEvent { public EndEvent() { super(null, ITmfContext.UNKNOWN_RANK, null, null, null); } } /** Fake event indicating we want to clear the current queue */ private static class EmptyQueueEvent extends TmfEvent { public EmptyQueueEvent() { super(null, ITmfContext.UNKNOWN_RANK, null, null, null); } } private static final EndEvent END_EVENT = new EndEvent(); private static final EmptyQueueEvent EMPTY_QUEUE_EVENT = new EmptyQueueEvent(); // ------------------------------------------------------------------------ // Inner classes // ------------------------------------------------------------------------ /** * This is the runner class for the second thread, which will take the * events from the queue and pass them through the state system. */ private class EventProcessor implements Runnable { private @Nullable ITmfEvent currentEvent; @Override public void run() { if (!fStateSystemAssigned) { Activator.logError("Cannot run event manager without assigning a target state system first!"); //$NON-NLS-1$ return; } /* * We never insert null in the queue. Cannot be checked at * compile-time until Java 8 annotations... */ @NonNull ITmfEvent event = fEventsQueue.take(); /* This is a singleton, we want to do != instead of !x.equals */ while (event != END_EVENT) { if (event == EMPTY_QUEUE_EVENT) { /* Synchronization event, should be ignored */ event = fEventsQueue.take(); continue; } currentEvent = event; fSafeTime = event.getTimestamp().toNanos() - 1; eventHandle(event); event = fEventsQueue.take(); } /* We've received the last event, clean up */ done(); closeStateSystem(); } private void closeStateSystem() { ITmfEvent event = currentEvent; final long endTime = (event == null) ? 0 : event.getTimestamp().toNanos(); if (fSS != null) { fSS.closeHistory(endTime); } } } // ------------------------------------------------------------------------ // Abstract methods // ------------------------------------------------------------------------ /** * Handle the given event and send the appropriate state transitions into * the the state system. * * This is basically the same thing as IStateChangeInput.processEvent(), * except here processEvent() and eventHandle() are run in two different * threads (and the AbstractStateChangeInput takes care of processEvent() * already). * * @param event * The event to process. If you need a specific event type, you * should check for its instance right at the beginning. */ protected abstract void eventHandle(ITmfEvent event); }