Add a --task--include-statistics flag

Add some task plugin statistics to the last change in the query when the
new --task--include-statistics flag is specified. This is intended to
help task configurators and system administrators profile task queries
to help improve their performance.

Change-Id: I708a5345be8369bec2c8cf5fbf2458ddb4a32023
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
index 3a8d903..6f8d5cd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
@@ -61,6 +61,9 @@
     @Option(name = "--evaluation-time", usage = "Include elapsed evaluation time on each task")
     public boolean evaluationTime = false;
 
+    @Option(name = "--include-statistics", usage = "Include statistcs about the task evaluations")
+    public boolean includeStatistics = false;
+
     @Option(
         name = "--preview",
         metaVar = "{CHANGE,PATCHSET}",
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
index 688faa2..e77b97a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
@@ -50,6 +50,12 @@
     FAIL;
   }
 
+  public static class Statistics {
+    public long numberOfChanges;
+    public long numberOfNodes;
+    public long numberOfTaskPluginAttributes;
+  }
+
   public static class TaskAttribute {
     public Boolean applicable;
     public Map<String, String> exported;
@@ -69,12 +75,15 @@
 
   public static class TaskPluginAttribute extends PluginDefinedInfo {
     public List<TaskAttribute> roots = new ArrayList<>();
+    public Statistics queryStatistics;
   }
 
   protected final TaskTree definitions;
   protected final PredicateCache predicateCache;
 
   protected Modules.MyOptions options;
+  protected TaskPluginAttribute lastTaskPluginAttribute;
+  protected Statistics statistics;
 
   @Inject
   public TaskAttributeFactory(TaskTree definitions, PredicateCache predicateCache) {
@@ -88,10 +97,14 @@
     Map<Change.Id, PluginDefinedInfo> pluginInfosByChange = new HashMap<>();
     options = (Modules.MyOptions) beanProvider.getDynamicBean(plugin);
     if (options.all || options.onlyApplicable || options.onlyInvalid) {
+      initStatistics();
       for (PatchSetArgument psa : options.patchSetArguments) {
         definitions.masquerade(psa);
       }
       cds.forEach(cd -> pluginInfosByChange.put(cd.getId(), createWithExceptions(cd)));
+      if (lastTaskPluginAttribute != null) {
+        lastTaskPluginAttribute.queryStatistics = getStatistics(pluginInfosByChange);
+      }
     }
     return pluginInfosByChange;
   }
@@ -114,6 +127,7 @@
     if (a.roots.isEmpty()) {
       return null;
     }
+    lastTaskPluginAttribute = a;
     return a;
   }
 
@@ -128,6 +142,9 @@
       this.matchCache = matchCache;
       this.task = node.task;
       this.attribute = new TaskAttribute(task.name());
+      if (options.includeStatistics) {
+        statistics.numberOfNodes++;
+      }
     }
 
     public Optional<TaskAttribute> create() {
@@ -295,7 +312,22 @@
     return System.nanoTime() / 1000000;
   }
 
-  protected TaskAttribute invalid() {
+  public void initStatistics() {
+    if (options.includeStatistics) {
+      statistics = new Statistics();
+    }
+  }
+
+  public Statistics getStatistics(Map<Change.Id, PluginDefinedInfo> pluginInfosByChange) {
+    if (statistics != null) {
+      statistics.numberOfChanges = pluginInfosByChange.size();
+      statistics.numberOfTaskPluginAttributes =
+          pluginInfosByChange.values().stream().filter(tpa -> tpa != null).count();
+    }
+    return statistics;
+  }
+
+  protected static TaskAttribute invalid() {
     // For security reasons, do not expose the task name without knowing
     // the visibility which is derived from its applicability.
     TaskAttribute a = unknown();
@@ -303,13 +335,13 @@
     return a;
   }
 
-  protected TaskAttribute unknown() {
+  protected static TaskAttribute unknown() {
     TaskAttribute a = new TaskAttribute("UNKNOWN");
     a.status = Status.UNKNOWN;
     return a;
   }
 
-  protected String getHint(Status status, Task def) {
+  protected static String getHint(Status status, Task def) {
     if (status != null) {
       switch (status) {
         case READY: