/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL)
*
* This file is part of GSN.
*
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GSN 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GSN. If not, see <http://www.gnu.org/licenses/>.
*
* File: src/ch/epfl/gsn/vsensor/AbstractVirtualSensor.java
*
* @author gsn_devs
* @author Ali Salehi
* @author Mehdi Riahi
*
*/
package ch.epfl.gsn.vsensor;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Map;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Iterator;
import java.lang.management.ThreadMXBean;
import org.slf4j.LoggerFactory;
import ch.epfl.gsn.ContainerImpl;
import ch.epfl.gsn.Main;
import ch.epfl.gsn.beans.DataField;
import ch.epfl.gsn.beans.DataTypes;
import ch.epfl.gsn.beans.StreamElement;
import ch.epfl.gsn.beans.VSensorConfig;
import ch.epfl.gsn.monitoring.AnomalyDetector;
import ch.epfl.gsn.monitoring.Monitorable;
import ch.epfl.gsn.vsensor.AbstractVirtualSensor;
import org.slf4j.Logger;
public abstract class AbstractVirtualSensor implements Monitorable{
private static final transient Logger logger = LoggerFactory.getLogger( AbstractVirtualSensor.class );
private VSensorConfig virtualSensorConfiguration;
private Long outputCount = 0L;
private Long inputCount = 0L;
private long lastOutputedTime = 0;
/*
* Map threads would keep track of thread ID (and name) of each wrapper associated with this Virtual Sensor
* It is populated in start method of VirtualSensor class when each wrapper thread is started
*/
private Map <Long, String> threads = new HashMap <Long, String> ();
private AnomalyDetector anomalyDetector;
public final boolean initialize_wrapper(){
anomalyDetector = new AnomalyDetector(this);
Main.getInstance().getToMonitor().add(this);
return initialize();
}
/**
* Called once while initializing an instance of the virtual sensor
*
* @return True if the initialization is done successfully.
*/
public abstract boolean initialize ( );
private void validateStreamElement ( StreamElement streamElement ,boolean adjust) {
if ( !compatibleStructure( streamElement, getVirtualSensorConfiguration( ).getOutputStructure( ),adjust ) ) {
StringBuilder exceptionMessage = new StringBuilder( ).append( "The streamElement produced by :" ).append( getVirtualSensorConfiguration( ).getName( ) ).append(
" Virtual Sensor is not compatible with the defined streamElement.\n" );
exceptionMessage.append( "The expected stream element structure (specified in " ).append( getVirtualSensorConfiguration( ).getFileName( ) ).append( " is [" );
for ( DataField df : getVirtualSensorConfiguration( ).getOutputStructure( ) )
exceptionMessage.append( df.getName( ) ).append( " (" ).append( DataTypes.TYPE_NAMES[ df.getDataTypeID( ) ] ).append( ") , " );
exceptionMessage.append( "] but the actual stream element received from the " + getVirtualSensorConfiguration( ).getName( ) ).append( " has the [" );
for ( int i = 0 ; i < streamElement.getFieldNames( ).length ; i++ )
exceptionMessage.append( streamElement.getFieldNames( )[ i ] ).append( "(" ).append( DataTypes.TYPE_NAMES[ streamElement.getFieldTypes( )[ i ] ] ).append( ")," );
exceptionMessage.append(" ] thus the stream element dropped !!!" );
throw new RuntimeException( exceptionMessage.toString( ) );
}
}
/**
* if Adjust is true then system checks the output structure of the virtual sensor and
* only publishes the fields defined in the output structure of the virtual sensor and
* ignores the rest. IF the adjust is set to false, the system will enforce strict
* compatibility of the output and the produced value.
*
* @param streamElement
* @param adjust Default is false.
*/
protected synchronized void dataProduced ( StreamElement streamElement,boolean adjust ) {
try {
validateStreamElement( streamElement,adjust );
} catch ( Exception e ) {
logger.error( e.getMessage( ) , e );
return;
}
if ( !streamElement.isTimestampSet( ) ) streamElement.setTimeStamp( System.currentTimeMillis( ) );
final int outputStreamRate = getVirtualSensorConfiguration( ).getOutputStreamRate( );
final long currentTime = System.currentTimeMillis( );
if ( ( currentTime - lastOutputedTime ) < outputStreamRate ) {
logger.info( "Called by *discarded* b/c of the rate limit reached." );
return;
}
lastOutputedTime = currentTime;
try {
ContainerImpl.getInstance().publishData( this ,streamElement);
outputCount = outputCount == Long.MAX_VALUE ? 0 : outputCount + 1;
} catch (SQLException e) {
if (e.getMessage().toLowerCase().contains("duplicate entry"))
logger.info(e.getMessage(),e);
else
logger.error(e.getMessage(),e);
}
}
/**
* Calls the dataProduced with adjust = false.
* @param streamElement
*/
protected synchronized void dataProduced ( StreamElement streamElement ) {
dataProduced(streamElement,true);
}
/**
* First checks compatibility of the data type of each output data item in the stream element with the
* defined output in the VSD file. (this check is done regardless of the value for adjust flag).
* <p>
* If the adjust flag is set to true, the method checks the newly generated stream element
* and returns true if and only if the number of data items is equal to the number of output
* data structure defined for this virtual sensor.
* If the adjust=true, then this test is not performed.
*
* @param se
* @param outputStructure
* @param adjust default is false.
* @return
*/
private static boolean compatibleStructure ( StreamElement se , DataField [] outputStructure ,boolean adjust ) {
if (!adjust && outputStructure.length != se.getFieldNames().length ) {
logger.warn( "Validation problem, the number of field doesn't match the number of output data strcture of the virtual sensor" );
return false;
}
int i =-1;
for (DataField field: outputStructure) {
Serializable value = se.getData(field.getName());
i++;
if (value==null)
continue;
if ( ( ( field.getDataTypeID() == DataTypes.BIGINT ||
field.getDataTypeID() == DataTypes.DOUBLE ||
field.getDataTypeID() == DataTypes.FLOAT ||
field.getDataTypeID() == DataTypes.INTEGER||
field.getDataTypeID() == DataTypes.SMALLINT||
field.getDataTypeID() == DataTypes.TINYINT ) &&!(value instanceof Number))
||
( (field.getDataTypeID() == DataTypes.VARCHAR || field.getDataTypeID() == DataTypes.CHAR) && !(value instanceof String)) ||
( (field.getDataTypeID() == DataTypes.BINARY) && !(value instanceof byte[]))
){
logger.warn( "Validation problem for output field >" + field.getName( ) + ", The field type declared as >" + field.getType()+"< while in VSD it is defined as >"+DataTypes.TYPE_NAMES[outputStructure[ i ].getDataTypeID( )]);
return false;
}
}
return true;
}
public final void dispose_decorated(){
Main.getInstance().getToMonitor().remove(this);
dispose();
}
/**
* Called when the container want to stop the pool and remove it's resources.
* The container will call this method once on each install of the virtual
* sensor in the pool. The programmer should release all the resources used by
* this virtual sensor instance in this method specially those resources
* acquired during the <code>initialize</code> call. <p/> Called once while
* finalizing an instance of the virtual sensor
*/
public abstract void dispose ( );
public boolean dataFromWeb ( String action,String[] paramNames, Serializable[] paramValues ) {
return false;
}
/**
* @return the virtualSensorConfiguration
*/
public VSensorConfig getVirtualSensorConfiguration ( ) {
if ( virtualSensorConfiguration == null ) { throw new RuntimeException( "The VirtualSensorParameter is not set !!!" ); }
return virtualSensorConfiguration;
}
/**
* @param virtualSensorConfiguration the virtualSensorConfiguration to set
*/
public void setVirtualSensorConfiguration ( VSensorConfig virtualSensorConfiguration ) {
this.virtualSensorConfiguration = virtualSensorConfiguration;
}
public Map <Long, String> getThreads() {
return threads;
}
public void setThreads (Map <Long, String> threads) {
this.threads = threads;
}
public Hashtable<String, Object> getStatistics(){
Hashtable<String, Object> stat = anomalyDetector.getStatistics();
stat.put("vs."+virtualSensorConfiguration.getName().replaceAll("\\.", "_") +".output.produced.counter", outputCount);
stat.put("vs."+virtualSensorConfiguration.getName().replaceAll("\\.", "_") +".input.produced.counter", inputCount);
/*
* We know the IDs of threads associated with this VSensor
* Using the IDs, we would extract the CPU times, sum them up and put them in the stats map
*/
ThreadMXBean threadBean = Main.getThreadMXBean();
if (!threadBean.isThreadCpuTimeEnabled()) {
logger.info("ThreadCpuTime is disabled. Enabling it | Thread time measurement might not be accurate");
threadBean.setThreadCpuTimeEnabled(true);
}
Iterator <Map.Entry<Long,String>> iter = threads.entrySet().iterator();
long totalCpuTime = 0L;
//TODO: Not using thread names | Should be used for logging in debug mode
while (iter.hasNext()) {
Map.Entry<Long,String> entry = iter.next();
Long id = entry.getKey();
long cpuTime = threadBean.getThreadCpuTime(id);
if (cpuTime == -1) { // Thread is not alive anymore
iter.remove();
continue;
}
if(Long.MAX_VALUE-totalCpuTime > cpuTime){
totalCpuTime += cpuTime;
}else{
totalCpuTime = cpuTime-(Long.MAX_VALUE-totalCpuTime);
}
}
stat.put("vs."+virtualSensorConfiguration.getName().replaceAll("\\.","_")+".cputime.totalCpuTime.counter", totalCpuTime);
return stat;
}
public final void dataAvailable_decorated ( String inputStreamName , StreamElement streamElement ){
dataAvailable ( inputStreamName , streamElement );
inputCount = inputCount == Long.MAX_VALUE ? 0 : inputCount + 1;
}
/**
* This method is going to be called by the container when one of the input
* streams has a data to be delivered to this virtual sensor. After receiving
* the data, the virtual sensor can do the processing on it and this
* processing could possibly result in producing a new stream element in this
* virtual sensor in which case the virtual sensor will notify the container
* by simply adding itself to the list of the virtual sensors which have
* produced data. (calling <code>container.publishData(this)</code>. For
* more information please check the <code>AbstractVirtalSensor</code>
* @param inputStreamName is the name of the input stream as specified in the
* configuration file of the virtual sensor. @param inputDataFromInputStream
* is actually the real data which is produced by the input stream and should
* be delivered to the virtual sensor for possible processing.
*/
public abstract void dataAvailable ( String inputStreamName , StreamElement streamElement );
}