Skip to content

Commit

Permalink
Updates to provide a service for reviewing the number of objects cach…
Browse files Browse the repository at this point in the history
…ed in EhCache including shallow and deep sizes from the largest object per cache.
  • Loading branch information
dxasupport committed Nov 8, 2024
1 parent 1f1985d commit 9985f4b
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.sdl.dxa.caching.LocalizationAwareKeyGenerator;
import com.sdl.dxa.caching.NamedCacheProvider;
import com.sdl.dxa.caching.NeverCached;
import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.webapp.common.api.model.ViewModel;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
Expand All @@ -26,6 +27,8 @@ public abstract class SimpleCacheWrapper<B, V> {

private NamedCacheProvider cacheProvider;

private CacheStatisticsProvider cacheStatisticsProvider;

@Autowired
public void setKeyGenerator(LocalizationAwareKeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
Expand All @@ -36,6 +39,11 @@ public void setCacheProvider(NamedCacheProvider cacheProvider) {
this.cacheProvider = cacheProvider;
}

@Autowired(required = false)
public void setCacheStatisticsProvider(CacheStatisticsProvider cacheStatisticsProvider) {
this.cacheStatisticsProvider = cacheStatisticsProvider;
}

@PostConstruct
public void init() {
log.debug("Creating of cache {} on startup", getCacheName());
Expand Down Expand Up @@ -115,6 +123,9 @@ public V addAndGet(Object key, V value) {

getCache().put(key, value);
logPut(key, getCache().getName());
if (cacheStatisticsProvider != null) {
cacheStatisticsProvider.storeStatsInfo(getCacheName(), value);
}
return value;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.sdl.dxa.caching.statistics;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import org.ehcache.sizeof.SizeOf;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Component
@Getter
@Slf4j
public class CacheStatisticsProvider {

private static final String SHALLOW_SIZE = "largestCachedObject-ShallowSize-InBytes";
private static final String DEEP_SIZE = "largestCachedObject-DeepSize-InBytes";

@Value("${dxa.caching.statistics.enabled:#{false}}")
private boolean enabled;

private final ConcurrentMap<String, Map<String, Long>> mapper = new ConcurrentHashMap<>();

public void storeStatsInfo(String cacheName, Object cachedObject) {
if (!enabled)
return;

SizeOf sizeOf = SizeOf.newInstance();
try {
long shallowSize = sizeOf.sizeOf(cachedObject);
long deepSize = sizeOf.deepSizeOf(cachedObject);

Map<String, Long> entry = getStatistics(cacheName);
if (entry == null) {
entry = new HashMap<>();
entry.put(SHALLOW_SIZE, shallowSize);
entry.put(DEEP_SIZE, deepSize);
mapper.put(cacheName, entry);
} else {
// We only want to keep the latest largest record
boolean update = false;
long mappedShallowSize = entry.get(SHALLOW_SIZE);
if (shallowSize > mappedShallowSize) {
entry.put(SHALLOW_SIZE, shallowSize);
update = true;
}
long mappedDeepSize = entry.get(DEEP_SIZE);
if (deepSize > mappedDeepSize) {
entry.put(DEEP_SIZE, deepSize);
update = true;
}
if (update) {
mapper.put(cacheName, entry);
}
}
}
catch(Exception exception) {
log.warn(String.format("There has been a problem fetching the size of the EhCache object [%s]",
exception.getMessage()));
}
}

public Map<String, Long> getStatistics(String cacheName) {
return mapper.get(cacheName);
}

public Map<String, Map<String, Long>> getAllStatistics() {
return mapper;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sdl.webapp.tridion.linking;

import com.google.common.base.Strings;
import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.dxa.common.util.PathUtils;
import com.sdl.dxa.tridion.annotations.impl.ValueAnnotationLogger;
import com.sdl.webapp.common.api.content.LinkResolver;
Expand All @@ -14,6 +15,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
Expand All @@ -23,6 +25,9 @@
public abstract class AbstractLinkResolver implements LinkResolver, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(AbstractLinkResolver.class);

@Autowired(required = false)
private CacheStatisticsProvider cacheStatisticsProvider;

@Value("${dxa.web.link-resolver.remove-extension:#{true}}")
private boolean shouldRemoveExtension;

Expand All @@ -34,7 +39,11 @@ public abstract class AbstractLinkResolver implements LinkResolver, Initializing

@Cacheable(value = "resolvedLinks", key = "{ #root.methodName, #url, #localizationId, #resolveToBinary, #contextId }", sync = true)
public String resolveLink(@Nullable String url, @Nullable String localizationId) {
return resolveLink(url, localizationId, false, null);
String resolvedLink = resolveLink(url, localizationId, false, null);
if (cacheStatisticsProvider != null) {
cacheStatisticsProvider.storeStatsInfo("resolvedLinks", resolvedLink);
}
return resolvedLink;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
package com.sdl.dxa.tridion.pcaclient;

import com.sdl.web.client.cache.CacheProvider;
import com.sdl.web.client.cache.CacheProviderInitializer;
import com.sdl.web.client.configuration.api.ConfigurationException;
import com.sdl.dxa.caching.NamedCacheProvider;
import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.web.content.client.util.ClientCacheKeyEnhancer;
import com.sdl.web.pca.client.DefaultGraphQLClient;
import com.sdl.web.pca.client.auth.Authentication;
import com.sdl.web.pca.client.exception.GraphQLClientException;
import com.sdl.web.pca.client.exception.UnauthorizedException;
import com.sdl.webapp.common.util.ApplicationContextHolder;

import com.tridion.ambientdata.web.WebContext;

import org.slf4j.Logger;

import javax.cache.Cache;
import java.io.Serializable;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
Expand All @@ -29,42 +26,28 @@ public class DXAGraphQLClient extends DefaultGraphQLClient {
private static final AtomicLong HIT_CACHE = new AtomicLong();
private static final AtomicLong MISS_CACHE = new AtomicLong();

private GraphQlServiceConfigurationLoader configurationLoader;
private CacheProvider cacheProvider;
private Cache<String, Serializable> queryCache;
private Cache<Object, Object> queryCache;
private NamedCacheProvider namedCacheProvider;
private CacheStatisticsProvider cacheStatisticsProvider;

public DXAGraphQLClient(String endpoint, Map<String, String> defaultHeaders) {
public DXAGraphQLClient(String endpoint, Map<String, String> defaultHeaders,
NamedCacheProvider namedCacheProvider, CacheStatisticsProvider cacheStatisticsProvider) {
super(endpoint, defaultHeaders);
initializeCacheProvider();
this.namedCacheProvider = namedCacheProvider;
this.queryCache = namedCacheProvider.getCache("queryCache");
this.cacheStatisticsProvider = cacheStatisticsProvider;
}

public DXAGraphQLClient(String endpoint, Map<String, String> defaultHeaders, Authentication auth) {
public DXAGraphQLClient(String endpoint, Map<String, String> defaultHeaders, Authentication auth,
NamedCacheProvider namedCacheProvider, CacheStatisticsProvider cacheStatisticsProvider) {
super(endpoint, defaultHeaders, auth);
initializeCacheProvider();
}

private void initializeCacheProvider() {
if (ApplicationContextHolder.getContext() == null) {
LOG.warn("The application context is not yet available. No caching for now.");
return;
}

try {
configurationLoader = ApplicationContextHolder.getContext().getBean(GraphQlServiceConfigurationLoader.class);
this.cacheProvider = CacheProviderInitializer.getCacheProvider(configurationLoader.getCacheConfiguration());
if (isCacheEnabled()) {
this.queryCache = this.cacheProvider.provideCacheForClass(String.class, Serializable.class);
}
}
catch (ConfigurationException e) {
LOG.error("Failed to initiate cache provider", e);
}
this.namedCacheProvider = namedCacheProvider;
this.queryCache = namedCacheProvider.getCache("queryCache");
this.cacheStatisticsProvider = cacheStatisticsProvider;
}

private boolean isCacheEnabled() {
if (configurationLoader == null)
initializeCacheProvider();
return this.cacheProvider != null && this.cacheProvider.isCacheEnabled();
return namedCacheProvider.isCacheEnabled("queryCache");
}

private String createCacheKey(String query) {
Expand All @@ -76,9 +59,9 @@ private String createCacheKey(String query) {
return UUID.nameUUIDFromBytes(query.getBytes()).toString();
}

private Serializable getFromCache(Cache<String, Serializable> cache, String cacheKey) {
private String getFromCache(Cache<Object, Object> cache, String cacheKey) {
LOG.debug("Cache is enabled, trying to get the cached response from cache, Hit/miss:" + HIT_CACHE.get() + "/" + MISS_CACHE.get() + ", key:" + cacheKey);
Serializable cachedResponse = cache.get(cacheKey);
String cachedResponse = (String)cache.get(cacheKey);
if (cachedResponse != null) {
HIT_CACHE.incrementAndGet();
return cachedResponse;
Expand All @@ -98,12 +81,15 @@ public String execute(String queryJsonEntity, int timeout) throws UnauthorizedEx
else {
LOG.debug("Cache is enabled, trying to get response from cache");
String cacheKey = createCacheKey(queryJsonEntity);
String response = (String)this.getFromCache(this.queryCache, cacheKey);
String response = this.getFromCache(this.queryCache, cacheKey);
if (response == null) {
LOG.debug("No such query in cache, getting from content service");
response = super.execute(queryJsonEntity, timeout);
if (response != null) {
this.queryCache.put(cacheKey, response);
if (this.cacheStatisticsProvider != null) {
this.cacheStatisticsProvider.storeStatsInfo("queryCache", response);
}
}
}
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sdl.dxa.caching.NamedCacheProvider;
import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.web.pca.client.ApiClient;
import com.sdl.web.pca.client.DefaultApiClient;
import com.sdl.web.pca.client.GraphQLClient;
Expand Down Expand Up @@ -41,6 +43,10 @@ public class DefaultApiClientProvider implements ApiClientProvider {

private Authentication auth;

private NamedCacheProvider namedCacheProvider;

private CacheStatisticsProvider cacheStatisticsProvider;

private static final String X_PREVIEW_SESSION_TOKEN = "x-preview-session-token";

private static final String PREVIEW_SESSION_TOKEN = "preview-session-token";
Expand All @@ -51,9 +57,13 @@ public class DefaultApiClientProvider implements ApiClientProvider {

@Autowired
public DefaultApiClientProvider(ApiClientConfigurationLoader configurationLoader,
Authentication auth) {
Authentication auth,
NamedCacheProvider namedCacheProvider,
CacheStatisticsProvider cacheStatisticsProvider) {
this.configurationLoader = configurationLoader;
this.auth = auth;
this.namedCacheProvider = namedCacheProvider;
this.cacheStatisticsProvider = cacheStatisticsProvider;
}

@Override
Expand Down Expand Up @@ -87,7 +97,12 @@ public ApiClient getClient() {
if (previewToken != null) {
defaultHeaders.put(HttpHeaders.COOKIE, String.format("%s=%s", PREVIEW_SESSION_TOKEN, previewToken));
}
GraphQLClient graphQLClient = new DXAGraphQLClient(configurationLoader.getServiceUrl(), defaultHeaders, auth);
GraphQLClient graphQLClient = new DXAGraphQLClient(
configurationLoader.getServiceUrl(),
defaultHeaders,
auth,
namedCacheProvider,
cacheStatisticsProvider);
Integer requestTimeout = Integer.valueOf(configurationLoader.getConfiguration().getOrDefault(CONNECTION_TIMEOUT, 0).toString());
ApiClient client = new DefaultApiClient(graphQLClient, requestTimeout);
client.setDefaultModelType(DataModelType.R2);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sdl.dxa.tridion.pcaclient;

import com.sdl.dxa.caching.NamedCacheProvider;
import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.web.client.configuration.api.ConfigurationException;
import com.sdl.web.pca.client.auth.Authentication;
import com.sdl.webapp.common.util.ApplicationContextHolder;
Expand Down Expand Up @@ -33,6 +35,10 @@ public class DefaultApiClientProviderTest {
private GraphQlServiceConfigurationLoader configurationLoader;
@Mock
private Authentication auth;
@Mock
private NamedCacheProvider namedCacheProvider;
@Mock
private CacheStatisticsProvider cacheStatisticsProvider;

private DefaultApiClientProvider apiClientProvider;

Expand All @@ -42,7 +48,7 @@ public void setup() throws ConfigurationException {
when(configurationLoader.getServiceUrl()).thenReturn("http://localhost:8082/cd/api");
when(configurationLoader.getConfiguration()).thenReturn(new Properties());
when(configurationLoader.getCacheConfiguration()).thenReturn(new Properties());
apiClientProvider = new DefaultApiClientProvider(configurationLoader, auth);
apiClientProvider = new DefaultApiClientProvider(configurationLoader, auth, namedCacheProvider, cacheStatisticsProvider);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sdl.dxa.tridion.mapping.impl;

import com.sdl.dxa.caching.statistics.CacheStatisticsProvider;
import com.sdl.dxa.common.ClaimValues;
import com.sdl.webapp.common.api.WebRequestContext;
import com.sdl.webapp.common.api.content.ConditionalEntityEvaluator;
Expand Down Expand Up @@ -30,6 +31,7 @@ public abstract class AbstractContentProvider {
protected WebRequestContext webRequestContext;
private final Cache pagemodelCache;
private final Cache entitymodelCache;
private CacheStatisticsProvider cacheStatisticsProvider;

protected AbstractContentProvider(WebRequestContext webRequestContext, CacheManager cacheManager) {
this.webRequestContext = webRequestContext;
Expand All @@ -42,6 +44,10 @@ public void setEntityEvaluators(List<ConditionalEntityEvaluator> entityEvaluator
this.entityEvaluators = entityEvaluators;
}

@Autowired(required = false)
public void setCacheStatisticsProvider(CacheStatisticsProvider cacheStatisticsProvider) {
this.cacheStatisticsProvider = cacheStatisticsProvider;
}

/**
* This default implementation handles caching and cloning the pagemodel.
Expand Down Expand Up @@ -71,6 +77,9 @@ public PageModel getPageModel(String path, Localization localization) throws Con
if (pageModel.canBeCached() && !webRequestContext.isSessionPreview()) {
pagemodelCache.put(key, pageModel);
pagemodelCache.put(createKeyForCacheByPath(pageModel.getId(), localization, "pagemodel"), pageModel);
if (cacheStatisticsProvider != null) {
cacheStatisticsProvider.storeStatsInfo("pageModels", pageModel);
}
}
}
try {
Expand Down Expand Up @@ -135,6 +144,9 @@ public PageModel getPageModel(int pageId, Localization localization) throws Cont
if (pageModel.canBeCached() && !webRequestContext.isSessionPreview()) {
pagemodelCache.put(key, pageModel);
pagemodelCache.put(createKeyForCacheByPath(pageModel.getUrl(), localization, "pagemodel"), pageModel);
if (cacheStatisticsProvider != null) {
cacheStatisticsProvider.storeStatsInfo("pageModels", pageModel);
}
}
}
try {
Expand Down Expand Up @@ -214,6 +226,9 @@ public EntityModel getEntityModel(@NotNull String id, Localization localization)
}
if (entityModel.canBeCached() && !webRequestContext.isSessionPreview()) {
entitymodelCache.put(key, entityModel);
if (cacheStatisticsProvider != null) {
cacheStatisticsProvider.storeStatsInfo("entityModels", entityModel);
}
}
}

Expand Down
Loading

0 comments on commit 9985f4b

Please sign in to comment.