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:
parent
0fa95e7eb9
commit
42c9cf61c8
@ -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: "<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 +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
|
||||
|
@ -0,0 +1,6 @@
|
||||
include_defs('//gerrit-acceptance-tests/tests.defs')
|
||||
|
||||
acceptance_tests(
|
||||
srcs = glob(['*IT.java']),
|
||||
labels = ['rest']
|
||||
)
|
@ -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());
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
50
gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java
vendored
Normal file
50
gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<RestView<CacheResource>> CACHE_KIND =
|
||||
new TypeLiteral<RestView<CacheResource>>() {};
|
||||
}
|
@ -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<ConfigResource, CacheResource> {
|
||||
|
||||
private final DynamicMap<RestView<CacheResource>> views;
|
||||
private final Provider<ListCaches> list;
|
||||
|
||||
@Inject
|
||||
CachesCollection(DynamicMap<RestView<CacheResource>> views,
|
||||
Provider<ListCaches> list) {
|
||||
this.views = views;
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestView<ConfigResource> list() {
|
||||
return list.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheResource parse(ConfigResource parent, IdString id)
|
||||
throws ResourceNotFoundException {
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicMap<RestView<CacheResource>> views() {
|
||||
return views;
|
||||
}
|
||||
}
|
@ -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<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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user