Merge topic 'singleton-ChangeJson' * changes: CreateChange: Don't use Factory ChangeJson: Don't use ChangesCollection ChangeJson: Make it semi stateless ChangeJson: Project control cache is dead; remove it
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt index 474893d..43ede06 100644 --- a/Documentation/config-project-config.txt +++ b/Documentation/config-project-config.txt
@@ -198,6 +198,41 @@ documentation for a full list of available capabilities. +[[branchOrder-section]] +=== branchOrder section + +Defines a branch ordering which is used for backporting of changes. +Backporting will be offered for a change (in the Gerrit UI) for all +more stable branches where the change can merge cleanly. + +[[branchOrder.branch]]branchOrder.branch:: ++ +A branch name, typically multiple values will be defined. The order of branch +names in this section defines the branch order. The topmost is considered to be +the least stable branch (typically the master branch) and the last one the +most stable (typically the last maintained release branch). + +Example: + +---- +[branchOrder] + branch = master + branch = stable-2.9 + branch = stable-2.8 + branch = stable-2.7 +---- + +The `branchOrder` section is inheritable. This is useful when multiple or all +projects follow the same branch rules. A `branchOrder` section in a child +project completely overrides any `branchOrder` section from a parent i.e. there +is no merging of `branchOrder` sections. A present but empty `branchOrder` +section removes all inherited branch order. + +Branches not listed in this section will not be included in the mergeability +check. If the `branchOrder` section is not defined then the mergeability of a +change into other branches will not be done. + + [[file-groups]] == The file +groups+
diff --git a/Documentation/json.txt b/Documentation/json.txt index 6ab35f9..3624b3f 100644 --- a/Documentation/json.txt +++ b/Documentation/json.txt
@@ -115,7 +115,7 @@ isDraft:: Whether or not the patch set is a draft patch set. -changeKind:: Kind of change uploaded. +kind:: Kind of change uploaded. REWORK;; Nontrivial content changes.
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index 6a3b34e..9ea4f13 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt
@@ -30,6 +30,261 @@ "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 @@ ---- +[[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 @@ |`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 0000000..c89da30 --- /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/GetCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java new file mode 100644 index 0000000..5a22b19 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
@@ -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()); + } +}
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 0000000..950b076 --- /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<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()); + } +}
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 de7613d..652ed30 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.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 @@ * * @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 @@ return mem.stats(); } + @Override public DiskStats diskStats() { return store.diskStats(); } @@ -193,29 +196,6 @@ }, 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 @@ } 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 @@ + " 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 @@ } 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 0000000..62623ea --- /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/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java index e785736..f2c4adc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -24,12 +24,14 @@ import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.git.BranchOrderSection; import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MergeException; import com.google.gerrit.server.git.strategy.SubmitStrategyFactory; import com.google.gerrit.server.index.ChangeIndexer; import com.google.gerrit.server.project.NoSuchProjectException; +import com.google.gerrit.server.project.ProjectCache; import com.google.gwtorm.server.AtomicUpdate; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -73,6 +75,7 @@ private final TestSubmitType.Get submitType; private final GitRepositoryManager gitManager; + private final ProjectCache projectCache; private final SubmitStrategyFactory submitStrategyFactory; private final Provider<ReviewDb> db; private final ChangeIndexer indexer; @@ -82,11 +85,13 @@ @Inject Mergeable(TestSubmitType.Get submitType, GitRepositoryManager gitManager, + ProjectCache projectCache, SubmitStrategyFactory submitStrategyFactory, Provider<ReviewDb> db, ChangeIndexer indexer) { this.submitType = submitType; this.gitManager = gitManager; + this.projectCache = projectCache; this.submitStrategyFactory = submitStrategyFactory; this.db = db; this.indexer = indexer; @@ -124,11 +129,17 @@ if (otherBranches) { result.mergeableInto = new ArrayList<>(); - for (Ref r : refs.values()) { - if (r.getName().startsWith(Constants.R_HEADS) - && !r.getName().equals(ref.getName())) { - if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, r)) { - result.mergeableInto.add(r.getName()); + BranchOrderSection branchOrder = + projectCache.get(change.getProject()).getBranchOrderSection(); + if (branchOrder != null) { + int prefixLen = Constants.R_HEADS.length(); + for (String n : branchOrder.getMoreStable(ref.getName())) { + Ref other = refs.get(n); + if (other == null) { + continue; + } + if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, other)) { + result.mergeableInto.add(other.getName().substring(prefixLen)); } } }
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 0000000..9019f4d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java
@@ -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; + } + } +}
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 0000000..e288456 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java
@@ -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; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java new file mode 100644 index 0000000..53628cc --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java
@@ -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()); + } +}
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 0000000..6c5e9f1 --- /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 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); + } + } +}
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 761b265..9d82640 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,12 @@ 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);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java new file mode 100644 index 0000000..c447d31 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java
@@ -0,0 +1,60 @@ +// 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.git; + +import com.google.common.collect.ImmutableList; + +import org.eclipse.jgit.lib.Constants; + +import java.util.List; + +public class BranchOrderSection { + + /** + * Branch names ordered from least to the most stable. + * + * Typically the order will be like: master, stable-M.N, stable-M.N-1, ... + */ + private final ImmutableList<String> order; + + public BranchOrderSection(String[] order) { + if (order.length == 0) { + this.order = ImmutableList.of(); + } else { + ImmutableList.Builder<String> builder = ImmutableList.builder(); + for (String b : order) { + builder.add(fullName(b)); + } + this.order = builder.build(); + } + } + + private static String fullName(String branch) { + if (branch.startsWith(Constants.R_HEADS)) { + return branch; + } else { + return Constants.R_HEADS + branch; + } + } + + public List<String> getMoreStable(String branch) { + int i = order.indexOf(fullName(branch)); + if (0 <= i) { + return order.subList(i + 1, order.size()); + } else { + return ImmutableList.of(); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index d5dae8e..c4c1b7f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -95,6 +95,9 @@ private static final String ACCOUNTS = "accounts"; private static final String KEY_SAME_GROUP_VISIBILITY = "sameGroupVisibility"; + private static final String BRANCH_ORDER = "branchOrder"; + private static final String BRANCH = "branch"; + private static final String CONTRIBUTOR_AGREEMENT = "contributor-agreement"; private static final String KEY_ACCEPTED = "accepted"; private static final String KEY_REQUIRE_CONTACT_INFORMATION = "requireContactInformation"; @@ -152,6 +155,7 @@ private AccountsSection accountsSection; private Map<AccountGroup.UUID, GroupReference> groupsByUUID; private Map<String, AccessSection> accessSections; + private BranchOrderSection branchOrderSection; private Map<String, ContributorAgreement> contributorAgreements; private Map<String, NotifyConfig> notifySections; private Map<String, LabelType> labelSections; @@ -242,6 +246,10 @@ return sort(accessSections.values()); } + public BranchOrderSection getBranchOrderSection() { + return branchOrderSection; + } + public void remove(AccessSection section) { if (section != null) { accessSections.remove(section.getName()); @@ -420,6 +428,7 @@ loadAccountsSection(rc, groupsByName); loadContributorAgreements(rc, groupsByName); loadAccessSections(rc, groupsByName); + loadBranchOrderSection(rc); loadNotifySections(rc, groupsByName); loadLabelSections(rc); loadCommentLinkSections(rc); @@ -570,6 +579,13 @@ } } + private void loadBranchOrderSection(Config rc) { + if (rc.getSections().contains(BRANCH_ORDER)) { + branchOrderSection = new BranchOrderSection( + rc.getStringList(BRANCH_ORDER, null, BRANCH)); + } + } + private List<PermissionRule> loadPermissionRules(Config rc, String section, String subsection, String varName, Map<String, GroupReference> groupsByName,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java index f6b96d7..715419d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -40,6 +40,7 @@ import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.git.BranchOrderSection; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectLevelConfig; @@ -445,6 +446,16 @@ return ImmutableList.copyOf(cls.values()); } + public BranchOrderSection getBranchOrderSection() { + for (ProjectState s : tree()) { + BranchOrderSection section = s.getConfig().getBranchOrderSection(); + if (section != null) { + return section; + } + } + return null; + } + public ThemeInfo getTheme() { ThemeInfo theme = this.theme; if (theme == null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java index 7f9e5db..3d568e1 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
@@ -38,7 +38,7 @@ if ("gerrit".equals(plugin)) { return name; } else { - return plugin + "." + name; + return plugin + "-" + name; } } }
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 397120f..00ccae2 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 @@ 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.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 @@ @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 @@ } 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 @@ 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; } - 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()), - "")); + 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); + } } } - 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 @@ } 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); - } }