/*
* Created on Apr 24, 2006 Copyright (C) 2001-5, Anthony Harrison anh23@pitt.edu
* (jactr.org) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version. This library is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.tools.tracer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import javolution.util.FastList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.model.IModel;
import org.jactr.core.model.event.IModelListener;
import org.jactr.core.model.event.ModelEvent;
import org.jactr.core.model.event.ModelListenerAdaptor;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.runtime.controller.IController;
import org.jactr.core.runtime.controller.debug.IDebugController;
import org.jactr.core.runtime.controller.debug.event.BreakpointEvent;
import org.jactr.core.runtime.controller.debug.event.IBreakpointListener;
import org.jactr.core.runtime.event.ACTRRuntimeAdapter;
import org.jactr.core.runtime.event.ACTRRuntimeEvent;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.instrument.IInstrument;
import org.jactr.tools.misc.ModelsLock;
import org.jactr.tools.tracer.listeners.ITraceListener;
import org.jactr.tools.tracer.sinks.ChainedSink;
/**
* tracer that can listen and record the actions of all running models. At the
* start of the run (via ACTRRuntimeEvent.STARTED), the runtime tracer snags the
* current controller, which must be NetworkedIOHandler
*
* @author developer
*/
public class RuntimeTracer implements IInstrument, IParameterized
{
/**
* logger definition
*/
static public final Log LOGGER = LogFactory
.getLog(RuntimeTracer.class);
static public final String EXECUTOR_PARAM = "Executor";
static public final String SINK_CLASS = "ITraceSinkClass";
static public final String LISTENERS = "ListenerClasses";
private ITraceSink _dataSink;
private Collection<ITraceListener> _listeners;
private Set<IModel> _attachedModels = new HashSet<IModel>();
private Map<String, String> _deferredParameters = new TreeMap<String, String>();
private IModelListener _modelListener = null;
private String _executorName = ExecutorServices.BACKGROUND;
private Executor _executor = ExecutorServices
.getExecutor(_executorName);
private static long SYNCHRONIZATION_THRESHOLD;
static
{
try
{
SYNCHRONIZATION_THRESHOLD = Long.parseLong(System.getProperty(
"jactr.runtimeTracer.synchronizationInterval", "2000"));
}
catch (Exception e)
{
SYNCHRONIZATION_THRESHOLD = 1000;
}
}
private AtomicLong _synchronizationCount = new AtomicLong(0);
private ModelsLock _modelsLock = new ModelsLock();
public RuntimeTracer()
{
_listeners = new ArrayList<ITraceListener>();
_modelListener = new ModelListenerAdaptor() {
@Override
public void cycleStopped(ModelEvent me)
{
if (_synchronizationCount.incrementAndGet() % SYNCHRONIZATION_THRESHOLD == 0)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String
.format("Requesting models to suspend until we can catch up"));
// trigger the synchronization, but fire it on the our executor
// so we know we've caught up.
CompletableFuture<Boolean> allClosed = _modelsLock.close();
allClosed.thenRunAsync(
() -> {
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("All models stopped. Sinking"));
if (_dataSink != null)
try
{
_dataSink.flush();
}
catch (Exception e)
{
LOGGER.error(
".cycleStopped threw Exception while flushing: ", e);
}
// and release
_modelsLock.open();
}, getExecutor());
}
}
};
}
public Executor getExecutor()
{
return _executor;
}
public void setExecutor(String executorName)
{
Executor ex = ExecutorServices.getExecutor(executorName);
if (ex == null)
throw new RuntimeException("Could not find executor named "
+ executorName);
_executorName = executorName;
_executor = ex;
}
public void add(ITraceListener listener)
{
_listeners.add(listener);
if (_dataSink != null)
{
listener.setTraceSink(_dataSink);
if (listener instanceof IParameterized)
applyDeferred((IParameterized) listener);
}
}
protected void applyDeferred(IParameterized parameterized)
{
for (Map.Entry<String, String> entry : _deferredParameters.entrySet())
parameterized.setParameter(entry.getKey(), entry.getValue());
}
/**
* where should we send all the transformed events to?
*
* @param sink
*/
public void setTraceSink(ITraceSink sink)
{
_dataSink = sink;
for (ITraceListener listener : _listeners)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Setting " + listener + "'s sink to " + sink);
listener.setTraceSink(sink);
}
}
public void install(IModel model)
{
if (_attachedModels.contains(model)) return;
if (LOGGER.isDebugEnabled()) LOGGER.debug("Installing " + model);
for (ITraceListener listener : _listeners)
listener.install(model, getExecutor());
model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
_modelsLock.install(model);
_attachedModels.add(model);
}
public void uninstall(IModel model)
{
model.removeListener(_modelListener);
for (ITraceListener listener : _listeners)
listener.uninstall(model);
_modelsLock.uninstall(model);
_attachedModels.remove(model);
}
/**
* we attach two listeners. The first is the runtime listener which when the
* runtime stops, will force us to flush our data. The other is the a
* breakpoint listener so that it a breakpoint is reached, we flush.
*
* @see org.jactr.instrument.IInstrument#initialize()
*/
public void initialize()
{
_modelsLock.initialize();
ACTRRuntime runtime = ACTRRuntime.getRuntime();
runtime.addListener(new ACTRRuntimeAdapter() {
@Override
public void modelAdded(ACTRRuntimeEvent event)
{
/*
* for any late additions. This is nice in principle, but the async
* controller needs to handle the addition and removal of the models as
* well
*/
// install(event.getModel());
}
@Override
public void runtimeStopped(ACTRRuntimeEvent event)
{
try
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Flushing data to data sink");
_dataSink.flush();
}
catch (Exception e)
{
LOGGER.error("Could not flush ", e);
}
}
}, getExecutor());
IController controller = runtime.getController();
if (controller instanceof IDebugController)
((IDebugController) controller).addListener(new IBreakpointListener() {
public void breakpointReached(BreakpointEvent be)
{
try
{
_dataSink.flush();
}
catch (Exception e)
{
LOGGER.error("Could not flush ", e);
}
}
}, getExecutor());
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getParameter(java.lang.String)
*/
public String getParameter(String key)
{
if (LOGGER.isWarnEnabled())
LOGGER.warn("RuntimeTracer.getParameter is not implemented");
return null;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getPossibleParameters()
*/
public Collection<String> getPossibleParameters()
{
ArrayList<String> rtn = new ArrayList<String>();
rtn.add(EXECUTOR_PARAM);
rtn.add(SINK_CLASS);
return rtn;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getSetableParameters()
*/
public Collection<String> getSetableParameters()
{
return getPossibleParameters();
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#setParameter(java.lang.String,
* java.lang.String)
*/
public void setParameter(String key, String value)
{
if (EXECUTOR_PARAM.equalsIgnoreCase(key))
setExecutor(value);
else if (SINK_CLASS.equalsIgnoreCase(key))
{
FastList<ITraceSink> sinks = FastList.newInstance();
String[] sinkClasses = value.split(",");
for (String sinkClass : sinkClasses)
{
sinkClass = sinkClass.trim();
if (sinkClass.length() > 0)
try
{
sinks.add((ITraceSink) getClass().getClassLoader()
.loadClass(sinkClass).newInstance());
}
catch (Exception e)
{
LOGGER.error("Could not create ITraceSink from " + sinkClass, e);
}
}
if (sinks.size() == 0)
throw new RuntimeException("No sinks could be created");
if (sinks.size() == 1)
setTraceSink(sinks.get(0));
else
{
ChainedSink sink = new ChainedSink();
for (ITraceSink tmp : sinks)
sink.add(tmp);
setTraceSink(sink);
}
FastList.recycle(sinks);
}
else if (LISTENERS.equalsIgnoreCase(key))
for (String name : value.split(","))
{
name = name.trim();
if (name.length() != 0)
try
{
Class lClass = getClass().getClassLoader().loadClass(name);
ITraceListener listener = (ITraceListener) lClass.newInstance();
add(listener);
}
catch (Exception e)
{
if (LOGGER.isWarnEnabled())
LOGGER.warn("Could not create new trace listener " + name, e);
}
}
else
{
_deferredParameters.put(key, value);
for (ITraceListener listener : _listeners)
if (listener instanceof IParameterized)
((IParameterized) listener).setParameter(key, value);
if (_dataSink instanceof IParameterized)
((IParameterized) _dataSink).setParameter(key, value);
}
}
}