Merge topic 'rest-caches'

* changes:
  Add REST endpoint to get a cache
  Add REST endpoint to list caches
  Use '-' in cache name as separator between plugin name and cache name
This commit is contained in:
Shawn Pearce 2014-05-28 21:31:09 +00:00 committed by Gerrit Code Review
commit 5ced398580
13 changed files with 932 additions and 125 deletions

View File

@ -30,6 +30,261 @@ 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
}
}
}
----
[[get-cache]]
=== Get Cache
--
'GET /config/server/caches/link:#cache-name[\{cache-name\}]'
--
Retrieves information about a cache.
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 link:#cache-info[CacheInfo] entity is returned.
.Request
----
GET /config/server/caches/projects HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
)]}'
{
"name": "projects",
"entries": {
"mem": 35
},
"average_get": " 8.6ms",
"hit_ratio": {
"mem": 99
}
}
----
[[list-capabilities]]
=== List Capabilities
--
@ -176,9 +431,48 @@ link:#top-menu-entry-info[TopMenuEntryInfo] entities is returned.
----
[[ids]]
== IDs
[[cache-name]]
=== \{cache-name\}
The name of the cache.
If the cache is defined by a plugin the cache name must include the
plugin name: "<plugin-name>-<cache-name>".
Gerrit core caches can optionally be prefixed with "gerrit":
"gerrit-<cache-name>".
[[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: "<plugin-name>-<cache-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 +485,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

View File

@ -0,0 +1,6 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
srcs = glob(['*IT.java']),
labels = ['rest']
)

View File

@ -0,0 +1,74 @@
// 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 org.apache.http.HttpStatus;
import org.junit.Test;
import java.io.IOException;
public class GetCacheIT extends AbstractDaemonTest {
@Test
public void getCache() throws IOException {
RestResponse r = adminSession.get("/config/server/caches/accounts");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class);
assertEquals("accounts", result.name);
assertNull(result.type);
assertEquals(1, result.entries.mem.longValue());
assertNotNull(result.averageGet);
assertTrue(result.averageGet.endsWith("s"));
assertNull(result.entries.disk);
assertNull(result.entries.space);
assertTrue(result.hitRatio.mem >= 0);
assertTrue(result.hitRatio.mem <= 100);
assertNull(result.hitRatio.disk);
userSession.get("/config/server/version").consume();
r = adminSession.get("/config/server/caches/accounts");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
result = newGson().fromJson(r.getReader(), CacheInfo.class);
assertEquals(2, result.entries.mem.longValue());
}
@Test
public void getCache_Forbidden() throws IOException {
RestResponse r = userSession.get("/config/server/caches/accounts");
assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
}
@Test
public void getCache_NotFound() throws IOException {
RestResponse r = adminSession.get("/config/server/caches/nonExisting");
assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
}
@Test
public void getCacheWithGerritPrefix() throws IOException {
RestResponse r = adminSession.get("/config/server/caches/gerrit-accounts");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
}
}

View File

@ -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<String, CacheInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<Map<String, CacheInfo>>() {}.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<Map<String, CacheInfo>>() {}.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());
}
}

View File

@ -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<K, V> extends AbstractLoadingCache<K, V> {
public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
PersistentCache {
private static final Logger log = LoggerFactory.getLogger(H2CacheImpl.class);
private final Executor executor;
@ -156,6 +158,7 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> {
return mem.stats();
}
@Override
public DiskStats diskStats() {
return store.diskStats();
}
@ -193,29 +196,6 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> {
}, 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<V> {
final V value;
long created;
@ -599,9 +579,8 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> {
}
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<K, V> extends AbstractLoadingCache<K, V> {
+ " 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<K, V> extends AbstractLoadingCache<K, V> {
} finally {
release(c);
}
return d;
return new DiskStats(size, space, hitCount.get(), missCount.get());
}
private SqlHandle acquire() throws SQLException {

View File

@ -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;
}
}
}

View File

@ -0,0 +1,49 @@
// 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.gerrit.extensions.restapi.RestView;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
public class CacheResource extends ConfigResource {
public static final TypeLiteral<RestView<CacheResource>> CACHE_KIND =
new TypeLiteral<RestView<CacheResource>>() {};
private final String name;
private final Provider<Cache<?, ?>> cacheProvider;
public CacheResource(String pluginName, String cacheName, Provider<Cache<?, ?>> cacheProvider) {
this.name = cacheNameOf(pluginName, cacheName);
this.cacheProvider = cacheProvider;
}
public String getName() {
return name;
}
public Cache<?, ?> getCache() {
return cacheProvider.get();
}
public static String cacheNameOf(String plugin, String name) {
if ("gerrit".equals(plugin)) {
return name;
} else {
return plugin + "-" + name;
}
}
}

View File

@ -0,0 +1,88 @@
// 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.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
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.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
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<ConfigResource, CacheResource> {
private final DynamicMap<RestView<CacheResource>> views;
private final Provider<ListCaches> list;
private final Provider<CurrentUser> self;
private final DynamicMap<Cache<?, ?>> cacheMap;
@Inject
CachesCollection(DynamicMap<RestView<CacheResource>> views,
Provider<ListCaches> list, Provider<CurrentUser> self,
DynamicMap<Cache<?, ?>> cacheMap) {
this.views = views;
this.list = list;
this.self = self;
this.cacheMap = cacheMap;
}
@Override
public RestView<ConfigResource> list() {
return list.get();
}
@Override
public CacheResource parse(ConfigResource parent, IdString id)
throws AuthException, ResourceNotFoundException {
CurrentUser user = self.get();
if (user instanceof AnonymousUser) {
throw new AuthException("Authentication required");
} else if (!user.isIdentifiedUser()) {
throw new ResourceNotFoundException();
} else if (!user.getCapabilities().canViewCaches()) {
throw new AuthException("not allowed to view caches");
}
String cacheName = id.get();
String pluginName = "gerrit";
int i = cacheName.lastIndexOf('-');
if (i != -1) {
pluginName = cacheName.substring(0, i);
cacheName = cacheName.length() > i + 1 ? cacheName.substring(i + 1) : "";
}
Provider<Cache<?, ?>> cacheProvider = cacheMap.byPlugin(pluginName).get(cacheName);
if (cacheProvider == null) {
throw new ResourceNotFoundException(id);
}
return new CacheResource(pluginName, cacheName, cacheProvider);
}
@Override
public DynamicMap<RestView<CacheResource>> views() {
return views;
}
}

View File

@ -0,0 +1,28 @@
// 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.RestReadView;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.inject.Singleton;
@Singleton
public class GetCache implements RestReadView<CacheResource> {
@Override
public CacheInfo apply(CacheResource rsrc) {
return new CacheInfo(rsrc.getName(), rsrc.getCache());
}
}

View File

@ -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 static com.google.gerrit.server.config.CacheResource.cacheNameOf;
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<ConfigResource> {
private final DynamicMap<Cache<?, ?>> cacheMap;
@Inject
public ListCaches(DynamicMap<Cache<?, ?>> cacheMap) {
this.cacheMap = cacheMap;
}
@Override
public Map<String, CacheInfo> apply(ConfigResource rsrc) {
Map<String, CacheInfo> cacheInfos = new TreeMap<>();
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
cacheInfos.put(cacheNameOf(e.getPluginName(), e.getExportName()),
new CacheInfo(e.getProvider().get()));
}
return cacheInfos;
}
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) {
this(null, cache);
}
public CacheInfo(String name, Cache<?,?> cache) {
this.name = name;
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);
}
}
}

View File

@ -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,12 @@ 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);
get(CACHE_KIND).to(GetCache.class);
child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class);
get(CONFIG_KIND, "version").to(GetVersion.class);

View File

@ -38,7 +38,7 @@ abstract class CacheCommand extends SshCommand {
if ("gerrit".equals(plugin)) {
return name;
} else {
return plugin + "." + name;
return plugin + "-" + name;
}
}
}

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);
}
}