Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cache info #210

Merged
merged 3 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -8,7 +8,6 @@
import com.sdl.webapp.common.api.localization.Localization;
import com.sdl.webapp.common.exceptions.DxaItemNotFoundException;
import com.sdl.webapp.common.util.MimeUtils;
import org.apache.commons.io.IOUtils;
import org.joda.time.Hours;
import org.joda.time.Weeks;
import org.slf4j.Logger;
Expand Down Expand Up @@ -99,7 +98,7 @@ private static void fallbackForContentProvider(ServletServerHttpRequest request,
isPreview)) {
try (final InputStream in = contentResource.openStream();
final OutputStream out = response.getBody();) {
IOUtils.copy(in, out);
in.transferTo(out);
}
}
}
Expand Down Expand Up @@ -143,7 +142,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
if (toBeRefreshed) {
try (final InputStream in = staticContentItem.getContent();
final OutputStream out = res.getBody()) {
IOUtils.copy(in, out);
in.transferTo(out);
}
}
} catch (StaticContentNotFoundException e) {
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,8 @@
package com.sdl.webapp.tridion.linking;

import com.google.common.base.Strings;
import com.sdl.dxa.caching.NamedCacheProvider;
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,14 +16,25 @@
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.stereotype.Component;

import javax.cache.Cache;
import java.util.UUID;

@Slf4j
@Component
public abstract class AbstractLinkResolver implements LinkResolver, InitializingBean {

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

@Autowired
private NamedCacheProvider namedCacheProvider;

@Autowired
private CacheStatisticsProvider cacheStatisticsProvider;

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

Expand All @@ -33,9 +46,30 @@ public abstract class AbstractLinkResolver implements LinkResolver, Initializing

@Override
public String resolveLink(@Nullable String url, @Nullable String localizationId, boolean resolveToBinary, @Nullable String contextId) {
final int publicationId = !Strings.isNullOrEmpty(localizationId) ? Integer.parseInt(localizationId) : 0;
if (!isCacheEnabled()) {
return processLink(url, localizationId, resolveToBinary, contextId);
}
else {
String cacheKeyInput = String.format("%s%s%b%s", url, localizationId, resolveToBinary, contextId);
String cacheKey = UUID.nameUUIDFromBytes(cacheKeyInput.getBytes()).toString();
Cache<Object, Object> resolvedLinksCache = namedCacheProvider.getCache("resolvedLinks");
String resolvedLink = (String)resolvedLinksCache.get(cacheKey);
if (resolvedLink == null) {
resolvedLink = processLink(url, localizationId, resolveToBinary, contextId);
if (resolvedLink != null) {
resolvedLinksCache.put(cacheKey, resolvedLink);
if (this.cacheStatisticsProvider != null) {
this.cacheStatisticsProvider.storeStatsInfo("resolvedLinks", resolvedLink);
}
}
}
return resolvedLink;
}
}

String resolvedLink = _resolveLink(url, publicationId, resolveToBinary, contextId);
private String processLink(String uri, String localizationId, boolean isBinary, String contextId) {
final int publicationId = !Strings.isNullOrEmpty(localizationId) ? Integer.parseInt(localizationId) : 0;
String resolvedLink = _resolveLink(uri, publicationId, isBinary, contextId);
String resolvedUrl = shouldStripIndexPath ? PathUtils.stripIndexPath(resolvedLink) : resolvedLink;
if (shouldKeepTrailingSlash && (! "/".equals(resolvedUrl)) && PathUtils.isIndexPath(resolvedLink)) {
resolvedUrl = resolvedUrl + "/";
Expand Down Expand Up @@ -74,6 +108,10 @@ private String _resolveLink(String uri, int publicationId, boolean isBinary, Str
}
}

private boolean isCacheEnabled() {
return namedCacheProvider != null && namedCacheProvider.isCacheEnabled("resolvedLinks");
}

private int getPageId(String contextId) {
int pageId;
if (TcmUtils.isTcmUri(contextId)) {
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
Loading
Loading