blob: 37d2845fb925796e30dc43c2616c6c35bd4b1ac6 [file] [log] [blame]
// Copyright (C) 2020 The Android Open Source Project
//
// 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 com.googlesource.gerrit.modules.cache.chroniclemap;
import static com.googlesource.gerrit.modules.cache.chroniclemap.ChronicleMapCacheFactory.PRUNE_DELAY;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.File;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import org.apache.commons.io.FilenameUtils;
import org.eclipse.jgit.lib.Config;
public class ChronicleMapCacheConfig {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final File cacheFile;
private final boolean cacheFileExists;
private final File indexFile;
private final long maxEntries;
private final long averageKeySize;
private final long averageValueSize;
private final Duration expireAfterWrite;
private final Duration refreshAfterWrite;
private final int maxBloatFactor;
private final int percentageFreeSpaceEvictionThreshold;
private final String configKey;
private final Duration persistIndexEvery;
private final long persistIndexEveryNthPrune;
public interface Factory {
ChronicleMapCacheConfig create(
@Assisted("ConfigKey") String configKey,
@Assisted File cacheFile,
@Nullable @Assisted("ExpireAfterWrite") Duration expireAfterWrite,
@Nullable @Assisted("RefreshAfterWrite") Duration refreshAfterWrite);
ChronicleMapCacheConfig createWithValues(
@Assisted("ConfigKey") String configKey,
@Assisted File cacheFile,
@Nullable @Assisted("ExpireAfterWrite") Duration expireAfterWrite,
@Nullable @Assisted("RefreshAfterWrite") Duration refreshAfterWrite,
@Assisted("maxEntries") long maxEntries,
@Assisted("avgKeySize") long avgKeySize,
@Assisted("avgValueSize") long avgValueSize,
@Assisted("maxBloatFactor") int maxBloatFactor,
@Assisted("PersistIndexEvery") Duration persistIndexEver);
}
@AssistedInject
ChronicleMapCacheConfig(
@GerritServerConfig Config cfg,
CachesWithoutChronicleMapConfigMetric cachesWithoutConfigMetric,
@Assisted("ConfigKey") String configKey,
@Assisted File cacheFile,
@Nullable @Assisted("ExpireAfterWrite") Duration expireAfterWrite,
@Nullable @Assisted("RefreshAfterWrite") Duration refreshAfterWrite) {
this(
cfg,
cachesWithoutConfigMetric,
configKey,
cacheFile,
expireAfterWrite,
refreshAfterWrite,
cfg.getLong("cache", configKey, "maxEntries", Defaults.maxEntriesFor(configKey)),
cfg.getLong("cache", configKey, "avgKeySize", Defaults.averageKeySizeFor(configKey)),
cfg.getLong("cache", configKey, "avgValueSize", Defaults.avgValueSizeFor(configKey)),
cfg.getInt("cache", configKey, "maxBloatFactor", Defaults.maxBloatFactorFor(configKey)),
Duration.ofSeconds(
cfg.getTimeUnit(
"cache",
null,
"persistIndexEvery",
Defaults.persistIndexEvery().toSeconds(),
SECONDS)));
}
@AssistedInject
ChronicleMapCacheConfig(
@GerritServerConfig Config cfg,
CachesWithoutChronicleMapConfigMetric cachesWithoutConfigMetric,
@Assisted("ConfigKey") String configKey,
@Assisted File cacheFile,
@Nullable @Assisted("ExpireAfterWrite") Duration expireAfterWrite,
@Nullable @Assisted("RefreshAfterWrite") Duration refreshAfterWrite,
@Assisted("maxEntries") long maxEntries,
@Assisted("avgKeySize") long avgKeySize,
@Assisted("avgValueSize") long avgValueSize,
@Assisted("maxBloatFactor") int maxBloatFactor,
@Assisted("PersistIndexEvery") Duration persistIndexEvery) {
this.cacheFile = cacheFile;
this.cacheFileExists = cacheFile.exists();
this.indexFile = resolveIndexFile(cacheFile);
this.configKey = configKey;
this.maxEntries = maxEntries;
this.averageKeySize = avgKeySize;
this.averageValueSize = avgValueSize;
this.maxBloatFactor = maxBloatFactor;
this.expireAfterWrite =
Duration.ofSeconds(
ConfigUtil.getTimeUnit(
cfg, "cache", configKey, "maxAge", toSeconds(expireAfterWrite), SECONDS));
this.refreshAfterWrite =
Duration.ofSeconds(
ConfigUtil.getTimeUnit(
cfg,
"cache",
configKey,
"refreshAfterWrite",
toSeconds(refreshAfterWrite),
SECONDS));
this.percentageFreeSpaceEvictionThreshold =
cfg.getInt(
"cache",
configKey,
"percentageFreeSpaceEvictionThreshold",
Defaults.percentageFreeSpaceEvictionThreshold());
long persistIndexEverySeconds = persistIndexEvery.getSeconds();
if (persistIndexEverySeconds < PRUNE_DELAY) {
logger.atWarning().log(
"Configured 'persistIndexEvery' duration [%ds] is lower than minimal threshold [%ds]."
+ " Minimal threshold will be used.",
persistIndexEverySeconds, PRUNE_DELAY);
persistIndexEverySeconds = PRUNE_DELAY;
} else if (persistIndexEverySeconds % PRUNE_DELAY != 0L) {
logger.atWarning().log(
"Configured 'persistIndexEvery' duration [%ds] will be rounded down to a multiple of"
+ " [%ds]. [%ds] is the minimal 'persistIndexEvery' resolution.",
persistIndexEverySeconds, PRUNE_DELAY, PRUNE_DELAY);
persistIndexEverySeconds = (persistIndexEverySeconds / PRUNE_DELAY) * PRUNE_DELAY;
}
this.persistIndexEvery = Duration.ofSeconds(persistIndexEverySeconds);
this.persistIndexEveryNthPrune = persistIndexEverySeconds / PRUNE_DELAY;
emitMetricForMissingConfig(cfg, cachesWithoutConfigMetric, configKey);
}
public int getPercentageFreeSpaceEvictionThreshold() {
return percentageFreeSpaceEvictionThreshold;
}
public Duration getExpireAfterWrite() {
return expireAfterWrite;
}
public Duration getRefreshAfterWrite() {
return refreshAfterWrite;
}
public Duration getPersistIndexEvery() {
return persistIndexEvery;
}
public long getPersistIndexEveryNthPrune() {
return persistIndexEveryNthPrune;
}
public long getMaxEntries() {
return maxEntries;
}
public File getCacheFile() {
return cacheFile;
}
public boolean getCacheFileExists() {
return cacheFileExists;
}
public File getIndexFile() {
return indexFile;
}
public long getAverageKeySize() {
return averageKeySize;
}
public long getAverageValueSize() {
return averageValueSize;
}
public int getMaxBloatFactor() {
return maxBloatFactor;
}
public String getConfigKey() {
return configKey;
}
private static void emitMetricForMissingConfig(
Config cfg,
CachesWithoutChronicleMapConfigMetric cachesWithoutConfigMetric,
String configKey) {
if (!cfg.getSubsections("cache").stream()
.filter(cache -> cache.equalsIgnoreCase(configKey))
.filter(
cache ->
isConfigured(cfg, cache, "maxEntries")
|| isConfigured(cfg, cache, "avgKeySize")
|| isConfigured(cfg, cache, "avgValueSize")
|| isConfigured(cfg, cache, "maxBloatFactor"))
.findAny()
.isPresent()) {
cachesWithoutConfigMetric.incrementForCache(configKey);
}
}
private static boolean isConfigured(Config cfg, String configKey, String name) {
return cfg.getString("cache", configKey, name) != null;
}
private static File resolveIndexFile(File persistedCacheFile) {
String cacheFileName = persistedCacheFile.getName();
String indexFileName = String.format("%s.index", FilenameUtils.getBaseName(cacheFileName));
return Path.of(persistedCacheFile.getParent()).resolve(indexFileName).toFile();
}
private static long toSeconds(@Nullable Duration duration) {
return duration != null ? duration.getSeconds() : 0;
}
@Singleton
static class Defaults {
public static final long DEFAULT_MAX_ENTRIES = 1000;
public static final long DEFAULT_AVG_KEY_SIZE = 128;
public static final long DEFAULT_AVG_VALUE_SIZE = 2048;
public static final int DEFAULT_MAX_BLOAT_FACTOR = 1;
public static final int DEFAULT_PERCENTAGE_FREE_SPACE_EVICTION_THRESHOLD = 90;
public static final Duration DEFAULT_PERSIST_INDEX_EVERY = Duration.ofMinutes(15);
@VisibleForTesting
static final ImmutableMap<String, DefaultConfig> defaultMap =
new ImmutableMap.Builder<String, DefaultConfig>()
.put("accounts", DefaultConfig.create(30, 256, 1000, 1))
.put("change_kind", DefaultConfig.create(59, 26, 1000, 1))
.put("change_notes", DefaultConfig.create(36, 10240, 1000, 3))
.put("comment_context", DefaultConfig.create(80, 662, 2000, 3))
.put("conflicts", DefaultConfig.create(70, 16, 1000, 1))
.put("diff_intraline", DefaultConfig.create(512, 2048, 1000, 2))
.put("diff_summary", DefaultConfig.create(128, 2048, 1000, 1))
.put("external_ids_map", DefaultConfig.create(128, 204800, 2, 1))
.put("gerrit_file_diff", DefaultConfig.create(342, 883, 1000, 1))
.put("git_file_diff", DefaultConfig.create(349, 943, 1000, 1))
.put("git_modified_files", DefaultConfig.create(349, 943, 1000, 1))
.put("git_tags", DefaultConfig.create(43, 6673, 1000, 3))
.put("groups_byuuid_persisted", DefaultConfig.create(64, 182, 1000, 1))
.put("mergeability", DefaultConfig.create(79, 16, 65000, 2))
.put("modified_files", DefaultConfig.create(138, 2600, 1000, 1))
.put("oauth_tokens", DefaultConfig.create(8, 2048, 1000, 1))
.put("persisted_projects", DefaultConfig.create(128, 1024, 250, 2))
.put("pure_revert", DefaultConfig.create(55, 16, 1000, 1))
.put("web_sessions", DefaultConfig.create(45, 221, 1000, 1))
.build();
public static long averageKeySizeFor(String configKey) {
return Optional.ofNullable(defaultMap.get(configKey))
.map(DefaultConfig::averageKey)
.orElse(DEFAULT_AVG_KEY_SIZE);
}
public static long avgValueSizeFor(String configKey) {
return Optional.ofNullable(defaultMap.get(configKey))
.map(DefaultConfig::averageValue)
.orElse(DEFAULT_AVG_VALUE_SIZE);
}
public static long maxEntriesFor(String configKey) {
return Optional.ofNullable(defaultMap.get(configKey))
.map(DefaultConfig::entries)
.orElse(DEFAULT_MAX_ENTRIES);
}
public static int maxBloatFactorFor(String configKey) {
return Optional.ofNullable(defaultMap.get(configKey))
.map(DefaultConfig::maxBloatFactor)
.orElse(DEFAULT_MAX_BLOAT_FACTOR);
}
public static int percentageFreeSpaceEvictionThreshold() {
return DEFAULT_PERCENTAGE_FREE_SPACE_EVICTION_THRESHOLD;
}
public static Duration persistIndexEvery() {
return DEFAULT_PERSIST_INDEX_EVERY;
}
}
}