/* Copyright (c) 2011 Danish Maritime Authority. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dk.dma.ais.store; import com.beust.jcommander.Parameter; import com.google.inject.Injector; import dk.dma.ais.packet.AisPacket; import dk.dma.ais.reader.AisReader; import dk.dma.ais.reader.AisReaders; import dk.dma.ais.store.importer.ImportConfigGenerator; import dk.dma.ais.store.importer.PacketsAreaCell10SSTableWriter; import dk.dma.ais.store.importer.PacketsAreaCell1SSTableWriter; import dk.dma.ais.store.importer.PacketsAreaUnknownSSTableWriter; import dk.dma.ais.store.importer.PacketsMmsiSSTableWriter; import dk.dma.ais.store.importer.PacketsTimeSSTableWriter; import dk.dma.commons.app.AbstractCommandLineTool; import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.config.Schema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; /** * @author Jens Tuxen */ public class FileSSTableConverter extends AbstractCommandLineTool { /** The logger. */ static final Logger LOG = LoggerFactory .getLogger(FileSSTableConverter.class); @Parameter(names = "-keyspace", description = "The keyspace in cassandra") String keyspace = "aisdata"; /** * Naming scheme "in directory" comes from Cassandra */ @Parameter(names = { "-path", "-output", "-o" }, description = "path to extract to") String inDirectory; @Parameter(names = { "-import", "-input", "-i" }, description = "Path to directory with files to import", required = true) String path; @Parameter(names = "-glob", description = "pattern for files to read (default *)") String glob = "*"; @Parameter(names = "-recursive", description = "recursive directory reader") boolean recursive = true; @Parameter(names = "-verbose", description = "verbose prints packets/second stats") boolean verbose; @Parameter(names = "-tag", description = "Overwrite the tag") String tag; @Parameter(names = "-compressor", description = "LZ4Compressor, DeflateCompressor") String compressor = "LZ4Compressor"; @Parameter(names = "-bufferSize", description = "buffer size in mb (roughly the size of each flush to sstable, beware of heap usage, 128m ~ 1g heap") int bufferSize = 128; static { org.apache.cassandra.config.Config.setClientMode(true); } /** {@inheritDoc} */ @Override protected void run(Injector injector) throws Exception { //bootstrap a valid cassandra.yaml config file into the inDirectory ImportConfigGenerator.generate(inDirectory); Properties props = System.getProperties(); props.setProperty("cassandra.config", Paths.get("file://", inDirectory, "cassandra.yaml").toString()); Arrays.asList( new PacketsTimeSSTableWriter(inDirectory, keyspace), new PacketsMmsiSSTableWriter(inDirectory, keyspace), new PacketsAreaCell1SSTableWriter(inDirectory, keyspace), new PacketsAreaCell10SSTableWriter(inDirectory, keyspace), new PacketsAreaUnknownSSTableWriter(inDirectory, keyspace) ).forEach(sstableWriter -> { try { LOG.info("Streaming AIS packets to " + sstableWriter.table()); streamAllAisPacketsTo(sstableWriter); sstableWriter.close(); clearKeyspaceDefinition(); } catch (IOException e) { LOG.error(e.getMessage(), e); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } }); shutdown(); System.exit(0); } private void streamAllAisPacketsTo(Consumer<AisPacket> consumer) throws IOException, InterruptedException { final AtomicLong acceptedCount = new AtomicLong(); final AtomicLong[] numberOfPacketsProcessedSinceLastOutput = {new AtomicLong()}; final Instant[] timeOfLastOutput = {Instant.now()}; AisReader reader = AisReaders.createDirectoryReader(path, glob, recursive); if (tag != null) { reader.setSourceId(tag); } // print stats if verbose if (verbose) { reader.registerPacketHandler(packet -> { if (numberOfPacketsProcessedSinceLastOutput[0].incrementAndGet() % 1000000 == 0) { Instant now = Instant.now(); Duration timeSinceLastOutput = Duration.between(timeOfLastOutput[0], now); LOG.info("Conversion rate " + ((int) (numberOfPacketsProcessedSinceLastOutput[0].floatValue() / ((float) timeSinceLastOutput.toMillis())*1e3) + " packets/s, " + (acceptedCount.longValue()+1L) + " total packets processed.")); numberOfPacketsProcessedSinceLastOutput[0] = new AtomicLong(); timeOfLastOutput[0] = now; } }); } //add "accepted" counter reader.registerPacketHandler(p -> acceptedCount.incrementAndGet()); reader.registerPacketHandler(consumer); reader.start(); reader.join(); LOG.info("Finished processing directory, " + acceptedCount + " packets was converted from " + path); } private void clearKeyspaceDefinition() { // http://stackoverflow.com/questions/26137083/cassandra-does-cqlsstablewriter-support-writing-to-multiple-column-families-co KSMetaData ksm = Schema.instance.getKSMetaData(keyspace); Schema.instance.clearKeyspaceDefinition(ksm); } public static void main(String[] args) throws Exception { new FileSSTableConverter().execute(args); } }