diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index 6a3b34ea35..1be8d13bd2 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt @@ -30,6 +30,223 @@ Returns the version of the Gerrit server. "2.7" ---- +[[list-caches]] +=== List Caches +-- +'GET /config/server/caches/' +-- + +Lists the caches of the server. Caches defined by plugins are included. + +The caller must be a member of a group that is granted the +link:access-control.html#capability_viewCaches[View Caches] capability +or the link:access-control.html#capability_administrateServer[ +Administrate Server] capability. + +As result a map of link:#cache-info[CacheInfo] entities is returned. + +The entries in the map are sorted by cache name. + +.Request +---- + GET /config/server/caches/ HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "accounts": { + "entries": { + "mem": 4 + }, + "average_get": "2.5ms", + "hit_ratio": { + "mem": 94 + } + }, + "accounts_byemail": { + "entries": { + "mem": 4 + }, + "average_get": "771.8us", + "hit_ratio": { + "mem": 95 + } + }, + "accounts_byname": { + "entries": { + "mem": 4 + }, + "hit_ratio": { + "mem": 100 + } + }, + "adv_bases": { + "entries": {}, + "hit_ratio": {} + }, + "change_kind": { + "type": "DISK", + "entries": { + "space": "0.00k" + }, + "hit_ratio": {} + }, + "changes": { + "entries": {}, + "hit_ratio": {} + }, + "conflicts": { + "type": "DISK", + "entries": { + "mem": 2, + "disk": 3, + "space": "2.75k" + }, + "hit_ratio": { + "mem": 0, + "disk": 100 + } + }, + "diff": { + "type": "DISK", + "entries": { + "mem": 177, + "disk": 253, + "space": "170.97k" + }, + "average_get": "1.1ms", + "hit_ratio": { + "mem": 67, + "disk": 100 + } + }, + "diff_intraline": { + "type": "DISK", + "entries": { + "mem": 1, + "disk": 1, + "space": "0.37k" + }, + "average_get": "6.8ms", + "hit_ratio": { + "mem": 0 + } + }, + "git_tags": { + "type": "DISK", + "entries": { + "space": "0.00k" + }, + "hit_ratio": {} + }, + groups": { + "entries": { + "mem": 27 + }, + "average_get": "183.2us", + "hit_ratio": { + "mem": 12 + } + }, + "groups_byinclude": { + "entries": {}, + "hit_ratio": {} + }, + "groups_byname": { + "entries": {}, + "hit_ratio": {} + }, + "groups_byuuid": { + "entries": { + "mem": 25 + }, + "average_get": "173.4us", + "hit_ratio": { + "mem": 13 + } + }, + "groups_external": { + "entries": {}, + "hit_ratio": {} + }, + groups_members": { + "entries": { + "mem": 4 + }, + "average_get": "697.8us", + "hit_ratio": { + "mem": 82 + } + }, + "permission_sort": { + "entries": { + "mem": 16 + }, + "hit_ratio": { + "mem": 96 + } + }, + "plugin_resources": { + "entries": { + "mem": 2 + }, + "hit_ratio": { + "mem": 83 + } + }, + "project_list": { + "entries": { + "mem": 1 + }, + "average_get": "18.6ms", + "hit_ratio": { + "mem": 0 + } + }, + "projects": { + "entries": { + "mem": 35 + }, + "average_get": "8.6ms", + "hit_ratio": { + "mem": 99 + } + }, + "quota-repo_size": { + "type": "DISK", + "entries": { + "space": "0.00k" + }, + "hit_ratio": {} + }, + "sshkeys": { + "entries": { + "mem": 1 + }, + "average_get": "3.2ms", + "hit_ratio": { + "mem": 50 + } + }, + "web_sessions": { + "type": "DISK", + "entries": { + "mem": 1, + "disk": 2, + "space": "0.78k" + }, + "hit_ratio": { + "mem": 82 + } + } + } +---- + [[list-capabilities]] === List Capabilities -- @@ -179,6 +396,31 @@ link:#top-menu-entry-info[TopMenuEntryInfo] entities is returned. [[json-entities]] == JSON Entities +[[cache-info]] +=== CacheInfo +The `CacheInfo` entity contains information about a cache. + +[options="header",width="50%",cols="1,^1,5"] +|================================== +|Field Name ||Description +|`name` | +not set if returned in a map where the cache name is used as map key| +The cache name. If the cache is defined by a plugin the cache name +includes the plugin name: "-". +|`type` |not set for in memory caches| +The type of the cache (`MEM`: in memory cache, `DISK`: disk cache). +|`entries` || +Information about the entries in the cache as a +link:#entries-info[EntriesInfo] entity. +|`average_get` |optional| +The average duration of getting one entry from the cache. The value is +returned with a standard time unit abbreviation (`ns`: nanoseconds, +`us`: microseconds, `ms`: milliseconds, `s`: seconds). +|`hit_ratio` || +Information about the hit ratio as a link:#hit-ration-info[ +HitRatioInfo] entity. +|================================== + [[capability-info]] === CapabilityInfo The `CapabilityInfo` entity contains information about a capability. @@ -191,6 +433,39 @@ The `CapabilityInfo` entity contains information about a capability. |`name` |capability name |================================= +[[entries-info]] +=== EntriesInfo +The `EntriesInfo` entity contains information about the entries in a +cache. + +[options="header",width="50%",cols="1,^1,5"] +|================================== +|Field Name ||Description +|`mem` |optional|Number of cache entries that are held in memory. +|`disk` |optional|Number of cache entries on the disk. For non-disk +caches this value is not set; for disk caches it is only set if there +are entries in the cache. +|`space` |optional| +The space that is consumed by the cache on disk. The value is returned +with a unit abbreviation (`k`: kilobytes, `m`: megabytes, +`g`: gigabytes). Only set for disk caches. +|================================== + +[[hit-ration-info]] +=== HitRatioInfo +The `HitRatioInfo` entity contains information about the hit ratio of a +cache. + +[options="header",width="50%",cols="1,^1,5"] +|================================== +|Field Name ||Description +|`mem` || +Hit ratio for cache entries that are held in memory (0 \<= value \<= 100). +|`disk` |optional| +Hit ratio for cache entries that are held on disk (0 \<= value \<= 100). +Only set for disk caches. +|================================== + [[top-menu-entry-info]] === TopMenuEntryInfo The `TopMenuEntryInfo` entity contains information about a top menu diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK new file mode 100644 index 0000000000..c89da306e2 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK @@ -0,0 +1,6 @@ +include_defs('//gerrit-acceptance-tests/tests.defs') + +acceptance_tests( + srcs = glob(['*IT.java']), + labels = ['rest'] +) diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java new file mode 100644 index 0000000000..950b07657e --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java @@ -0,0 +1,68 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance.rest.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.RestResponse; +import com.google.gerrit.server.config.ListCaches.CacheInfo; +import com.google.gson.reflect.TypeToken; + +import org.apache.http.HttpStatus; +import org.junit.Test; + +import java.io.IOException; +import java.util.Map; + +public class ListCachesIT extends AbstractDaemonTest { + + @Test + public void listCaches() throws IOException { + RestResponse r = adminSession.get("/config/server/caches/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = + newGson().fromJson(r.getReader(), + new TypeToken>() {}.getType()); + + assertTrue(result.containsKey("accounts")); + CacheInfo accountsCacheInfo = result.get("accounts"); + assertNull(accountsCacheInfo.type); + assertEquals(1, accountsCacheInfo.entries.mem.longValue()); + assertNotNull(accountsCacheInfo.averageGet); + assertTrue(accountsCacheInfo.averageGet.endsWith("s")); + assertNull(accountsCacheInfo.entries.disk); + assertNull(accountsCacheInfo.entries.space); + assertTrue(accountsCacheInfo.hitRatio.mem >= 0); + assertTrue(accountsCacheInfo.hitRatio.mem <= 100); + assertNull(accountsCacheInfo.hitRatio.disk); + + userSession.get("/config/server/version").consume(); + r = adminSession.get("/config/server/caches/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + result = newGson().fromJson(r.getReader(), + new TypeToken>() {}.getType()); + assertEquals(2, result.get("accounts").entries.mem.longValue()); + } + + @Test + public void listCaches_Forbidden() throws IOException { + RestResponse r = userSession.get("/config/server/caches/"); + assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode()); + } +} diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java index de7613d7e3..652ed30bc5 100644 --- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java +++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java @@ -11,6 +11,7 @@ import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnel; import com.google.common.hash.Funnels; import com.google.common.hash.PrimitiveSink; +import com.google.gerrit.server.cache.PersistentCache; import com.google.gerrit.server.util.TimeUtil; import com.google.inject.TypeLiteral; @@ -63,7 +64,8 @@ import java.util.concurrent.atomic.AtomicLong; * * @see H2CacheFactory */ -public class H2CacheImpl extends AbstractLoadingCache { +public class H2CacheImpl extends AbstractLoadingCache implements + PersistentCache { private static final Logger log = LoggerFactory.getLogger(H2CacheImpl.class); private final Executor executor; @@ -156,6 +158,7 @@ public class H2CacheImpl extends AbstractLoadingCache { return mem.stats(); } + @Override public DiskStats diskStats() { return store.diskStats(); } @@ -193,29 +196,6 @@ public class H2CacheImpl extends AbstractLoadingCache { }, delay, TimeUnit.MILLISECONDS); } - public static class DiskStats { - long size; - long space; - long hitCount; - long missCount; - - public long size() { - return size; - } - - public long space() { - return space; - } - - public long hitCount() { - return hitCount; - } - - public long requestCount() { - return hitCount + missCount; - } - } - static class ValueHolder { final V value; long created; @@ -599,9 +579,8 @@ public class H2CacheImpl extends AbstractLoadingCache { } DiskStats diskStats() { - DiskStats d = new DiskStats(); - d.hitCount = hitCount.get(); - d.missCount = missCount.get(); + long size = 0; + long space = 0; SqlHandle c = null; try { c = acquire(); @@ -613,8 +592,8 @@ public class H2CacheImpl extends AbstractLoadingCache { + " FROM data"); try { if (r.next()) { - d.size = r.getLong(1); - d.space = r.getLong(2); + size = r.getLong(1); + space = r.getLong(2); } } finally { r.close(); @@ -628,7 +607,7 @@ public class H2CacheImpl extends AbstractLoadingCache { } finally { release(c); } - return d; + return new DiskStats(size, space, hitCount.get(), missCount.get()); } private SqlHandle acquire() throws SQLException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java new file mode 100644 index 0000000000..62623ea50f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java @@ -0,0 +1,50 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.cache; + +public interface PersistentCache { + + DiskStats diskStats(); + + public static class DiskStats { + private final long size; + private final long space; + private final long hitCount; + private final long missCount; + + public DiskStats(long size, long space, long hitCount, long missCount) { + this.size = size; + this.space = space; + this.hitCount = hitCount; + this.missCount = missCount; + } + + public long size() { + return size; + } + + public long space() { + return space; + } + + public long hitCount() { + return hitCount; + } + + public long requestCount() { + return hitCount + missCount; + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java new file mode 100644 index 0000000000..1f1945b6e7 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java @@ -0,0 +1,23 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.TypeLiteral; + +public class CacheResource extends ConfigResource { + public static final TypeLiteral> CACHE_KIND = + new TypeLiteral>() {}; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java new file mode 100644 index 0000000000..3d7de162ae --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java @@ -0,0 +1,58 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +@RequiresCapability(GlobalCapability.VIEW_CACHES) +@Singleton +public class CachesCollection implements + ChildCollection { + + private final DynamicMap> views; + private final Provider list; + + @Inject + CachesCollection(DynamicMap> views, + Provider list) { + this.views = views; + this.list = list; + } + + @Override + public RestView list() { + return list.get(); + } + + @Override + public CacheResource parse(ConfigResource parent, IdString id) + throws ResourceNotFoundException { + throw new ResourceNotFoundException(id); + } + + @Override + public DynamicMap> views() { + return views; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java new file mode 100644 index 0000000000..d4fd8c5a2e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java @@ -0,0 +1,163 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheStats; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.server.cache.PersistentCache; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.util.Map; +import java.util.TreeMap; + +@RequiresCapability(GlobalCapability.VIEW_CACHES) +@Singleton +public class ListCaches implements RestReadView { + private final DynamicMap> cacheMap; + + @Inject + public ListCaches(DynamicMap> cacheMap) { + this.cacheMap = cacheMap; + } + + @Override + public Map apply(ConfigResource rsrc) { + Map cacheInfos = new TreeMap<>(); + for (DynamicMap.Entry> e : cacheMap) { + cacheInfos.put(cacheNameOf(e.getPluginName(), e.getExportName()), + new CacheInfo(e.getProvider().get())); + } + return cacheInfos; + } + + private static String cacheNameOf(String plugin, String name) { + if ("gerrit".equals(plugin)) { + return name; + } else { + return plugin + "-" + name; + } + } + + public enum CacheType { + MEM, DISK; + } + + public static class CacheInfo { + public String name; + public CacheType type; + public EntriesInfo entries; + public String averageGet; + public HitRatioInfo hitRatio; + + public CacheInfo(Cache cache) { + CacheStats stat = cache.stats(); + + entries = new EntriesInfo(); + entries.setMem(cache.size()); + + averageGet = duration(stat.averageLoadPenalty()); + + hitRatio = new HitRatioInfo(); + hitRatio.setMem(stat.hitCount(), stat.requestCount()); + + if (cache instanceof PersistentCache) { + type = CacheType.DISK; + PersistentCache.DiskStats diskStats = + ((PersistentCache) cache).diskStats(); + entries.setDisk(diskStats.size()); + entries.setSpace(diskStats.space()); + hitRatio.setDisk(diskStats.hitCount(), diskStats.requestCount()); + } + } + + private static String duration(double ns) { + if (ns < 0.5) { + return null; + } + 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).trim(); + } + } + + public static class EntriesInfo { + public Long mem; + public Long disk; + public String space; + + public void setMem(long mem) { + this.mem = mem != 0 ? mem : null; + } + + public void setDisk(long disk) { + this.disk = disk != 0 ? disk : null; + } + + public void setSpace(double value) { + space = bytes(value); + } + + private static String bytes(double value) { + value /= 1024; + String suffix = "k"; + + if (value > 1024) { + value /= 1024; + suffix = "m"; + } + if (value > 1024) { + value /= 1024; + suffix = "g"; + } + return String.format("%1$6.2f%2$s", value, suffix).trim(); + } + } + + public static class HitRatioInfo { + public Integer mem; + public Integer disk; + + public void setMem(long value, long total) { + mem = percent(value, total); + } + + public void setDisk(long value, long total) { + disk = percent(value, total); + } + + private static Integer percent(long value, long total) { + if (total <= 0) { + return null; + } + return (int) ((100 * value) / total); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java index 761b26597c..80811af72c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java @@ -14,6 +14,7 @@ package com.google.gerrit.server.config; +import static com.google.gerrit.server.config.CacheResource.CACHE_KIND; import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND; import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND; import static com.google.gerrit.server.config.TopMenuResource.TOP_MENU_KIND; @@ -24,9 +25,11 @@ import com.google.gerrit.extensions.restapi.RestApiModule; public class Module extends RestApiModule { @Override protected void configure() { + DynamicMap.mapOf(binder(), CACHE_KIND); + DynamicMap.mapOf(binder(), CAPABILITY_KIND); DynamicMap.mapOf(binder(), CONFIG_KIND); DynamicMap.mapOf(binder(), TOP_MENU_KIND); - DynamicMap.mapOf(binder(), CAPABILITY_KIND); + child(CONFIG_KIND, "caches").to(CachesCollection.class); child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class); child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class); get(CONFIG_KIND, "version").to(GetVersion.class); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java index 397120f484..00ccae271b 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java @@ -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; + @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> disks = Maps.newTreeMap(); - printMemoryCaches(disks, sortedCoreCaches()); - printMemoryCaches(disks, sortedPluginCaches()); - for (Map.Entry> 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 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> disks, - Map> caches) { - for (Map.Entry> entry : caches.entrySet()) { - Cache cache = entry.getValue(); - if (cache instanceof H2CacheImpl) { - disks.put(entry.getKey(), (H2CacheImpl)cache); - continue; + private Collection getCaches() { + Map caches = listCaches.get().apply(new ConfigResource()); + for (Map.Entry 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 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> sortedCoreCaches() { - SortedMap> m = Maps.newTreeMap(); - for (Map.Entry>> entry : - cacheMap.byPlugin("gerrit").entrySet()) { - m.put(cacheNameOf("gerrit", entry.getKey()), entry.getValue().get()); - } - return m; - } - - private Map> sortedPluginCaches() { - SortedMap> m = Maps.newTreeMap(); - for (DynamicMap.Entry> e : cacheMap) { - if (!"gerrit".equals(e.getPluginName())) { - m.put(cacheNameOf(e.getPluginName(), e.getExportName()), - e.getProvider().get()); + private void printMemoryPluginCaches(Collection caches) { + for (CacheInfo cache : caches) { + if (cache.name.contains("-") && CacheType.MEM.equals(cache.type)) { + printCache(cache); } } - return m; + } + + private void printDiskCaches(Collection 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); - } }