Add a metric for counting changes with many files

Release-Notes: skip
Google-Bug-Id: b/289102495
Change-Id: I9190eee3fe52d702302f5cb2a08f9b151289eacf
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index a560b84..7dab1ad 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -415,6 +415,13 @@
 * `permissions/ref_filter/skip_filter_count`: Rate of ref filter operations
   where we skip full evaluation because the user can read all refs
 
+=== Validation
+
+* `validation/file_count`: Track number of files per change during commit
+  validation, if it exceeds the FILE_COUNT_WARNING_THRESHOLD threshold.
+** `file_count`: number of files in the patchset
+** `host_repo`: host and repository of the change in the format 'host/repo'
+
 === Reviewer Suggestion
 
 * `reviewer_suggestion/query_accounts`: Latency for querying accounts for
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 6adaae2..45618e5 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -36,6 +36,10 @@
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.restapi.AuthException;
+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.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -115,6 +119,7 @@
     private final DiffOperations diffOperations;
     private final Config config;
     private final ChangeUtil changeUtil;
+    private final MetricMaker metricMaker;
 
     @Inject
     Factory(
@@ -131,7 +136,8 @@
         ProjectCache projectCache,
         ProjectConfig.Factory projectConfigFactory,
         DiffOperations diffOperations,
-        ChangeUtil changeUtil) {
+        ChangeUtil changeUtil,
+        MetricMaker metricMaker) {
       this.gerritIdent = gerritIdent;
       this.urlFormatter = urlFormatter;
       this.config = config;
@@ -146,6 +152,7 @@
       this.projectConfigFactory = projectConfigFactory;
       this.diffOperations = diffOperations;
       this.changeUtil = changeUtil;
+      this.metricMaker = metricMaker;
     }
 
     public CommitValidators forReceiveCommits(
@@ -166,7 +173,7 @@
           .add(new ProjectStateValidationListener(projectState))
           .add(new AmendedGerritMergeCommitValidationListener(perm, gerritIdent))
           .add(new AuthorUploaderValidator(user, perm, urlFormatter.get()))
-          .add(new FileCountValidator(config, urlFormatter.get(), diffOperations))
+          .add(new FileCountValidator(config, urlFormatter.get(), diffOperations, metricMaker))
           .add(new CommitterUploaderValidator(user, perm, urlFormatter.get()))
           .add(new SignedOffByValidator(user, perm, projectState))
           .add(
@@ -198,7 +205,7 @@
           .add(new ProjectStateValidationListener(projectState))
           .add(new AmendedGerritMergeCommitValidationListener(perm, gerritIdent))
           .add(new AuthorUploaderValidator(user, perm, urlFormatter.get()))
-          .add(new FileCountValidator(config, urlFormatter.get(), diffOperations))
+          .add(new FileCountValidator(config, urlFormatter.get(), diffOperations, metricMaker))
           .add(new SignedOffByValidator(user, perm, projectState))
           .add(
               new ChangeIdValidator(
@@ -445,10 +452,25 @@
     private final int maxFileCount;
     private final UrlFormatter urlFormatter;
     private final DiffOperations diffOperations;
+    private final Counter2<Integer, String> metricCountManyFilesPerChange;
 
-    FileCountValidator(Config config, UrlFormatter urlFormatter, DiffOperations diffOperations) {
+    FileCountValidator(
+        Config config,
+        UrlFormatter urlFormatter,
+        DiffOperations diffOperations,
+        MetricMaker metricMaker) {
       this.urlFormatter = urlFormatter;
       this.diffOperations = diffOperations;
+      this.metricCountManyFilesPerChange =
+          metricMaker.newCounter(
+              "validation/file_count",
+              new Description("Count commits with many files per change."),
+              Field.ofInteger("file_count", (meta, value) -> {})
+                  .description("number of files in the patchset")
+                  .build(),
+              Field.ofString("host_repo", (meta, value) -> {})
+                  .description("host and repository of the change in the format 'host/repo'")
+                  .build());
       maxFileCount = config.getInt("change", null, "maxFiles", 100_000);
     }
 
@@ -482,6 +504,9 @@
           logger.atWarning().log(
               "Warning: Change with %d files on host %s, project %s, ref %s",
               changedFiles, host, project, refName);
+
+          this.metricCountManyFilesPerChange.increment(
+              Math.toIntExact(changedFiles), String.format("%s/%s", host, project));
         }
       } catch (DiffNotAvailableException e) {
         // This happens e.g. for cherrypicks.