package com.scopely.infrastructure.kinesis; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.kinesis.AmazonKinesis; import com.amazonaws.services.kinesis.AmazonKinesisClient; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.S3ObjectSummary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class KinesisVcr { private static final Logger LOGGER = LoggerFactory.getLogger(KinesisVcr.class); public static void main(String[] args) { VcrConfiguration vcrConfiguration = new VcrConfiguration(System.getenv()); vcrConfiguration.validateConfiguration(); AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); AmazonS3 s3 = new AmazonS3Client(credentialsProvider); AmazonKinesis kinesis = new AmazonKinesisClient(credentialsProvider); if (args.length > 0 && ("play".equals(args[0]) || "estimate".equals(args[0]))) { if (vcrConfiguration.targetStream == null) { throw new IllegalArgumentException("Must specify a target stream for playback or estimation."); } if (args.length == 1) { throw new IllegalArgumentException("Must be called with at least two arguments: e.g., `kinesis-vcr play 2014-05-01T00:00:00 2015-05-01T00:00:00` " + "or `kinesis-vcr play 2014-05-01T00:00:00`"); } String startDateArg = args[1]; LocalDateTime start = parseToLocalDateTime(startDateArg); if (start == null) { throw new IllegalArgumentException("Could not parse start date; should be formatted 2015-08-01 or 2015-08-01T12:12:00"); } LocalDateTime end = null; if (args.length > 2) { end = parseToLocalDateTime(args[2]); if (end == null) { throw new IllegalArgumentException("Could not parse end date; should be formatted 2015-08-01 or 2015-08-01T12:12:00"); } } KinesisPlayer player = new KinesisPlayer(vcrConfiguration, s3, kinesis); if ("play".equals(args[0])) { replay(player, start, end, vcrConfiguration); } else { estimateReplayTime(player, start, end, vcrConfiguration); } } else { KinesisRecorder recorder = new KinesisRecorder(vcrConfiguration, s3, credentialsProvider); recorder.run(); } } private static void estimateReplayTime(KinesisPlayer player, LocalDateTime start, LocalDateTime end, VcrConfiguration vcrConfiguration) { AtomicLong fileCount = new AtomicLong(); long dataSizeInBytes = player .playableObjects(start, end) .doOnNext(ignore -> fileCount.incrementAndGet()) .map(S3ObjectSummary::getSize) .reduce((lhs, rhs) -> lhs + rhs) .toBlocking() .first(); // number of shards of the target stream int numberOfShards = player.getNumberOfShards(); // all replayable data size in MB long dataSizeInMegaBytes = dataSizeInBytes / 1000 / 1000; // each shard can receive up to 1MB/s long estimatedTimeInMinutes = dataSizeInMegaBytes / numberOfShards / 60; // ceil time to biggest (reasonable) time unit String estimatedTimeHuman = minsToHumanReadableTimeSpan(estimatedTimeInMinutes); LOGGER.info("Target stream ({}) has {} shards", vcrConfiguration.targetStream, numberOfShards); LOGGER.info("It would take around {} to replay the data in the provided range, which has {} files and a total size of {} MB", estimatedTimeHuman, fileCount, dataSizeInMegaBytes); } private static void replay(KinesisPlayer player, LocalDateTime start, LocalDateTime end, VcrConfiguration vcrConfiguration) { AtomicInteger recordsCounter = new AtomicInteger(); int count = player .play(start, end) .doOnNext(each -> System.out.print("Sent " + recordsCounter.incrementAndGet() + " records to kinesis\r")) .count() .toBlocking() .first(); LOGGER.info("Wrote {} records to output Kinesis stream {}", count, vcrConfiguration.targetStream); System.exit(0); } private static LocalDateTime parseToLocalDateTime(String input) { LocalDateTime dateTime = null; try { dateTime = LocalDateTime.parse(input); } catch (DateTimeParseException ignored) { // no-op } try { dateTime = LocalDate.parse(input).atTime(0, 0); } catch (DateTimeParseException ignored) { // no-op } return dateTime; } /** * Rounds up the provided time in minutes to the biggest time unit (up to months) */ private static String minsToHumanReadableTimeSpan(long timeInMinutes) { if (timeInMinutes < 60) { return String.format(Locale.US, "%d mins", timeInMinutes); } if (TimeUnit.MINUTES.toHours(timeInMinutes) < 24) { return String.format(Locale.US, "%d hours", TimeUnit.MINUTES.toHours(timeInMinutes)); } long timeInDays = TimeUnit.MINUTES.toDays(timeInMinutes); if (timeInDays < 30) { return String.format(Locale.US, "%d days", timeInDays); } return String.format(Locale.US, "%d months", timeInDays / 30); } }