Merge changes from topic 'metrics-core'

* changes:
  Support parameterized metrics
  Export metrics through REST API
  DropWizard metric support
This commit is contained in:
Shawn Pearce 2015-11-11 18:29:52 +00:00 committed by Gerrit Code Review
commit a03a09ff5b
43 changed files with 2610 additions and 6 deletions

View File

@ -30,6 +30,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommits;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackMetricsHook;
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.project.NoSuchProjectException;
@ -196,11 +197,15 @@ public class GitOverHttpServlet extends GitServlet {
static class UploadFactory implements UploadPackFactory<HttpServletRequest> {
private final TransferConfig config;
private final UploadPackMetricsHook uploadMetrics;
private final DynamicSet<PreUploadHook> preUploadHooks;
@Inject
UploadFactory(TransferConfig tc, DynamicSet<PreUploadHook> preUploadHooks) {
UploadFactory(TransferConfig tc,
UploadPackMetricsHook uploadMetrics,
DynamicSet<PreUploadHook> preUploadHooks) {
this.config = tc;
this.uploadMetrics = uploadMetrics;
this.preUploadHooks = preUploadHooks;
}
@ -211,6 +216,7 @@ public class GitOverHttpServlet extends GitServlet {
up.setTimeout(config.getTimeout());
up.setPreUploadHook(PreUploadHookChain.newChain(
Lists.newArrayList(preUploadHooks)));
up.setPostUploadHook(uploadMetrics);
return up;
}
}

View File

@ -36,6 +36,7 @@ import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
@ -323,6 +324,7 @@ public class Daemon extends SiteProgram {
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(SchemaVersionCheck.module());
modules.add(new DropWizardMetricMaker.Module());
modules.add(new LogFileCompressor.Module());
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());

View File

@ -53,6 +53,7 @@ java_library(
'//lib/commons:lang',
'//lib/commons:net',
'//lib/commons:validator',
'//lib/dropwizard:dropwizard-core',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',

View File

@ -0,0 +1,27 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Metric whose value is supplied when the trigger is invoked.
*
* @see CallbackMetric0
* @param <V> type of the metric value, typically Integer or Long.
*/
public interface CallbackMetric<V> extends RegistrationHandle {
}

View File

@ -0,0 +1,40 @@
// 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;
/**
* Metric whose value is supplied when the trigger is invoked.
*
* <pre>
* CallbackMetric0<Long> hits = metricMaker.newCallbackMetric("hits", ...);
* CallbackMetric0<Long> total = metricMaker.newCallbackMetric("total", ...);
* metricMaker.newTrigger(hits, total, new Runnable() {
* public void run() {
* hits.set(1);
* total.set(5);
* }
* });
* </pre>
*
* @param <V> type of the metric value, typically Integer or Long.
*/
public abstract class CallbackMetric0<V> implements CallbackMetric<V> {
/**
* Supply the current value of the metric.
*
* @param value current value.
*/
public abstract void set(V value);
}

View File

@ -0,0 +1,41 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Metric whose value increments during the life of the process.
* <p>
* Suitable uses are "total requests handled", "bytes sent", etc.
* Use {@link Description#setRate()} to suggest the monitoring system
* should also track the rate of increments if this is of interest.
* <p>
* For an instantaneous read of a value that can change over time
* (e.g. "memory in use") use a {@link CallbackMetric}.
*/
public abstract class Counter0 implements RegistrationHandle {
/** Increment the counter by one event. */
public void increment() {
incrementBy(1);
}
/**
* Increment the counter by a specified amount.
*
* @param value value to increment by, must be >= 0.
*/
public abstract void incrementBy(long value);
}

View File

@ -0,0 +1,44 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Metric whose value increments during the life of the process.
* <p>
* Suitable uses are "total requests handled", "bytes sent", etc.
* Use {@link Description#setRate()} to suggest the monitoring system
* should also track the rate of increments if this is of interest.
* <p>
* For an instantaneous read of a value that can change over time
* (e.g. "memory in use") use a {@link CallbackMetric}.
*
* @param <F1> type of the field.
*/
public abstract class Counter1<F1> implements RegistrationHandle {
/** Increment the counter by one event. */
public void increment(F1 field1) {
incrementBy(field1, 1);
}
/**
* Increment the counter by a specified amount.
*
* @param field1 bucket to increment.
* @param value value to increment by, must be >= 0.
*/
public abstract void incrementBy(F1 field1, long value);
}

View File

@ -0,0 +1,46 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Metric whose value increments during the life of the process.
* <p>
* Suitable uses are "total requests handled", "bytes sent", etc.
* Use {@link Description#setRate()} to suggest the monitoring system
* should also track the rate of increments if this is of interest.
* <p>
* For an instantaneous read of a value that can change over time
* (e.g. "memory in use") use a {@link CallbackMetric}.
*
* @param <F1> type of the field.
* @param <F2> type of the field.
*/
public abstract class Counter2<F1, F2> implements RegistrationHandle {
/** Increment the counter by one event. */
public void increment(F1 field1, F2 field2) {
incrementBy(field1, field2, 1);
}
/**
* Increment the counter by a specified amount.
*
* @param field1 bucket to increment.
* @param field2 bucket to increment.
* @param value value to increment by, must be >= 0.
*/
public abstract void incrementBy(F1 field1, F2 field2, long value);
}

View File

@ -0,0 +1,48 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Metric whose value increments during the life of the process.
* <p>
* Suitable uses are "total requests handled", "bytes sent", etc.
* Use {@link Description#setRate()} to suggest the monitoring system
* should also track the rate of increments if this is of interest.
* <p>
* For an instantaneous read of a value that can change over time
* (e.g. "memory in use") use a {@link CallbackMetric}.
*
* @param <F1> type of the field.
* @param <F2> type of the field.
* @param <F3> type of the field.
*/
public abstract class Counter3<F1, F2, F3> implements RegistrationHandle {
/** Increment the counter by one event. */
public void increment(F1 field1, F2 field2, F3 field3) {
incrementBy(field1, field2, field3, 1);
}
/**
* Increment the counter by a specified amount.
*
* @param field1 bucket to increment.
* @param field2 bucket to increment.
* @param field3 bucket to increment.
* @param value value to increment by, must be >= 0.
*/
public abstract void incrementBy(F1 field1, F2 field2, F3 field3, long value);
}

View File

@ -0,0 +1,172 @@
// 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 com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** Describes a metric created by {@link MetricMaker}. */
public class Description {
public static final String DESCRIPTION = "DESCRIPTION";
public static final String UNIT = "UNIT";
public static final String CUMULATIVE = "CUMULATIVE";
public static final String RATE = "RATE";
public static final String GAUGE = "GAUGE";
public static final String FIELD_ORDERING = "FIELD_ORDERING";
public static final String TRUE_VALUE = "1";
public static class Units {
public static final String SECONDS = "seconds";
public static final String MILLISECONDS = "milliseconds";
public static final String MICROSECONDS = "microseconds";
public static final String NANOSECONDS = "nanoseconds";
public static final String BYTES = "bytes";
private Units() {
}
}
public static enum FieldOrdering {
/** Default ordering places fields at end of the parent metric name. */
AT_END,
/**
* Splits the metric name by inserting field values before the last '/' in
* the metric name. For example {@code "plugins/replication/push_latency"}
* with a {@code Field.ofString("remote")} will create submetrics named
* {@code "plugins/replication/some-server/push_latency"}.
*/
PREFIX_FIELDS_BASENAME;
}
private final Map<String, String> annotations;
/**
* Describe a metric.
*
* @param helpText a short one-sentence string explaining the values captured
* by the metric. This may be made available to administrators as
* documentation in the reporting tools.
*/
public Description(String helpText) {
annotations = Maps.newLinkedHashMapWithExpectedSize(4);
annotations.put(DESCRIPTION, helpText);
}
/** Unit used to describe the value, e.g. "requests", "seconds", etc. */
public Description setUnit(String unitName) {
annotations.put(UNIT, unitName);
return this;
}
/**
* Indicates the metric may be usefully interpreted as a count over short
* periods of time, such as request arrival rate. May only be applied to a
* {@link Counter0}.
*/
public Description setRate() {
annotations.put(RATE, TRUE_VALUE);
return this;
}
/**
* Instantaneously sampled value that may increase or decrease at a later
* time. Memory allocated or open network connections are examples of gauges.
*/
public Description setGauge() {
annotations.put(GAUGE, TRUE_VALUE);
return this;
}
/**
* Indicates the metric accumulates over the lifespan of the process. A
* {@link Counter0} like total requests handled accumulates over the process
* and should be {@code setCumulative()}.
*/
public Description setCumulative() {
annotations.put(CUMULATIVE, TRUE_VALUE);
return this;
}
/** Configure how fields are ordered into submetric names. */
public Description setFieldOrdering(FieldOrdering ordering) {
annotations.put(FIELD_ORDERING, ordering.name());
return this;
}
/** True if the metric may be interpreted as a rate over time. */
public boolean isRate() {
return TRUE_VALUE.equals(annotations.get(RATE));
}
/** True if the metric is an instantaneous sample. */
public boolean isGauge() {
return TRUE_VALUE.equals(annotations.get(GAUGE));
}
/** True if the metric accumulates over the lifespan of the process. */
public boolean isCumulative() {
return TRUE_VALUE.equals(annotations.get(CUMULATIVE));
}
/** Get the suggested field ordering. */
public FieldOrdering getFieldOrdering() {
String o = annotations.get(FIELD_ORDERING);
return o != null ? FieldOrdering.valueOf(o) : FieldOrdering.AT_END;
}
/**
* Decode the unit as a unit of time.
*
* @return valid time unit.
* @throws IllegalArgumentException if the unit is not a valid unit of time.
*/
public TimeUnit getTimeUnit() {
return getTimeUnit(annotations.get(UNIT));
}
private static final ImmutableMap<String, TimeUnit> TIME_UNITS = ImmutableMap.of(
Units.NANOSECONDS, TimeUnit.NANOSECONDS,
Units.MICROSECONDS, TimeUnit.MICROSECONDS,
Units.MILLISECONDS, TimeUnit.MILLISECONDS,
Units.SECONDS, TimeUnit.SECONDS);
public static TimeUnit getTimeUnit(String unit) {
if (Strings.isNullOrEmpty(unit)) {
throw new IllegalArgumentException("no unit configured");
}
TimeUnit u = TIME_UNITS.get(unit);
if (u == null) {
throw new IllegalArgumentException(String.format(
"unit %s not TimeUnit", unit));
}
return u;
}
/** Immutable copy of all annotations (configurable properties). */
public ImmutableMap<String, String> getAnnotations() {
return ImmutableMap.copyOf(annotations);
}
@Override
public String toString() {
return annotations.toString();
}
}

View File

@ -0,0 +1,136 @@
// 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 com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Function;
import com.google.common.base.Functions;
/** Describes a bucketing field used by a metric. */
public class Field<T> {
/** Break down metrics by boolean true/false. */
public static Field<Boolean> ofBoolean(String name) {
return ofBoolean(name, null);
}
/** Break down metrics by boolean true/false. */
public static Field<Boolean> ofBoolean(String name, String description) {
return new Field<>(name, Boolean.class, description);
}
/** Break down metrics by cases of an enum. */
public static <E extends Enum<E>> Field<E> ofEnum(Class<E> enumType,
String name) {
return ofEnum(enumType, name, null);
}
/** Break down metrics by cases of an enum. */
public static <E extends Enum<E>> Field<E> ofEnum(Class<E> enumType,
String name, String description) {
return new Field<>(name, enumType, description);
}
/**
* Break down metrics by string.
* <p>
* Each unique string will allocate a new submetric. <b>Do not use user
* content as a field value</b> as field values are never reclaimed.
*/
public static Field<String> ofString(String name) {
return ofString(name, null);
}
/**
* Break down metrics by string.
* <p>
* Each unique string will allocate a new submetric. <b>Do not use user
* content as a field value</b> as field values are never reclaimed.
*/
public static Field<String> ofString(String name, String description) {
return new Field<>(name, String.class, description);
}
/**
* Break down metrics by integer.
* <p>
* Each unique integer will allocate a new submetric. <b>Do not use user
* content as a field value</b> as field values are never reclaimed.
*/
public static Field<Integer> ofInteger(String name) {
return ofInteger(name, null);
}
/**
* Break down metrics by integer.
* <p>
* Each unique integer will allocate a new submetric. <b>Do not use user
* content as a field value</b> as field values are never reclaimed.
*/
public static Field<Integer> ofInteger(String name, String description) {
return new Field<>(name, Integer.class, description);
}
private final String name;
private final Class<T> keyType;
private final Function<T, String> formatter;
private final String description;
private Field(String name, Class<T> keyType, String description) {
checkArgument(name.matches("^[a-z_]+$"), "name must match [a-z_]");
this.name = name;
this.keyType = keyType;
this.formatter = initFormatter(keyType);
this.description = description;
}
/** Name of this field within the metric. */
public String getName() {
return name;
}
/** Type of value used within the field. */
public Class<T> getType() {
return keyType;
}
/** Description text for the field explaining its range of values. */
public String getDescription() {
return description;
}
public Function<T, String> formatter() {
return formatter;
}
@SuppressWarnings("unchecked")
private static <T> Function<T, String> initFormatter(Class<T> keyType) {
if (keyType == String.class) {
return (Function<T, String>) Functions.<String> identity();
} else if (keyType == Integer.class || keyType == Boolean.class) {
return (Function<T, String>) Functions.toStringFunction();
} else if (Enum.class.isAssignableFrom(keyType)) {
return new Function<T, String>() {
@Override
public String apply(T in) {
return ((Enum<?>) in).name();
}
};
}
throw new IllegalStateException("unsupported type " + keyType.getName());
}
}

View File

@ -0,0 +1,101 @@
// 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 com.google.common.base.Supplier;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import java.util.Set;
/** Factory to create metrics for monitoring. */
public abstract class MetricMaker {
/** Metric whose value increments during the life of the process. */
public abstract Counter0 newCounter(String name, Description desc);
public abstract <F1> Counter1<F1> newCounter(
String name, Description desc,
Field<F1> field1);
public abstract <F1, F2> Counter2<F1, F2> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2);
public abstract <F1, F2, F3> Counter3<F1, F2, F3> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3);
/** Metric recording time spent on an operation. */
public abstract Timer0 newTimer(String name, Description desc);
public abstract <F1> Timer1<F1> newTimer(
String name, Description desc,
Field<F1> field1);
public abstract <F1, F2> Timer2<F1, F2> newTimer(
String name, Description desc,
Field<F1> field1, Field<F2> field2);
public abstract <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3);
/**
* Instantaneous reading of a value.
*
* <pre>
* metricMaker.newCallbackMetric(&quot;memory&quot;,
* new Description(&quot;Total bytes of memory used&quot;)
* .setGauge()
* .setUnit(Units.BYTES),
* new Supplier&lt;Long&gt;() {
* public Long get() {
* return Runtime.getRuntime().totalMemory();
* }
* });
* </pre>
*
* @param name unique name of the metric.
* @param valueClass type of value recorded by the metric.
* @param desc description of the metric.
* @param trigger function to compute the value of the metric.
*/
public <V> void newCallbackMetric(String name,
Class<V> valueClass, Description desc, final Supplier<V> trigger) {
final CallbackMetric0<V> metric = newCallbackMetric(name, valueClass, desc);
newTrigger(metric, new Runnable() {
@Override
public void run() {
metric.set(trigger.get());
}
});
}
/** Instantaneous reading of a single value. */
public abstract <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc);
/** Connect logic to populate a previously created {@link CallbackMetric}. */
public RegistrationHandle newTrigger(CallbackMetric<?> metric1, Runnable trigger) {
return newTrigger(ImmutableSet.<CallbackMetric<?>>of(metric1), trigger);
}
public RegistrationHandle newTrigger(CallbackMetric<?> metric1,
CallbackMetric<?> metric2, Runnable trigger) {
return newTrigger(ImmutableSet.of(metric1, metric2), trigger);
}
public RegistrationHandle newTrigger(CallbackMetric<?> metric1,
CallbackMetric<?> metric2, CallbackMetric<?> metric3, Runnable trigger) {
return newTrigger(ImmutableSet.of(metric1, metric2, metric3), trigger);
}
public abstract RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics,
Runnable trigger);
}

View File

@ -0,0 +1,54 @@
// 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.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
/**
* Records elapsed time for an operation or span.
* <p>
* Typical usage in a try-with-resources block:
*
* <pre>
* try (Timer.Context ctx = timer.start()) {
* }
* </pre>
*/
public abstract class Timer0 implements RegistrationHandle {
public class Context implements AutoCloseable {
private final long startNanos;
Context() {
this.startNanos = System.nanoTime();
}
@Override
public void close() {
record(System.nanoTime() - startNanos, NANOSECONDS);
}
}
/** Begin a timer for the current block, value will be recorded when closed. */
public Context start() {
return new Context();
}
/** Record a value in the distribution. */
public abstract void record(long value, TimeUnit unit);
}

View File

@ -0,0 +1,61 @@
// 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.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
/**
* Records elapsed time for an operation or span.
* <p>
* Typical usage in a try-with-resources block:
*
* <pre>
* try (Timer1.Context ctx = timer.start(field)) {
* }
* </pre>
*
* @param <F1> type of the field.
*/
public abstract class Timer1<F1> implements RegistrationHandle {
public static class Context implements AutoCloseable {
private final Timer1<Object> timer;
private final Object field1;
private final long startNanos;
@SuppressWarnings("unchecked")
<F1> Context(Timer1<F1> timer, F1 field1) {
this.timer = (Timer1<Object>) timer;
this.field1 = field1;
this.startNanos = System.nanoTime();
}
@Override
public void close() {
timer.record(field1, System.nanoTime() - startNanos, NANOSECONDS);
}
}
/** Begin a timer for the current block, value will be recorded when closed. */
public Context start(F1 field1) {
return new Context(this, field1);
}
/** Record a value in the distribution. */
public abstract void record(F1 field1, long value, TimeUnit unit);
}

View File

@ -0,0 +1,64 @@
// 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.gerrit.extensions.registration.RegistrationHandle;
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 {
public static class Context implements AutoCloseable {
private final Timer2<Object, Object> timer;
private final Object field1;
private final Object field2;
private final long startNanos;
@SuppressWarnings("unchecked")
<F1, F2> Context(Timer2<F1, F2> timer, F1 field1, F2 field2) {
this.timer = (Timer2<Object, Object>) timer;
this.field1 = field1;
this.field2 = field2;
this.startNanos = System.nanoTime();
}
@Override
public void close() {
timer.record(field1, field2, System.nanoTime() - startNanos, NANOSECONDS);
}
}
/** Begin a timer for the current block, value will be recorded when closed. */
public Context start(F1 field1, F2 field2) {
return new Context(this, field1, field2);
}
/** Record a value in the distribution. */
public abstract void record(F1 field1, F2 field2, long value, TimeUnit unit);
}

View File

@ -0,0 +1,69 @@
// 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.gerrit.extensions.registration.RegistrationHandle;
import java.util.concurrent.TimeUnit;
/**
* Records elapsed time for an operation or span.
* <p>
* Typical usage in a try-with-resources block:
*
* <pre>
* try (Timer3.Context ctx = timer.start(field)) {
* }
* </pre>
*
* @param <F1> type of the field.
* @param <F2> type of the field.
* @param <F3> type of the field.
*/
public abstract class Timer3<F1, F2, F3> implements RegistrationHandle {
public static class Context implements AutoCloseable {
private final Timer3<Object, Object, Object> timer;
private final Object field1;
private final Object field2;
private final Object field3;
private final long startNanos;
@SuppressWarnings("unchecked")
<F1, F2, F3> Context(Timer3<F1, F2, F3> timer, F1 f1, F2 f2, F3 f3) {
this.timer = (Timer3<Object, Object, Object>) timer;
this.field1 = f1;
this.field2 = f2;
this.field3 = f3;
this.startNanos = System.nanoTime();
}
@Override
public void close() {
timer.record(field1, field2, field3,
System.nanoTime() - startNanos, NANOSECONDS);
}
}
/** Begin a timer for the current block, value will be recorded when closed. */
public Context start(F1 field1, F2 field2, F3 field3) {
return new Context(this, field1, field2, field3);
}
/** Record a value in the distribution. */
public abstract void record(F1 field1, F2 field2, F3 field3,
long value, TimeUnit unit);
}

View File

@ -0,0 +1,109 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.CounterImpl;
import com.codahale.metrics.Metric;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** Abstract counter broken down into buckets by {@link Field} values. */
abstract class BucketedCounter implements BucketedMetric {
private final DropWizardMetricMaker metrics;
private final String name;
private final boolean isRate;
private final Description.FieldOrdering ordering;
protected final Field<?>[] fields;
protected final CounterImpl total;
private final Map<Object, CounterImpl> cells;
BucketedCounter(DropWizardMetricMaker metrics,
String name, Description desc, Field<?>... fields) {
this.metrics = metrics;
this.name = name;
this.isRate = desc.isRate();
this.ordering = desc.getFieldOrdering();
this.fields = fields;
this.total = metrics.newCounterImpl(name + "_total", isRate);
this.cells = new ConcurrentHashMap<>();
}
void doRemove() {
for (CounterImpl c : cells.values()) {
c.remove();
}
total.remove();
metrics.remove(name);
}
CounterImpl forceCreate(Object f1, Object f2) {
return forceCreate(ImmutableList.of(f1, f2));
}
CounterImpl forceCreate(Object f1, Object f2, Object f3) {
return forceCreate(ImmutableList.of(f1, f2, f3));
}
CounterImpl forceCreate(Object key) {
CounterImpl c = cells.get(key);
if (c != null) {
return c;
}
synchronized (cells) {
c = cells.get(key);
if (c == null) {
c = metrics.newCounterImpl(submetric(key), isRate);
cells.put(key, c);
}
return c;
}
}
private String submetric(Object key) {
return DropWizardMetricMaker.name(ordering, name, name(key));
}
abstract String name(Object key);
@Override
public Metric getTotal() {
return total.metric;
}
@Override
public Field<?>[] getFields() {
return fields;
}
@Override
public Map<Object, Metric> getCells() {
return Maps.transformValues(
cells,
new Function<CounterImpl, Metric> () {
@Override
public Metric apply(CounterImpl in) {
return in.metric;
}
});
}
}

View File

@ -0,0 +1,29 @@
// 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.dropwizard;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.metrics.Field;
import com.codahale.metrics.Metric;
import java.util.Map;
/** Metric broken down into buckets by {@link Field} values. */
interface BucketedMetric extends Metric {
@Nullable Metric getTotal();
Field<?>[] getFields();
Map<?, Metric> getCells();
}

View File

@ -0,0 +1,107 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.TimerImpl;
import com.codahale.metrics.Metric;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** Abstract timer broken down into buckets by {@link Field} values. */
abstract class BucketedTimer implements BucketedMetric {
private final DropWizardMetricMaker metrics;
private final String name;
private final Description.FieldOrdering ordering;
protected final Field<?>[] fields;
protected final TimerImpl total;
private final Map<Object, TimerImpl> cells;
BucketedTimer(DropWizardMetricMaker metrics, String name,
Description desc, Field<?>... fields) {
this.metrics = metrics;
this.name = name;
this.ordering = desc.getFieldOrdering();
this.fields = fields;
this.total = metrics.newTimerImpl(name + "_total");
this.cells = new ConcurrentHashMap<>();
}
void doRemove() {
for (TimerImpl c : cells.values()) {
c.remove();
}
total.remove();
metrics.remove(name);
}
TimerImpl forceCreate(Object f1, Object f2) {
return forceCreate(ImmutableList.of(f1, f2));
}
TimerImpl forceCreate(Object f1, Object f2, Object f3) {
return forceCreate(ImmutableList.of(f1, f2, f3));
}
TimerImpl forceCreate(Object key) {
TimerImpl c = cells.get(key);
if (c != null) {
return c;
}
synchronized (cells) {
c = cells.get(key);
if (c == null) {
c = metrics.newTimerImpl(submetric(key));
cells.put(key, c);
}
return c;
}
}
private String submetric(Object key) {
return DropWizardMetricMaker.name(ordering, name, name(key));
}
abstract String name(Object key);
@Override
public Metric getTotal() {
return total.metric;
}
@Override
public Field<?>[] getFields() {
return fields;
}
@Override
public Map<Object, Metric> getCells() {
return Maps.transformValues(
cells,
new Function<TimerImpl, Metric> () {
@Override
public Metric apply(TimerImpl in) {
return in.metric;
}
});
}
}

View File

@ -0,0 +1,49 @@
// 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.dropwizard;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
class CallbackGroup implements Runnable {
private static final long PERIOD = TimeUnit.SECONDS.toNanos(2);
private final AtomicLong reloadAt;
private final Runnable trigger;
CallbackGroup(Runnable trigger) {
this.reloadAt = new AtomicLong(0);
this.trigger = trigger;
}
@Override
public void run() {
if (reload()) {
trigger.run();
}
}
private boolean reload() {
for (;;) {
long now = System.nanoTime();
long next = reloadAt.get();
if (next > now) {
return false;
} else if (reloadAt.compareAndSet(next, now + PERIOD)) {
return true;
}
}
}
}

View File

@ -0,0 +1,67 @@
// 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.dropwizard;
import com.google.gerrit.metrics.CallbackMetric0;
class CallbackMetricImpl0<V> extends CallbackMetric0<V> {
@SuppressWarnings("unchecked")
static <V> V zeroFor(Class<V> valueClass) {
if (valueClass == Integer.class) {
return (V) Integer.valueOf(0);
} else if (valueClass == Long.class) {
return (V) Long.valueOf(0);
} else if (valueClass == Double.class) {
return (V) Double.valueOf(0);
} else if (valueClass == Float.class) {
return (V) Float.valueOf(0);
} else if (valueClass == String.class) {
return (V) "";
} else if (valueClass == Boolean.class) {
return (V) Boolean.FALSE;
} else {
throw new IllegalArgumentException("unsupported value type "
+ valueClass.getName());
}
}
final String name;
private V value;
CallbackMetricImpl0(String name, Class<V> valueType) {
this.name = name;
this.value = zeroFor(valueType);
}
@Override
public void set(V value) {
this.value = value;
}
@Override
public void remove() {
// Triggers register and remove the metric.
}
com.codahale.metrics.Gauge<V> gauge(final Runnable trigger) {
return new com.codahale.metrics.Gauge<V>() {
@Override
public V getValue() {
trigger.run();
return value;
}
};
}
}

View File

@ -0,0 +1,52 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
/** Optimized version of {@link BucketedCounter} for single dimension. */
class CounterImpl1<F1> extends BucketedCounter {
CounterImpl1(DropWizardMetricMaker metrics, String name, Description desc,
Field<F1> field1) {
super(metrics, name, desc, field1);
}
Counter1<F1> counter() {
return new Counter1<F1>() {
@Override
public void incrementBy(F1 field1, long value) {
total.incrementBy(value);
forceCreate(field1).incrementBy(value);
}
@Override
public void remove() {
doRemove();
}
};
}
@Override
String name(Object field1) {
@SuppressWarnings("unchecked")
Function<Object, String> fmt =
(Function<Object, String>) fields[0].formatter();
return fmt.apply(field1).replace('/', '-');
}
}

View File

@ -0,0 +1,75 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
/** Generalized implementation of N-dimensional counter metrics. */
class CounterImplN extends BucketedCounter implements BucketedMetric {
CounterImplN(DropWizardMetricMaker metrics, String name, Description desc,
Field<?>... fields) {
super(metrics, name, desc, fields);
}
<F1, F2> Counter2<F1, F2> counter2() {
return new Counter2<F1, F2>() {
@Override
public void incrementBy(F1 field1, F2 field2, long value) {
total.incrementBy(value);
forceCreate(field1, field2).incrementBy(value);
}
@Override
public void remove() {
doRemove();
}
};
}
<F1, F2, F3> Counter3<F1, F2, F3> counter3() {
return new Counter3<F1, F2, F3>() {
@Override
public void incrementBy(F1 field1, F2 field2, F3 field3, long value) {
total.incrementBy(value);
forceCreate(field1, field2, field3).incrementBy(value);
}
@Override
public void remove() {
doRemove();
}
};
}
@SuppressWarnings("unchecked")
@Override
String name(Object key) {
ImmutableList<Object> keyList = (ImmutableList<Object>) key;
String[] parts = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
Function<Object, String> fmt =
(Function<Object, String>) fields[i].formatter();
parts[i] = fmt.apply(keyList.get(i)).replace('/', '-');
}
return Joiner.on('/').join(parts);
}
}

View File

@ -0,0 +1,317 @@
// 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.dropwizard;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.metrics.dropwizard.MetricResource.METRIC_KIND;
import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.metrics.CallbackMetric;
import com.google.gerrit.metrics.CallbackMetric0;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.metrics.Timer2;
import com.google.gerrit.metrics.Timer3;
import com.google.gerrit.metrics.Description.FieldOrdering;
import com.google.inject.Inject;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Connects Gerrit metric package onto DropWizard.
*
* @see <a href="http://www.dropwizard.io/">DropWizard</a>
*/
@Singleton
public class DropWizardMetricMaker extends MetricMaker {
public static class Module extends RestApiModule {
@Override
protected void configure() {
bind(MetricRegistry.class).in(Scopes.SINGLETON);
bind(DropWizardMetricMaker.class).in(Scopes.SINGLETON);
bind(MetricMaker.class).to(DropWizardMetricMaker.class);
DynamicMap.mapOf(binder(), METRIC_KIND);
child(CONFIG_KIND, "metrics").to(MetricsCollection.class);
get(METRIC_KIND).to(GetMetric.class);
}
}
private final MetricRegistry registry;
private final Map<String, BucketedMetric> bucketed;
private final Map<String, ImmutableMap<String, String>> descriptions;
@Inject
DropWizardMetricMaker(MetricRegistry registry) {
this.registry = registry;
this.bucketed = new ConcurrentHashMap<>();
this.descriptions = new ConcurrentHashMap<>();
}
Iterable<String> getMetricNames() {
return descriptions.keySet();
}
/** Get the underlying metric implementation. */
public Metric getMetric(String name) {
Metric m = bucketed.get(name);
return m != null ? m : registry.getMetrics().get(name);
}
/** Lookup annotations from a metric's {@link Description}. */
public ImmutableMap<String, String> getAnnotations(String name) {
return descriptions.get(name);
}
@Override
public synchronized Counter0 newCounter(String name, Description desc) {
checkCounterDescription(desc);
define(name, desc);
return newCounterImpl(name, desc.isRate());
}
@Override
public synchronized <F1> Counter1<F1> newCounter(
String name, Description desc,
Field<F1> field1) {
checkCounterDescription(desc);
CounterImpl1<F1> m = new CounterImpl1<>(this, name, desc, field1);
define(name, desc);
bucketed.put(name, m);
return m.counter();
}
@Override
public synchronized <F1, F2> Counter2<F1, F2> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2) {
checkCounterDescription(desc);
CounterImplN m = new CounterImplN(this, name, desc, field1, field2);
define(name, desc);
bucketed.put(name, m);
return m.counter2();
}
@Override
public synchronized <F1, F2, F3> Counter3<F1, F2, F3> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
checkCounterDescription(desc);
CounterImplN m = new CounterImplN(this, name, desc, field1, field2, field3);
define(name, desc);
bucketed.put(name, m);
return m.counter3();
}
private static void checkCounterDescription(Description desc) {
checkArgument(!desc.isGauge(), "counters must not be gauge");
}
CounterImpl newCounterImpl(String name, boolean isRate) {
if (isRate) {
final com.codahale.metrics.Meter m = registry.meter(name);
return new CounterImpl(name, m) {
@Override
public void incrementBy(long delta) {
checkArgument(delta >= 0, "counter delta must be >= 0");
m.mark(delta);
}
};
} else {
final com.codahale.metrics.Counter m = registry.counter(name);
return new CounterImpl(name, m) {
@Override
public void incrementBy(long delta) {
checkArgument(delta >= 0, "counter delta must be >= 0");
m.inc(delta);
}
};
}
}
@Override
public synchronized Timer0 newTimer(String name, Description desc) {
checkTimerDescription(desc);
define(name, desc);
return newTimerImpl(name);
}
@Override
public synchronized <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
checkTimerDescription(desc);
TimerImpl1<F1> m = new TimerImpl1<>(this, name, desc, field1);
define(name, desc);
bucketed.put(name, m);
return m.timer();
}
@Override
public synchronized <F1, F2> Timer2<F1, F2> newTimer(String name, Description desc,
Field<F1> field1, Field<F2> field2) {
checkTimerDescription(desc);
TimerImplN m = new TimerImplN(this, name, desc, field1, field2);
define(name, desc);
bucketed.put(name, m);
return m.timer2();
}
@Override
public synchronized <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
checkTimerDescription(desc);
TimerImplN m = new TimerImplN(this, name, desc, field1, field2, field3);
define(name, desc);
bucketed.put(name, m);
return m.timer3();
}
private static void checkTimerDescription(Description desc) {
checkArgument(!desc.isGauge(), "timer must not be a gauge");
checkArgument(!desc.isRate(), "timer must not be a rate");
checkArgument(desc.isCumulative(), "timer must be cumulative");
checkArgument(desc.getTimeUnit() != null, "timer must have a unit");
}
TimerImpl newTimerImpl(String name) {
return new TimerImpl(name, registry.timer(name));
}
@Override
public <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc) {
define(name, desc);
return new CallbackMetricImpl0<>(name, valueClass);
}
@Override
public synchronized RegistrationHandle newTrigger(
Set<CallbackMetric<?>> metrics, Runnable trigger) {
if (metrics.size() > 1) {
trigger = new CallbackGroup(trigger);
}
for (CallbackMetric<?> m : metrics) {
CallbackMetricImpl0<?> metric = (CallbackMetricImpl0<?>) m;
if (registry.getMetrics().containsKey(metric.name)) {
throw new IllegalStateException(String.format(
"metric %s already configured", metric.name));
}
}
final List<String> names = new ArrayList<>(metrics.size());
for (CallbackMetric<?> m : metrics) {
CallbackMetricImpl0<?> metric = (CallbackMetricImpl0<?>) m;
registry.register(metric.name, metric.gauge(trigger));
names.add(metric.name);
}
return new RegistrationHandle() {
@Override
public void remove() {
for (String name : names) {
descriptions.remove(name);
registry.remove(name);
}
}
};
}
synchronized void remove(String name) {
bucketed.remove(name);
descriptions.remove(name);
}
private synchronized void define(String name, Description desc) {
if (descriptions.containsKey(name)) {
throw new IllegalStateException(String.format(
"metric %s already defined", name));
}
descriptions.put(name, desc.getAnnotations());
}
static String name(Description.FieldOrdering ordering,
String codeName,
String fieldValues) {
if (ordering == FieldOrdering.PREFIX_FIELDS_BASENAME) {
int s = codeName.lastIndexOf('/');
if (s > 0) {
String prefix = codeName.substring(0, s);
String metric = codeName.substring(s + 1);
return prefix + '/' + fieldValues + '/' + metric;
}
}
return codeName + '/' + fieldValues;
}
abstract class CounterImpl extends Counter0 {
private final String name;
final Metric metric;
CounterImpl(String name, Metric metric) {
this.name = name;
this.metric = metric;
}
@Override
public void remove() {
descriptions.remove(name);
registry.remove(name);
}
}
class TimerImpl extends Timer0 {
private final String name;
final com.codahale.metrics.Timer metric;
private TimerImpl(String name, com.codahale.metrics.Timer metric) {
this.name = name;
this.metric = metric;
}
@Override
public void record(long value, TimeUnit unit) {
checkArgument(value >= 0, "timer delta must be >= 0");
metric.update(value, unit);
}
@Override
public void remove() {
descriptions.remove(name);
registry.remove(name);
}
}
}

View File

@ -0,0 +1,47 @@
// 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.dropwizard;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import org.kohsuke.args4j.Option;
class GetMetric implements RestReadView<MetricResource> {
private final CurrentUser user;
private final DropWizardMetricMaker metrics;
@Option(name = "--data-only", usage = "return only values")
boolean dataOnly;
@Inject
GetMetric(CurrentUser user, DropWizardMetricMaker metrics) {
this.user = user;
this.metrics = metrics;
}
@Override
public MetricJson apply(MetricResource resource) throws AuthException {
if (!user.getCapabilities().canViewCaches()) {
throw new AuthException("restricted to viewCaches");
}
return new MetricJson(
resource.getMetric(),
metrics.getAnnotations(resource.getName()),
dataOnly);
}
}

View File

@ -0,0 +1,96 @@
// 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.dropwizard;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.codahale.metrics.Metric;
import org.kohsuke.args4j.Option;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
class ListMetrics implements RestReadView<ConfigResource> {
private final CurrentUser user;
private final DropWizardMetricMaker metrics;
@Option(name = "--data-only", usage = "return only values")
boolean dataOnly;
@Option(name = "--prefix", aliases = {"-p"}, metaVar = "PREFIX",
usage = "match metric by exact match or prefix")
List<String> query = new ArrayList<>();
@Inject
ListMetrics(CurrentUser user, DropWizardMetricMaker metrics) {
this.user = user;
this.metrics = metrics;
}
@Override
public Map<String, MetricJson> apply(ConfigResource resource)
throws AuthException {
if (!user.getCapabilities().canViewCaches()) {
throw new AuthException("restricted to viewCaches");
}
SortedMap<String, MetricJson> out = new TreeMap<>();
List<String> prefixes = new ArrayList<>(query.size());
for (String q : query) {
if (q.endsWith("/")) {
prefixes.add(q);
} else {
Metric m = metrics.getMetric(q);
if (m != null) {
out.put(q, toJson(q, m));
}
}
}
if (query.isEmpty() || !prefixes.isEmpty()) {
for (String name : metrics.getMetricNames()) {
if (include(prefixes, name)) {
out.put(name, toJson(name, metrics.getMetric(name)));
}
}
}
return out;
}
private MetricJson toJson(String q, Metric m) {
return new MetricJson(m, metrics.getAnnotations(q), dataOnly);
}
private static boolean include(List<String> prefixes, String name) {
if (prefixes.isEmpty()) {
return true;
}
for (String p : prefixes) {
if (name.startsWith(p)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,187 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
class MetricJson {
String description;
String unit;
Boolean rate;
Boolean gauge;
Boolean cumulative;
Long count;
Object value;
Double rate_1m;
Double rate_5m;
Double rate_15m;
Double rate_mean;
Double p50;
Double p75;
Double p95;
Double p98;
Double p99;
Double p99_9;
Double min;
Double max;
Double std_dev;
List<FieldJson> fields;
Map<String, Object> buckets;
MetricJson(Metric metric, ImmutableMap<String, String> atts, boolean dataOnly) {
if (!dataOnly) {
description = atts.get(Description.DESCRIPTION);
unit = atts.get(Description.UNIT);
rate = toBool(atts, Description.RATE);
gauge = toBool(atts, Description.GAUGE);
cumulative = toBool(atts, Description.CUMULATIVE);
}
init(metric, atts);
}
private void init(Metric metric, ImmutableMap<String, String> atts) {
if (metric instanceof BucketedMetric) {
BucketedMetric m = (BucketedMetric) metric;
if (m.getTotal() != null) {
init(m.getTotal(), atts);
}
Field<?>[] fieldList = m.getFields();
fields = new ArrayList<>(fieldList.length);
for (Field<?> f : fieldList) {
fields.add(new FieldJson(f));
}
buckets = makeBuckets(fieldList, m.getCells(), atts);
} else if (metric instanceof Counter) {
Counter c = (Counter) metric;
count = c.getCount();
} else if (metric instanceof Gauge) {
Gauge<?> g = (Gauge<?>) metric;
value = g.getValue();
} else if (metric instanceof Meter) {
Meter m = (Meter) metric;
count = m.getCount();
rate_1m = m.getOneMinuteRate();
rate_5m = m.getFiveMinuteRate();
rate_15m = m.getFifteenMinuteRate();
} else if (metric instanceof Timer) {
Timer m = (Timer) metric;
Snapshot s = m.getSnapshot();
count = m.getCount();
rate_1m = m.getOneMinuteRate();
rate_5m = m.getFiveMinuteRate();
rate_15m = m.getFifteenMinuteRate();
double div =
Description.getTimeUnit(atts.get(Description.UNIT)).toNanos(1);
p50 = s.getMedian() / div;
p75 = s.get75thPercentile() / div;
p95 = s.get95thPercentile() / div;
p98 = s.get98thPercentile() / div;
p99 = s.get99thPercentile() / div;
p99_9 = s.get999thPercentile() / div;
min = s.getMin() / div;
max = s.getMax() / div;
std_dev = s.getStdDev() / div;
}
}
private static Boolean toBool(ImmutableMap<String, String> atts, String key) {
return Description.TRUE_VALUE.equals(atts.get(key)) ? true : null;
}
@SuppressWarnings("unchecked")
private static Map<String, Object> makeBuckets(
Field<?>[] fields,
Map<?, Metric> metrics,
ImmutableMap<String, String> atts) {
if (fields.length == 1) {
Function<Object, String> fmt =
(Function<Object, String>) fields[0].formatter();
Map<String, Object> out = new TreeMap<>();
for (Map.Entry<?, Metric> e : metrics.entrySet()) {
out.put(
fmt.apply(e.getKey()),
new MetricJson(e.getValue(), atts, true));
}
return out;
}
Map<String, Object> out = new TreeMap<>();
for (Map.Entry<?, Metric> e : metrics.entrySet()) {
ImmutableList<Object> keys = (ImmutableList<Object>) e.getKey();
Map<String, Object> dst = out;
for (int i = 0; i < fields.length - 1; i++) {
Function<Object, String> fmt =
(Function<Object, String>) fields[i].formatter();
String key = fmt.apply(keys.get(i));
Map<String, Object> t = (Map<String, Object>) dst.get(key);
if (t == null) {
t = new TreeMap<>();
dst.put(key, t);
}
dst = t;
}
Function<Object, String> fmt =
(Function<Object, String>) fields[fields.length - 1].formatter();
dst.put(
fmt.apply(keys.get(fields.length - 1)),
new MetricJson(e.getValue(), atts, true));
}
return out;
}
static class FieldJson {
String name;
String type;
String description;
FieldJson(Field<?> field) {
this.name = field.getName();
this.description = field.getDescription();
this.type = Enum.class.isAssignableFrom(field.getType())
? field.getType().getSimpleName()
: null;
}
}
}

View File

@ -0,0 +1,42 @@
// 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.dropwizard;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.TypeLiteral;
import com.codahale.metrics.Metric;
class MetricResource extends ConfigResource {
static final TypeLiteral<RestView<MetricResource>> METRIC_KIND =
new TypeLiteral<RestView<MetricResource>>() {};
private final String name;
private final Metric metric;
MetricResource(String name, Metric metric) {
this.name = name;
this.metric = metric;
}
String getName() {
return name;
}
Metric getMetric() {
return metric;
}
}

View File

@ -0,0 +1,72 @@
// 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.dropwizard;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.codahale.metrics.Metric;
@Singleton
class MetricsCollection implements
ChildCollection<ConfigResource, MetricResource> {
private final DynamicMap<RestView<MetricResource>> views;
private final Provider<ListMetrics> list;
private final Provider<CurrentUser> user;
private final DropWizardMetricMaker metrics;
@Inject
MetricsCollection(DynamicMap<RestView<MetricResource>> views,
Provider<ListMetrics> list, Provider<CurrentUser> user,
DropWizardMetricMaker metrics) {
this.views = views;
this.list = list;
this.user = user;
this.metrics = metrics;
}
@Override
public DynamicMap<RestView<MetricResource>> views() {
return views;
}
@Override
public RestView<ConfigResource> list() {
return list.get();
}
@Override
public MetricResource parse(ConfigResource parent, IdString id)
throws ResourceNotFoundException, AuthException {
if (!user.get().getCapabilities().canViewCaches()) {
throw new AuthException("restricted to viewCaches");
}
Metric metric = metrics.getMetric(id.get());
if (metric == null) {
throw new ResourceNotFoundException(id.get());
}
return new MetricResource(id.get(), metric);
}
}

View File

@ -0,0 +1,54 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Timer1;
import java.util.concurrent.TimeUnit;
/** Optimized version of {@link BucketedTimer} for single dimension. */
class TimerImpl1<F1> extends BucketedTimer implements BucketedMetric {
TimerImpl1(DropWizardMetricMaker metrics, String name,
Description desc, Field<F1> field1) {
super(metrics, name, desc, field1);
}
Timer1<F1> timer() {
return new Timer1<F1>() {
@Override
public void record(F1 field1, long value, TimeUnit unit) {
total.record(value, unit);
forceCreate(field1).record(value, unit);
}
@Override
public void remove() {
doRemove();
}
};
}
@Override
String name(Object field1) {
@SuppressWarnings("unchecked")
Function<Object, String> fmt =
(Function<Object, String>) fields[0].formatter();
return fmt.apply(field1).replace('/', '-');
}
}

View File

@ -0,0 +1,78 @@
// 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.dropwizard;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Timer2;
import com.google.gerrit.metrics.Timer3;
import java.util.concurrent.TimeUnit;
/** Generalized implementation of N-dimensional timer metrics. */
class TimerImplN extends BucketedTimer implements BucketedMetric {
TimerImplN(DropWizardMetricMaker metrics, String name,
Description desc, Field<?>... fields) {
super(metrics, name, desc, fields);
}
<F1, F2> Timer2<F1, F2> timer2() {
return new Timer2<F1, F2>() {
@Override
public void record(F1 field1, F2 field2, long value, TimeUnit unit) {
total.record(value, unit);
forceCreate(field1, field2).record(value, unit);
}
@Override
public void remove() {
doRemove();
}
};
}
<F1, F2, F3> Timer3<F1, F2, F3> timer3() {
return new Timer3<F1, F2, F3>() {
@Override
public void record(F1 field1, F2 field2, F3 field3,
long value, TimeUnit unit) {
total.record(value, unit);
forceCreate(field1, field2, field3).record(value, unit);
}
@Override
public void remove() {
doRemove();
}
};
}
@SuppressWarnings("unchecked")
@Override
String name(Object key) {
ImmutableList<Object> keyList = (ImmutableList<Object>) key;
String[] parts = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
Function<Object, String> fmt =
(Function<Object, String>) fields[i].formatter();
parts[i] = fmt.apply(keyList.get(i)).replace('/', '-');
}
return Joiner.on('/').join(parts);
}
}

View File

@ -0,0 +1,55 @@
// 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.server.git;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.storage.pack.PackStatistics;
import org.eclipse.jgit.transport.PostUploadHook;
@Singleton
public class UploadPackMetricsHook implements PostUploadHook {
enum Operation {
CLONE,
FETCH;
}
private final Counter1<Operation> upload;
@Inject
UploadPackMetricsHook(MetricMaker metricMaker) {
upload = metricMaker.newCounter(
"git/upload-pack",
new Description("Total number of git-upload-pack requests")
.setRate()
.setUnit("requests"),
Field.ofEnum(Operation.class, "operation"));
}
@Override
public void onPostUpload(PackStatistics stats) {
Operation op = Operation.FETCH;
if (stats.getUninterestingObjects() == null
|| stats.getUninterestingObjects().isEmpty()) {
op = Operation.CLONE;
}
upload.increment(op);
}
}

View File

@ -34,6 +34,7 @@ import com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.util.PluginRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@ -77,6 +78,7 @@ public class PluginGuiceEnvironment {
private final List<StartPluginListener> onStart;
private final List<StopPluginListener> onStop;
private final List<ReloadPluginListener> onReload;
private final MetricMaker serverMetrics;
private Module sysModule;
private Module sshModule;
@ -102,12 +104,14 @@ public class PluginGuiceEnvironment {
Injector sysInjector,
ThreadLocalRequestContext local,
ServerInformation srvInfo,
CopyConfigModule ccm) {
CopyConfigModule ccm,
MetricMaker serverMetrics) {
this.sysInjector = sysInjector;
this.srvInfo = srvInfo;
this.local = local;
this.copyConfigModule = ccm;
this.copyConfigKeys = Guice.createInjector(ccm).getAllBindings().keySet();
this.serverMetrics = serverMetrics;
onStart = new CopyOnWriteArrayList<>();
onStart.addAll(listeners(sysInjector, StartPluginListener.class));
@ -127,6 +131,10 @@ public class PluginGuiceEnvironment {
return srvInfo;
}
MetricMaker getServerMetrics() {
return serverMetrics;
}
boolean hasDynamicItem(TypeLiteral<?> type) {
return sysItems.containsKey(type)
|| (sshItems != null && sshItems.containsKey(type))
@ -424,6 +432,7 @@ public class PluginGuiceEnvironment {
}
}
}
private void reattachItem(
ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles,
Map<TypeLiteral<?>, DynamicItem<?>> items,
@ -564,6 +573,9 @@ public class PluginGuiceEnvironment {
if (StopPluginListener.class.isAssignableFrom(type)) {
return false;
}
if (MetricMaker.class.isAssignableFrom(type)) {
return false;
}
if (type.getName().startsWith("com.google.inject.")) {
return false;

View File

@ -0,0 +1,155 @@
// 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.server.plugins;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.metrics.CallbackMetric;
import com.google.gerrit.metrics.CallbackMetric0;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.metrics.Timer2;
import com.google.gerrit.metrics.Timer3;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
class PluginMetricMaker extends MetricMaker implements LifecycleListener {
private final MetricMaker root;
private final String prefix;
private final Set<RegistrationHandle> cleanup;
PluginMetricMaker(MetricMaker root, String pluginName) {
this.root = root;
this.prefix = "plugins/" + pluginName;
cleanup = Collections.synchronizedSet(new HashSet<RegistrationHandle>());
}
@Override
public Counter0 newCounter(String name, Description desc) {
Counter0 m = root.newCounter(prefix + name, desc);
cleanup.add(m);
return m;
}
@Override
public <F1> Counter1<F1> newCounter(
String name, Description desc,
Field<F1> field1) {
Counter1<F1> m = root.newCounter(prefix + name, desc, field1);
cleanup.add(m);
return m;
}
@Override
public <F1, F2> Counter2<F1, F2> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2) {
Counter2<F1, F2> m = root.newCounter(prefix + name, desc, field1, field2);
cleanup.add(m);
return m;
}
@Override
public <F1, F2, F3> Counter3<F1, F2, F3> newCounter(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
Counter3<F1, F2, F3> m =
root.newCounter(prefix + name, desc, field1, field2, field3);
cleanup.add(m);
return m;
}
@Override
public Timer0 newTimer(String name, Description desc) {
Timer0 m = root.newTimer(prefix + name, desc);
cleanup.add(m);
return m;
}
@Override
public <F1> Timer1<F1> newTimer(
String name, Description desc,
Field<F1> field1) {
Timer1<F1> m = root.newTimer(prefix + name, desc, field1);
cleanup.add(m);
return m;
}
@Override
public <F1, F2> Timer2<F1, F2> newTimer(
String name, Description desc,
Field<F1> field1, Field<F2> field2) {
Timer2<F1, F2> m = root.newTimer(prefix + name, desc, field1, field2);
cleanup.add(m);
return m;
}
@Override
public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
Timer3<F1, F2, F3> m =
root.newTimer(prefix + name, desc, field1, field2, field3);
cleanup.add(m);
return m;
}
@Override
public <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc) {
CallbackMetric0<V> m = root.newCallbackMetric(prefix + name, valueClass, desc);
cleanup.add(m);
return m;
}
@Override
public RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics,
Runnable trigger) {
final RegistrationHandle handle = root.newTrigger(metrics, trigger);
cleanup.add(handle);
return new RegistrationHandle() {
@Override
public void remove() {
handle.remove();
cleanup.remove(handle);
}
};
}
@Override
public void start() {
}
@Override
public void stop() {
synchronized (cleanup) {
Iterator<RegistrationHandle> itr = cleanup.iterator();
while (itr.hasNext()) {
itr.next().remove();
itr.remove();
}
}
}
}

View File

@ -236,7 +236,7 @@ public class ServerPlugin extends Plugin {
if (getApiType() == ApiType.PLUGIN) {
modules.add(env.getSysModule());
}
modules.add(new ServerPluginInfoModule(this));
modules.add(new ServerPluginInfoModule(this, env.getServerMetrics()));
return Guice.createInjector(modules);
}

View File

@ -17,6 +17,8 @@ package com.google.gerrit.server.plugins;
import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.PluginUser;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@ -32,10 +34,12 @@ class ServerPluginInfoModule extends AbstractModule {
private final Path dataDir;
private volatile boolean ready;
private final MetricMaker serverMetrics;
ServerPluginInfoModule(ServerPlugin plugin) {
ServerPluginInfoModule(ServerPlugin plugin, MetricMaker serverMetrics) {
this.plugin = plugin;
this.dataDir = plugin.getDataDir();
this.serverMetrics = serverMetrics;
}
@Override
@ -47,6 +51,17 @@ class ServerPluginInfoModule extends AbstractModule {
bind(String.class)
.annotatedWith(PluginCanonicalWebUrl.class)
.toInstance(plugin.getPluginCanonicalWebUrl());
install(new LifecycleModule() {
@Override
public void configure() {
PluginMetricMaker metrics = new PluginMetricMaker(
serverMetrics,
plugin.getName());
bind(MetricMaker.class).toInstance(metrics);
listener().toInstance(metrics);
}
});
}
@Provides

View File

@ -20,6 +20,9 @@ import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.IndexConfig;
@ -32,6 +35,7 @@ import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
@ -42,6 +46,7 @@ public class QueryProcessor {
private final ChangeControl.GenericFactory changeControlFactory;
private final IndexRewriter rewriter;
private final IndexConfig indexConfig;
private final Metrics metrics;
private int limitFromCaller;
private int start;
@ -52,12 +57,14 @@ public class QueryProcessor {
Provider<CurrentUser> userProvider,
ChangeControl.GenericFactory changeControlFactory,
IndexRewriter rewriter,
IndexConfig indexConfig) {
IndexConfig indexConfig,
Metrics metrics) {
this.db = db;
this.userProvider = userProvider;
this.changeControlFactory = changeControlFactory;
this.rewriter = rewriter;
this.indexConfig = indexConfig;
this.metrics = metrics;
}
public QueryProcessor enforceVisibility(boolean enforce) {
@ -114,6 +121,9 @@ public class QueryProcessor {
private List<QueryResult> queryChanges(List<String> queryStrings,
List<Predicate<ChangeData>> queries)
throws OrmException, QueryParseException {
@SuppressWarnings("resource")
Timer0.Context context = metrics.executionTime.start();
Predicate<ChangeData> visibleToMe = enforceVisibility
? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get())
: null;
@ -170,6 +180,7 @@ public class QueryProcessor {
limits.get(i),
matches.get(i).toList()));
}
context.close(); // only measure successful queries
return out;
}
@ -203,4 +214,19 @@ public class QueryProcessor {
}
return Ordering.natural().min(possibleLimits);
}
@Singleton
static class Metrics {
final Timer0 executionTime;
@Inject
Metrics(MetricMaker metricMaker) {
executionTime = metricMaker.newTimer(
"change/query/query_latency",
new Description("Successful change query latency,"
+ " accumulated over the life of the process")
.setCumulative()
.setUnit(Description.Units.MILLISECONDS));
}
}
}

View File

@ -22,6 +22,7 @@ import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.DisabledChangeHooks;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@ -132,6 +133,7 @@ public class InMemoryModule extends FactoryModule {
.toInstance(cfg);
}
});
install(new DropWizardMetricMaker.Module());
install(cfgInjector.getInstance(GerritGlobalModule.class));
install(new ChangeCacheImplModule(false));
factory(GarbageCollection.Factory.class);

View File

@ -21,6 +21,7 @@ java_library(
'//lib/auto:auto-value',
'//lib/commons:codec',
'//lib/commons:collections',
'//lib/dropwizard:dropwizard-core',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet', # SSH should not depend on servlet

View File

@ -19,10 +19,14 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
@ -126,6 +130,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* SSH daemon to communicate with Gerrit.
@ -170,7 +175,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
@GerritServerConfig final Config cfg, final SshLog sshLog,
@SshListenAddresses final List<SocketAddress> listen,
@SshAdvertisedAddresses final List<String> advertised) {
@SshAdvertisedAddresses final List<String> advertised,
MetricMaker metricMaker) {
setPort(IANA_SSH_PORT /* never used */);
this.cfg = cfg;
@ -245,10 +251,33 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory);
setShellFactory(noShell);
final AtomicInteger connected = new AtomicInteger();
metricMaker.newCallbackMetric(
"sshd/sessions/connected",
Integer.class,
new Description("Currently connected SSH sessions")
.setGauge()
.setUnit("sessions"),
new Supplier<Integer>() {
@Override
public Integer get() {
return connected.get();
}
});
final Counter0 sesssionsCreated = metricMaker.newCounter(
"sshd/sessions/created",
new Description("Rate of new SSH sessions")
.setRate()
.setUnit("sessions"));
setSessionFactory(new SessionFactory() {
@Override
protected AbstractSession createSession(final IoSession io)
throws Exception {
connected.incrementAndGet();
sesssionsCreated.increment();
if (io instanceof MinaSession) {
if (((MinaSession) io).getSession()
.getConfig() instanceof SocketSessionConfig) {
@ -269,6 +298,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
s.addCloseSessionListener(new SshFutureListener<CloseFuture>() {
@Override
public void operationComplete(CloseFuture future) {
connected.decrementAndGet();
if (sd.isAuthenticationError()) {
sshLog.onAuthFail(sd);
}

View File

@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.ChangeCache;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackMetricsHook;
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.git.validators.UploadValidationException;
import com.google.gerrit.server.git.validators.UploadValidators;
@ -58,6 +59,9 @@ final class Upload extends AbstractGitCommand {
@Inject
private SshSession session;
@Inject
private UploadPackMetricsHook uploadMetrics;
@Override
protected void runImpl() throws IOException, Failure {
if (!projectControl.canRunUploadPack()) {
@ -71,6 +75,7 @@ final class Upload extends AbstractGitCommand {
}
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
up.setPostUploadHook(uploadMetrics);
List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks);
allPreUploadHooks.add(uploadValidatorsFactory.create(project, repo,

View File

@ -26,6 +26,7 @@ import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
@ -288,6 +289,7 @@ public class WebAppInitializer extends GuiceServletContextListener
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.Module());
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());

8
lib/dropwizard/BUCK Normal file
View File

@ -0,0 +1,8 @@
include_defs('//lib/maven.defs')
maven_jar(
name = 'dropwizard-core',
id = 'io.dropwizard.metrics:metrics-core:3.1.2',
sha1 = '224f03afd2521c6c94632f566beb1bb5ee32cf07',
license = 'Apache2.0',
)