Introduce the `*metricscollectiontime` meta metrics

The git repo metrics plugin, in order to spare system's resources,
typically collects metrics periodically and then serves them from cache.
Each group of metrics (git, fs, refs) is collected independently and
also requires different amount of time for collection.
The change introduces `[git|fs|refs]metricscollectiontime` meta metrics
(in millis) to indicate when was the last time that particular group of
metrics was collected. Note that metrics are populated upon successful
collection.

Bug: Issue 427093248
Change-Id: I4b22920bfcdaeb55259842261a0cf8cab7601aa8
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/FSMetricsCollector.java b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/FSMetricsCollector.java
index 15fd4c9..7d84061 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/FSMetricsCollector.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/FSMetricsCollector.java
@@ -81,10 +81,17 @@
       new GitRepoMetric("numberOfDirectories", "Number of directories on filesystem", "Count");
   protected static final GitRepoMetric numberOfFiles =
       new GitRepoMetric("numberOfFiles", "Number of directories on filesystem", "Count");
+  private static final GitRepoMetric collectionTime =
+      new GitRepoMetric(
+          "fsMetricsCollectionTime", "Timestamp at which metrics were collected", "Milliseconds");
 
   private static final ImmutableList<GitRepoMetric> availableMetrics =
       ImmutableList.of(
-          numberOfKeepFiles, numberOfEmptyDirectories, numberOfFiles, numberOfDirectories);
+          numberOfKeepFiles,
+          numberOfEmptyDirectories,
+          numberOfFiles,
+          numberOfDirectories,
+          collectionTime);
 
   private final ExecutorService executorService;
 
@@ -101,7 +108,10 @@
 
     executorService.submit(
         () -> {
-          populateMetrics.accept(filesAndDirectoriesCount(repository, projectName));
+          long collectionStartTime = System.currentTimeMillis();
+          HashMap<GitRepoMetric, Long> metrics = filesAndDirectoriesCount(repository, projectName);
+          metrics.put(collectionTime, collectionStartTime);
+          populateMetrics.accept(metrics);
         });
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitRefsMetricsCollector.java b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitRefsMetricsCollector.java
index 525195b..ab3e529 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitRefsMetricsCollector.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitRefsMetricsCollector.java
@@ -39,8 +39,12 @@
   protected static final GitRepoMetric combinedRefsSha1 =
       new GitRepoMetric("combinedRefsSha1", "Numeric value of combined refs SHA-1's", "Number");
 
+  private static final GitRepoMetric collectionTime =
+      new GitRepoMetric(
+          "refsMetricsCollectionTime", "Timestamp at which metrics were collected", "Milliseconds");
+
   private static final ImmutableList<GitRepoMetric> availableMetrics =
-      ImmutableList.of(combinedRefsSha1);
+      ImmutableList.of(combinedRefsSha1, collectionTime);
 
   private final ExecutorService executorService;
 
@@ -57,6 +61,7 @@
     executorService.submit(
         () -> {
           try {
+            long collectionStartTime = System.currentTimeMillis();
             MessageDigest md = MessageDigest.getInstance("SHA-1");
             repository.getRefDatabase().getRefs().stream()
                 .filter(ref -> !ref.isSymbolic())
@@ -66,6 +71,7 @@
 
             HashMap<GitRepoMetric, Long> metrics = new HashMap<>();
             metrics.put(combinedRefsSha1, (long) sha1Int);
+            metrics.put(collectionTime, collectionStartTime);
             populateMetrics.accept(metrics);
           } catch (NoSuchAlgorithmException e) {
             logger.atSevere().withCause(e).log(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitStatsMetricsCollector.java b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitStatsMetricsCollector.java
index 5c988ce..06b2c48 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitStatsMetricsCollector.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitrepometrics/collectors/GitStatsMetricsCollector.java
@@ -56,6 +56,9 @@
           "numberOfPackFilesSinceBitmap",
           "The number of pack files that were created after the last bitmap generation",
           "Count");
+  private static final GitRepoMetric collectionTime =
+      new GitRepoMetric(
+          "gitMetricsCollectionTime", "Timestamp at which metrics were collected", "Milliseconds");
 
   private static final ImmutableList<GitRepoMetric> availableMetrics =
       ImmutableList.of(
@@ -68,7 +71,8 @@
           sizeOfPackedObjects,
           numberOfBitmaps,
           numberOfObjectsSinceBitmap,
-          numberOfPackFilesSinceBitmap);
+          numberOfPackFilesSinceBitmap,
+          collectionTime);
 
   private final ExecutorService executorService;
 
@@ -88,6 +92,7 @@
         () -> {
           HashMap<GitRepoMetric, Long> metrics = new HashMap<>();
           try {
+            long collectionStartTime = System.currentTimeMillis();
             GC.RepoStatistics statistics = new GC(repository).getStatistics();
             metrics.put(numberOfPackedObjects, statistics.numberOfPackedObjects);
             metrics.put(numberOfPackFiles, statistics.numberOfPackFiles);
@@ -99,6 +104,7 @@
             metrics.put(numberOfBitmaps, statistics.numberOfBitmaps);
             metrics.put(numberOfObjectsSinceBitmap, statistics.numberOfObjectsSinceBitmap);
             metrics.put(numberOfPackFilesSinceBitmap, statistics.numberOfPackFilesSinceBitmap);
+            metrics.put(collectionTime, collectionStartTime);
             logger.atFine().log("New Git Statistics metrics collected: %s", statistics.toString());
           } catch (IOException e) {
             logger.atSevere().log("Something went wrong: %s", e.getMessage());
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 10128f4..a7c9898 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -16,13 +16,16 @@
 plugins_git_repo_metrics_numberofpackfiles_<repo_name>
 plugins_git_repo_metrics_sizeoflooseobjects_<repo_name>
 plugins_git_repo_metrics_sizeofpackedobjects_<repo_name>
+plugins_git_repo_metrics_numberofobjectssincebitmap_<repo_name>
+plugins_git_repo_metrics_numberofpackfilessincebitmap_<repo_name>
+plugins_git_repo_metrics_gitmetricscollectiontime_<repo_name>
 plugins_git_repo_metrics_numberofkeepfiles_<repo_name>
 plugins_git_repo_metrics_numberoffiles_<repo_name>
 plugins_git_repo_metrics_numberofdirectories_<repo_name>
 plugins_git_repo_metrics_numberofemptydirectories_<repo_name>
+plugins_git_repo_metrics_fsmetricscollectiontime_<repo_name>
 plugins_git_repo_metrics_combinedrefssha1_<repo_name>
-plugins_git_repo_metrics_numberofobjectssincebitmap_<repo_name>
-plugins_git_repo_metrics_numberofpackfilessincebitmap_<repo_name>
+plugins_git_repo_metrics_refsmetricscollectiontime_<repo_name>
 ```
 
 > **NOTE**: The `<repo_name>` is a subject of sanitization in order to avoid collision between repository names.
@@ -31,6 +34,8 @@
 >   string for instance `repo/name` is sanitized to `repo_0x2F_name`
 > - if the repository name contains the replacement prefix `(_0x)` it is prefixed with another `_0x`
 >   e.g. `repo_0x2F_name` becomes `repo_0x_0x2F_name`.
+> - the `[git|fs|refs]metricscollectiontime` is a meta metric and represents system time (in millis) when the
+>   particular group of metrics was collected - to easily verify if the corresponding metrics are stale
 
 Settings
 --------