Skip to content

Commit

Permalink
Add basic cache and stats for config
Browse files Browse the repository at this point in the history
  • Loading branch information
hgschmie committed Dec 27, 2023
1 parent babd7e8 commit 83bbc41
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 22 deletions.
22 changes: 15 additions & 7 deletions core/src/main/java/org/jdbi/v3/core/argument/Arguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import java.sql.Types;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -27,6 +26,8 @@
import org.jdbi.v3.core.array.SqlArrayArgumentFactory;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.config.JdbiConfig;
import org.jdbi.v3.core.config.cache.JdbiConfigCache;
import org.jdbi.v3.core.config.cache.JdbiConfigCacheStats;
import org.jdbi.v3.core.qualifier.QualifiedType;
import org.jdbi.v3.meta.Beta;

Expand All @@ -39,7 +40,7 @@
*/
public class Arguments implements JdbiConfig<Arguments> {
private final List<QualifiedArgumentFactory> factories;
private final Map<QualifiedType<?>, Function<Object, Argument>> preparedFactories = new ConcurrentHashMap<>();
private final JdbiConfigCache<QualifiedType<?>, Function<Object, Argument>> preparedFactories;
private final Set<QualifiedType<?>> didPrepare = ConcurrentHashMap.newKeySet();

private ConfigRegistry registry;
Expand All @@ -48,7 +49,8 @@ public class Arguments implements JdbiConfig<Arguments> {
private boolean preparedArgumentsEnabled = true;

public Arguments(final ConfigRegistry registry) {
factories = new CopyOnWriteArrayList<>();
this.factories = new CopyOnWriteArrayList<>();
this.preparedFactories = new JdbiConfigCache<>("argument factories");
this.registry = registry;

// register built-in factories, priority of factories is by reverse registration order
Expand All @@ -73,10 +75,11 @@ public Arguments(final ConfigRegistry registry) {
}

private Arguments(final Arguments that) {
factories = new CopyOnWriteArrayList<>(that.factories);
untypedNullArgument = that.untypedNullArgument;
bindingNullToPrimitivesPermitted = that.bindingNullToPrimitivesPermitted;
preparedArgumentsEnabled = that.preparedArgumentsEnabled;
this.factories = new CopyOnWriteArrayList<>(that.factories);
this.preparedFactories = that.preparedFactories.copy();
this.untypedNullArgument = that.untypedNullArgument;
this.bindingNullToPrimitivesPermitted = that.bindingNullToPrimitivesPermitted;
this.preparedArgumentsEnabled = that.preparedArgumentsEnabled;
}

@Override
Expand Down Expand Up @@ -245,4 +248,9 @@ public void setPreparedArgumentsEnabled(final boolean preparedArgumentsEnabled)
public Arguments createCopy() {
return new Arguments(this);
}

@Override
public Set<JdbiConfigCacheStats> reportStats() {
return Collections.singleton(preparedFactories.getStats());
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/org/jdbi/v3/core/config/ConfigRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
*/
package org.jdbi.v3.core.config;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import org.jdbi.v3.core.argument.Arguments;
import org.jdbi.v3.core.collector.JdbiCollectors;
import org.jdbi.v3.core.config.cache.JdbiConfigCacheStats;
import org.jdbi.v3.core.config.internal.ConfigCaches;
import org.jdbi.v3.core.internal.JdbiClassUtils;
import org.jdbi.v3.core.mapper.ColumnMappers;
Expand Down Expand Up @@ -101,4 +105,10 @@ private Function<ConfigRegistry, JdbiConfig<?>> configFactory(Class<? extends Jd
public ConfigRegistry createCopy() {
return new ConfigRegistry(this);
}

public Set<JdbiConfigCacheStats> reportStats() {
Set<JdbiConfigCacheStats> stats = new LinkedHashSet<>();
configs.values().forEach(config -> stats.addAll(config.reportStats()));
return Collections.unmodifiableSet(stats);
}
}
13 changes: 12 additions & 1 deletion core/src/main/java/org/jdbi/v3/core/config/JdbiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@
*/
package org.jdbi.v3.core.config;

import java.util.Collections;
import java.util.Set;

import org.jdbi.v3.core.config.cache.JdbiConfigCacheStats;

/**
* Interface for classes that hold configuration. Implementations of this interface must have a public
* constructor that optionally takes the {@link ConfigRegistry}.
*
* <p>
* Implementors should ensure that implementations are thread-safe for access and caching purposes, but not
* necessarily for reconfiguration.
*
* @param <This> A "This" type. Should always be the configuration class.
*/
public interface JdbiConfig<This extends JdbiConfig<This>> {

/**
* Returns a copy of this configuration object.
* Changes to the copy should not modify the original, and vice-versa.
Expand All @@ -35,7 +41,12 @@ public interface JdbiConfig<This extends JdbiConfig<This>> {
* The registry will inject itself into the configuration object.
* This can be useful if you need to look up dependencies.
* You will get a new registry after being copied.
*
* @param registry the registry that owns this configuration object
*/
default void setRegistry(ConfigRegistry registry) {}

default Set<JdbiConfigCacheStats> reportStats() {
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 org.jdbi.v3.core.config.cache;

import java.util.concurrent.ConcurrentHashMap;

import org.inferred.freebuilder.shaded.com.google.common.cache.CacheStats;

/**
* Caching class for any config related caches. Keeps track of various statistics that are useful for
* performance measurement.
*
* @param <K> key type of the cache
* @param <V> value type of the cache
*/
public final class JdbiConfigCache<K, V> {

private final String configName;
private final ConcurrentHashMap<K, V> cache;

private final JdbiConfigCacheMetrics cacheMetrics = new JdbiConfigCacheMetrics();
private final JdbiConfigCacheMetrics globalMetrics;

public JdbiConfigCache(String configName) {
this.configName = configName;
this.cache = new ConcurrentHashMap<>();
this.globalMetrics = new JdbiConfigCacheMetrics();
}

private JdbiConfigCache(JdbiConfigCache<K, V> that) {
this.configName = that.configName;
this.cache = new ConcurrentHashMap<>(that.cache);
this.globalMetrics = that.globalMetrics;
}

public JdbiConfigCache<K, V> copy() {
return new JdbiConfigCache<>(this);
}

public void clear() {
this.cache.clear();
}

public V put(K key, V value) {
globalMetrics.put();
cacheMetrics.put();
return this.cache.put(key, value);
}

public V putIfAbsent(K key, V value) {
globalMetrics.put();
cacheMetrics.put();
return this.cache.putIfAbsent(key, value);
}

public V get(K key) {
V value = cache.get(key);
if (value != null) {
globalMetrics.hit();
cacheMetrics.hit();
} else {
globalMetrics.miss();
cacheMetrics.miss();
}
return value;
}

public JdbiConfigCacheStats getStats() {
return new JdbiConfigCacheStats(configName, cache.size(), cacheMetrics, globalMetrics);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.jdbi.v3.core.config.cache;

import java.util.concurrent.atomic.AtomicInteger;

class JdbiConfigCacheMetrics {
private final AtomicInteger cacheHits = new AtomicInteger();
private final AtomicInteger cacheMisses = new AtomicInteger();
private final AtomicInteger cacheWrites = new AtomicInteger();

JdbiConfigCacheMetrics() {
}

void hit() {
cacheHits.incrementAndGet();
}

void miss() {
cacheMisses.incrementAndGet();
}

void put() {
cacheWrites.incrementAndGet();
}

int getCacheWrites() {
return cacheWrites.incrementAndGet();
}

int getCacheHits() {
return cacheHits.get();
}

int getCacheMisses() {
return cacheMisses.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 org.jdbi.v3.core.config.cache;

import java.util.StringJoiner;

@SuppressWarnings("PMD.DataClass")
public final class JdbiConfigCacheStats {

private final String configName;
private final int size;
private final int cacheHits;
private final int cacheMisses;
private final int cacheWrites;
private final int globalHits;
private final int globalMisses;
private final int globalWrites;

JdbiConfigCacheStats(String configName, int cacheSize, JdbiConfigCacheMetrics cacheMetrics, JdbiConfigCacheMetrics globalMetrics) {
this.configName = configName;
this.size = cacheSize;
this.cacheHits = cacheMetrics.getCacheHits();
this.cacheMisses = cacheMetrics.getCacheMisses();
this.cacheWrites = cacheMetrics.getCacheWrites();
this.globalHits = globalMetrics.getCacheHits();
this.globalMisses = globalMetrics.getCacheMisses();
this.globalWrites = globalMetrics.getCacheWrites();
}

public String configName() {
return configName;
}

public String getConfigName() {
return configName;
}

public int getSize() {
return size;
}

public int getCacheHits() {
return cacheHits;
}

public int getCacheMisses() {
return cacheMisses;
}

public int getCacheAccesses() {
return cacheMisses + cacheHits;
}

public int getCacheWrites() {
return cacheWrites;
}

public int getGlobalHits() {
return globalHits;
}

public int getGlobalMisses() {
return globalMisses;
}

public int getGlobalAccesses() {
return globalMisses + globalHits;
}

public int getGlobalWrites() {
return globalWrites;
}

@Override
public String toString() {
return new StringJoiner(", ", JdbiConfigCacheStats.class.getSimpleName() + "[", "]")
.add("configName='" + configName + "'")
.add("size=" + size)
.add("cacheHits=" + cacheHits)
.add("cacheMisses=" + cacheMisses)
.add("cacheWrites=" + cacheWrites)
.add("globalHits=" + globalHits)
.add("globalMisses=" + globalMisses)
.add("globalWrites=" + globalWrites)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 org.jdbi.v3.core.config.cache;

import java.util.Set;

import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.HandleListener;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.core.statement.StatementContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.lang.String.format;

public final class JdbiConfigCacheStatsReporter implements StatementContextListener, HandleListener {

public static final JdbiConfigCacheStatsReporter INSTANCE = new JdbiConfigCacheStatsReporter();

private static final Logger LOG = LoggerFactory.getLogger(JdbiConfigCacheStatsReporter.class);

@Override
public void handleClosed(Handle handle) {
Set<JdbiConfigCacheStats> cacheStats = handle.getConfig().reportStats();
report("Handle", cacheStats);
}

@Override
public void contextCleaned(StatementContext statementContext) {
Set<JdbiConfigCacheStats> cacheStats = statementContext.getConfig().reportStats();
report("Context", cacheStats);
}

private void report(String context, Set<JdbiConfigCacheStats> stats) {
LOG.info("------------------------------------------------------------------------");
LOG.info(format("Cache Stats for %s", context));
stats.forEach(s -> LOG.info(s.toString()));
}
}
Loading

0 comments on commit 83bbc41

Please sign in to comment.