ReceiveCommits: expose change IDs touched; add metrics

ReceiveCommits now populates the change IDs that were successfully
updated.

AsyncReceiveCommits uses this to record the following metrics:

 receivecommits/changes - number of changes uploaded in a single push.
 receivecommits/latency - average delay per updated change
 receivecommits/timeout - rate of push timeouts

Change-Id: Ibbee943ec87141e8d06cf549f40a98f0111353b0
This commit is contained in:
Han-Wen Nienhuys 2018-10-08 16:45:49 +02:00
parent 4eac09b97a
commit f7f6712cca
5 changed files with 159 additions and 3 deletions

View File

@ -20,6 +20,15 @@ by RetryHelper to execute an action (1 == single attempt, no retry)
* `action/retry_timeout_count`: Number of action executions of RetryHelper
that ultimately timed out
=== Pushes
* `receivecommits/changes`: histogram of number of changes processed
in a single upload, split up by update type (new change created,
existing changed updated, change autoclosed).
* `receivecommits/latency`: latency per change for processing a push,
split up by update type (create+replace, and autoclose)
* `receivecommits/timeout`: number of timeouts during push processing.
=== Process
* `proc/birth_timestamp`: Time at which the Gerrit process started.

View File

@ -14,11 +14,21 @@
package com.google.gerrit.server.git.receive;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ConfigUtil;
@ -164,7 +174,39 @@ public class AsyncReceiveCommits implements PreReceiveHook {
}
}
@Singleton
private static class Metrics {
private final Histogram1<ResultChangeIds.Key> changes;
private final Timer1<String> latencyPerChange;
private final Counter0 timeouts;
@Inject
Metrics(MetricMaker metricMaker) {
changes =
metricMaker.newHistogram(
"receivecommits/changes",
new Description("number of changes uploaded in a single push.").setCumulative(),
Field.ofEnum(
ResultChangeIds.Key.class,
"type",
"type of update (replace, create, autoclose)"));
latencyPerChange =
metricMaker.newTimer(
"receivecommits/latency",
new Description("average delay per updated change")
.setUnit(Units.MILLISECONDS)
.setCumulative(),
Field.ofString("type", "type of update (create/replace, autoclose)"));
timeouts =
metricMaker.newCounter(
"receivecommits/timeout", new Description("rate of push timeouts").setRate());
}
}
private final Metrics metrics;
private final ReceiveCommits receiveCommits;
private final ResultChangeIds resultChangeIds;
private final PermissionBackend.ForProject perm;
private final ReceivePack receivePack;
private final ExecutorService executor;
@ -188,6 +230,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
TransferConfig transferConfig,
Provider<LazyPostReceiveHookChain> lazyPostReceive,
ContributorAgreementsChecker contributorAgreements,
Metrics metrics,
@Named(TIMEOUT_NAME) long timeoutMillis,
@Assisted ProjectState projectState,
@Assisted IdentifiedUser user,
@ -202,6 +245,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
this.projectState = projectState;
this.user = user;
this.repo = repo;
this.metrics = metrics;
Project.NameKey projectName = projectState.getNameKey();
receivePack = new ReceivePack(repo);
@ -237,7 +281,10 @@ public class AsyncReceiveCommits implements PreReceiveHook {
advHooks.add(new HackPushNegotiateHook());
receivePack.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
receiveCommits = factory.create(projectState, user, receivePack, allRefsWatcher, messageSender);
resultChangeIds = new ResultChangeIds();
receiveCommits =
factory.create(
projectState, user, receivePack, allRefsWatcher, messageSender, resultChangeIds);
receiveCommits.init();
}
@ -268,11 +315,14 @@ public class AsyncReceiveCommits implements PreReceiveHook {
// pre-receive hooks
return;
}
long startNanos = System.nanoTime();
Worker w = new Worker(commands);
try {
w.progress.waitFor(
executor.submit(scopePropagator.wrap(w)), timeoutMillis, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
metrics.timeouts.increment();
logger.atWarning().withCause(e).log(
"Error in ReceiveCommits while processing changes for project %s",
projectState.getName());
@ -287,6 +337,23 @@ public class AsyncReceiveCommits implements PreReceiveHook {
} finally {
w.sendMessages();
}
long deltaNanos = System.nanoTime() - startNanos;
int totalChanges = 0;
for (ResultChangeIds.Key key : ResultChangeIds.Key.values()) {
List<Change.Id> ids = resultChangeIds.get(key);
metrics.changes.record(key, ids.size());
totalChanges += ids.size();
}
if (totalChanges > 0) {
metrics.latencyPerChange.record(
resultChangeIds.get(ResultChangeIds.Key.AUTOCLOSED).isEmpty()
? "CREATE_REPLACE"
: ResultChangeIds.Key.AUTOCLOSED.name(),
deltaNanos / totalChanges,
NANOSECONDS);
}
}
public ReceivePack getReceivePack() {

View File

@ -6,6 +6,7 @@ java_library(
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/logging",

View File

@ -107,6 +107,7 @@ import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.git.ReceivePackInitializer;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.receive.ResultChangeIds.Key;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.RefOperationValidationException;
import com.google.gerrit.server.git.validators.RefOperationValidators;
@ -228,7 +229,8 @@ class ReceiveCommits {
IdentifiedUser user,
ReceivePack receivePack,
AllRefsWatcher allRefsWatcher,
MessageSender messageSender);
MessageSender messageSender,
ResultChangeIds resultChangeIds);
}
private class ReceivePackMessageSender implements MessageSender {
@ -352,6 +354,7 @@ class ReceiveCommits {
private Optional<String> tracePushOption;
private MessageSender messageSender;
private ResultChangeIds resultChangeIds;
@Inject
ReceiveCommits(
@ -393,7 +396,8 @@ class ReceiveCommits {
@Assisted IdentifiedUser user,
@Assisted ReceivePack rp,
@Assisted AllRefsWatcher allRefsWatcher,
@Nullable @Assisted MessageSender messageSender)
@Nullable @Assisted MessageSender messageSender,
@Assisted ResultChangeIds resultChangeIds)
throws IOException {
// Injected fields.
this.accountResolver = accountResolver;
@ -460,6 +464,7 @@ class ReceiveCommits {
// Handles for outputting back over the wire to the end user.
this.messageSender = messageSender != null ? messageSender : new ReceivePackMessageSender();
this.resultChangeIds = resultChangeIds;
}
void init() {
@ -810,6 +815,13 @@ class ReceiveCommits {
} catch (UpdateException e) {
throw INSERT_EXCEPTION.apply(e);
}
replaceByChange
.values()
.stream()
.forEach(req -> resultChangeIds.add(Key.REPLACED, req.ontoChange));
newChanges.stream().forEach(req -> resultChangeIds.add(Key.CREATED, req.changeId));
if (magicBranchCmd != null) {
magicBranchCmd.setResult(OK);
}
@ -3031,6 +3043,7 @@ class ReceiveCommits {
private void autoCloseChanges(ReceiveCommand cmd, Task progress) {
logger.atFine().log("Starting auto-closing of changes");
String refName = cmd.getRefName();
Set<Change.Id> ids = new HashSet<>();
// TODO(dborowitz): Combine this BatchUpdate with the main one in
// handleRegularCommands
@ -3107,6 +3120,7 @@ class ReceiveCommits {
.create(requestScopePropagator, req.psId, refName)
.setPatchSetProvider(req.replaceOp::getPatchSet));
bu.addOp(id, new ChangeProgressOp(progress));
ids.add(id);
}
logger.atFine().log(
@ -3115,7 +3129,14 @@ class ReceiveCommits {
bu.execute();
} catch (IOException | OrmException | PermissionBackendException e) {
logger.atSevere().withCause(e).log("Failed to auto-close changes");
return null;
}
// If we are here, we didn't throw UpdateException. Record the result.
// The ordering is indeterminate due to the HashSet; unfortunately, Change.Id doesn't
// fit into TreeSet.
ids.stream().forEach(id -> resultChangeIds.add(Key.AUTOCLOSED, id));
return null;
},
// Use a multiple of the default timeout to account for inner retries that may otherwise

View File

@ -0,0 +1,58 @@
// Copyright (C) 2018 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.google.gerrit.server.git.receive;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.client.Change;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
/**
* Keeps track of the change IDs thus far updated by ReceiveCommit.
*
* <p>This class is thread-safe.
*/
public class ResultChangeIds {
enum Key {
CREATED,
REPLACED,
AUTOCLOSED,
}
private Map<Key, List<Change.Id>> ids;
ResultChangeIds() {
ids = new EnumMap<>(Key.class);
for (Key k : Key.values()) {
ids.put(k, new ArrayList<>());
}
}
/** Record a change ID update as having completed. Thread-safe. */
public void add(Key key, Change.Id id) {
synchronized (this) {
ids.get(key).add(id);
}
}
/** Returns change IDs of the given type for which the BatchUpdate succeeded. Thread-safe. */
public List<Change.Id> get(Key key) {
synchronized (this) {
return ImmutableList.copyOf(ids.get(key));
}
}
}