/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.spotify.heroic.aggregation; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.spotify.heroic.common.DateRange; import lombok.Data; import lombok.RequiredArgsConstructor; public interface BucketStrategy { long MAX_BUCKET_COUNT = 100000L; BucketStrategy START = new Start(); BucketStrategy END = new End(); Mapping setup(DateRange range, long size, long extent); @JsonCreator static BucketStrategy create(final String strategy) { switch (strategy) { case "start": return START; case "end": return END; default: throw new IllegalArgumentException(strategy); } } class Start implements BucketStrategy { @JsonValue public String value() { return "start"; } @Override public Mapping setup(final DateRange range, final long size, final long extent) { final long start = range.start(); final long count = range.diff() / size; if (count < 1 || count > MAX_BUCKET_COUNT) { throw new IllegalArgumentException(String.format("range %s, size %d", range, size)); } return new StartMapping(range.start(), start, size, extent, (int) count); } @RequiredArgsConstructor private static class StartMapping implements Mapping { private final long start; private final long offset; private final long size; private final long extent; private final int buckets; /** * Calculate the start and end index of the buckets that should be seeded for the given * timestamp. * * This guarantees that each timestamp ends up in the range [start, start + extent) for * any given bucket. * * @param timestamp timestamp to map * @return a start end and index */ @Override public StartEnd map(final long timestamp) { /* adjust the timestamp to the number of buckets */ final long adjusted = timestamp - offset; final int start = Math.max((int) ((adjusted + (size - extent)) / size), 0); final int end = Math.min((int) ((adjusted + size) / size), buckets); return new BucketStrategy.StartEnd(start, end); } @Override public long start() { return start; } @Override public int buckets() { return buckets; } } } class End implements BucketStrategy { @JsonValue public String value() { return "end"; } @Override public Mapping setup(final DateRange range, final long size, final long extent) { final long start = range.start() + size; final long count = (range.diff() + size) / size - 1; if (count < 1 || count > MAX_BUCKET_COUNT) { throw new IllegalArgumentException(String.format("range %s, size %d", range, size)); } return new EndMapping(start, range.start(), size, extent, (int) count); } @RequiredArgsConstructor private static class EndMapping implements Mapping { private final long start; private final long offset; private final long size; private final long extent; private final int buckets; /** * Calculate the start and end index of the buckets that should be seeded for the given * timestamp. * * This guarantees that each timestamp ends up in the range (end - extent, end] for * any given bucket. * * @param timestamp timestamp to map * @return a start end and index */ @Override public StartEnd map(final long timestamp) { /* adjust the timestamp to the number of buckets */ final long adjusted = timestamp - offset; final int start = Math.max((int) ((adjusted - 1) / size), 0); final int end = Math.min((int) ((adjusted + extent - 1) / size), buckets); return new StartEnd(start, end); } @Override public long start() { return start; } @Override public int buckets() { return buckets; } } } interface Mapping { StartEnd map(final long timestamp); long start(); int buckets(); } /** * A start, and an end bucket (exclusive) selected. */ @Data class StartEnd { private final int start; private final int end; } }