/**
* Copyright 2015-2017 The OpenZipkin Authors
*
* 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 zipkin.storage.elasticsearch.http;
import com.google.auto.value.AutoValue;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import zipkin.internal.Util;
@AutoValue
abstract class IndexNameFormatter {
static Builder builder() {
return new AutoValue_IndexNameFormatter.Builder();
}
abstract Builder toBuilder();
private static final String DAILY_INDEX_FORMAT = "yyyy-MM-dd";
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
abstract String index();
abstract char dateSeparator();
abstract ThreadLocal<SimpleDateFormat> dateFormat(); // SimpleDateFormat isn't thread-safe
@AutoValue.Builder static abstract class Builder {
abstract Builder index(String index);
abstract Builder dateSeparator(char dateSeparator);
abstract Builder dateFormat(ThreadLocal<SimpleDateFormat> dateFormat);
abstract char dateSeparator();
final IndexNameFormatter build() {
return dateFormat(new ThreadLocal<SimpleDateFormat>() {
@Override protected SimpleDateFormat initialValue() {
SimpleDateFormat result =
new SimpleDateFormat(DAILY_INDEX_FORMAT.replace('-', dateSeparator()));
result.setTimeZone(TimeZone.getTimeZone("UTC"));
return result;
}
}).autoBuild();
}
abstract IndexNameFormatter autoBuild();
}
/**
* Returns a set of index patterns that represent the range provided. Notably, this compresses
* months or years using wildcards (in order to send smaller API calls).
*
* <p>For example, if {@code beginMillis} is 2016-11-30 and {@code endMillis} is 2017-01-02, the
* result will be 2016-11-30, 2016-12-*, 2017-01-01 and 2017-01-02.
*/
List<String> indexNamePatternsForRange(long beginMillis, long endMillis) {
GregorianCalendar current = midnightUTC(beginMillis);
GregorianCalendar end = midnightUTC(endMillis);
if (current.equals(end)) {
return Collections.singletonList(indexNameForTimestamp(current.getTimeInMillis()));
}
List<String> indices = new ArrayList<>();
while (current.compareTo(end) <= 0) {
if (current.get(Calendar.MONTH) == 0 && current.get(Calendar.DATE) == 1) {
// attempt to compress a year
current.set(Calendar.DAY_OF_YEAR, current.getActualMaximum(Calendar.DAY_OF_YEAR));
if (current.compareTo(end) <= 0) {
indices.add(
String.format("%s-%s%c*", index(), current.get(Calendar.YEAR), dateSeparator()));
current.add(Calendar.DATE, 1); // rollover to next year
continue;
} else {
current.set(Calendar.DAY_OF_YEAR, 1); // rollback to first of the year
}
} else if (current.get(Calendar.DATE) == 1) {
// attempt to compress a month
current.set(Calendar.DATE, current.getActualMaximum(Calendar.DATE));
if (current.compareTo(end) <= 0) {
indices.add(String.format("%s-%s%c%02d%c*", index(),
current.get(Calendar.YEAR), dateSeparator(),
current.get(Calendar.MONTH) + 1, dateSeparator()
));
current.add(Calendar.DATE, 1); // rollover to next month
continue;
} else {
current.set(Calendar.DATE, 1); // rollback to first of the month
}
}
indices.add(indexNameForTimestamp(current.getTimeInMillis()));
current.add(Calendar.DATE, 1);
}
return indices;
}
static GregorianCalendar midnightUTC(long epochMillis) {
GregorianCalendar result = new GregorianCalendar(UTC);
result.setTimeInMillis(Util.midnightUTC(epochMillis));
return result;
}
String indexNameForTimestamp(long timestampMillis) {
return index() + "-" + dateFormat().get().format(new Date(timestampMillis));
}
// for testing
long parseDate(String timestamp) {
try {
return dateFormat().get().parse(timestamp).getTime();
} catch (ParseException e) {
throw new AssertionError(e);
}
}
String allIndices() {
return index() + "-*";
}
}