You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2020/09/21 15:00:32 UTC
[sling-org-apache-sling-graphql-core] branch master updated:
SLING-9744 - Add metrics for the persisted queries
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/master by this push:
new d9eb854 SLING-9744 - Add metrics for the persisted queries
d9eb854 is described below
commit d9eb85499e82e25025b73d0730d41b9eb2486b9b
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Mon Sep 21 16:59:41 2020 +0200
SLING-9744 - Add metrics for the persisted queries
* added metrics and documented them
---
docs/metrics.md | 60 ++++++++++
pom.xml | 12 ++
.../core/cache/SimpleGraphQLCacheProvider.java | 72 +++++++++---
.../sling/graphql/core/servlet/GraphQLServlet.java | 129 +++++++++++++++------
4 files changed, 225 insertions(+), 48 deletions(-)
diff --git a/docs/metrics.md b/docs/metrics.md
new file mode 100644
index 0000000..318752a
--- /dev/null
+++ b/docs/metrics.md
@@ -0,0 +1,60 @@
+Apache Sling GraphQL Core - Metrics
+----
+
+This page documents the metrics the Apache Sling GraphQL Core bundle exposes to the Metrics Registry.
+
+## `org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider`
+
+<dl>
+
+<dt>org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider.evictions</dt>
+<dd>
+ the number of cache evictions for <a href="../README.md#caching-persisted-queries-api">persisted queries</a>
+</dd>
+
+<dt>org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider.capacity</dt>
+<dd>
+ the maximum number of entries the cache can store
+</dd>
+
+<dt>org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider.elements<dt>
+<dd>
+ the current number of elements the cache stores
+</dd>
+
+<dt>org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider.maxMemory</dt>
+<dd>
+ the maximum amount of memory the stored queries can consume (in bytes)
+</dd>
+
+<dt>org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider.currentMemory</dt>
+<dd>
+ the current amount of memory used to store the persisted queries (in bytes); this is calculated through an approximation of the amount of bytes each entry requires
+</dd>
+
+</dl>
+
+## `org.apache.sling.graphql.core.servlet.GraphQLServlet`
+
+For each service instance of the `org.apache.sling.graphql.core.servlet.GraphQLServlet` servlet, the following additional
+metrics are available, where the `<qualifier>` is a string using the
+`[rt=list of servlet resource types][m=list of servlet methods][s=list of servlet selectors][e=list of servlet extensions]` pattern:
+
+<dl>
+
+<dt>org.apache.sling.graphql.core.servlet.GraphQLServlet.<qualifier>.cache_hits</dt>
+<dd>the number of times a persisted query was retrieved from the cache</dd>
+
+<dt>org.apache.sling.graphql.core.servlet.GraphQLServlet.<qualifier>.cache_misses</dt>
+<dd>the number of times a persisted query was not found in the cache</dd>
+
+<dt>org.apache.sling.graphql.core.servlet.GraphQLServlet.<qualifier>.cache_hit_rate</dt>
+<dd>the cache hit rate: a float number between 0 and 1.0; multiply the number by 100 to get the percentage</dd>
+
+<dt>org.apache.sling.graphql.core.servlet.GraphQLServlet.<qualifier>.total_requests</dt>
+<dd>total number of requests served by this servlet</dd>
+
+<dt>org.apache.sling.graphql.core.servlet.GraphQLServlet.<qualifier>.requests_timer</dt>
+<dd>request timing metrics for this servlet</dd>
+
+</dl>
diff --git a/pom.xml b/pom.xml
index 6f36856..eb79816 100644
--- a/pom.xml
+++ b/pom.xml
@@ -158,6 +158,18 @@
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.metrics</artifactId>
+ <version>1.2.8</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>3.2.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.resource.presence</artifactId>
<version>0.0.2</version>
<scope>test</scope>
diff --git a/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
index b2def0f..7fe1f59 100644
--- a/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
+++ b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
@@ -21,21 +21,29 @@ package org.apache.sling.graphql.core.cache;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.commons.metrics.Counter;
+import org.apache.sling.commons.metrics.MetricsService;
import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
import org.apache.sling.graphql.core.engine.SlingGraphQLException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
@@ -43,6 +51,9 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+
@Component()
@Designate(ocd = SimpleGraphQLCacheProvider.Config.class)
public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
@@ -73,29 +84,61 @@ public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleGraphQLCacheProvider.class);
+ @Reference
+ private MetricsService metricsService;
+
+ @Reference(target = "(name=sling)")
+ private MetricRegistry metricRegistry;
+
private InMemoryLRUCache persistedQueriesCache;
private Lock readLock;
private Lock writeLock;
+ private Counter evictions;
+
+ private static final String METRIC_NS = SimpleGraphQLCacheProvider.class.getName();
+ private static final String GAUGE_CAPACITY = METRIC_NS + ".capacity";
+ private static final String GAUGE_ELEMENTS = METRIC_NS + ".elements";
+ private static final String GAUGE_MAX_MEMORY = METRIC_NS + ".maxMemory";
+ private static final String GAUGE_CURRENT_MEMORY = METRIC_NS + ".currentMemory";
+ private static final String COUNTER_EVICTIONS = METRIC_NS + ".evictions";
+ private static final Set<String> MANUALLY_REGISTERED_METRICS = new HashSet<>(Arrays.asList(GAUGE_CAPACITY, GAUGE_ELEMENTS,
+ GAUGE_MAX_MEMORY, GAUGE_CURRENT_MEMORY));
+
@Activate
- private void activate(Config config) {
+ private void activate(Config config, BundleContext bundleContext) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readLock = readWriteLock.readLock();
writeLock = readWriteLock.writeLock();
- int cacheSize = config.capacity();
- if (cacheSize < 0) {
- cacheSize = 0;
+ int capacity;
+ if (config.capacity() < 0) {
+ capacity = 0;
LOGGER.debug("Cache capacity set to {}. Defaulting to 0.", config.capacity());
+ } else {
+ capacity = config.capacity();
}
- long maxSize = config.maxSize();
- if (maxSize < 0) {
- maxSize = 0;
- LOGGER.debug("Cache size set to {}. Defaulting to 0.", config.maxSize());
+ long maxMemory;
+ if (config.maxSize() < 0) {
+ maxMemory = 0;
+ LOGGER.debug("Cache max memory set to {}. Defaulting to 0.", config.maxSize());
+ } else {
+ maxMemory = config.maxSize();
}
- persistedQueriesCache = new InMemoryLRUCache(cacheSize, maxSize);
- LOGGER.debug("In-memory cache initialized: cacheSize={}, maxSize={}.", cacheSize, maxSize);
+ persistedQueriesCache = new InMemoryLRUCache(capacity, maxMemory);
+ LOGGER.debug("In-memory cache initialized: capacity={}, maxMemory={}.", capacity, maxMemory);
+ metricRegistry.register(GAUGE_CAPACITY, (Gauge<Integer>) () -> capacity);
+ metricRegistry.register(GAUGE_MAX_MEMORY, (Gauge<Long>) () -> maxMemory);
+ metricRegistry.register(GAUGE_CURRENT_MEMORY, (Gauge<Long>) () -> persistedQueriesCache.currentSizeInBytes);
+ metricRegistry.register(GAUGE_ELEMENTS, (Gauge<Integer>) () -> persistedQueriesCache.size());
+ evictions = metricsService.counter(COUNTER_EVICTIONS);
}
+ @Deactivate
+ private void deactivate() {
+ for (String manuallyRegisteredMetric : MANUALLY_REGISTERED_METRICS) {
+ metricRegistry.remove(manuallyRegisteredMetric);
+ }
+ }
@Override
@Nullable
@@ -155,7 +198,7 @@ public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
* This implementation provides a simple LRU eviction based on either the number of entries or the memory used by the stored values.
* Synchronization has to happen externally.
*/
- private static class InMemoryLRUCache extends LinkedHashMap<String, String> {
+ private class InMemoryLRUCache extends LinkedHashMap<String, String> {
private final int capacity;
private final long maxSizeInBytes;
@@ -174,9 +217,10 @@ public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
willRemove = size() > capacity;
} else if (maxSizeInBytes > 0) {
willRemove = currentSizeInBytes > maxSizeInBytes;
- if (willRemove) {
- currentSizeInBytes -= getApproximateStringSizeInBytes(eldest.getValue());
- }
+ }
+ if (willRemove) {
+ evictions.increment();
+ currentSizeInBytes -= getApproximateStringSizeInBytes(eldest.getValue());
}
return willRemove;
}
diff --git a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
index 640e5c0..2797906 100644
--- a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
+++ b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
@@ -33,6 +33,9 @@ import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.commons.metrics.Counter;
+import org.apache.sling.commons.metrics.MetricsService;
+import org.apache.sling.commons.metrics.Timer;
import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
import org.apache.sling.graphql.core.engine.GraphQLResourceQuery;
import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
@@ -43,12 +46,15 @@ import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
import graphql.ExecutionResult;
/** Servlet that can be activated to implement the standard
@@ -128,11 +134,26 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
@Reference
private GraphQLCacheProvider cacheProvider;
+ @Reference
+ private MetricsService metricsService;
+
+ @Reference(target = "(name=sling)")
+ private MetricRegistry metricRegistry;
+
private String suffixPersisted;
private Pattern patternGetPersistedQuery;
private int cacheControlMaxAge;
private final JsonSerializer jsonSerializer = new JsonSerializer();
+ private Counter cacheHits;
+ private Counter cacheMisses;
+ private Counter requestsServed;
+ private Timer requestTimer;
+
+ private static final String METRIC_NS = GraphQLServlet.class.getName();
+ private String servletRegistrationProperties;
+ private String gaugeCacheHitRate;
+
@Activate
private void activate(Config config) {
String[] extensions = config.sling_servlet_extensions();
@@ -157,60 +178,100 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
suffixPersisted = null;
patternGetPersistedQuery = null;
}
+ StringBuilder sb = new StringBuilder();
+ sb.append("[rt=").append(String.join(",", config.sling_servlet_resourceTypes())).append("]");
+ if (config.sling_servlet_methods().length > 0) {
+ sb.append("[m=").append(String.join(",", config.sling_servlet_methods())).append("]");
+ }
+ if (config.sling_servlet_selectors().length > 0) {
+ sb.append("[s=").append(String.join(",", config.sling_servlet_selectors())).append("]");
+ }
+ if (config.sling_servlet_extensions().length > 0) {
+ sb.append("[e=").append(String.join(",", config.sling_servlet_extensions())).append("]");
+ }
+ servletRegistrationProperties = sb.toString();
+ cacheHits = metricsService.counter(METRIC_NS + "." + servletRegistrationProperties + ".cache_hits");
+ cacheMisses = metricsService.counter(METRIC_NS + "." + servletRegistrationProperties + ".cache_misses");
+ requestsServed = metricsService.counter(METRIC_NS + "." + servletRegistrationProperties + ".total_requests");
+ gaugeCacheHitRate = METRIC_NS + "." + servletRegistrationProperties + ".cache_hit_rate";
+ metricRegistry.register(gaugeCacheHitRate,
+ (Gauge<Float>) () -> (float) (cacheHits.getCount() / (float) (cacheHits.getCount() + cacheMisses.getCount())));
+ requestTimer = metricsService.timer(METRIC_NS + "." + servletRegistrationProperties + ".requests_timer");
+ }
+
+ @Deactivate
+ private void deactivate() {
+ if (StringUtils.isNotEmpty(gaugeCacheHitRate)) {
+ metricRegistry.remove(gaugeCacheHitRate);
+ }
}
@Override
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
- String suffix = request.getRequestPathInfo().getSuffix();
- if (suffix != null) {
- if (StringUtils.isNotEmpty(suffixPersisted) && suffix.startsWith(suffixPersisted)) {
- Matcher matcher = patternGetPersistedQuery.matcher(suffix);
- if (matcher.matches()) {
- String queryHash = matcher.group(1);
- String extension = matcher.group(2);
- String requestExtension = request.getRequestPathInfo().getExtension();
- if (requestExtension != null && requestExtension.equals(extension)) {
- if (StringUtils.isNotEmpty(queryHash)) {
- String query = cacheProvider.getQuery(queryHash, request.getResource().getResourceType(),
- request.getRequestPathInfo().getSelectorString());
- if (query != null) {
- boolean isAuthenticated = request.getHeaders("Authorization").hasMoreElements();
- StringBuilder cacheControlValue = new StringBuilder("max-age=").append(cacheControlMaxAge);
- if (isAuthenticated) {
- cacheControlValue.append(",private");
+ requestsServed.increment();
+ Timer.Context requestTimerContext = requestTimer.time();
+ try {
+ String suffix = request.getRequestPathInfo().getSuffix();
+ if (suffix != null) {
+ if (StringUtils.isNotEmpty(suffixPersisted) && suffix.startsWith(suffixPersisted)) {
+ Matcher matcher = patternGetPersistedQuery.matcher(suffix);
+ if (matcher.matches()) {
+ String queryHash = matcher.group(1);
+ String extension = matcher.group(2);
+ String requestExtension = request.getRequestPathInfo().getExtension();
+ if (requestExtension != null && requestExtension.equals(extension)) {
+ if (StringUtils.isNotEmpty(queryHash)) {
+ String query = cacheProvider.getQuery(queryHash, request.getResource().getResourceType(),
+ request.getRequestPathInfo().getSelectorString());
+ if (query != null) {
+ boolean isAuthenticated = request.getHeaders("Authorization").hasMoreElements();
+ StringBuilder cacheControlValue = new StringBuilder("max-age=").append(cacheControlMaxAge);
+ if (isAuthenticated) {
+ cacheControlValue.append(",private");
+ }
+ response.addHeader("Cache-Control", cacheControlValue.toString());
+ execute(query, request, response);
+ cacheHits.increment();
+ } else {
+ cacheMisses.increment();
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot find persisted query " + queryHash);
}
- response.addHeader("Cache-Control", cacheControlValue.toString());
- execute(query, request, response);
- } else {
- response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot find persisted query " + queryHash);
}
+ } else {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "The persisted query's extension does not match the " +
+ "servlet extension.");
}
} else {
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "The persisted query's extension does not match the " +
- "servlet extension.");
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unexpected hash.");
}
} else {
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unexpected hash.");
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Persisted queries are disabled.");
}
} else {
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Persisted queries are disabled.");
+ execute(request.getResource(), request, response);
}
- } else {
- execute(request.getResource(), request, response);
+ } finally {
+ requestTimerContext.stop();
}
}
@Override
public void doPost(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException {
- String suffix = request.getRequestPathInfo().getSuffix();
- if (suffix != null) {
- if (StringUtils.isNotEmpty(suffixPersisted) && suffix.equals(suffixPersisted)) {
- doPostPersistedQuery(request, response);
+ requestsServed.increment();
+ Timer.Context requestTimerContext = requestTimer.time();
+ try {
+ String suffix = request.getRequestPathInfo().getSuffix();
+ if (suffix != null) {
+ if (StringUtils.isNotEmpty(suffixPersisted) && suffix.equals(suffixPersisted)) {
+ doPostPersistedQuery(request, response);
+ } else {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
} else {
- response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ execute(request.getResource(), request, response);
}
- } else {
- execute(request.getResource(), request, response);
+ } finally {
+ requestTimerContext.stop();
}
}