Introduce basic metrics for the owners plugin

The following metrics groups were introduced:

1. configuration load:
  * plugins/owners/count_configuration_loads: config loads counter
  * plugins/owners/load_configuration_latency: latency for config load

2. owners submit rule execution (note that it is referenced from both
  Gerrit internals and from the submit requirements predicate):
  * plugins/owners/count_submit_rule_runs: rule evaluation counter
  * plugins/owners/run_submit_rule_latency: rule evaluation latency

Bug: Issue 15556
Change-Id: Ibffd10b79affbe5280a63384e713074e9c9d34ca
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersMetrics.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersMetrics.java
new file mode 100644
index 0000000..5cc8e19
--- /dev/null
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersMetrics.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 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.googlesource.gerrit.owners;
+
+import com.google.gerrit.metrics.Counter0;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+class OwnersMetrics {
+  final Counter0 countConfigLoads;
+  final Timer0 loadConfig;
+
+  final Counter0 countSubmitRuleRuns;
+  final Timer0 runSubmitRule;
+
+  @Inject
+  OwnersMetrics(MetricMaker metricMaker) {
+    this.countConfigLoads =
+        createCounter(
+            metricMaker, "count_configuration_loads", "Total number of owners configuration loads");
+    this.loadConfig =
+        createTimer(
+            metricMaker,
+            "load_configuration_latency",
+            "Latency for loading owners configuration for a change");
+
+    this.countSubmitRuleRuns =
+        createCounter(
+            metricMaker, "count_submit_rule_runs", "Total number of owners submit rule runs");
+    this.runSubmitRule =
+        createTimer(
+            metricMaker, "run_submit_rule_latency", "Latency for running the owners submit rule");
+  }
+
+  private static Counter0 createCounter(MetricMaker metricMaker, String name, String description) {
+    return metricMaker.newCounter(name, new Description(description).setRate());
+  }
+
+  private static Timer0 createTimer(MetricMaker metricMaker, String name, String description) {
+    return metricMaker.newTimer(
+        name, new Description(description).setCumulative().setUnit(Units.MILLISECONDS));
+  }
+}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
index cfa4064..c6af4c8 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersSubmitRequirement.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.SubmitRecord;
 import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.server.approval.ApprovalsUtil;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -75,6 +76,7 @@
   private static final LegacySubmitRequirement SUBMIT_REQUIREMENT =
       LegacySubmitRequirement.builder().setFallbackText("Owners").setType("owners").build();
 
+  private final OwnersMetrics metrics;
   private final PluginSettings pluginSettings;
   private final ProjectCache projectCache;
   private final Accounts accounts;
@@ -84,12 +86,14 @@
 
   @Inject
   OwnersSubmitRequirement(
+      OwnersMetrics metrics,
       PluginSettings pluginSettings,
       ProjectCache projectCache,
       Accounts accounts,
       GitRepositoryManager repoManager,
       DiffOperations diffOperations,
       ApprovalsUtil approvalsUtil) {
+    this.metrics = metrics;
     this.pluginSettings = pluginSettings;
     this.projectCache = projectCache;
     this.accounts = accounts;
@@ -112,7 +116,8 @@
       return Optional.empty();
     }
 
-    try {
+    metrics.countSubmitRuleRuns.increment();
+    try (Timer0.Context ctx = metrics.runSubmitRule.start()) {
       ProjectState projectState = getProjectState(project);
       PathOwners pathOwners = getPathOwners(cd, projectState);
       Map<String, Set<Account.Id>> fileOwners = pathOwners.getFileOwners();
@@ -187,26 +192,29 @@
 
   private PathOwners getPathOwners(ChangeData cd, ProjectState projectState)
       throws IOException, DiffNotAvailableException {
-    String branch = cd.change().getDest().branch();
+    metrics.countConfigLoads.increment();
+    try (Timer0.Context ctx = metrics.loadConfig.start()) {
+      String branch = cd.change().getDest().branch();
 
-    List<Project.NameKey> parents =
-        Optional.<Project.NameKey>ofNullable(projectState.getProject().getParent())
-            .map(Arrays::asList)
-            .orElse(Collections.emptyList());
+      List<Project.NameKey> parents =
+          Optional.<Project.NameKey>ofNullable(projectState.getProject().getParent())
+              .map(Arrays::asList)
+              .orElse(Collections.emptyList());
 
-    Project.NameKey nameKey = projectState.getNameKey();
-    try (Repository repo = repoManager.openRepository(nameKey)) {
-      PathOwners pathOwners =
-          new PathOwners(
-              accounts,
-              repoManager,
-              repo,
-              parents,
-              pluginSettings.isBranchDisabled(branch) ? Optional.empty() : Optional.of(branch),
-              getDiff(nameKey, cd.currentPatchSet().commitId()),
-              pluginSettings.expandGroups());
+      Project.NameKey nameKey = projectState.getNameKey();
+      try (Repository repo = repoManager.openRepository(nameKey)) {
+        PathOwners pathOwners =
+            new PathOwners(
+                accounts,
+                repoManager,
+                repo,
+                parents,
+                pluginSettings.isBranchDisabled(branch) ? Optional.empty() : Optional.of(branch),
+                getDiff(nameKey, cd.currentPatchSet().commitId()),
+                pluginSettings.expandGroups());
 
-      return pathOwners;
+        return pathOwners;
+      }
     }
   }
 
diff --git a/owners/src/main/resources/Documentation/metrcis.md b/owners/src/main/resources/Documentation/metrcis.md
new file mode 100644
index 0000000..c1927b7
--- /dev/null
+++ b/owners/src/main/resources/Documentation/metrcis.md
@@ -0,0 +1,17 @@
+Metrics
+=============
+
+The following metrics are emitted when submit requirements are enabled
+(`owners.enableSubmitRequirement = true`):
+
+* plugins/owners/count_configuration_loads
+  : the total number of owners configuration loads.
+
+* plugins/owners/load_configuration_latency
+  : the latency for loading owners configuration for a change.
+
+* plugins/owners/count_submit_rule_runs
+  : the total number of owners submit rule runs.
+
+* plugins/owners/run_submit_rule_latency
+  : the latency for running the owners submit rule.
diff --git a/owners/src/test/java/com/googlesource/gerrit/owners/OwnersMetricsIT.java b/owners/src/test/java/com/googlesource/gerrit/owners/OwnersMetricsIT.java
new file mode 100644
index 0000000..b241b66
--- /dev/null
+++ b/owners/src/test/java/com/googlesource/gerrit/owners/OwnersMetricsIT.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 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.googlesource.gerrit.owners;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.codahale.metrics.MetricRegistry;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GlobalPluginConfig;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@TestPlugin(name = "owners", sysModule = "com.googlesource.gerrit.owners.OwnersModule")
+@UseLocalDisk
+public class OwnersMetricsIT extends LightweightPluginDaemonTest {
+  @Inject MetricRegistry metricRegistry;
+
+  @Test
+  @GlobalPluginConfig(
+      pluginName = "owners",
+      name = "owners.enableSubmitRequirement",
+      value = "true")
+  public void shouldOwnersMetricsBeAvailable() throws Exception {
+    // one needs to at least create the OWNERS file to have metrics emitted
+    TestAccount admin2 = accountCreator.admin2();
+    addOwnerFileToRoot(true, admin2);
+
+    assertMetricExists("plugins/owners/count_configuration_loads");
+    assertMetricExists("plugins/owners/load_configuration_latency");
+    assertMetricExists("plugins/owners/count_submit_rule_runs");
+    assertMetricExists("plugins/owners/run_submit_rule_latency");
+  }
+
+  private void assertMetricExists(String name) {
+    assertWithMessage(name).that(metricRegistry.getMetrics().get(name)).isNotNull();
+  }
+
+  private void addOwnerFileToRoot(boolean inherit, TestAccount u) throws Exception {
+    // Add OWNERS file to root:
+    //
+    // inherited: true
+    // owners:
+    // - u.email()
+    merge(
+        createChange(
+            testRepo,
+            "master",
+            "Add OWNER file",
+            "OWNERS",
+            String.format("inherited: %s\nowners:\n- %s\n", inherit, u.email()),
+            ""));
+  }
+}