diff --git a/solr/bin/solr b/solr/bin/solr
index 704efcb724f..c8b85473949 100755
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -85,7 +85,7 @@ done
CDPATH='' # Prevent "file or directory not found" for 'cdpath' users
SOLR_TIP=`dirname "$SOLR_SCRIPT"`/..
-SOLR_TIP=`cd "$SOLR_TIP"; pwd`
+SOLR_TIP=`cd "$SOLR_TIP"; pwd -P`
DEFAULT_SERVER_DIR="$SOLR_TIP/server"
# If an include wasn't specified in the environment, then search for one...
@@ -1502,7 +1502,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
fi
if [[ "$2" == "." || "$2" == "./" || "$2" == ".." || "$2" == "../" ]]; then
- SOLR_SERVER_DIR="$(pwd)/$2"
+ SOLR_SERVER_DIR="$(pwd -P)/$2"
else
# see if the arg value is relative to the tip vs full path
if [[ "$2" != /* ]] && [[ -d "$SOLR_TIP/$2" ]]; then
@@ -1512,7 +1512,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
fi
fi
# resolve it to an absolute path
- SOLR_SERVER_DIR="$(cd "$SOLR_SERVER_DIR"; pwd)"
+ SOLR_SERVER_DIR="$(cd "$SOLR_SERVER_DIR"; pwd -P)"
shift 2
;;
-s|-solr.home)
@@ -1549,8 +1549,8 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
if [ -z "${SOLR_HOME:-}" ]; then
SOLR_HOME="$SOLR_SERVER_DIR/solr"
elif [[ $SOLR_HOME != /* ]]; then
- if [[ -d "`pwd`/$SOLR_HOME" ]]; then
- SOLR_HOME="$(pwd)/$SOLR_HOME"
+ if [[ -d "`pwd -P`/$SOLR_HOME" ]]; then
+ SOLR_HOME="$(pwd -P)/$SOLR_HOME"
elif [[ -d "$SOLR_SERVER_DIR/$SOLR_HOME" ]]; then
SOLR_HOME="$SOLR_SERVER_DIR/$SOLR_HOME"
SOLR_PID_DIR="$SOLR_HOME"
@@ -1652,7 +1652,7 @@ if [ $# -gt 0 ]; then
fi
if [[ "$2" == "." || "$2" == "./" || "$2" == ".." || "$2" == "../" ]]; then
- SOLR_SERVER_DIR="$(pwd)/$2"
+ SOLR_SERVER_DIR="$(pwd -P)/$2"
else
# see if the arg value is relative to the tip vs full path
if [[ "$2" != /* ]] && [[ -d "$SOLR_TIP/$2" ]]; then
@@ -1662,7 +1662,7 @@ if [ $# -gt 0 ]; then
fi
fi
# resolve it to an absolute path
- SOLR_SERVER_DIR="$(cd "$SOLR_SERVER_DIR"; pwd)"
+ SOLR_SERVER_DIR="$(cd "$SOLR_SERVER_DIR"; pwd -P)"
shift 2
;;
-s|-solr.home)
@@ -1946,8 +1946,8 @@ fi
if [ -z "${SOLR_HOME:-}" ]; then
SOLR_HOME="$SOLR_SERVER_DIR/solr"
elif [[ $SOLR_HOME != /* ]]; then
- if [[ -d "`pwd`/$SOLR_HOME" ]]; then
- SOLR_HOME="$(pwd)/$SOLR_HOME"
+ if [[ -d "`pwd -P`/$SOLR_HOME" ]]; then
+ SOLR_HOME="$(pwd -P)/$SOLR_HOME"
elif [[ -d "$SOLR_SERVER_DIR/$SOLR_HOME" ]]; then
SOLR_HOME="$SOLR_SERVER_DIR/$SOLR_HOME"
SOLR_PID_DIR="$SOLR_HOME"
diff --git a/solr/core/src/java/org/apache/solr/handler/CacheInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/CacheInfoHandler.java
new file mode 100644
index 00000000000..c03e6beab1e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/CacheInfoHandler.java
@@ -0,0 +1,99 @@
+package org.apache.solr.handler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.lucene.search.Query;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.DocSet;
+import org.apache.solr.search.SolrCache;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.util.plugin.SolrCoreAware;
+
+/**
+ * Handler that returns useful info
+ */
+public class CacheInfoHandler extends RequestHandlerBase implements SolrCoreAware {
+ private final static String ACTION = "action";
+ private final static String ACTION_DUMP = "dump";
+ private final static String CACHE_TYPE_FILTER = "filter";
+ private final static String COUNT = "count";
+ private final static String DESCRIPTION = "Returns keys from the specified cache type";
+ private static SolrCore solrCore = null;
+
+ @Override
+ public Name getPermissionName(AuthorizationContext request) {
+ return null;
+ }
+
+ @Override
+ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ // TODO Auto-generated method stub
+ String action = req.getParams().get(ACTION, ACTION_DUMP);
+ String type = req.getParams().get(TYPE, CACHE_TYPE_FILTER);
+ @SuppressWarnings("unused")
+ int count = Integer.parseInt(req.getParams().get(COUNT, "-1"));
+
+ switch (action) {
+ case ACTION_DUMP:
+ switch (type) {
+ case CACHE_TYPE_FILTER:
+ // TODO: NOCOMMIT: implement.
+ SolrCache<Query,DocSet> cache = solrCore.getSearcher().get().getFilterCache();
+ Set<Query> keys = cache.keySet();
+ // List<String> queries = new ArrayList<>();
+ Map<String,Long> queryMap = new HashMap<>();
+ Map<String,Long> sortedMap = new LinkedHashMap<>();
+ List<Long> tmpList = new ArrayList<>();
+ for (Query k : keys) {
+ queryMap.put(k.toString(), cache.getHitCount(k));
+ }
+ for (Map.Entry<String,Long> entry : queryMap.entrySet()) {
+ tmpList.add(entry.getValue());
+ }
+ Collections.sort(tmpList, new Comparator<Long>() {
+ public int compare(Long long1, Long long2) {
+ return long2.compareTo(long1);
+ }
+ });
+ for (Long value : tmpList) {
+ for (Entry<String,Long> entry : queryMap.entrySet()) {
+ if (entry.getValue().equals(value)) {
+ sortedMap.put(entry.getKey(), value);
+ }
+ }
+ }
+ rsp.add("queries", sortedMap);
+ break;
+
+ default:
+ rsp.add("error", "Unknown type: " + type);
+ break;
+ }
+ break;
+
+ default:
+ rsp.add("error", "Unknown action: " + action);
+ break;
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+
+ @Override
+ public void inform(SolrCore core) {
+ solrCore = core;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
index e1263a6ed49..4a6bf1a4918 100644
--- a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
+++ b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
@@ -16,14 +16,6 @@
*/
package org.apache.solr.search;
-import com.github.benmanes.caffeine.cache.AsyncCache;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.cache.Policy.Eviction;
-import com.github.benmanes.caffeine.cache.RemovalCause;
-import com.github.benmanes.caffeine.cache.RemovalListener;
-import com.github.benmanes.caffeine.cache.stats.CacheStats;
-import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
@@ -33,22 +25,33 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
+
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.RamUsageEstimator;
-import org.apache.solr.common.SolrException;
import org.apache.solr.metrics.MetricsMap;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.util.IOFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.github.benmanes.caffeine.cache.AsyncCache;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.Policy.Eviction;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+import com.github.benmanes.caffeine.cache.stats.CacheStats;
+import com.google.common.annotations.VisibleForTesting;
+
/**
* A SolrCache backed by the Caffeine caching library [1]. By default it uses the Window TinyLFU
* (W-TinyLFU) eviction policy.
@@ -89,6 +92,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase
private LongAdder hits;
private LongAdder inserts;
private LongAdder lookups;
+ private Map<K,LongAdder> hitCounterMap = null;
private Cache<K, V> cache;
private AsyncCache<K, V> asyncCache;
private long warmupTime;
@@ -98,6 +102,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase
private int maxIdleTimeSec;
private boolean cleanupThread;
private boolean async;
+ private boolean extraStats;
private MetricsMap cacheMap;
private SolrMetricsContext solrMetricsContext;
@@ -114,6 +119,8 @@ public class CaffeineCache<K, V> extends SolrCacheBase
super.init(args, regenerator);
String str = args.get(SIZE_PARAM);
maxSize = (str == null) ? 1024 : Integer.parseInt(str);
+ str = args.get(EXTRA_STATS_PARAM);
+ extraStats = Boolean.parseBoolean(str);
str = args.get(INITIAL_SIZE_PARAM);
initialSize = Math.min((str == null) ? 1024 : Integer.parseInt(str), maxSize);
str = args.get(MAX_IDLE_TIME_PARAM);
@@ -149,6 +156,10 @@ public class CaffeineCache<K, V> extends SolrCacheBase
+ RamUsageEstimator.shallowSizeOfInstance(executor.getClass())
+ RamUsageEstimator.sizeOfObject(description);
+ if (extraStats) {
+ hitCounterMap = new ConcurrentHashMap<>();
+ }
+
return persistence;
}
@@ -200,7 +211,13 @@ public class CaffeineCache<K, V> extends SolrCacheBase
@Override
public V get(K key) {
- return cache.getIfPresent(key);
+ V v = cache.getIfPresent(key);
+ if (extraStats) {
+ if (v != null) {
+ hitCounterMap.get(key).increment();
+ }
+ }
+ return v;
}
private V computeAsync(K key, IOFunction<? super K, ? extends V> mappingFunction)
@@ -274,6 +291,9 @@ public class CaffeineCache<K, V> extends SolrCacheBase
@Override
public V put(K key, V val) {
+ if (extraStats) {
+ hitCounterMap.put(key, new LongAdder());
+ }
inserts.increment();
V old = cache.asMap().put(key, val);
recordRamBytes(key, old, val);
@@ -308,6 +328,20 @@ public class CaffeineCache<K, V> extends SolrCacheBase
return cache.asMap().remove(key);
}
+ @Override
+ public long getHitCount(K key) {
+ long hits = 0;
+ if (hitCounterMap != null && hitCounterMap.containsKey(key)) {
+ hits = hitCounterMap.get(key).sum();
+ }
+ return hits;
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return cache.asMap().keySet();
+ }
+
@Override
public void clear() {
cache.invalidateAll();
@@ -405,7 +439,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase
break;
}
} catch (Exception e) {
- SolrException.log(log, "Error during auto-warming of key:" + entry.getKey(), e);
+ log.error("Error during auto-warming of key:" + entry.getKey(), e);
}
}
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCache.java b/solr/core/src/java/org/apache/solr/search/SolrCache.java
index 870d18ea2c9..5ff477833ff 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrCache.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCache.java
@@ -18,7 +18,9 @@ package org.apache.solr.search;
import java.io.IOException;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
+
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.util.IOFunction;
@@ -36,6 +38,7 @@ public interface SolrCache<K, V> extends SolrInfoBean {
String MAX_RAM_MB_PARAM = "maxRamMB";
String MAX_IDLE_TIME_PARAM = "maxIdleTime";
String INITIAL_SIZE_PARAM = "initialSize";
+ String EXTRA_STATS_PARAM = "extraStats";
String CLEANUP_THREAD_PARAM = "cleanupThread";
String ASYNC_PARAM = "async";
@@ -89,6 +92,21 @@ public interface SolrCache<K, V> extends SolrInfoBean {
public V remove(K key);
+ /**
+ * Gets the number of hits for the provided key.
+ *
+ * @param key The key to look up.
+ * @return The number of hits for the provided key, 0 if the key is not present.
+ */
+ public long getHitCount(K key);
+
+ /**
+ * Gets the keys that the cache contains.
+ *
+ * @return A set of keys found in the cache.
+ */
+ public Set<K> keySet();
+
/**
* Get an existing element or atomically compute it if missing.
*
diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json
index b0ee1fec3ca..783cdd03e40 100644
--- a/solr/core/src/resources/ImplicitPlugins.json
+++ b/solr/core/src/resources/ImplicitPlugins.json
@@ -58,6 +58,9 @@
"q": "{!lucene}*:*"
}
},
+ "/admin/cache": {
+ "class": "org.apache.solr.handler.CacheInfoHandler"
+ },
"/admin/segments": {
"class": "solr.SegmentsInfoRequestHandler",
"useParams":"_ADMIN_SEGMENTS"