Files
gerrit/java/com/google/gerrit/metrics/Timer2.java
Edwin Kempin 103c5d14fe Have a dedicated class for metadata that is provided to TraceTimer and PerformanceLogger
Currently all metadata are key-value pairs, where both keys and values
are strings. This leads to an API that is difficult to use:

* change I776138eaa had to fix callers which forgot to specify metadata
  keys
* change I758660f8b had to make metadata keys consistent
* there are many similar methods with different numbers of key-value
  pairs for metadata

This change introduces a dedicated Metadata class that is used in the
API instead of key-value pairs that are strings. The Metadata class
contains dedicated fields for all known metadata types. Using the
Metadata.Builder it's now easy to provide metadata consistenly in all
places (there are dedicated methods to set pieces of metadata, so that
there is no longer a need to provide string keys for them).

For plugins that want to provide metadata for which Gerrit core doesn't
provide a suitable metadata field, Metadata contains a pluginMetadata
field in which arbitrary key-value pairs can be stored. That should only
be used for plugins. If Gerrit core needs additional metadata fields the
Metadata class should be extended.

The fields in Metadata only use basic Java types because the logging
package, that contains the Metadata class, should not have any
dependency on the large Gerrit packages that define the Gerrit types
(e.g. Project.NameKey).

For PerformanceLoggers it is important to know about the semantics of
the provided metadata, as some of the metadata may be considered
sensitive and must not end up in a performance log. Having the Metadata
class allows PerformanceLogger implementations to decide which metadata
they want to log and which metadata they want to omit.

A speciality is the recording of performance log entries from metric
timers. Whenever a metric timer is used we automatically report the
measured time as a performance log record. As metadata we provide the
value of the metric fields which are used for bucketing. As metadata
keys we used the field names. To properly populate the metadata in the
new Metadata type now, the definition of the metric fields must do the
mapping of the field value to a field in the Metadata class. For this a
MetadataMapper must be provided when the field is created.

Change-Id: Idedf6368365cd7b54a78c86457d26933746477e8
Signed-off-by: Edwin Kempin <ekempin@google.com>
2019-07-12 13:40:06 +02:00

115 lines
3.7 KiB
Java

// Copyright (C) 2015 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.metrics;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.server.logging.LoggingContext;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.PerformanceLogRecord;
import java.util.concurrent.TimeUnit;
/**
* Records elapsed time for an operation or span.
*
* <p>Typical usage in a try-with-resources block:
*
* <pre>
* try (Timer2.Context ctx = timer.start(field)) {
* }
* </pre>
*
* @param <F1> type of the field.
* @param <F2> type of the field.
*/
public abstract class Timer2<F1, F2> implements RegistrationHandle {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static class Context<F1, F2> extends TimerContext {
private final Timer2<F1, F2> timer;
private final F1 fieldValue1;
private final F2 fieldValue2;
Context(Timer2<F1, F2> timer, F1 fieldValue1, F2 fieldValue2) {
this.timer = timer;
this.fieldValue1 = fieldValue1;
this.fieldValue2 = fieldValue2;
}
@Override
public void record(long elapsed) {
timer.record(fieldValue1, fieldValue2, elapsed, NANOSECONDS);
}
}
protected final String name;
protected final Field<F1> field1;
protected final Field<F2> field2;
public Timer2(String name, Field<F1> field1, Field<F2> field2) {
this.name = name;
this.field1 = field1;
this.field2 = field2;
}
/**
* Begin a timer for the current block, value will be recorded when closed.
*
* @param fieldValue1 bucket to record the timer
* @param fieldValue2 bucket to record the timer
* @return timer context
*/
public Context<F1, F2> start(F1 fieldValue1, F2 fieldValue2) {
return new Context<>(this, fieldValue1, fieldValue2);
}
/**
* Record a value in the distribution.
*
* @param fieldValue1 bucket to record the timer
* @param fieldValue2 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
public final void record(F1 fieldValue1, F2 fieldValue2, long value, TimeUnit unit) {
long durationMs = unit.toMillis(value);
Metadata.Builder metadataBuilder = Metadata.builder();
field1.metadataMapper().accept(metadataBuilder, fieldValue1);
field2.metadataMapper().accept(metadataBuilder, fieldValue2);
Metadata metadata = metadataBuilder.build();
LoggingContext.getInstance()
.addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs, metadata));
logger.atFinest().log(
"%s (%s = %s, %s = %s) took %dms",
name, field1.name(), fieldValue1, field2.name(), fieldValue2, durationMs);
doRecord(fieldValue1, fieldValue2, value, unit);
}
/**
* Record a value in the distribution.
*
* @param fieldValue1 bucket to record the timer
* @param fieldValue2 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
protected abstract void doRecord(F1 fieldValue1, F2 fieldValue2, long value, TimeUnit unit);
}