net.openhft.chronicle.queue.impl.RollingResourcesCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chronicle-queue Show documentation
Show all versions of chronicle-queue Show documentation
Java library for persisted low latency messaging (Java 8+)
/*
* Copyright 2016-2020 chronicle.software
*
* https://chronicle.software
*
* 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 net.openhft.chronicle.queue.impl;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.queue.RollCycle;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.WeekFields;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
public class RollingResourcesCache {
public static final ParseCount NO_PARSE_COUNT = new ParseCount("", Integer.MIN_VALUE);
private static final int CACHE_SIZE = Jvm.getInteger("chronicle.queue.rollingResourceCache.size", 128);
private static final int ONE_DAY_IN_MILLIS = 86400000;
private static final int MAX_TIMESTAMP_CACHE_SIZE = 32;
@NotNull
private final Function fileFactory;
@NotNull
private final DateTimeFormatter formatter;
@NotNull
private final Resource[] values;
private final int length;
@NotNull
private final Function fileToName;
private final String format;
private final ConcurrentMap filenameToTimestampCache =
new ConcurrentHashMap<>(MAX_TIMESTAMP_CACHE_SIZE);
private final long epoch;
private ParseCount lastParseCount = NO_PARSE_COUNT;
public RollingResourcesCache(@NotNull final RollCycle cycle, long epoch,
@NotNull Function nameToFile,
@NotNull Function fileToName) {
this(cycle.lengthInMillis(), cycle.format(), epoch, nameToFile, fileToName);
}
private RollingResourcesCache(final int length,
@NotNull String format, long epoch,
@NotNull Function nameToFile,
@NotNull Function fileToName) {
this.length = length;
this.fileToName = fileToName;
this.values = new Resource[CACHE_SIZE];
final long millisInDay = epoch % ONE_DAY_IN_MILLIS;
this.epoch = millisInDay >= 0 ? epoch - millisInDay : -ONE_DAY_IN_MILLIS;
this.format = format;
this.formatter = DateTimeFormatter.ofPattern(this.format).withZone(ZoneId.of("UTC"));
this.fileFactory = nameToFile;
}
/**
* Cache some resources for a rollCycle number.
*
* @param cycle the rollCycle number to format
* @return the Resource
*/
@NotNull
public Resource resourceFor(long cycle) {
long millisSinceBeginningOfEpoch = (cycle * length);
long h = Maths.hash64(millisSinceBeginningOfEpoch);
h ^= h >> 32;
int hash = (int) h & (CACHE_SIZE - 1);
Resource dv = values[hash];
if (dv == null || dv.millis != millisSinceBeginningOfEpoch) {
final Instant instant = Instant.ofEpochMilli(millisSinceBeginningOfEpoch + epoch);
@NotNull String text = formatter.format(instant);
values[hash] = dv = new Resource(millisSinceBeginningOfEpoch, text, fileFactory.apply(text));
}
return dv;
}
public int parseCount(@NotNull String name) {
ParseCount last = this.lastParseCount;
if (name.equals(last.name))
return last.count;
int count = parseCount0(name);
lastParseCount = new ParseCount(name, count);
return count;
}
private int parseCount0(@NotNull String name) {
try {
TemporalAccessor parse = formatter.parse(name);
if (!parse.isSupported(ChronoField.EPOCH_DAY)) {
final WeekFields weekFields = WeekFields.of(formatter.getLocale());
if (parse.isSupported(weekFields.weekBasedYear()) && parse.isSupported(weekFields.weekOfWeekBasedYear())) {
int year = Math.toIntExact(parse.getLong(weekFields.weekBasedYear()));
int week = Math.toIntExact(parse.getLong(weekFields.weekOfWeekBasedYear()));
LocalDate ld = LocalDate.now()
.withYear(year)
.with(weekFields.weekOfYear(), week)
.with(weekFields.dayOfWeek(), 1);
return Math.toIntExact(ld.toEpochDay());
}
throw new UnsupportedOperationException("Unable to parse " + name + " using format " + format);
}
long epochDay = parse.getLong(ChronoField.EPOCH_DAY) * 86400;
if (parse.isSupported(ChronoField.SECOND_OF_DAY))
epochDay += parse.getLong(ChronoField.SECOND_OF_DAY);
return Maths.toInt32((epochDay - ((epoch) / 1000)) / (length / 1000));
} catch (DateTimeParseException e) {
throw new RuntimeException(String.format(
"Unable to parse %s using format %s", name, format), e);
}
}
public Long toLong(File file) {
final Long cachedValue = filenameToTimestampCache.get(file);
if (cachedValue != null) {
return cachedValue;
}
final TemporalAccessor parse = formatter.parse(fileToName.apply(file));
final long value;
if (length == ONE_DAY_IN_MILLIS) {
value = parse.getLong(ChronoField.EPOCH_DAY);
} else if (length < ONE_DAY_IN_MILLIS) {
value = Instant.from(parse).toEpochMilli() / length;
} else {
long daysSinceEpoch = parse.getLong(ChronoField.EPOCH_DAY);
long adjShift = daysSinceEpoch < 0 ? -1 : 0;
value = adjShift + ((daysSinceEpoch * 86400) / (length / 1000));
}
if (filenameToTimestampCache.size() >= MAX_TIMESTAMP_CACHE_SIZE) {
filenameToTimestampCache.clear();
}
filenameToTimestampCache.put(file, value);
return value;
}
static final class ParseCount {
final String name;
final int count;
public ParseCount(String name, int count) {
this.name = name;
this.count = count;
}
}
public static final class Resource {
public final long millis;
public final String text;
public final File path;
public final File parentPath;
public boolean pathExists;
Resource(long millis, String text, File path) {
this.millis = millis;
this.text = text;
this.path = path;
this.parentPath = path.getParentFile();
}
}
}