package bucket; import org.junit.Test; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; public class BucketTest { @Test public void sample() throws Exception { Instant now = Instant.now(); Instant earlier = now.minus(20, ChronoUnit.MINUTES); List<Event> events = Arrays.asList(new Event(now, "now"), new Event(earlier, "earlier")); List<Event> sortedEvents = events.stream().sorted(byTimeStamp()).collect(Collectors.toList()); Duration samplingRate = Duration.ofMinutes(1); Duration inspectionRange = Duration.ofMinutes(10); Instant earliest = sortedEvents.get(0).timestamp; Instant latest = sortedEvents.get(sortedEvents.size() - 1).timestamp; BucketAssemblyLine bucketAssemblyLine = new BucketAssemblyLine(samplingRate, inspectionRange, earlier); } private Comparator<Event> byTimeStamp() { return new Comparator<Event>() { @Override public int compare(Event o1, Event o2) { return o1.timestamp.compareTo(o2.timestamp); } }; } public static class BucketAssemblyLine { private final LinkedList<Bucket> allBuckets = new LinkedList<>(); private final LinkedList<Bucket> assemblyLine = new LinkedList<>(); private final Duration samplingRate; private final Duration inspectionRange; public BucketAssemblyLine(Duration samplingRate, Duration inspectionRange, Instant earliest) { this.samplingRate = samplingRate; this.inspectionRange = inspectionRange; addBucket(new Bucket(earliest, earliest.plus(inspectionRange))); } public void putIntoBucket(Event event) { assemblyLine.removeIf(bucket -> !event.timestamp.isBefore(bucket.latest)); Instant nextNewBucketStart = allBuckets.peekLast().earliest().plus(samplingRate); if (wouldAlreadyBeAddedToTheNextBucket(event, nextNewBucketStart)) { for (; wouldAlreadyBeAddedToTheNextBucket(event, nextNewBucketStart); nextNewBucketStart = nextNewBucketStart.plus(samplingRate)) { Bucket newBucket = new Bucket(nextNewBucketStart, nextNewBucketStart.plus(inspectionRange)); addBucket(newBucket); } } Boolean addedToAtLeastOneBucket = assemblyLine.stream().reduce(Boolean.FALSE, (aBoolean, aBucket) -> aBucket.put(event), (a, b) -> a || b); if (!addedToAtLeastOneBucket) { throw new IllegalStateException("There was no bucket to take the event"); } } private boolean wouldAlreadyBeAddedToTheNextBucket(Event event, Instant nextNewBucketStart) { return !nextNewBucketStart.isAfter(event.timestamp); } public List<Bucket> allBuckets() { return allBuckets; } private void addBucket(Bucket bucket) { allBuckets.add(bucket); assemblyLine.add(bucket); } } public static class Bucket { private final List<Event> events = new LinkedList<>(); private final Instant earliest; private final Instant latest; public Bucket(Instant earliest, Instant latest) { this.latest = latest; this.earliest = earliest; } public Instant earliest() { return earliest; } public Instant latest() { return latest; } public boolean put(Event event) { if (!event.timestamp.isBefore(latest)) { return false; } if (earliest().isAfter(event.timestamp)) { return false; } return this.events.add(event); } public List<Event> items() { return events; } } public static class Event { public final Instant timestamp; public final String data; public Event(Instant timestamp, String data) { this.timestamp = timestamp; this.data = data; } } }