Merge "Instrument Group Metrics"
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 9df4b04..70352dc 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -456,6 +456,15 @@
 === Group
 
 * `group/guess_relevant_groups_latency`: Latency for guessing relevant groups.
+* `group/handles_count`: Number of calls to GroupBackend.handles.
+* `group/get_count`: Number of calls to GroupBackend.get.
+* `group/suggest_count`: Number of calls to GroupBackend.suggest.
+* `group/contains_count`: Number of calls to GroupMemberships.contains.
+* `group/contains_any_of_count`: Number of calls to
+  GroupMemberships.containsAnyOf.
+* `group/intersection_count`: Number of calls to GroupMemberships.intersection.
+* `group/known_groups_count`: Number of calls to GroupMemberships.getKnownGroups.
+
 
 === Replication Plugin
 
diff --git a/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index 5bd9bea..1587bc5 100644
--- a/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -27,10 +27,16 @@
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.GroupDescription;
 import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.metrics.Counter1;
+import com.google.gerrit.metrics.Counter2;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.StartupCheck;
 import com.google.gerrit.server.StartupException;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
 import com.google.gerrit.server.project.ProjectState;
@@ -49,11 +55,57 @@
 public class UniversalGroupBackend implements GroupBackend {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  private static final Field<String> SYSTEM_FIELD =
+      Field.ofString("system", Metadata.Builder::groupSystem).build();
+
   private final PluginSetContext<GroupBackend> backends;
+  private final Counter1<String> handlesCount;
+  private final Counter1<String> getCount;
+  private final Counter2<String, Integer> suggestCount;
+  private final Counter2<String, Boolean> containsCount;
+  private final Counter2<String, Boolean> containsAnyCount;
+  private final Counter2<String, Integer> intersectionCount;
+  private final Counter2<String, Integer> knownGroupsCount;
 
   @Inject
-  UniversalGroupBackend(PluginSetContext<GroupBackend> backends) {
+  UniversalGroupBackend(PluginSetContext<GroupBackend> backends, MetricMaker metricMaker) {
     this.backends = backends;
+    this.handlesCount =
+        metricMaker.newCounter(
+            "group/handles_count", new Description("Calls to GroupBackend.handles"), SYSTEM_FIELD);
+    this.getCount =
+        metricMaker.newCounter(
+            "group/get_count", new Description("Calls to GroupBackend.get"), SYSTEM_FIELD);
+    this.suggestCount =
+        metricMaker.newCounter(
+            "group/suggest_count",
+            new Description("Calls to GroupBackend.suggest"),
+            SYSTEM_FIELD,
+            Field.ofInteger("num_suggested", (meta, value) -> {}).build());
+    this.containsCount =
+        metricMaker.newCounter(
+            "group/contains_count",
+            new Description("Calls to GroupMemberships.contains"),
+            SYSTEM_FIELD,
+            Field.ofBoolean("contains", (meta, value) -> {}).build());
+    this.containsAnyCount =
+        metricMaker.newCounter(
+            "group/contains_any_of_count",
+            new Description("Calls to GroupMemberships.containsAnyOf"),
+            SYSTEM_FIELD,
+            Field.ofBoolean("contains_any_of", (meta, value) -> {}).build());
+    this.intersectionCount =
+        metricMaker.newCounter(
+            "group/intersection_count",
+            new Description("Calls to GroupMemberships.intersection"),
+            SYSTEM_FIELD,
+            Field.ofInteger("num_intersection", (meta, value) -> {}).build());
+    this.knownGroupsCount =
+        metricMaker.newCounter(
+            "group/known_groups_count",
+            new Description("Calls to GroupMemberships.getKnownGroups"),
+            SYSTEM_FIELD,
+            Field.ofInteger("num_known_groups", (meta, value) -> {}).build());
   }
 
   @Nullable
@@ -70,7 +122,12 @@
 
   @Override
   public boolean handles(AccountGroup.UUID uuid) {
-    return backend(uuid) != null;
+    GroupBackend b = backend(uuid);
+    if (b == null) {
+      return false;
+    }
+    handlesCount.increment(name(b));
+    return true;
   }
 
   @Override
@@ -83,13 +140,19 @@
       logger.atFine().log("Unknown GroupBackend for UUID: %s", uuid);
       return null;
     }
+    getCount.increment(name(b));
     return b.get(uuid);
   }
 
   @Override
   public Collection<GroupReference> suggest(String name, ProjectState project) {
     Set<GroupReference> groups = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
-    backends.runEach(g -> groups.addAll(g.suggest(name, project)));
+    backends.runEach(
+        g -> {
+          Collection<GroupReference> suggestions = g.suggest(name, project);
+          suggestCount.increment(name(g), suggestions.size());
+          groups.addAll(suggestions);
+        });
     return groups;
   }
 
@@ -108,11 +171,11 @@
     }
 
     @Nullable
-    private GroupMembership membership(AccountGroup.UUID uuid) {
+    private Map.Entry<GroupBackend, GroupMembership> membership(AccountGroup.UUID uuid) {
       if (uuid != null) {
         for (Map.Entry<GroupBackend, GroupMembership> m : memberships.entrySet()) {
           if (m.getKey().handles(uuid)) {
-            return m.getValue();
+            return m;
           }
         }
       }
@@ -125,51 +188,57 @@
       if (uuid == null) {
         return false;
       }
-      GroupMembership m = membership(uuid);
+      Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
       if (m == null) {
         return false;
       }
-      return m.contains(uuid);
+      boolean contains = m.getValue().contains(uuid);
+      containsCount.increment(name(m.getKey()), contains);
+      return contains;
     }
 
     @Override
     public boolean containsAnyOf(Iterable<AccountGroup.UUID> uuids) {
-      ListMultimap<GroupMembership, AccountGroup.UUID> lookups =
+      ListMultimap<Map.Entry<GroupBackend, GroupMembership>, AccountGroup.UUID> lookups =
           MultimapBuilder.hashKeys().arrayListValues().build();
       for (AccountGroup.UUID uuid : uuids) {
         if (uuid == null) {
           continue;
         }
-        GroupMembership m = membership(uuid);
+        Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
         if (m == null) {
           continue;
         }
         lookups.put(m, uuid);
       }
-      for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry :
-          lookups.asMap().entrySet()) {
-        GroupMembership m = entry.getKey();
-        Collection<AccountGroup.UUID> ids = entry.getValue();
+      for (Map.Entry<GroupBackend, GroupMembership> groupBackends : lookups.asMap().keySet()) {
+
+        GroupMembership m = groupBackends.getValue();
+        Collection<AccountGroup.UUID> ids = lookups.asMap().get(groupBackends);
         if (ids.size() == 1) {
           if (m.contains(Iterables.getOnlyElement(ids))) {
+            containsAnyCount.increment(name(groupBackends.getKey()), true);
             return true;
           }
         } else if (m.containsAnyOf(ids)) {
+          containsAnyCount.increment(name(groupBackends.getKey()), true);
           return true;
         }
+        // We would have returned if contains was true.
+        containsAnyCount.increment(name(groupBackends.getKey()), false);
       }
       return false;
     }
 
     @Override
     public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> uuids) {
-      ListMultimap<GroupMembership, AccountGroup.UUID> lookups =
+      ListMultimap<Map.Entry<GroupBackend, GroupMembership>, AccountGroup.UUID> lookups =
           MultimapBuilder.hashKeys().arrayListValues().build();
       for (AccountGroup.UUID uuid : uuids) {
         if (uuid == null) {
           continue;
         }
-        GroupMembership m = membership(uuid);
+        Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
         if (m == null) {
           logger.atFine().log("Unknown GroupMembership for UUID: %s", uuid);
           continue;
@@ -177,9 +246,11 @@
         lookups.put(m, uuid);
       }
       Set<AccountGroup.UUID> groups = new HashSet<>();
-      for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry :
-          lookups.asMap().entrySet()) {
-        groups.addAll(entry.getKey().intersection(entry.getValue()));
+      for (Map.Entry<GroupBackend, GroupMembership> groupBackend : lookups.asMap().keySet()) {
+        Set<AccountGroup.UUID> intersection =
+            groupBackend.getValue().intersection(lookups.asMap().get(groupBackend));
+        intersectionCount.increment(name(groupBackend.getKey()), intersection.size());
+        groups.addAll(intersection);
       }
       return groups;
     }
@@ -187,8 +258,10 @@
     @Override
     public Set<AccountGroup.UUID> getKnownGroups() {
       Set<AccountGroup.UUID> groups = new HashSet<>();
-      for (GroupMembership m : memberships.values()) {
-        groups.addAll(m.getKnownGroups());
+      for (Map.Entry<GroupBackend, GroupMembership> entry : memberships.entrySet()) {
+        Set<AccountGroup.UUID> knownGroups = entry.getValue().getKnownGroups();
+        knownGroupsCount.increment(name(entry.getKey()), knownGroups.size());
+        groups.addAll(knownGroups);
       }
       return groups;
     }
@@ -204,6 +277,13 @@
     return false;
   }
 
+  private static String name(GroupBackend backend) {
+    if (backend == null) {
+      return "none";
+    }
+    return backend.getClass().getSimpleName();
+  }
+
   public static class ConfigCheck implements StartupCheck {
     private final Config cfg;
     private final UniversalGroupBackend universalGroupBackend;
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index 5cd0e98..b433e9f 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -102,6 +102,9 @@
   /** The name of a group. */
   public abstract Optional<String> groupName();
 
+  /** The group system being queried. */
+  public abstract Optional<String> groupSystem();
+
   /** The UUID of a group. */
   public abstract Optional<String> groupUuid();
 
@@ -328,6 +331,8 @@
 
     public abstract Builder groupName(@Nullable String groupName);
 
+    public abstract Builder groupSystem(@Nullable String groupSystem);
+
     public abstract Builder groupUuid(@Nullable String groupUuid);
 
     public abstract Builder httpStatus(int httpStatus);
diff --git a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
index 1e3063e..5f062be 100644
--- a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
+++ b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.AccountGroup.UUID;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics;
@@ -56,7 +57,8 @@
     backends.add("gerrit", new SystemGroupBackend(new Config()));
     backend =
         new UniversalGroupBackend(
-            new PluginSetContext<>(backends, PluginMetrics.DISABLED_INSTANCE));
+            new PluginSetContext<>(backends, PluginMetrics.DISABLED_INSTANCE),
+            new DisabledMetricMaker());
   }
 
   @Test
@@ -124,7 +126,8 @@
     backends.add("gerrit", backend);
     backend =
         new UniversalGroupBackend(
-            new PluginSetContext<>(backends, PluginMetrics.DISABLED_INSTANCE));
+            new PluginSetContext<>(backends, PluginMetrics.DISABLED_INSTANCE),
+            new DisabledMetricMaker());
 
     GroupMembership checker = backend.membershipsOf(member);
     assertFalse(checker.contains(REGISTERED_USERS));