Add REST endpoint to list caches

GET /config/server/caches/ lists the server caches. Caches defined by
plugins are included.

The implementation of the SSH show-caches was adapted so that it uses
the new REST endpoint to get the information about the caches. This
avoids code duplication.

For disk caches a new interface was introduced in gerrit-server. This
is done because the list caches REST endpoint needs extra information
for disk caches which can only be provided by the disk cache, aka
H2CacheImpl. H2CacheImpl is defined in gerrit-cache-h2, but
gerrit-server doesn't have a dependency on it and hence can't use
instanceof to check whether a cache is a disk cache. Adding this
dependency is not wanted and also not possible (because
gerrit-cache-h2 already depends on gerrit-server and we can't have
cycles in the dependencies).

Change-Id: I21bf6e114310f98051a0a8db01012511f58d4add
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2014-05-23 08:45:56 +02:00
parent 0fa95e7eb9
commit 42c9cf61c8
10 changed files with 720 additions and 124 deletions

View File

@@ -16,20 +16,21 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
import com.google.common.collect.Maps;
import com.google.common.base.Strings;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.cache.h2.H2CacheImpl;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.ListCaches;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.ListCaches.CacheType;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -52,13 +53,12 @@ import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
/** Show the current cache states. */
@RequiresCapability(GlobalCapability.VIEW_CACHES)
@CommandMetaData(name = "show-caches", description = "Display current cache statistics",
runsAt = MASTER_OR_SLAVE)
final class ShowCaches extends CacheCommand {
final class ShowCaches extends SshCommand {
private static volatile long serverStarted;
static class StartupListener implements LifecycleListener {
@@ -88,6 +88,9 @@ final class ShowCaches extends CacheCommand {
@SitePath
private File sitePath;
@Inject
private Provider<ListCaches> listCaches;
@Option(name = "--width", aliases = {"-w"}, metaVar = "COLS", usage = "width of output table")
private int columns = 80;
private int nw;
@@ -145,23 +148,10 @@ final class ShowCaches extends CacheCommand {
}
stdout.print("+---------------------+---------+---------+\n");
Map<String, H2CacheImpl<?, ?>> disks = Maps.newTreeMap();
printMemoryCaches(disks, sortedCoreCaches());
printMemoryCaches(disks, sortedPluginCaches());
for (Map.Entry<String, H2CacheImpl<?, ?>> entry : disks.entrySet()) {
H2CacheImpl<?, ?> cache = entry.getValue();
CacheStats stat = cache.stats();
H2CacheImpl.DiskStats disk = cache.diskStats();
stdout.print(String.format(
"D %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
entry.getKey(),
count(cache.size()),
count(disk.size()),
bytes(disk.space()),
duration(stat.averageLoadPenalty()),
percent(stat.hitCount(), stat.requestCount()),
percent(disk.hitCount(), disk.requestCount())));
}
Collection<CacheInfo> caches = getCaches();
printMemoryCoreCaches(caches);
printMemoryPluginCaches(caches);
printDiskCaches(caches);
stdout.print('\n');
if (gc) {
@@ -181,46 +171,62 @@ final class ShowCaches extends CacheCommand {
stdout.flush();
}
private void printMemoryCaches(
Map<String, H2CacheImpl<?, ?>> disks,
Map<String, Cache<?,?>> caches) {
for (Map.Entry<String, Cache<?,?>> entry : caches.entrySet()) {
Cache<?,?> cache = entry.getValue();
if (cache instanceof H2CacheImpl) {
disks.put(entry.getKey(), (H2CacheImpl<?,?>)cache);
continue;
private Collection<CacheInfo> getCaches() {
Map<String, CacheInfo> caches = listCaches.get().apply(new ConfigResource());
for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
CacheInfo cache = entry.getValue();
if (cache.type == null) {
cache.type = CacheType.MEM;
}
cache.name = entry.getKey();
}
return caches.values();
}
private void printMemoryCoreCaches(Collection<CacheInfo> caches) {
for (CacheInfo cache : caches) {
if (!cache.name.contains("-") && CacheType.MEM.equals(cache.type)) {
printCache(cache);
}
CacheStats stat = cache.stats();
stdout.print(String.format(
" %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
entry.getKey(),
count(cache.size()),
"",
"",
duration(stat.averageLoadPenalty()),
percent(stat.hitCount(), stat.requestCount()),
""));
}
}
private Map<String, Cache<?, ?>> sortedCoreCaches() {
SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
cacheMap.byPlugin("gerrit").entrySet()) {
m.put(cacheNameOf("gerrit", entry.getKey()), entry.getValue().get());
}
return m;
}
private Map<String, Cache<?, ?>> sortedPluginCaches() {
SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
if (!"gerrit".equals(e.getPluginName())) {
m.put(cacheNameOf(e.getPluginName(), e.getExportName()),
e.getProvider().get());
private void printMemoryPluginCaches(Collection<CacheInfo> caches) {
for (CacheInfo cache : caches) {
if (cache.name.contains("-") && CacheType.MEM.equals(cache.type)) {
printCache(cache);
}
}
return m;
}
private void printDiskCaches(Collection<CacheInfo> caches) {
for (CacheInfo cache : caches) {
if (CacheType.DISK.equals(cache.type)) {
printCache(cache);
}
}
}
private void printCache(CacheInfo cache) {
stdout.print(String.format(
"%1s %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
CacheType.DISK.equals(cache.type) ? "D" : "",
cache.name,
nullToEmpty(cache.entries.mem),
nullToEmpty(cache.entries.disk),
Strings.nullToEmpty(cache.entries.space),
Strings.nullToEmpty(cache.averageGet),
formatAsPercent(cache.hitRatio.mem),
formatAsPercent(cache.hitRatio.disk)
));
}
private static String nullToEmpty(Long l) {
return l != null ? String.valueOf(l) : "";
}
private static String formatAsPercent(Integer i) {
return i != null ? String.valueOf(i) + "%" : "";
}
private void memSummary() {
@@ -358,39 +364,4 @@ final class ShowCaches extends CacheCommand {
}
return String.format("%1$6.2f%2$s", value, suffix);
}
private String count(long cnt) {
if (cnt == 0) {
return "";
}
return String.format("%6d", cnt);
}
private String duration(double ns) {
if (ns < 0.5) {
return "";
}
String suffix = "ns";
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "us";
}
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "ms";
}
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "s ";
}
return String.format("%4.1f%s", ns, suffix);
}
private String percent(final long value, final long total) {
if (total <= 0) {
return "";
}
final long pcent = (100 * value) / total;
return String.format("%3d%%", (int) pcent);
}
}