Merge "Add username as a fallback in gr-anonymous-name-behavior"
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 742c8c2..640178e 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -68,6 +68,17 @@
 * `query/query_latency`: Successful query latency, accumulated over the life
 of the process.
 
+=== Queue
+
+The metrics below are per queue.
+
+* `queue/<queueName>/pool_size`: Current number of threads in the pool
+* `queue/<queueName>/max_pool_size`: Maximum allowed number of threads in the pool
+* `queue/<queueName>/active_threads`: Number of threads that are actively executing tasks
+* `queue/<queueName>/scheduled_tasks`: Number of scheduled tasks in the queue
+* `queue/<queueName>/total_scheduled_tasks_count`: Total number of tasks that have been scheduled
+* `queue/<queueName>/total_completed_tasks_count`: Total number of tasks that have completed execution
+
 === SSH sessions
 
 * `sshd/sessions/connected`: Number of currently connected SSH sessions.
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index 99d9a49..93ac34f 100755
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -21,6 +21,7 @@
 TODO(hiesel): Add comments
 """
 
+from __future__ import print_function
 import atexit
 import json
 import optparse
@@ -280,7 +281,7 @@
   (options, _) = p.parse_args()
   global BASE_URL
   BASE_URL = BASE_URL % options.port
-  print BASE_URL
+  print(BASE_URL)
 
   set_up()
   gerrit_users = get_random_users(options.user_count)
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index 74f986e..29915ef5 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -14,8 +14,12 @@
 
 package com.google.gerrit.server.git;
 
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Supplier;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.ScheduleConfig.Schedule;
@@ -84,17 +88,19 @@
         }
       };
 
+  private final MetricMaker metrics;
   private final ScheduledExecutorService defaultQueue;
   private final IdGenerator idGenerator;
   private final CopyOnWriteArrayList<Executor> queues;
 
   @Inject
-  WorkQueue(IdGenerator idGenerator, @GerritServerConfig Config cfg) {
-    this(idGenerator, cfg.getInt("execution", "defaultThreadPoolSize", 1));
+  WorkQueue(MetricMaker metrics, IdGenerator idGenerator, @GerritServerConfig Config cfg) {
+    this(metrics, idGenerator, cfg.getInt("execution", "defaultThreadPoolSize", 1));
   }
 
   /** Constructor to allow binding the WorkQueue more explicitly in a vhost setup. */
-  public WorkQueue(IdGenerator idGenerator, int defaultThreadPoolSize) {
+  public WorkQueue(MetricMaker metrics, IdGenerator idGenerator, int defaultThreadPoolSize) {
+    this.metrics = metrics;
     this.idGenerator = idGenerator;
     this.queues = new CopyOnWriteArrayList<>();
     this.defaultQueue = createQueue(defaultThreadPoolSize, "WorkQueue");
@@ -224,6 +230,86 @@
               corePoolSize + 4 // concurrency level
               );
       queueName = prefix;
+      buildMetrics(queueName, metrics);
+    }
+
+    private void buildMetrics(String queueName, MetricMaker metric) {
+      metric.newCallbackMetric(
+          getMetricName(queueName, "max_pool_size"),
+          Long.class,
+          new Description("Maximum allowed number of threads in the pool")
+              .setGauge()
+              .setUnit("threads"),
+          new Supplier<Long>() {
+            @Override
+            public Long get() {
+              return (long) getMaximumPoolSize();
+            }
+          });
+      metric.newCallbackMetric(
+          getMetricName(queueName, "pool_size"),
+          Long.class,
+          new Description("Current number of threads in the pool").setGauge().setUnit("threads"),
+          new Supplier<Long>() {
+            @Override
+            public Long get() {
+              return (long) getPoolSize();
+            }
+          });
+      metric.newCallbackMetric(
+          getMetricName(queueName, "active_threads"),
+          Long.class,
+          new Description("Number number of threads that are actively executing tasks")
+              .setGauge()
+              .setUnit("threads"),
+          new Supplier<Long>() {
+            @Override
+            public Long get() {
+              return (long) getActiveCount();
+            }
+          });
+      metric.newCallbackMetric(
+          getMetricName(queueName, "scheduled_tasks"),
+          Integer.class,
+          new Description("Number of scheduled tasks in the queue").setGauge().setUnit("tasks"),
+          new Supplier<Integer>() {
+            @Override
+            public Integer get() {
+              return getQueue().size();
+            }
+          });
+      metric.newCallbackMetric(
+          getMetricName(queueName, "total_scheduled_tasks_count"),
+          Long.class,
+          new Description("Total number of tasks that have been scheduled for execution")
+              .setCumulative()
+              .setUnit("tasks"),
+          new Supplier<Long>() {
+            @Override
+            public Long get() {
+              return (long) getTaskCount();
+            }
+          });
+      metric.newCallbackMetric(
+          getMetricName(queueName, "total_completed_tasks_count"),
+          Long.class,
+          new Description("Total number of tasks that have completed execution")
+              .setCumulative()
+              .setUnit("tasks"),
+          new Supplier<Long>() {
+            @Override
+            public Long get() {
+              return (long) getCompletedTaskCount();
+            }
+          });
+    }
+
+    private String getMetricName(String queueName, String metricName) {
+      String name =
+          CaseFormat.UPPER_CAMEL.to(
+              CaseFormat.LOWER_UNDERSCORE,
+              queueName.replaceFirst("SSH", "Ssh").replaceAll("-", ""));
+      return String.format("queue/%s/%s", name, metricName);
     }
 
     @Override
diff --git a/plugins/hooks b/plugins/hooks
index aa42524..4ed6ba0 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit aa4252417f54e28db3a7fa1f98d9a06d8ce49465
+Subproject commit 4ed6ba03134f95ba7e31cde58f7e33edc72156a5
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
index b738829..826a6dc 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
@@ -75,7 +75,7 @@
       const isWip = true;
       return this.$.restAPI.createChange(this.repoName, this.branch,
           this.subject, this.topic, isPrivate, isWip, this.baseChange,
-          this.baseCommit)
+          this.baseCommit || null)
           .then(changeCreated => {
             if (!changeCreated) { return; }
             Gerrit.Nav.navigateToChange(changeCreated);
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
index 6a3ea54..5cb3b77 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
@@ -144,10 +144,11 @@
     },
 
     _getReviewerSuggestions(input) {
-      if (!this.change) { return Promise.resolve([]); }
+      if (!this.change || !this.change._number) { return Promise.resolve([]); }
+
       const api = this.$.restAPI;
       const xhr = this.allowAnyUser ?
-          api.getSuggestedAccounts(input) :
+          api.getSuggestedAccounts(`cansee:${this.change._number} ${input}`) :
           api.getChangeSuggestedReviewers(this.change._number, input);
 
       return xhr.then(reviewers => {
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
index 07c1554..7d5ddd8 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
@@ -80,6 +80,7 @@
 
       element = fixture('basic');
       element.change = {
+        _number: 42,
         owner,
         reviewers: {
           CC: [existingReviewer1],
@@ -179,12 +180,14 @@
 
       element._getReviewerSuggestions('').then(() => {
         assert.isTrue(suggestReviewerStub.calledOnce);
+        assert.isTrue(suggestReviewerStub.calledWith(42, ''));
         assert.isFalse(suggestAccountStub.called);
         element.allowAnyUser = true;
 
         element._getReviewerSuggestions('').then(() => {
           assert.isTrue(suggestReviewerStub.calledOnce);
           assert.isTrue(suggestAccountStub.calledOnce);
+          assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
           done();
         });
       });
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 65ccce7..0fa037c 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -101,7 +101,8 @@
       _filesByPath: Object,
       _files: {
         type: Array,
-        computed: '_computeFiles(_filesByPath, changeComments, patchRange)',
+        computed: '_computeFiles(_filesByPath, changeComments, patchRange, ' +
+            '_reviewed)',
         observer: '_filesChanged',
         value() { return []; },
       },
@@ -180,7 +181,6 @@
 
     observers: [
       '_expandedPathsChanged(_expandedFilePaths.splices)',
-      '_setReviewedFiles(_shownFiles, _files, _reviewed.*, _loggedIn)',
     ],
 
     keyBindings: {
@@ -429,18 +429,14 @@
       return GrCountStringFormatter.computeShortString(commentCount, 'c');
     },
 
-    _computeReviewed(file, _reviewed) {
-      return _reviewed.includes(file.__path);
-    },
-
     _reviewFile(path) {
       if (this.editMode) { return; }
-      const index = this._reviewed.indexOf(path);
-      const reviewed = index !== -1;
-      if (reviewed) {
-        this.splice('_reviewed', index, 1);
-      } else {
-        this.push('_reviewed', path);
+      const index = this._files.findIndex(file => file.__path === path);
+      const reviewed = this._files[index].isReviewed;
+
+      this.set(['_files', index, 'isReviewed'], !reviewed);
+      if (index < this._shownFiles.length) {
+        this.set(['_shownFiles', index, 'isReviewed'], !reviewed);
       }
 
       this._saveReviewedState(path, !reviewed);
@@ -780,7 +776,7 @@
           'gr-icons:expand-less' : 'gr-icons:expand-more';
     },
 
-    _computeFiles(filesByPath, changeComments, patchRange) {
+    _computeFiles(filesByPath, changeComments, patchRange, reviewed) {
       const commentedPaths = changeComments.getPaths(patchRange);
       const files = Object.assign({}, filesByPath);
       Object.keys(commentedPaths).forEach(commentedPath => {
@@ -789,6 +785,12 @@
         }
         files[commentedPath] = {status: 'U'};
       });
+      const reviewedSet = new Set(reviewed || []);
+      for (const filePath in files) {
+        if (!files.hasOwnProperty(filePath)) { continue; }
+        files[filePath].isReviewed = reviewedSet.has(filePath);
+      }
+
       return this._normalizeChangeFilesResponse(files);
     },
 
@@ -798,19 +800,6 @@
       return filesShown;
     },
 
-    _setReviewedFiles(shownFiles, files, reviewedRecord, loggedIn) {
-      if (!loggedIn) { return; }
-      const reviewed = reviewedRecord.base;
-      let fileReviewed;
-      for (let i = 0; i < files.length; i++) {
-        fileReviewed = this._computeReviewed(files[i], reviewed);
-        this._files[i].isReviewed = fileReviewed;
-        if (i < shownFiles.length) {
-          this.set(['_shownFiles', i, 'isReviewed'], fileReviewed);
-        }
-      }
-    },
-
     _updateDiffCursor() {
       const diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 9987521..8541edf 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -557,18 +557,21 @@
       });
 
       test('r key toggles reviewed flag', () => {
+        const reducer = (accum, file) => file.isReviewed ? ++accum : accum;
+        const getNumReviewed = () => element._files.reduce(reducer, 0);
         flushAsynchronousOperations();
 
         // Default state should be unreviewed.
-        assert.equal(element._reviewed.length, 0);
+        assert.equal(getNumReviewed(), 0);
 
         // Press the review key to toggle it (set the flag).
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
-        assert.equal(element._reviewed.length, 1);
+        flushAsynchronousOperations();
+        assert.equal(getNumReviewed(), 1);
 
         // Press the review key to toggle it (clear the flag).
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
-        assert.equal(element._reviewed.length, 0);
+        assert.equal(getNumReviewed(), 0);
       });
 
       suite('_handleOKey', () => {
@@ -655,12 +658,12 @@
     });
 
     test('file review status', () => {
+      element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
       element._filesByPath = {
         '/COMMIT_MSG': {},
         'file_added_in_rev2.txt': {},
         'myfile.txt': {},
       };
-      element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
       element._loggedIn = true;
       element.changeNum = '42';
       element.patchRange = {
@@ -1025,6 +1028,7 @@
     });
 
     test('_loadingChanged fired from reload in debouncer', done => {
+      sandbox.stub(element, '_getReviewedFiles').returns(Promise.resolve([]));
       element.changeNum = 123;
       element.patchRange = {patchNum: 12};
       element._filesByPath = {'foo.bar': {}};
@@ -1042,6 +1046,7 @@
     });
 
     test('_loadingChanged does not set class when there are no files', () => {
+      sandbox.stub(element, '_getReviewedFiles').returns(Promise.resolve([]));
       element.changeNum = 123;
       element.patchRange = {patchNum: 12};
       element.reload();
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 3dc19ef..b866088 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -131,7 +131,7 @@
       // router has been initialized. @see Issue 7837
       this._settingsUrl = Gerrit.Nav.getUrlForSettings();
 
-      this.$.reporting.appStarted(document.hidden);
+      this.$.reporting.appStarted(document.visibilityState === 'hidden');
 
       this._viewState = {
         changeView: {
diff --git a/resources/com/google/gerrit/pgm/Startup.py b/resources/com/google/gerrit/pgm/Startup.py
index cf6fac9..469d5df 100644
--- a/resources/com/google/gerrit/pgm/Startup.py
+++ b/resources/com/google/gerrit/pgm/Startup.py
@@ -16,6 +16,7 @@
 # Startup script for Gerrit Inspector - a Jython introspector
 # -----------------------------------------------------------------------
 
+from __future__ import print_function
 import sys
 
 def print_help():
@@ -23,9 +24,9 @@
     if not n.startswith("__") and not n in ['help', 'reload'] \
        and str(type(v)) != "<type 'javapackage'>"             \
        and not str(v).startswith("<module"):
-       print "\"%s\" is \"%s\"" % (n, v)
-  print
-  print "Welcome to the Gerrit Inspector"
-  print "Enter help() to see the above again, EOF to quit and stop Gerrit"
+       print("\"%s\" is \"%s\"" % (n, v))
+  print()
+  print("Welcome to the Gerrit Inspector")
+  print("Enter help() to see the above again, EOF to quit and stop Gerrit")
 
 print_help()