/* Copyright (c) 2011 Danish Maritime Authority
*
* 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 3 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 General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package dk.dma.ais.abnormal.stat;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.assistedinject.Assisted;
import dk.dma.ais.abnormal.stat.statistics.CourseOverGroundStatistic;
import dk.dma.ais.abnormal.stat.statistics.ShipTypeAndSizeStatistic;
import dk.dma.ais.abnormal.stat.statistics.SpeedOverGroundStatistic;
import dk.dma.ais.abnormal.stat.statistics.TrackingEventListener;
import dk.dma.ais.concurrency.stripedexecutor.StripedExecutorService;
import dk.dma.ais.filter.ReplayDownSampleFilter;
import dk.dma.ais.message.AisMessage;
import dk.dma.ais.message.AisMessage5;
import dk.dma.ais.message.IPositionMessage;
import dk.dma.ais.packet.AisPacket;
import dk.dma.ais.tracker.eventEmittingTracker.EventEmittingTracker;
import dk.dma.ais.tracker.eventEmittingTracker.EventEmittingTrackerImpl;
import eu.javaspecialists.tjsn.concurrency.StripedRunnable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Set;
/**
* Handler for read AIS packets
*/
public class PacketHandlerImpl implements PacketHandler {
static final Logger LOG = LoggerFactory.getLogger(PacketHandler.class);
private AppStatisticsService statisticsService; // = new AppStatisticsServiceImpl(1, TimeUnit.MINUTES);
private EventEmittingTracker trackingService;
private ReplayDownSampleFilter downSampleFilter;
private StripedExecutorService workerThreads;
private final boolean multiThreaded;
private volatile boolean cancel;
private Set<TrackingEventListener> statistics;
private static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
@Inject
public PacketHandlerImpl(AppStatisticsService statisticsService, EventEmittingTracker trackingService, ReplayDownSampleFilter downSampleFilter, StripedExecutorService executorService, @Assisted boolean multiThreaded) {
LOG.debug("Detected " + NUMBER_OF_CORES + " CPU cores.");
LOG.info("Creating " + (multiThreaded ? "multi threaded ":"single threaded ")+ "AIS packet handler.");
this.statisticsService = statisticsService;
this.trackingService = trackingService;
this.downSampleFilter = downSampleFilter;
this.workerThreads = executorService;
this.multiThreaded = multiThreaded;
initStatistics();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
public void accept(final AisPacket packet) {
if (cancel) {
return;
}
statisticsService.incUnfilteredPacketCount();
if (downSampleFilter.rejectedByFilter(packet)) {
return;
}
statisticsService.incFilteredPacketCount();
long n = statisticsService.getFilteredPacketCount();
if (n % 100000L == 0) {
LOG.debug(n + " packets through filter.");
}
// Get AisMessage from packet or drop
AisMessage message = packet.tryGetAisMessage();
if (message == null) {
return;
}
statisticsService.incMessageCount();
if (message instanceof IPositionMessage) {
statisticsService.incPosMsgCount();
} else if (message instanceof AisMessage5) {
statisticsService.incStatMsgCount();
}
if (multiThreaded) {
Object stripe = assignStripe(message);
workerThreads.submit(new Task(packet, stripe));
} else {
doWork(packet);
}
}
private static int hash(int a) {
// https://gist.github.com/badboy/6267743
a = ~a + (a << 15); // key = (key << 15) - key - 1;
a = a ^ (a >>> 12);
a = a + (a << 2);
a = a ^ (a >>> 4);
a = a * 2057; // key = (key + (key << 3)) + (key << 11);
a = a ^ (a >>> 16);
return a;
}
private static Object assignStripe(AisMessage message) {
return Integer.valueOf(Math.abs(hash(message.getUserId())) % NUMBER_OF_CORES);
}
private void doWork(AisPacket p) {
trackingService.update(p);
if (trackingService instanceof EventEmittingTrackerImpl) {
statisticsService.setTrackCount(((EventEmittingTrackerImpl) trackingService).getNumberOfTracks());
}
}
@Override
public void cancel() {
cancel = true;
// TODO close down and clean up
}
@Override
public AppStatisticsService getBuildStats() {
return statisticsService;
}
private void initStatistics() {
Injector injector = AbnormalStatBuilderApp.getInjector();
this.statistics = new ImmutableSet.Builder<TrackingEventListener>()
.add(injector.getInstance(ShipTypeAndSizeStatistic.class))
.add(injector.getInstance(CourseOverGroundStatistic.class))
.add(injector.getInstance(SpeedOverGroundStatistic.class))
.build();
Iterator<TrackingEventListener> statisticIterator = this.statistics.iterator();
while (statisticIterator.hasNext()) {
statisticIterator.next().start();
}
}
private final class Task implements StripedRunnable {
final AisPacket packet;
final Object stripe;
public Task(AisPacket packet, Object stripe) {
this.packet = packet;
this.stripe = stripe;
}
@Override
public void run() {
try {
doWork(packet);
} catch(Throwable t) {
LOG.error(t.getMessage(), t);
}
}
@Override
public Object getStripe() {
return stripe;
}
}
}