DropWizard metric support

Gerrit server supports defining and recording metrics.  Metric
reporters for monitoring can be implemented as plugins.  A basic
Graphite reporter is available here:

  https://gerrit-review.googlesource.com/#/c/72202/

Some example metrics are included in this change:

  change/query/query_latency
  (Query latency)

  sshd/sessions/connected
  (SSH sessions connected)

  sshd/sessions/created/count
  (SSH connections created)

  git/upload-pack
  (Upload packs requests)

Partially-by: Gustaf Lundh <gustaflh@axis.com>
Change-Id: I46a07aace57efe236ee724ec8d34c581e2c37965
This commit is contained in:
Shawn Pearce 2015-11-08 11:44:03 -08:00
parent 40d64f6d43
commit f70a242ad5
21 changed files with 817 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.ReceiveCommits;
import com.google.gerrit.server.git.TagCache; import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig; 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.VisibleRefFilter;
import com.google.gerrit.server.git.validators.UploadValidators; import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
@ -196,11 +197,15 @@ public class GitOverHttpServlet extends GitServlet {
static class UploadFactory implements UploadPackFactory<HttpServletRequest> { static class UploadFactory implements UploadPackFactory<HttpServletRequest> {
private final TransferConfig config; private final TransferConfig config;
private final UploadPackMetricsHook uploadMetrics;
private final DynamicSet<PreUploadHook> preUploadHooks; private final DynamicSet<PreUploadHook> preUploadHooks;
@Inject @Inject
UploadFactory(TransferConfig tc, DynamicSet<PreUploadHook> preUploadHooks) { UploadFactory(TransferConfig tc,
UploadPackMetricsHook uploadMetrics,
DynamicSet<PreUploadHook> preUploadHooks) {
this.config = tc; this.config = tc;
this.uploadMetrics = uploadMetrics;
this.preUploadHooks = preUploadHooks; this.preUploadHooks = preUploadHooks;
} }
@ -211,6 +216,7 @@ public class GitOverHttpServlet extends GitServlet {
up.setTimeout(config.getTimeout()); up.setTimeout(config.getTimeout());
up.setPreUploadHook(PreUploadHookChain.newChain( up.setPreUploadHook(PreUploadHookChain.newChain(
Lists.newArrayList(preUploadHooks))); Lists.newArrayList(preUploadHooks)));
up.setPostUploadHook(uploadMetrics);
return up; 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.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager; import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule; 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.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule; import com.google.gerrit.pgm.http.jetty.JettyModule;
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter; import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
@ -323,6 +324,7 @@ public class Daemon extends SiteProgram {
private Injector createSysInjector() { private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>(); final List<Module> modules = new ArrayList<>();
modules.add(SchemaVersionCheck.module()); modules.add(SchemaVersionCheck.module());
modules.add(new DropWizardMetricMaker.Module());
modules.add(new LogFileCompressor.Module()); modules.add(new LogFileCompressor.Module());
modules.add(new WorkQueue.Module()); modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module()); modules.add(new ChangeHookRunner.Module());

View File

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

View File

@ -0,0 +1,43 @@
// 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.
*
* <pre>
* CallbackMetric<Long> hits = metricMaker.newCallbackMetric("hits", ...);
* CallbackMetric<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 CallbackMetric<V> implements RegistrationHandle {
/**
* 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 Counter 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,141 @@
// 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.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 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() {
}
}
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 Counter}.
*/
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 Counter} like total requests handled accumulates over the process
* and should be {@code setCumulative()}.
*/
public Description setCumulative() {
annotations.put(CUMULATIVE, TRUE_VALUE);
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));
}
/**
* Decode the unit as a unit of time.
*
* @return valid time unit.
* @throws IllegalStateException if the unit is not a valid unit of time.
*/
public TimeUnit getTimeUnit() {
String unit = annotations.get(UNIT);
if (unit == null) {
throw new IllegalStateException("no unit configured");
} else if (Units.NANOSECONDS.equals(unit)) {
return TimeUnit.NANOSECONDS;
} else if (Units.MICROSECONDS.equals(unit)) {
return TimeUnit.MICROSECONDS;
} else if (Units.MILLISECONDS.equals(unit)) {
return TimeUnit.MILLISECONDS;
} else if (Units.SECONDS.equals(unit)) {
return TimeUnit.SECONDS;
} else {
throw new IllegalStateException(String.format(
"unit %s not TimeUnit", unit));
}
}
/** 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,83 @@
// 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 Counter newCounter(String name, Description desc);
/** Metric recording time spent on an operation. */
public abstract Timer newTimer(String name, Description desc);
/**
* 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 CallbackMetric<V> metric = newCallbackMetric(name, valueClass, desc);
newTrigger(metric, new Runnable() {
@Override
public void run() {
metric.set(trigger.get());
}
});
}
/** Instantaneous reading of a particular value. */
public abstract <V> CallbackMetric<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 Timer 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,205 @@
// 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 com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.metrics.CallbackMetric;
import com.google.gerrit.metrics.Counter;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.codahale.metrics.MetricRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
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 AbstractModule {
@Override
protected void configure() {
bind(MetricRegistry.class).in(Scopes.SINGLETON);
bind(MetricMaker.class).to(DropWizardMetricMaker.class);
}
}
private final MetricRegistry registry;
@Inject
DropWizardMetricMaker(MetricRegistry registry) {
this.registry = registry;
}
@Override
public synchronized Counter newCounter(String name, Description desc) {
checkArgument(!desc.isGauge(), "counters must not be gauge");
checkNotDefined(name);
if (desc.isRate()) {
final com.codahale.metrics.Meter metric = registry.meter(name);
return new CounterImpl(name) {
@Override
public void incrementBy(long delta) {
checkArgument(delta >= 0, "counter delta must be >= 0");
metric.mark(delta);
}
};
} else {
final com.codahale.metrics.Counter metric = registry.counter(name);
return new CounterImpl(name) {
@Override
public void incrementBy(long delta) {
checkArgument(delta >= 0, "counter delta must be >= 0");
metric.inc(delta);
}
};
}
}
@Override
public synchronized Timer newTimer(final String name, 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");
checkNotDefined(name);
final com.codahale.metrics.Timer metric = registry.timer(name);
return new Timer() {
@Override
public void record(long value, TimeUnit unit) {
checkArgument(value >= 0, "timer delta must be >= 0");
metric.update(value, unit);
}
@Override
public void remove() {
registry.remove(name);
}
};
}
@SuppressWarnings("unused")
@Override
public <V> CallbackMetric<V> newCallbackMetric(String name,
Class<V> valueClass, Description desc) {
checkNotDefined(name);
return new CallbackMetricImpl<V>(name, valueClass);
}
@Override
public synchronized RegistrationHandle newTrigger(
Set<CallbackMetric<?>> metrics, Runnable trigger) {
for (CallbackMetric<?> m : metrics) {
checkNotDefined(((CallbackMetricImpl<?>) m).name);
}
final List<String> names = new ArrayList<>(metrics.size());
for (CallbackMetric<?> m : metrics) {
CallbackMetricImpl<?> metric = (CallbackMetricImpl<?>) m;
registry.register(metric.name, metric.gauge(trigger));
names.add(metric.name);
}
return new RegistrationHandle() {
@Override
public void remove() {
for (String name : names) {
registry.remove(name);
}
}
};
}
private void checkNotDefined(String name) {
if (registry.getNames().contains(name)) {
throw new IllegalStateException(String.format(
"metric %s already defined", name));
}
}
private abstract class CounterImpl extends Counter {
private final String name;
CounterImpl(String name) {
this.name = name;
}
@Override
public void remove() {
registry.remove(name);
}
}
private static class CallbackMetricImpl<V> extends CallbackMetric<V> {
private final String name;
private V value;
@SuppressWarnings("unchecked")
CallbackMetricImpl(String name, Class<V> valueClass) {
this.name = name;
if (valueClass == Integer.class) {
value = (V) Integer.valueOf(0);
} else if (valueClass == Long.class) {
value = (V) Long.valueOf(0);
} else if (valueClass == Double.class) {
value = (V) Double.valueOf(0);
} else if (valueClass == Float.class) {
value = (V) Float.valueOf(0);
} else if (valueClass == String.class) {
value = (V) "";
} else if (valueClass == Boolean.class) {
value = (V) Boolean.FALSE;
} else {
throw new IllegalArgumentException("unsupported value type "
+ valueClass.getName());
}
}
@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,43 @@
// 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.Counter;
import com.google.gerrit.metrics.Description;
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 {
private final Counter upload;
@Inject
UploadPackMetricsHook(MetricMaker metricMaker) {
upload = metricMaker.newCounter(
"git/upload-pack",
new Description("Total number of git-upload-pack requests")
.setRate()
.setUnit("requests"));
}
@Override
public void onPostUpload(PackStatistics stats) {
upload.increment();
}
}

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

View File

@ -0,0 +1,91 @@
// 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.Counter;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer;
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 Counter newCounter(String name, Description desc) {
Counter m = root.newCounter(prefix + name, desc);
cleanup.add(m);
return m;
}
@Override
public Timer newTimer(String name, Description desc) {
Timer m = root.newTimer(prefix + name, desc);
cleanup.add(m);
return m;
}
@Override
public <V> CallbackMetric<V> newCallbackMetric(String name,
Class<V> valueClass, Description desc) {
CallbackMetric<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) { if (getApiType() == ApiType.PLUGIN) {
modules.add(env.getSysModule()); modules.add(env.getSysModule());
} }
modules.add(new ServerPluginInfoModule(this)); modules.add(new ServerPluginInfoModule(this, env.getServerMetrics()));
return Guice.createInjector(modules); 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.PluginCanonicalWebUrl;
import com.google.gerrit.extensions.annotations.PluginData; import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.extensions.annotations.PluginName; 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.gerrit.server.PluginUser;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides; import com.google.inject.Provides;
@ -32,10 +34,12 @@ class ServerPluginInfoModule extends AbstractModule {
private final Path dataDir; private final Path dataDir;
private volatile boolean ready; private volatile boolean ready;
private final MetricMaker serverMetrics;
ServerPluginInfoModule(ServerPlugin plugin) { ServerPluginInfoModule(ServerPlugin plugin, MetricMaker serverMetrics) {
this.plugin = plugin; this.plugin = plugin;
this.dataDir = plugin.getDataDir(); this.dataDir = plugin.getDataDir();
this.serverMetrics = serverMetrics;
} }
@Override @Override
@ -47,6 +51,17 @@ class ServerPluginInfoModule extends AbstractModule {
bind(String.class) bind(String.class)
.annotatedWith(PluginCanonicalWebUrl.class) .annotatedWith(PluginCanonicalWebUrl.class)
.toInstance(plugin.getPluginCanonicalWebUrl()); .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 @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.ImmutableList;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.IndexConfig; 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.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -42,6 +46,7 @@ public class QueryProcessor {
private final ChangeControl.GenericFactory changeControlFactory; private final ChangeControl.GenericFactory changeControlFactory;
private final IndexRewriter rewriter; private final IndexRewriter rewriter;
private final IndexConfig indexConfig; private final IndexConfig indexConfig;
private final Metrics metrics;
private int limitFromCaller; private int limitFromCaller;
private int start; private int start;
@ -52,12 +57,14 @@ public class QueryProcessor {
Provider<CurrentUser> userProvider, Provider<CurrentUser> userProvider,
ChangeControl.GenericFactory changeControlFactory, ChangeControl.GenericFactory changeControlFactory,
IndexRewriter rewriter, IndexRewriter rewriter,
IndexConfig indexConfig) { IndexConfig indexConfig,
Metrics metrics) {
this.db = db; this.db = db;
this.userProvider = userProvider; this.userProvider = userProvider;
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
this.rewriter = rewriter; this.rewriter = rewriter;
this.indexConfig = indexConfig; this.indexConfig = indexConfig;
this.metrics = metrics;
} }
public QueryProcessor enforceVisibility(boolean enforce) { public QueryProcessor enforceVisibility(boolean enforce) {
@ -114,6 +121,9 @@ public class QueryProcessor {
private List<QueryResult> queryChanges(List<String> queryStrings, private List<QueryResult> queryChanges(List<String> queryStrings,
List<Predicate<ChangeData>> queries) List<Predicate<ChangeData>> queries)
throws OrmException, QueryParseException { throws OrmException, QueryParseException {
@SuppressWarnings("resource")
Timer.Context context = metrics.executionTime.start();
Predicate<ChangeData> visibleToMe = enforceVisibility Predicate<ChangeData> visibleToMe = enforceVisibility
? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get()) ? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get())
: null; : null;
@ -170,6 +180,7 @@ public class QueryProcessor {
limits.get(i), limits.get(i),
matches.get(i).toList())); matches.get(i).toList()));
} }
context.close(); // only measure successful queries
return out; return out;
} }
@ -203,4 +214,19 @@ public class QueryProcessor {
} }
return Ordering.natural().min(possibleLimits); return Ordering.natural().min(possibleLimits);
} }
@Singleton
static class Metrics {
final Timer 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.common.DisabledChangeHooks;
import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.gpg.GpgModule; 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.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
@ -132,6 +133,7 @@ public class InMemoryModule extends FactoryModule {
.toInstance(cfg); .toInstance(cfg);
} }
}); });
install(new DropWizardMetricMaker.Module());
install(cfgInjector.getInstance(GerritGlobalModule.class)); install(cfgInjector.getInstance(GerritGlobalModule.class));
install(new ChangeCacheImplModule(false)); install(new ChangeCacheImplModule(false));
factory(GarbageCollection.Factory.class); factory(GarbageCollection.Factory.class);

View File

@ -21,6 +21,7 @@ java_library(
'//lib/auto:auto-value', '//lib/auto:auto-value',
'//lib/commons:codec', '//lib/commons:codec',
'//lib/commons:collections', '//lib/commons:collections',
'//lib/dropwizard:dropwizard-core',
'//lib/guice:guice', '//lib/guice:guice',
'//lib/guice:guice-assistedinject', '//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet', # SSH should not depend on servlet '//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 static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gerrit.common.Version; import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.metrics.Counter;
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.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.ssh.SshAdvertisedAddresses; import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
@ -126,6 +130,7 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* SSH daemon to communicate with Gerrit. * SSH daemon to communicate with Gerrit.
@ -170,7 +175,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator, final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
@GerritServerConfig final Config cfg, final SshLog sshLog, @GerritServerConfig final Config cfg, final SshLog sshLog,
@SshListenAddresses final List<SocketAddress> listen, @SshListenAddresses final List<SocketAddress> listen,
@SshAdvertisedAddresses final List<String> advertised) { @SshAdvertisedAddresses final List<String> advertised,
MetricMaker metricMaker) {
setPort(IANA_SSH_PORT /* never used */); setPort(IANA_SSH_PORT /* never used */);
this.cfg = cfg; this.cfg = cfg;
@ -245,10 +251,33 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setKeyPairProvider(hostKeyProvider); setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory); setCommandFactory(commandFactory);
setShellFactory(noShell); 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 Counter sesssionsCreated = metricMaker.newCounter(
"sshd/sessions/created",
new Description("Rate of new SSH sessions")
.setRate()
.setUnit("sessions"));
setSessionFactory(new SessionFactory() { setSessionFactory(new SessionFactory() {
@Override @Override
protected AbstractSession createSession(final IoSession io) protected AbstractSession createSession(final IoSession io)
throws Exception { throws Exception {
connected.incrementAndGet();
sesssionsCreated.increment();
if (io instanceof MinaSession) { if (io instanceof MinaSession) {
if (((MinaSession) io).getSession() if (((MinaSession) io).getSession()
.getConfig() instanceof SocketSessionConfig) { .getConfig() instanceof SocketSessionConfig) {
@ -269,6 +298,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
s.addCloseSessionListener(new SshFutureListener<CloseFuture>() { s.addCloseSessionListener(new SshFutureListener<CloseFuture>() {
@Override @Override
public void operationComplete(CloseFuture future) { public void operationComplete(CloseFuture future) {
connected.decrementAndGet();
if (sd.isAuthenticationError()) { if (sd.isAuthenticationError()) {
sshLog.onAuthFail(sd); 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.ChangeCache;
import com.google.gerrit.server.git.TagCache; import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig; 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.VisibleRefFilter;
import com.google.gerrit.server.git.validators.UploadValidationException; import com.google.gerrit.server.git.validators.UploadValidationException;
import com.google.gerrit.server.git.validators.UploadValidators; import com.google.gerrit.server.git.validators.UploadValidators;
@ -58,6 +59,9 @@ final class Upload extends AbstractGitCommand {
@Inject @Inject
private SshSession session; private SshSession session;
@Inject
private UploadPackMetricsHook uploadMetrics;
@Override @Override
protected void runImpl() throws IOException, Failure { protected void runImpl() throws IOException, Failure {
if (!projectControl.canRunUploadPack()) { if (!projectControl.canRunUploadPack()) {
@ -71,6 +75,7 @@ final class Upload extends AbstractGitCommand {
} }
up.setPackConfig(config.getPackConfig()); up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout()); up.setTimeout(config.getTimeout());
up.setPostUploadHook(uploadMetrics);
List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks); List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks);
allPreUploadHooks.add(uploadValidatorsFactory.create(project, repo, 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.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule; import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory; import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory; import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
@ -288,6 +289,7 @@ public class WebAppInitializer extends GuiceServletContextListener
private Injector createSysInjector() { private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>(); final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.Module());
modules.add(new WorkQueue.Module()); modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module()); modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule()); 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',
)