/*******************************************************************************
* Copyright (c) 2012, 2014 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 fr.inria.linuxtools.tmf.core.statesystem;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import fr.inria.linuxtools.statesystem.core.ITmfStateSystem;
import fr.inria.linuxtools.statesystem.core.ITmfStateSystemBuilder;
import fr.inria.linuxtools.tmf.core.event.ITmfEvent;
import fr.inria.linuxtools.tmf.core.event.TmfEvent;
import fr.inria.linuxtools.tmf.core.timestamp.ITmfTimestamp;
import fr.inria.linuxtools.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
* @since 2.0
*/
public abstract class AbstractTmfStateProvider implements ITmfStateProvider {
private static final int DEFAULT_EVENTS_QUEUE_SIZE = 10000;
private final ITmfTrace trace;
private final Class<? extends ITmfEvent> eventType;
private final BlockingQueue<ITmfEvent> eventsQueue;
private final Thread eventHandlerThread;
private boolean ssAssigned;
/** State system in which to insert the state changes */
protected ITmfStateSystemBuilder ss = null;
/**
* Instantiate a new state provider plugin.
*
* @param trace
* The LTTng 2.0 kernel trace directory
* @param eventType
* The specific class for the event type that will be used within
* the subclass
* @param id
* Name given to this state change input. Only used internally.
*/
public AbstractTmfStateProvider(ITmfTrace trace,
Class<? extends ITmfEvent> eventType, String id) {
this.trace = trace;
this.eventType = eventType;
eventsQueue = new ArrayBlockingQueue<>(DEFAULT_EVENTS_QUEUE_SIZE);
ssAssigned = false;
String id2 = (id == null ? "Unamed" : id); //$NON-NLS-1$
eventHandlerThread = new Thread(new EventProcessor(), id2 + " Event Handler"); //$NON-NLS-1$
}
@Override
public ITmfTrace getTrace() {
return trace;
}
/**
* @since 3.0
*/
@Override
public long getStartTime() {
return trace.getStartTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue();
}
/**
* @since 3.0
*/
@Override
public void assignTargetStateSystem(ITmfStateSystemBuilder ssb) {
ss = ssb;
ssAssigned = true;
eventHandlerThread.start();
}
/**
* @since 3.0
*/
@Override
public ITmfStateSystem getAssignedStateSystem() {
return ss;
}
@Override
public void dispose() {
/* Insert a null event in the queue to stop the event handler's thread. */
try {
eventsQueue.put(END_EVENT);
eventHandlerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
ssAssigned = false;
ss = null;
}
@Override
public final Class<? extends ITmfEvent> getExpectedEventType() {
return eventType;
}
@Override
public final void processEvent(ITmfEvent event) {
/* Make sure the target state system has been assigned */
if (!ssAssigned) {
System.err.println("Cannot process event without a target state system"); //$NON-NLS-1$
return;
}
/* Insert the event we're received into the events queue */
ITmfEvent curEvent = event;
try {
eventsQueue.put(curEvent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 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 {
eventsQueue.put(EMPTY_QUEUE_EVENT);
while (!eventsQueue.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 {}
/** Fake event indicating we want to clear the current queue */
private static class EmptyQueueEvent extends TmfEvent {}
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 ITmfEvent currentEvent;
@Override
public void run() {
if (ss == null) {
System.err.println("Cannot run event manager without assigning a target state system first!"); //$NON-NLS-1$
return;
}
ITmfEvent event;
try {
event = eventsQueue.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 = eventsQueue.take();
continue;
}
currentEvent = event;
/* Make sure this is an event the sub-class can process */
if (eventType.isInstance(event) && event.getType() != null) {
eventHandle(event);
}
event = eventsQueue.take();
}
/* We've received the last event, clean up */
closeStateSystem();
} catch (InterruptedException e) {
/* We've been interrupted abnormally */
System.out.println("Event handler interrupted!"); //$NON-NLS-1$
e.printStackTrace();
}
}
private void closeStateSystem() {
final long endTime = (currentEvent == null) ? 0 :
currentEvent.getTimestamp().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue();
ss.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);
}